mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-25 01:07:47 +00:00
Audio duration label.
Non-continues updates from waveform view seeking.
This commit is contained in:
parent
7a9e73fb13
commit
6df3264692
@ -73,32 +73,33 @@
|
||||
<!-- TODO: Extract styling attributes into a theme. -->
|
||||
<org.thoughtcrime.securesms.loki.views.WaveformSeekBar
|
||||
android:id="@+id/seek"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
app:wave_background_color="#bbb"
|
||||
app:wave_progress_color="?colorPrimary"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:wave_gravity="center"
|
||||
app:wave_width="4dp"
|
||||
app:wave_corner_radius="2dp"
|
||||
app:wave_gap="1dp"/>
|
||||
app:wave_gap="1dp"
|
||||
tools:wave_background_color="#bbb"
|
||||
tools:wave_progress_color="?colorPrimary"/>
|
||||
|
||||
<TextView android:id="@+id/total_duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/conversation_item_date_text_size"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:autoLink="none"
|
||||
android:visibility="gone"
|
||||
tools:text="0:05"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView android:id="@+id/timestamp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="76dip"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?conversation_item_sent_text_secondary_color"
|
||||
android:textSize="@dimen/conversation_item_date_text_size"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:autoLink="none"
|
||||
android:visibility="gone"
|
||||
tools:text="00:15"
|
||||
tools:visibility="visible"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -46,6 +46,7 @@ public class DecodedAudio {
|
||||
private final long mFileSize;
|
||||
private final int mAvgBitRate; // Average bit rate in kbps.
|
||||
private final int mSampleRate;
|
||||
private final long mDuration; // In microseconds.
|
||||
private final int mChannels;
|
||||
private final int mNumSamples; // total number of samples per channel in audio file
|
||||
private final ShortBuffer mDecodedSamples; // shared buffer with mDecodedBytes.
|
||||
@ -81,29 +82,31 @@ public class DecodedAudio {
|
||||
public DecodedAudio(MediaExtractor extractor, long size) throws IOException {
|
||||
mFileSize = size;
|
||||
|
||||
MediaFormat mediaFormat = null;
|
||||
int numTracks = extractor.getTrackCount();
|
||||
// find and select the first audio track present in the file.
|
||||
MediaFormat format = null;
|
||||
int trackIndex;
|
||||
for (trackIndex = 0; trackIndex < numTracks; trackIndex++) {
|
||||
format = extractor.getTrackFormat(trackIndex);
|
||||
MediaFormat format = extractor.getTrackFormat(trackIndex);
|
||||
if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
|
||||
extractor.selectTrack(trackIndex);
|
||||
mediaFormat = format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trackIndex == numTracks) {
|
||||
if (mediaFormat == null) {
|
||||
throw new IOException("No audio track found in the data source.");
|
||||
}
|
||||
|
||||
mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
|
||||
mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
|
||||
mChannels = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
|
||||
mSampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
|
||||
mDuration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
|
||||
// Expected total number of samples per channel.
|
||||
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));
|
||||
codec.configure(format, null, null, 0);
|
||||
MediaCodec codec = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME));
|
||||
codec.configure(mediaFormat, null, null, 0);
|
||||
codec.start();
|
||||
|
||||
try {
|
||||
@ -135,7 +138,7 @@ public class DecodedAudio {
|
||||
if (!doneReading && inputBufferIndex >= 0) {
|
||||
sampleSize = extractor.readSampleData(codec.getInputBuffer(inputBufferIndex), 0);
|
||||
if (firstSampleData
|
||||
&& format.getString(MediaFormat.KEY_MIME).equals("audio/mp4a-latm")
|
||||
&& mediaFormat.getString(MediaFormat.KEY_MIME).equals("audio/mp4a-latm")
|
||||
&& sampleSize == 2) {
|
||||
// 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
|
||||
@ -285,6 +288,11 @@ public class DecodedAudio {
|
||||
return mChannels;
|
||||
}
|
||||
|
||||
/** @return Total duration in milliseconds. */
|
||||
public long getDuration() {
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
public int getNumSamples() {
|
||||
return mNumSamples; // Number of samples per channel.
|
||||
}
|
||||
|
@ -14,8 +14,10 @@ import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import com.pnikosis.materialishprogress.ProgressWheel
|
||||
import kotlinx.coroutines.*
|
||||
import network.loki.messenger.R
|
||||
@ -37,6 +39,7 @@ import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.lang.Exception
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
|
||||
|
||||
@ -51,7 +54,7 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
|
||||
private val downloadButton: ImageView
|
||||
private val downloadProgress: ProgressWheel
|
||||
private val seekBar: WaveformSeekBar
|
||||
private val timestamp: TextView
|
||||
private val totalDuration: TextView
|
||||
|
||||
private var downloadListener: SlideClickListener? = null
|
||||
private var audioSlidePlayer: AudioSlidePlayer? = null
|
||||
@ -73,7 +76,7 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
|
||||
downloadButton = findViewById(R.id.download)
|
||||
downloadProgress = findViewById(R.id.download_progress)
|
||||
seekBar = findViewById(R.id.seek)
|
||||
timestamp = findViewById(R.id.timestamp)
|
||||
totalDuration = findViewById(R.id.total_duration)
|
||||
|
||||
playButton.setOnClickListener {
|
||||
try {
|
||||
@ -158,6 +161,7 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
|
||||
if (downloadProgress.isSpinning) {
|
||||
downloadProgress.stopSpinning()
|
||||
}
|
||||
|
||||
// Post to make sure it executes only when the view is attached to a window.
|
||||
post(::updateSeekBarFromAudio)
|
||||
}
|
||||
@ -175,7 +179,7 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
|
||||
downloadListener = listener
|
||||
}
|
||||
|
||||
fun setTint(foregroundTint: Int, backgroundTint: Int) {
|
||||
fun setTint(@ColorInt foregroundTint: Int, @ColorInt backgroundTint: Int) {
|
||||
playButton.backgroundTintList = ColorStateList.valueOf(foregroundTint)
|
||||
playButton.imageTintList = ColorStateList.valueOf(backgroundTint)
|
||||
pauseButton.backgroundTintList = ColorStateList.valueOf(foregroundTint)
|
||||
@ -183,11 +187,13 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
|
||||
|
||||
downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN)
|
||||
downloadProgress.barColor = foregroundTint
|
||||
timestamp.setTextColor(foregroundTint)
|
||||
totalDuration.setTextColor(foregroundTint)
|
||||
|
||||
// val colorFilter = createBlendModeColorFilterCompat(foregroundTint, BlendModeCompat.SRC_IN)
|
||||
// seekBar.progressDrawable.colorFilter = colorFilter
|
||||
// seekBar.thumb.colorFilter = colorFilter
|
||||
seekBar.waveProgressColor = foregroundTint
|
||||
seekBar.waveBackgroundColor = ColorUtils.blendARGB(foregroundTint, backgroundTint, 0.75f)
|
||||
}
|
||||
|
||||
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() }
|
||||
}
|
||||
|
||||
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.
|
||||
generateFakeRms(extractAttachmentRandomSeed(attachment))
|
||||
rmsValues = generateFakeRms(extractAttachmentRandomSeed(attachment))
|
||||
} else {
|
||||
try {
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
PartAuthority.getAttachmentStream(context, attachment.dataUri!!).use {
|
||||
DecodedAudio(InputStreamMediaDataSource(it)).calculateRms(rmsFrames)
|
||||
val decodedAudio = PartAuthority.getAttachmentStream(context, attachment.dataUri!!).use {
|
||||
DecodedAudio(InputStreamMediaDataSource(it))
|
||||
}
|
||||
rmsValues = decodedAudio.calculateRms(rmsFrames)
|
||||
totalDurationMs = (decodedAudio.duration / 1000.0).toLong()
|
||||
} catch (e: Exception) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,21 +80,13 @@ class WaveformSeekBar : View {
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var waveGap: Float =
|
||||
dp(
|
||||
context,
|
||||
2f
|
||||
)
|
||||
var waveGap: Float = dp(context, 2f)
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var waveWidth: Float =
|
||||
dp(
|
||||
context,
|
||||
5f
|
||||
)
|
||||
var waveWidth: Float = dp(context, 5f)
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
@ -106,11 +98,7 @@ class WaveformSeekBar : View {
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var waveCornerRadius: Float =
|
||||
dp(
|
||||
context,
|
||||
2.5f
|
||||
)
|
||||
var waveCornerRadius: Float = dp(context, 2.5f)
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
@ -235,24 +223,26 @@ class WaveformSeekBar : View {
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
userSeeking = true
|
||||
// preUserSeekingProgress = _progress
|
||||
if (isParentScrolling()) {
|
||||
touchDownX = event.x
|
||||
} else {
|
||||
updateProgress(event, true)
|
||||
updateProgress(event, false)
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
updateProgress(event, true)
|
||||
updateProgress(event, false)
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
userSeeking = false
|
||||
if (abs(event.x - touchDownX) > scaledTouchSlop) {
|
||||
updateProgress(event, false)
|
||||
updateProgress(event, true)
|
||||
}
|
||||
performClick()
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
userSeeking = false
|
||||
// updateProgress(preUserSeekingProgress, false)
|
||||
}
|
||||
}
|
||||
return true
|
||||
@ -276,19 +266,32 @@ class WaveformSeekBar : View {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProgress(event: MotionEvent, delayNotification: Boolean) {
|
||||
_progress = event.x / getAvailableWith()
|
||||
private fun updateProgress(event: MotionEvent, notify: Boolean) {
|
||||
updateProgress(event.x / getAvailableWith(), notify)
|
||||
}
|
||||
|
||||
private fun updateProgress(progress: Float, notify: Boolean) {
|
||||
_progress = progress
|
||||
invalidate()
|
||||
|
||||
postponedProgressUpdateHandler.removeCallbacks(postponedProgressUpdateRunnable)
|
||||
if (delayNotification) {
|
||||
// Re-post delayed user update notification to throttle a bit.
|
||||
postponedProgressUpdateHandler.postDelayed(postponedProgressUpdateRunnable, 150)
|
||||
} else {
|
||||
if (notify) {
|
||||
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 {
|
||||
super.performClick()
|
||||
return true
|
||||
|
Loading…
x
Reference in New Issue
Block a user