Audio duration label.

Non-continues updates from waveform view seeking.
This commit is contained in:
Anton Chekulaev 2020-10-08 16:51:34 +11:00
parent 7a9e73fb13
commit 6df3264692
5 changed files with 94 additions and 395 deletions

View File

@ -73,31 +73,32 @@
<!-- TODO: Extract styling attributes into a theme. --> <!-- TODO: Extract styling attributes into a theme. -->
<org.thoughtcrime.securesms.loki.views.WaveformSeekBar <org.thoughtcrime.securesms.loki.views.WaveformSeekBar
android:id="@+id/seek" android:id="@+id/seek"
android:layout_width="fill_parent" android:layout_width="0dp"
android:layout_height="40dp" android:layout_height="38dp"
android:layout_weight="1"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
app:wave_background_color="#bbb" android:layout_marginStart="4dp"
app:wave_progress_color="?colorPrimary" android:layout_marginEnd="4dp"
app:wave_gravity="center" app:wave_gravity="center"
app:wave_width="4dp" app:wave_width="4dp"
app:wave_corner_radius="2dp" app:wave_corner_radius="2dp"
app:wave_gap="1dp"/> app:wave_gap="1dp"
tools:wave_background_color="#bbb"
tools:wave_progress_color="?colorPrimary"/>
</LinearLayout> <TextView android:id="@+id/total_duration"
<TextView android:id="@+id/timestamp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="76dip" android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?conversation_item_sent_text_secondary_color"
android:textSize="@dimen/conversation_item_date_text_size" android:textSize="@dimen/conversation_item_date_text_size"
android:fontFamily="sans-serif-light" android:fontFamily="sans-serif-light"
android:autoLink="none" android:autoLink="none"
android:visibility="gone" android:visibility="gone"
tools:text="00:15" tools:text="0:05"
tools:visibility="visible" tools:visibility="visible"/>
/>
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -1,331 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pnikosis.materialishprogress.ProgressWheel;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.AudioSlide;
import org.thoughtcrime.securesms.mms.SlideClickListener;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import network.loki.messenger.R;
public class AudioViewOld extends FrameLayout implements AudioSlidePlayer.Listener {
private static final String TAG = AudioViewOld.class.getSimpleName();
private final @NonNull AnimatingToggle controlToggle;
private final @NonNull ViewGroup container;
private final @NonNull ImageView playButton;
private final @NonNull ImageView pauseButton;
private final @NonNull ImageView downloadButton;
private final @NonNull ProgressWheel downloadProgress;
private final @NonNull SeekBar seekBar;
private final @NonNull TextView timestamp;
private @Nullable SlideClickListener downloadListener;
private @Nullable AudioSlidePlayer audioSlidePlayer;
private int backwardsCounter;
public AudioViewOld(Context context) {
this(context, null);
}
public AudioViewOld(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AudioViewOld(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflate(context, R.layout.message_audio_view, this);
this.container = (ViewGroup) findViewById(R.id.audio_widget_container);
this.controlToggle = (AnimatingToggle) findViewById(R.id.control_toggle);
this.playButton = (ImageView) findViewById(R.id.play);
this.pauseButton = (ImageView) findViewById(R.id.pause);
this.downloadButton = (ImageView) findViewById(R.id.download);
this.downloadProgress = (ProgressWheel) findViewById(R.id.download_progress);
this.seekBar = (SeekBar) findViewById(R.id.seek);
this.timestamp = (TextView) findViewById(R.id.timestamp);
this.playButton.setOnClickListener(new PlayClickedListener());
this.pauseButton.setOnClickListener(new PauseClickedListener());
this.seekBar.setOnSeekBarChangeListener(new SeekBarModifiedListener());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this.playButton.setImageDrawable(context.getDrawable(R.drawable.play_icon));
this.pauseButton.setImageDrawable(context.getDrawable(R.drawable.pause_icon));
this.playButton.setBackground(context.getDrawable(R.drawable.ic_circle_fill_white_48dp));
this.pauseButton.setBackground(context.getDrawable(R.drawable.ic_circle_fill_white_48dp));
}
if (attrs != null) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MessageAudioView, 0, 0);
setTint(typedArray.getColor(R.styleable.MessageAudioView_foregroundTintColor, Color.WHITE),
typedArray.getColor(R.styleable.MessageAudioView_backgroundTintColor, Color.WHITE));
container.setBackgroundColor(typedArray.getColor(R.styleable.MessageAudioView_widgetBackground, Color.TRANSPARENT));
typedArray.recycle();
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
EventBus.getDefault().unregister(this);
}
public void setAudio(final @NonNull AudioSlide audio,
final boolean showControls)
{
if (showControls && audio.isPendingDownload()) {
controlToggle.displayQuick(downloadButton);
seekBar.setEnabled(false);
downloadButton.setOnClickListener(new DownloadClickedListener(audio));
if (downloadProgress.isSpinning()) downloadProgress.stopSpinning();
} else if (showControls && audio.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) {
controlToggle.displayQuick(downloadProgress);
seekBar.setEnabled(false);
downloadProgress.spin();
} else {
controlToggle.displayQuick(playButton);
seekBar.setEnabled(true);
if (downloadProgress.isSpinning()) downloadProgress.stopSpinning();
}
this.audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), audio, this);
}
public void cleanup() {
if (this.audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) {
this.audioSlidePlayer.stop();
}
}
public void setDownloadClickListener(@Nullable SlideClickListener listener) {
this.downloadListener = listener;
}
@Override
public void onPlayerStart(@NonNull AudioSlidePlayer player) {
if (this.pauseButton.getVisibility() != View.VISIBLE) {
togglePlayToPause();
}
}
@Override
public void onPlayerStop(@NonNull AudioSlidePlayer player) {
if (this.playButton.getVisibility() != View.VISIBLE) {
togglePauseToPlay();
}
if (seekBar.getProgress() + 5 >= seekBar.getMax()) {
backwardsCounter = 4;
onPlayerProgress(player, 0.0, 0);
}
}
@Override
public void setFocusable(boolean focusable) {
super.setFocusable(focusable);
this.playButton.setFocusable(focusable);
this.pauseButton.setFocusable(focusable);
this.seekBar.setFocusable(focusable);
this.seekBar.setFocusableInTouchMode(focusable);
this.downloadButton.setFocusable(focusable);
}
@Override
public void setClickable(boolean clickable) {
super.setClickable(clickable);
this.playButton.setClickable(clickable);
this.pauseButton.setClickable(clickable);
this.seekBar.setClickable(clickable);
this.seekBar.setOnTouchListener(clickable ? null : new TouchIgnoringListener());
this.downloadButton.setClickable(clickable);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
this.playButton.setEnabled(enabled);
this.pauseButton.setEnabled(enabled);
this.seekBar.setEnabled(enabled);
this.downloadButton.setEnabled(enabled);
}
@Override
public void onPlayerProgress(@NonNull AudioSlidePlayer player, double progress, long millis) {
int seekProgress = (int)Math.floor(progress * this.seekBar.getMax());
if (seekProgress > seekBar.getProgress() || backwardsCounter > 3) {
backwardsCounter = 0;
this.seekBar.setProgress(seekProgress);
this.timestamp.setText(String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(millis),
TimeUnit.MILLISECONDS.toSeconds(millis)));
} else {
backwardsCounter++;
}
}
public void setTint(int foregroundTint, int backgroundTint) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this.playButton.setBackgroundTintList(ColorStateList.valueOf(foregroundTint));
this.playButton.setImageTintList(ColorStateList.valueOf(backgroundTint));
this.pauseButton.setBackgroundTintList(ColorStateList.valueOf(foregroundTint));
this.pauseButton.setImageTintList(ColorStateList.valueOf(backgroundTint));
} else {
this.playButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
this.pauseButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
}
this.downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
this.downloadProgress.setBarColor(foregroundTint);
this.timestamp.setTextColor(foregroundTint);
this.seekBar.getProgressDrawable().setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
this.seekBar.getThumb().setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
}
private double getProgress() {
if (this.seekBar.getProgress() <= 0 || this.seekBar.getMax() <= 0) {
return 0;
} else {
return (double)this.seekBar.getProgress() / (double)this.seekBar.getMax();
}
}
private void togglePlayToPause() {
controlToggle.displayQuick(pauseButton);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AnimatedVectorDrawable playToPauseDrawable = (AnimatedVectorDrawable)getContext().getDrawable(R.drawable.play_to_pause_animation);
pauseButton.setImageDrawable(playToPauseDrawable);
playToPauseDrawable.start();
}
}
private void togglePauseToPlay() {
controlToggle.displayQuick(playButton);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AnimatedVectorDrawable pauseToPlayDrawable = (AnimatedVectorDrawable)getContext().getDrawable(R.drawable.pause_to_play_animation);
playButton.setImageDrawable(pauseToPlayDrawable);
pauseToPlayDrawable.start();
}
}
private class PlayClickedListener implements OnClickListener {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onClick(View v) {
try {
Log.d(TAG, "playbutton onClick");
if (audioSlidePlayer != null) {
togglePlayToPause();
audioSlidePlayer.play(getProgress());
}
} catch (IOException e) {
Log.w(TAG, e);
}
}
}
private class PauseClickedListener implements OnClickListener {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onClick(View v) {
Log.d(TAG, "pausebutton onClick");
if (audioSlidePlayer != null) {
togglePauseToPlay();
audioSlidePlayer.stop();
}
}
}
private class DownloadClickedListener implements OnClickListener {
private final @NonNull AudioSlide slide;
private DownloadClickedListener(@NonNull AudioSlide slide) {
this.slide = slide;
}
@Override
public void onClick(View v) {
if (downloadListener != null) downloadListener.onClick(v, slide);
}
}
private class SeekBarModifiedListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}
@Override
public synchronized void onStartTrackingTouch(SeekBar seekBar) {
if (audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) {
audioSlidePlayer.stop();
}
}
@Override
public synchronized void onStopTrackingTouch(SeekBar seekBar) {
try {
if (audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) {
audioSlidePlayer.play(getProgress());
}
} catch (IOException e) {
Log.w(TAG, e);
}
}
}
private static class TouchIgnoringListener implements OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventAsync(final PartProgressEvent event) {
if (audioSlidePlayer != null && event.attachment.equals(audioSlidePlayer.getAudioSlide().asAttachment())) {
downloadProgress.setInstantProgress(((float) event.progress) / event.total);
}
}
}

View File

@ -46,6 +46,7 @@ public class DecodedAudio {
private final long mFileSize; private final long mFileSize;
private final int mAvgBitRate; // Average bit rate in kbps. private final int mAvgBitRate; // Average bit rate in kbps.
private final int mSampleRate; private final int mSampleRate;
private final long mDuration; // In microseconds.
private final int mChannels; private final int mChannels;
private final int mNumSamples; // total number of samples per channel in audio file private final int mNumSamples; // total number of samples per channel in audio file
private final ShortBuffer mDecodedSamples; // shared buffer with mDecodedBytes. private final ShortBuffer mDecodedSamples; // shared buffer with mDecodedBytes.
@ -81,29 +82,31 @@ public class DecodedAudio {
public DecodedAudio(MediaExtractor extractor, long size) throws IOException { public DecodedAudio(MediaExtractor extractor, long size) throws IOException {
mFileSize = size; mFileSize = size;
MediaFormat mediaFormat = null;
int numTracks = extractor.getTrackCount(); int numTracks = extractor.getTrackCount();
// find and select the first audio track present in the file. // find and select the first audio track present in the file.
MediaFormat format = null;
int trackIndex; int trackIndex;
for (trackIndex = 0; trackIndex < numTracks; trackIndex++) { for (trackIndex = 0; trackIndex < numTracks; trackIndex++) {
format = extractor.getTrackFormat(trackIndex); MediaFormat format = extractor.getTrackFormat(trackIndex);
if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) { if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
extractor.selectTrack(trackIndex); extractor.selectTrack(trackIndex);
mediaFormat = format;
break; break;
} }
} }
if (trackIndex == numTracks) { if (mediaFormat == null) {
throw new IOException("No audio track found in the data source."); throw new IOException("No audio track found in the data source.");
} }
mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); mChannels = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); mSampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
mDuration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
// Expected total number of samples per channel. // Expected total number of samples per channel.
int expectedNumSamples = int expectedNumSamples =
(int) ((format.getLong(MediaFormat.KEY_DURATION) / 1000000.f) * mSampleRate + 0.5f); (int) ((mDuration / 1000000.f) * mSampleRate + 0.5f);
MediaCodec codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)); MediaCodec codec = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME));
codec.configure(format, null, null, 0); codec.configure(mediaFormat, null, null, 0);
codec.start(); codec.start();
try { try {
@ -135,7 +138,7 @@ public class DecodedAudio {
if (!doneReading && inputBufferIndex >= 0) { if (!doneReading && inputBufferIndex >= 0) {
sampleSize = extractor.readSampleData(codec.getInputBuffer(inputBufferIndex), 0); sampleSize = extractor.readSampleData(codec.getInputBuffer(inputBufferIndex), 0);
if (firstSampleData if (firstSampleData
&& format.getString(MediaFormat.KEY_MIME).equals("audio/mp4a-latm") && mediaFormat.getString(MediaFormat.KEY_MIME).equals("audio/mp4a-latm")
&& sampleSize == 2) { && sampleSize == 2) {
// For some reasons on some devices (e.g. the Samsung S3) you should not // For some reasons on some devices (e.g. the Samsung S3) you should not
// provide the first two bytes of an AAC stream, otherwise the MediaCodec will // provide the first two bytes of an AAC stream, otherwise the MediaCodec will
@ -285,6 +288,11 @@ public class DecodedAudio {
return mChannels; return mChannels;
} }
/** @return Total duration in milliseconds. */
public long getDuration() {
return mDuration;
}
public int getNumSamples() { public int getNumSamples() {
return mNumSamples; // Number of samples per channel. return mNumSamples; // Number of samples per channel.
} }

View File

@ -14,8 +14,10 @@ import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import com.pnikosis.materialishprogress.ProgressWheel import com.pnikosis.materialishprogress.ProgressWheel
import kotlinx.coroutines.* import kotlinx.coroutines.*
import network.loki.messenger.R import network.loki.messenger.R
@ -37,6 +39,7 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.lang.Exception import java.lang.Exception
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit
class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener { class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
@ -51,7 +54,7 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
private val downloadButton: ImageView private val downloadButton: ImageView
private val downloadProgress: ProgressWheel private val downloadProgress: ProgressWheel
private val seekBar: WaveformSeekBar private val seekBar: WaveformSeekBar
private val timestamp: TextView private val totalDuration: TextView
private var downloadListener: SlideClickListener? = null private var downloadListener: SlideClickListener? = null
private var audioSlidePlayer: AudioSlidePlayer? = null private var audioSlidePlayer: AudioSlidePlayer? = null
@ -73,7 +76,7 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
downloadButton = findViewById(R.id.download) downloadButton = findViewById(R.id.download)
downloadProgress = findViewById(R.id.download_progress) downloadProgress = findViewById(R.id.download_progress)
seekBar = findViewById(R.id.seek) seekBar = findViewById(R.id.seek)
timestamp = findViewById(R.id.timestamp) totalDuration = findViewById(R.id.total_duration)
playButton.setOnClickListener { playButton.setOnClickListener {
try { try {
@ -158,6 +161,7 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
if (downloadProgress.isSpinning) { if (downloadProgress.isSpinning) {
downloadProgress.stopSpinning() downloadProgress.stopSpinning()
} }
// Post to make sure it executes only when the view is attached to a window. // Post to make sure it executes only when the view is attached to a window.
post(::updateSeekBarFromAudio) post(::updateSeekBarFromAudio)
} }
@ -175,7 +179,7 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
downloadListener = listener downloadListener = listener
} }
fun setTint(foregroundTint: Int, backgroundTint: Int) { fun setTint(@ColorInt foregroundTint: Int, @ColorInt backgroundTint: Int) {
playButton.backgroundTintList = ColorStateList.valueOf(foregroundTint) playButton.backgroundTintList = ColorStateList.valueOf(foregroundTint)
playButton.imageTintList = ColorStateList.valueOf(backgroundTint) playButton.imageTintList = ColorStateList.valueOf(backgroundTint)
pauseButton.backgroundTintList = ColorStateList.valueOf(foregroundTint) pauseButton.backgroundTintList = ColorStateList.valueOf(foregroundTint)
@ -183,11 +187,13 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN) downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN)
downloadProgress.barColor = foregroundTint downloadProgress.barColor = foregroundTint
timestamp.setTextColor(foregroundTint) totalDuration.setTextColor(foregroundTint)
// val colorFilter = createBlendModeColorFilterCompat(foregroundTint, BlendModeCompat.SRC_IN) // val colorFilter = createBlendModeColorFilterCompat(foregroundTint, BlendModeCompat.SRC_IN)
// seekBar.progressDrawable.colorFilter = colorFilter // seekBar.progressDrawable.colorFilter = colorFilter
// seekBar.thumb.colorFilter = colorFilter // seekBar.thumb.colorFilter = colorFilter
seekBar.waveProgressColor = foregroundTint
seekBar.waveBackgroundColor = ColorUtils.blendARGB(foregroundTint, backgroundTint, 0.75f)
} }
override fun onPlayerStart(player: AudioSlidePlayer) { override fun onPlayerStart(player: AudioSlidePlayer) {
@ -284,24 +290,36 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
return Random(seed.toLong()).let { (0 until frames).map { i -> it.nextFloat() }.toFloatArray() } return Random(seed.toLong()).let { (0 until frames).map { i -> it.nextFloat() }.toFloatArray() }
} }
val rmsValues: FloatArray var rmsValues: FloatArray = floatArrayOf()
var totalDurationMs: Long = -1
rmsValues = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Due to API version incompatibility, we just display some random waveform for older API. // Due to API version incompatibility, we just display some random waveform for older API.
generateFakeRms(extractAttachmentRandomSeed(attachment)) rmsValues = generateFakeRms(extractAttachmentRandomSeed(attachment))
} else { } else {
try { try {
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
PartAuthority.getAttachmentStream(context, attachment.dataUri!!).use { val decodedAudio = PartAuthority.getAttachmentStream(context, attachment.dataUri!!).use {
DecodedAudio(InputStreamMediaDataSource(it)).calculateRms(rmsFrames) DecodedAudio(InputStreamMediaDataSource(it))
} }
rmsValues = decodedAudio.calculateRms(rmsFrames)
totalDurationMs = (decodedAudio.duration / 1000.0).toLong()
} catch (e: Exception) { } catch (e: Exception) {
android.util.Log.w(TAG, "Failed to decode sample values for the audio attachment \"${attachment.fileName}\".", e) android.util.Log.w(TAG, "Failed to decode sample values for the audio attachment \"${attachment.fileName}\".", e)
generateFakeRms(extractAttachmentRandomSeed(attachment)) rmsValues = generateFakeRms(extractAttachmentRandomSeed(attachment))
} }
} }
post { seekBar.sample = rmsValues } post {
seekBar.sample = rmsValues
if (totalDurationMs > 0) {
totalDuration.visibility = View.VISIBLE
totalDuration.text = String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(totalDurationMs),
TimeUnit.MILLISECONDS.toSeconds(totalDurationMs))
}
}
} }
} }

View File

@ -80,21 +80,13 @@ class WaveformSeekBar : View {
invalidate() invalidate()
} }
var waveGap: Float = var waveGap: Float = dp(context, 2f)
dp(
context,
2f
)
set(value) { set(value) {
field = value field = value
invalidate() invalidate()
} }
var waveWidth: Float = var waveWidth: Float = dp(context, 5f)
dp(
context,
5f
)
set(value) { set(value) {
field = value field = value
invalidate() invalidate()
@ -106,11 +98,7 @@ class WaveformSeekBar : View {
invalidate() invalidate()
} }
var waveCornerRadius: Float = var waveCornerRadius: Float = dp(context, 2.5f)
dp(
context,
2.5f
)
set(value) { set(value) {
field = value field = value
invalidate() invalidate()
@ -235,24 +223,26 @@ class WaveformSeekBar : View {
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
userSeeking = true userSeeking = true
// preUserSeekingProgress = _progress
if (isParentScrolling()) { if (isParentScrolling()) {
touchDownX = event.x touchDownX = event.x
} else { } else {
updateProgress(event, true) updateProgress(event, false)
} }
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
updateProgress(event, true) updateProgress(event, false)
} }
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
userSeeking = false userSeeking = false
if (abs(event.x - touchDownX) > scaledTouchSlop) { if (abs(event.x - touchDownX) > scaledTouchSlop) {
updateProgress(event, false) updateProgress(event, true)
} }
performClick() performClick()
} }
MotionEvent.ACTION_CANCEL -> { MotionEvent.ACTION_CANCEL -> {
userSeeking = false userSeeking = false
// updateProgress(preUserSeekingProgress, false)
} }
} }
return true return true
@ -276,19 +266,32 @@ class WaveformSeekBar : View {
} }
} }
private fun updateProgress(event: MotionEvent, delayNotification: Boolean) { private fun updateProgress(event: MotionEvent, notify: Boolean) {
_progress = event.x / getAvailableWith() updateProgress(event.x / getAvailableWith(), notify)
}
private fun updateProgress(progress: Float, notify: Boolean) {
_progress = progress
invalidate() invalidate()
postponedProgressUpdateHandler.removeCallbacks(postponedProgressUpdateRunnable) if (notify) {
if (delayNotification) {
// Re-post delayed user update notification to throttle a bit.
postponedProgressUpdateHandler.postDelayed(postponedProgressUpdateRunnable, 150)
} else {
postponedProgressUpdateRunnable.run() postponedProgressUpdateRunnable.run()
} }
} }
// private fun updateProgress(event: MotionEvent, delayNotification: Boolean) {
// _progress = event.x / getAvailableWith()
// invalidate()
//
// postponedProgressUpdateHandler.removeCallbacks(postponedProgressUpdateRunnable)
// if (delayNotification) {
// // Re-post delayed user update notification to throttle a bit.
// postponedProgressUpdateHandler.postDelayed(postponedProgressUpdateRunnable, 150)
// } else {
// postponedProgressUpdateRunnable.run()
// }
// }
override fun performClick(): Boolean { override fun performClick(): Boolean {
super.performClick() super.performClick()
return true return true