mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-26 11:51:10 +01:00
Audio wave forms on voice notes.
This commit is contained in:
committed by
Greyson Parrelli
parent
69adcd1d69
commit
daace9bd1a
@@ -14,6 +14,7 @@ import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -29,6 +30,7 @@ import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||
import org.thoughtcrime.securesms.audio.AudioWaveForm;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
@@ -57,6 +59,10 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
|
||||
private final boolean autoRewind;
|
||||
|
||||
@Nullable private final TextView timestamp;
|
||||
@Nullable private final TextView duration;
|
||||
|
||||
@ColorInt private final int waveFormPlayedBarsColor;
|
||||
@ColorInt private final int waveFormUnplayedBarsColor;
|
||||
|
||||
@Nullable private SlideClickListener downloadListener;
|
||||
@Nullable private AudioSlidePlayer audioSlidePlayer;
|
||||
@@ -91,6 +97,7 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
|
||||
this.circleProgress = findViewById(R.id.circle_progress);
|
||||
this.seekBar = findViewById(R.id.seek);
|
||||
this.timestamp = findViewById(R.id.timestamp);
|
||||
this.duration = findViewById(R.id.duration);
|
||||
|
||||
lottieDirection = REVERSE;
|
||||
this.playPauseButton.setOnClickListener(new PlayPauseClickedListener());
|
||||
@@ -98,6 +105,10 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
|
||||
|
||||
setTint(typedArray.getColor(R.styleable.AudioView_foregroundTintColor, Color.WHITE),
|
||||
typedArray.getColor(R.styleable.AudioView_backgroundTintColor, Color.WHITE));
|
||||
|
||||
this.waveFormPlayedBarsColor = typedArray.getColor(R.styleable.AudioView_waveformPlayedBarsColor, Color.WHITE);
|
||||
this.waveFormUnplayedBarsColor = typedArray.getColor(R.styleable.AudioView_waveformUnplayedBarsColor, Color.WHITE);
|
||||
|
||||
container.setBackgroundColor(typedArray.getColor(R.styleable.AudioView_widgetBackground, Color.TRANSPARENT));
|
||||
} finally {
|
||||
if (typedArray != null) {
|
||||
@@ -141,6 +152,28 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
|
||||
}
|
||||
|
||||
this.audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), audio, this);
|
||||
|
||||
if (seekBar instanceof WaveFormSeekBarView) {
|
||||
WaveFormSeekBarView waveFormView = (WaveFormSeekBarView) seekBar;
|
||||
waveFormView.setColors(waveFormPlayedBarsColor, waveFormUnplayedBarsColor);
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23) {
|
||||
new AudioWaveForm(getContext(), audio).generateWaveForm(
|
||||
data -> {
|
||||
waveFormView.setWaveData(data.getWaveForm());
|
||||
if (duration != null) {
|
||||
long durationSecs = data.getDuration(TimeUnit.SECONDS);
|
||||
duration.setText(getContext().getResources().getString(R.string.AudioView_duration, durationSecs / 60, durationSecs % 60));
|
||||
duration.setVisibility(VISIBLE);
|
||||
}
|
||||
},
|
||||
e -> waveFormView.setWaveMode(false));
|
||||
} else {
|
||||
waveFormView.setWaveMode(false);
|
||||
if (duration != null) {
|
||||
duration.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
@@ -232,6 +265,9 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
|
||||
if (this.timestamp != null) {
|
||||
this.timestamp.setTextColor(foregroundTint);
|
||||
}
|
||||
if (this.duration != null) {
|
||||
this.duration.setTextColor(foregroundTint);
|
||||
}
|
||||
this.seekBar.getProgressDrawable().setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
|
||||
this.seekBar.getThumb().setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.Px;
|
||||
import androidx.appcompat.widget.AppCompatSeekBar;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class WaveFormSeekBarView extends AppCompatSeekBar {
|
||||
|
||||
private static final int ANIM_DURATION = 450;
|
||||
private static final int ANIM_BAR_OFF_SET_DURATION = 12;
|
||||
|
||||
private final Interpolator overshoot = new OvershootInterpolator();
|
||||
private final Paint paint = new Paint();
|
||||
private float[] data = new float[0];
|
||||
private long dataSetTime;
|
||||
private Drawable progressDrawable;
|
||||
private boolean waveMode;
|
||||
|
||||
@ColorInt private int playedBarColor = 0xffffffff;
|
||||
@ColorInt private int unplayedBarColor = 0x7fffffff;
|
||||
@Px private int barWidth;
|
||||
|
||||
public WaveFormSeekBarView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public WaveFormSeekBarView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public WaveFormSeekBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setWillNotDraw(false);
|
||||
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
paint.setAntiAlias(true);
|
||||
|
||||
progressDrawable = super.getProgressDrawable();
|
||||
|
||||
if (isInEditMode()) {
|
||||
setWaveData(sinusoidalExampleData());
|
||||
dataSetTime = 0;
|
||||
}
|
||||
|
||||
barWidth = getResources().getDimensionPixelSize(R.dimen.wave_form_bar_width);
|
||||
}
|
||||
|
||||
public void setColors(@ColorInt int playedBarColor, @ColorInt int unplayedBarColor) {
|
||||
this.playedBarColor = playedBarColor;
|
||||
this.unplayedBarColor = unplayedBarColor;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgressDrawable(Drawable progressDrawable) {
|
||||
this.progressDrawable = progressDrawable;
|
||||
if (!waveMode) {
|
||||
super.setProgressDrawable(progressDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getProgressDrawable() {
|
||||
return progressDrawable;
|
||||
}
|
||||
|
||||
public void setWaveData(@NonNull float[] data) {
|
||||
if (!Arrays.equals(data, this.data)) {
|
||||
this.data = data;
|
||||
this.dataSetTime = System.currentTimeMillis();
|
||||
setWaveMode(data.length > 0);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setWaveMode(boolean waveMode) {
|
||||
this.waveMode = waveMode;
|
||||
super.setProgressDrawable(this.waveMode ? null : progressDrawable);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
drawWave(canvas);
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
|
||||
private void drawWave(Canvas canvas) {
|
||||
paint.setStrokeWidth(barWidth);
|
||||
|
||||
int usableHeight = getHeight() - getPaddingTop() - getPaddingBottom();
|
||||
int usableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
||||
float midpoint = usableHeight / 2f;
|
||||
float maxHeight = usableHeight / 2f - barWidth;
|
||||
float barGap = (usableWidth - data.length * barWidth) / (float) (data.length - 1);
|
||||
|
||||
boolean hasMoreFrames = false;
|
||||
|
||||
canvas.save();
|
||||
canvas.translate(getPaddingLeft(), getPaddingTop());
|
||||
|
||||
for (int bar = 0; bar < data.length; bar++) {
|
||||
float x = bar * (barWidth + barGap) + barWidth / 2f;
|
||||
float y = data[bar] * maxHeight;
|
||||
float progress = x / usableWidth;
|
||||
|
||||
paint.setColor(progress * getMax() < getProgress() ? playedBarColor : unplayedBarColor);
|
||||
|
||||
long time = System.currentTimeMillis() - bar * ANIM_BAR_OFF_SET_DURATION - dataSetTime;
|
||||
float timeX = Math.max(0, Math.min(1, time / (float) ANIM_DURATION));
|
||||
float interpolatedTime = overshoot.getInterpolation(timeX);
|
||||
float interpolatedY = y * interpolatedTime;
|
||||
|
||||
canvas.drawLine(x, midpoint - interpolatedY, x, midpoint + interpolatedY, paint);
|
||||
|
||||
if (time < ANIM_DURATION) {
|
||||
hasMoreFrames = true;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
|
||||
if (hasMoreFrames) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private static float[] sinusoidalExampleData() {
|
||||
float[] data = new float[21];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i] = (float) Math.sin(i / (float) (data.length - 1) * 2 * Math.PI);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user