Show remaining time on wave form view and cache wave form in database.

This commit is contained in:
Alan Evans 2020-06-01 18:10:10 -03:00 committed by Greyson Parrelli
parent e01838e996
commit 3fec23fd36
33 changed files with 357 additions and 162 deletions

View file

@ -1,9 +1,11 @@
package org.thoughtcrime.securesms.attachments;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.audio.AudioHash;
import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties;
@ -51,6 +53,9 @@ public abstract class Attachment {
@Nullable
private final BlurHash blurHash;
@Nullable
private final AudioHash audioHash;
@NonNull
private final TransformProperties transformProperties;
@ -58,7 +63,7 @@ public abstract class Attachment {
int cdnNumber, @Nullable String location, @Nullable String key, @Nullable String relay,
@Nullable byte[] digest, @Nullable String fastPreflightId, boolean voiceNote,
int width, int height, boolean quote, long uploadTimestamp, @Nullable String caption,
@Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash,
@Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, @Nullable AudioHash audioHash,
@Nullable TransformProperties transformProperties)
{
this.contentType = contentType;
@ -79,6 +84,7 @@ public abstract class Attachment {
this.stickerLocator = stickerLocator;
this.caption = caption;
this.blurHash = blurHash;
this.audioHash = audioHash;
this.transformProperties = transformProperties != null ? transformProperties : TransformProperties.empty();
}
@ -172,6 +178,10 @@ public abstract class Attachment {
return blurHash;
}
public @Nullable AudioHash getAudioHash() {
return audioHash;
}
public @Nullable String getCaption() {
return caption;
}

View file

@ -4,6 +4,7 @@ import android.net.Uri;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.audio.AudioHash;
import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties;
import org.thoughtcrime.securesms.mms.PartAuthority;
@ -25,11 +26,11 @@ public class DatabaseAttachment extends Attachment {
String fileName, int cdnNumber, String location, String key, String relay,
byte[] digest, String fastPreflightId, boolean voiceNote,
int width, int height, boolean quote, @Nullable String caption,
@Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash,
@Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, @Nullable AudioHash audioHash,
@Nullable TransformProperties transformProperties, int displayOrder,
long uploadTimestamp)
{
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, transformProperties);
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties);
this.attachmentId = attachmentId;
this.hasData = hasData;
this.hasThumbnail = hasThumbnail;

View file

@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
public class MmsNotificationAttachment extends Attachment {
public MmsNotificationAttachment(int status, long size) {
super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, false, 0, 0, false, 0, null, null, null, null);
super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, false, 0, 0, false, 0, null, null, null, null, null);
}
@Nullable

View file

@ -4,6 +4,7 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.audio.AudioHash;
import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.stickers.StickerLocator;
@ -24,7 +25,7 @@ public class PointerAttachment extends Attachment {
int width, int height, long uploadTimestamp, @Nullable String caption, @Nullable StickerLocator stickerLocator,
@Nullable BlurHash blurHash)
{
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null);
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null);
}
@Nullable

View file

@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase;
public class TombstoneAttachment extends Attachment {
public TombstoneAttachment(@NonNull String contentType, boolean quote) {
super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, false, 0, 0, quote, 0, null, null, null, null);
super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, false, 0, 0, quote, 0, null, null, null, null, null);
}
@Override

View file

@ -5,6 +5,7 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.audio.AudioHash;
import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties;
import org.thoughtcrime.securesms.stickers.StickerLocator;
@ -16,18 +17,18 @@ public class UriAttachment extends Attachment {
public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size,
@Nullable String fileName, boolean voiceNote, boolean quote, @Nullable String caption,
@Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, @Nullable TransformProperties transformProperties)
@Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, @Nullable AudioHash audioHash, @Nullable TransformProperties transformProperties)
{
this(uri, uri, contentType, transferState, size, 0, 0, fileName, null, voiceNote, quote, caption, stickerLocator, blurHash, transformProperties);
this(uri, uri, contentType, transferState, size, 0, 0, fileName, null, voiceNote, quote, caption, stickerLocator, blurHash, audioHash, transformProperties);
}
public UriAttachment(@NonNull Uri dataUri, @Nullable Uri thumbnailUri,
@NonNull String contentType, int transferState, long size, int width, int height,
@Nullable String fileName, @Nullable String fastPreflightId,
boolean voiceNote, boolean quote, @Nullable String caption, @Nullable StickerLocator stickerLocator,
@Nullable BlurHash blurHash, @Nullable TransformProperties transformProperties)
@Nullable BlurHash blurHash, @Nullable AudioHash audioHash, @Nullable TransformProperties transformProperties)
{
super(contentType, transferState, size, fileName, 0, null, null, null, null, fastPreflightId, voiceNote, width, height, quote, 0, caption, stickerLocator, blurHash, transformProperties);
super(contentType, transferState, size, fileName, 0, null, null, null, null, fastPreflightId, voiceNote, width, height, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties);
this.dataUri = dataUri;
this.thumbnailUri = thumbnailUri;
}

View file

@ -0,0 +1,57 @@
package org.thoughtcrime.securesms.audio;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData;
import org.whispersystems.util.Base64;
import java.io.IOException;
/**
* An AudioHash is a compact string representation of the wave form and duration for an audio file.
*/
public final class AudioHash {
@NonNull private final String hash;
@NonNull private final AudioWaveFormData audioWaveForm;
private AudioHash(@NonNull String hash, @NonNull AudioWaveFormData audioWaveForm) {
this.hash = hash;
this.audioWaveForm = audioWaveForm;
}
public AudioHash(@NonNull AudioWaveFormData audioWaveForm) {
this(Base64.encodeBytes(audioWaveForm.toByteArray()), audioWaveForm);
}
public static @Nullable AudioHash parseOrNull(@Nullable String hash) {
if (hash == null) return null;
try {
return new AudioHash(hash, AudioWaveFormData.parseFrom(Base64.decode(hash)));
} catch (IOException e) {
return null;
}
}
@NonNull AudioWaveFormData getAudioWaveForm() {
return audioWaveForm;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AudioHash other = (AudioHash) o;
return hash.equals(other.hash);
}
@Override
public int hashCode() {
return hash.hashCode();
}
public @NonNull String getHash() {
return hash;
}
}

View file

@ -14,11 +14,18 @@ import androidx.annotation.RequiresApi;
import androidx.annotation.WorkerThread;
import androidx.core.util.Consumer;
import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.media.DecryptableUriMediaInput;
import org.thoughtcrime.securesms.media.MediaInput;
import org.thoughtcrime.securesms.mms.AudioSlide;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.SerialExecutor;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import java.io.IOException;
@ -32,7 +39,7 @@ public final class AudioWaveForm {
private static final String TAG = Log.tag(AudioWaveForm.class);
private static final int BARS = 46;
private static final int BAR_COUNT = 46;
private static final int SAMPLES_PER_BAR = 4;
private final Context context;
@ -43,34 +50,68 @@ public final class AudioWaveForm {
this.slide = slide;
}
private static final LruCache<Uri, AudioFileInfo> WAVE_FORM_CACHE = new LruCache<>(200);
private static final Executor AUDIO_DECODER_EXECUTOR = SignalExecutors.BOUNDED;
private static final LruCache<String, AudioFileInfo> WAVE_FORM_CACHE = new LruCache<>(200);
private static final Executor AUDIO_DECODER_EXECUTOR = new SerialExecutor(SignalExecutors.BOUNDED);
@AnyThread
public void generateWaveForm(@NonNull Consumer<AudioFileInfo> onSuccess, @NonNull Consumer<IOException> onFailure) {
public void getWaveForm(@NonNull Consumer<AudioFileInfo> onSuccess, @NonNull Consumer<IOException> onFailure) {
Uri uri = slide.getUri();
Attachment attachment = slide.asAttachment();
if (uri == null) {
Log.w(TAG, "No uri");
Util.runOnMain(() -> onFailure.accept(null));
return;
}
if (!(attachment instanceof DatabaseAttachment)) {
Log.i(TAG, "Not yet in database");
Util.runOnMain(() -> onFailure.accept(null));
return;
}
String cacheKey = uri.toString();
AudioFileInfo cached = WAVE_FORM_CACHE.get(cacheKey);
if (cached != null) {
Log.i(TAG, "Loaded wave form from cache " + cacheKey);
Util.runOnMain(() -> onSuccess.accept(cached));
return;
}
AUDIO_DECODER_EXECUTOR.execute(() -> {
AudioFileInfo cachedInExecutor = WAVE_FORM_CACHE.get(cacheKey);
if (cachedInExecutor != null) {
Log.i(TAG, "Loaded wave form from cache inside executor" + cacheKey);
Util.runOnMain(() -> onSuccess.accept(cachedInExecutor));
return;
}
AudioHash audioHash = attachment.getAudioHash();
if (audioHash != null) {
AudioFileInfo audioFileInfo = AudioFileInfo.fromDatabaseProtobuf(audioHash.getAudioWaveForm());
if (audioFileInfo.waveForm.length != BAR_COUNT) {
Log.w(TAG, "Wave form from database does not match bar count, regenerating " + cacheKey);
} else {
WAVE_FORM_CACHE.put(cacheKey, audioFileInfo);
Log.i(TAG, "Loaded wave form from DB " + cacheKey);
Util.runOnMain(() -> onSuccess.accept(audioFileInfo));
return;
}
}
try {
long startTime = System.currentTimeMillis();
Uri uri = slide.getUri();
if (uri == null) {
Util.runOnMain(() -> onFailure.accept(null));
return;
}
DatabaseAttachment dbAttachment = (DatabaseAttachment) attachment;
long startTime = System.currentTimeMillis();
AudioFileInfo fileInfo = generateWaveForm(uri);
AudioFileInfo cached = WAVE_FORM_CACHE.get(uri);
if (cached != null) {
Util.runOnMain(() -> onSuccess.accept(cached));
return;
}
Log.i(TAG, String.format(Locale.US, "Audio wave form generation time %d ms (%s)", System.currentTimeMillis() - startTime, cacheKey));
AudioFileInfo fileInfo = generateWaveForm(uri);
WAVE_FORM_CACHE.put(uri, fileInfo);
Log.i(TAG, String.format(Locale.US, "Audio wave form generation time %d ms", System.currentTimeMillis() - startTime));
DatabaseFactory.getAttachmentDatabase(context).writeAudioHash(dbAttachment.getAttachmentId(), fileInfo.toDatabaseProtobuf());
WAVE_FORM_CACHE.put(cacheKey, fileInfo);
Util.runOnMain(() -> onSuccess.accept(fileInfo));
} catch (IOException e) {
Log.e(TAG, "", e);
Log.w(TAG, "Failed to create audio wave form for " + cacheKey, e);
onFailure.accept(e);
}
});
@ -83,17 +124,36 @@ public final class AudioWaveForm {
*/
@WorkerThread
@RequiresApi(api = 23)
private AudioFileInfo generateWaveForm(@NonNull Uri uri) throws IOException {
private @NonNull AudioFileInfo generateWaveForm(@NonNull Uri uri) throws IOException {
try (MediaInput dataSource = DecryptableUriMediaInput.createForUri(context, uri)) {
long[] wave = new long[BARS];
int[] waveSamples = new int[BARS];
int[] inputSamples = new int[BARS * SAMPLES_PER_BAR];
long[] wave = new long[BAR_COUNT];
int[] waveSamples = new int[BAR_COUNT];
int[] inputSamples = new int[BAR_COUNT * SAMPLES_PER_BAR];
MediaExtractor extractor = dataSource.createExtractor();
MediaFormat format = extractor.getTrackFormat(0);
long totalDurationUs = format.getLong(MediaFormat.KEY_DURATION);
String mime = requireAudio(format.getString(MediaFormat.KEY_MIME));
MediaCodec codec = MediaCodec.createDecoderByType(mime);
MediaExtractor extractor = dataSource.createExtractor();
if (extractor.getTrackCount() == 0) {
throw new IOException("No audio track");
}
MediaFormat format = extractor.getTrackFormat(0);
if (!format.containsKey(MediaFormat.KEY_DURATION)) {
throw new IOException("Unknown duration");
}
long totalDurationUs = format.getLong(MediaFormat.KEY_DURATION);
String mime = format.getString(MediaFormat.KEY_MIME);
if (!mime.startsWith("audio/")) {
throw new IOException("Mime not audio");
}
MediaCodec codec = MediaCodec.createDecoderByType(mime);
if (totalDurationUs == 0) {
throw new IOException("Zero duration");
}
codec.configure(format, null, null, 0);
codec.start();
@ -158,29 +218,24 @@ public final class AudioWaveForm {
}
ByteBuffer buf = codecOutputBuffers[outputBufferIndex];
int barIndex = (int) ((wave.length * info.presentationTimeUs) / totalDurationUs) - 1;
int barIndex = (int) ((wave.length * info.presentationTimeUs) / totalDurationUs);
long total = 0;
for (int i = 0; i < info.size; i += 2 * 4) {
short aShort = buf.getShort(i);
total += Math.abs(aShort);
}
if (barIndex > 0) {
if (barIndex >= 0 && barIndex < wave.length) {
wave[barIndex] += total;
waveSamples[barIndex] += info.size / 2;
}
codec.releaseOutputBuffer(outputBufferIndex, false);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.d(TAG, "saw output EOS.");
sawOutputEOS = true;
}
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
codecOutputBuffers = codec.getOutputBuffers();
Log.d(TAG, "output buffers have changed.");
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat oformat = codec.getOutputFormat();
Log.d(TAG, "output format has changed to " + oformat);
} else {
Log.d(TAG, "dequeueOutputBuffer returned " + outputBufferIndex);
Log.d(TAG, "output format has changed to " + codec.getOutputFormat());
}
} while (outputBufferIndex >= 0);
}
@ -189,36 +244,46 @@ public final class AudioWaveForm {
codec.release();
extractor.release();
float[] floats = new float[AudioWaveForm.BARS];
float max = 0;
for (int i = 0; i < AudioWaveForm.BARS; i++) {
float[] floats = new float[BAR_COUNT];
byte[] bytes = new byte[BAR_COUNT];
float max = 0;
for (int i = 0; i < BAR_COUNT; i++) {
if (waveSamples[i] == 0) continue;
floats[i] = wave[i] / (float) waveSamples[i];
if (floats[i] > max) {
max = floats[i];
}
}
for (int i = 0; i < AudioWaveForm.BARS; i++) {
floats[i] /= max;
for (int i = 0; i < BAR_COUNT; i++) {
float normalized = floats[i] / max;
bytes[i] = (byte) (255 * normalized);
}
return new AudioFileInfo(totalDurationUs, floats);
}
}
private static @NonNull String requireAudio(@NonNull String mime) {
if (!mime.startsWith("audio/")) {
throw new AssertionError();
return new AudioFileInfo(totalDurationUs, bytes);
}
return mime;
}
public static class AudioFileInfo {
private final long durationUs;
private final byte[] waveFormBytes;
private final float[] waveForm;
private AudioFileInfo(long durationUs, float[] waveForm) {
this.durationUs = durationUs;
this.waveForm = waveForm;
private static @NonNull AudioFileInfo fromDatabaseProtobuf(@NonNull AudioWaveFormData audioWaveForm) {
return new AudioFileInfo(audioWaveForm.getDurationUs(), audioWaveForm.getWaveForm().toByteArray());
}
private AudioFileInfo(long durationUs, byte[] waveFormBytes) {
this.durationUs = durationUs;
this.waveFormBytes = waveFormBytes;
this.waveForm = new float[waveFormBytes.length];
for (int i = 0; i < waveFormBytes.length; i++) {
int unsigned = waveFormBytes[i] & 0xff;
this.waveForm[i] = unsigned / 255f;
}
}
public long getDuration(@NonNull TimeUnit timeUnit) {
@ -228,5 +293,12 @@ public final class AudioWaveForm {
public float[] getWaveForm() {
return waveForm;
}
private @NonNull AudioWaveFormData toDatabaseProtobuf() {
return AudioWaveFormData.newBuilder()
.setDurationUs(durationUs)
.setWaveForm(ByteString.copyFrom(waveFormBytes))
.build();
}
}
}

View file

@ -8,7 +8,6 @@ import android.graphics.Rect;
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;
@ -38,7 +37,7 @@ import org.thoughtcrime.securesms.mms.AudioSlide;
import org.thoughtcrime.securesms.mms.SlideClickListener;
import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public final class AudioView extends FrameLayout implements AudioSlidePlayer.Listener {
@ -49,7 +48,6 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
private static final int REVERSE = -1;
@NonNull private final AnimatingToggle controlToggle;
@NonNull private final ViewGroup container;
@NonNull private final View progressAndPlay;
@NonNull private final LottieAnimationView playPauseButton;
@NonNull private final ImageView downloadButton;
@ -58,7 +56,6 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
private final boolean smallView;
private final boolean autoRewind;
@Nullable private final TextView timestamp;
@Nullable private final TextView duration;
@ColorInt private final int waveFormPlayedBarsColor;
@ -69,6 +66,7 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
private int backwardsCounter;
private int lottieDirection;
private boolean isPlaying;
private long durationMillis;
public AudioView(Context context) {
this(context, null);
@ -89,14 +87,12 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
inflate(context, smallView ? R.layout.audio_view_small : R.layout.audio_view, this);
this.container = findViewById(R.id.audio_widget_container);
this.controlToggle = findViewById(R.id.control_toggle);
this.playPauseButton = findViewById(R.id.play);
this.progressAndPlay = findViewById(R.id.progress_and_play);
this.downloadButton = findViewById(R.id.download);
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;
@ -108,8 +104,6 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
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) {
typedArray.recycle();
@ -132,6 +126,14 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
public void setAudio(final @NonNull AudioSlide audio,
final boolean showControls)
{
if (seekBar instanceof WaveFormSeekBarView) {
if (audioSlidePlayer != null && !Objects.equals(audioSlidePlayer.getAudioSlide().getUri(), audio.getUri())) {
WaveFormSeekBarView waveFormView = (WaveFormSeekBarView) seekBar;
waveFormView.setWaveMode(false);
seekBar.setProgress(0);
durationMillis = 0;
}
}
if (showControls && audio.isPendingDownload()) {
controlToggle.displayQuick(downloadButton);
@ -157,14 +159,14 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
WaveFormSeekBarView waveFormView = (WaveFormSeekBarView) seekBar;
waveFormView.setColors(waveFormPlayedBarsColor, waveFormUnplayedBarsColor);
if (android.os.Build.VERSION.SDK_INT >= 23) {
new AudioWaveForm(getContext(), audio).generateWaveForm(
new AudioWaveForm(getContext(), audio).getWaveForm(
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));
durationMillis = data.getDuration(TimeUnit.MILLISECONDS);
updateProgress(0, 0);
duration.setVisibility(VISIBLE);
}
waveFormView.setWaveData(data.getWaveForm());
},
e -> waveFormView.setWaveMode(false));
} else {
@ -243,10 +245,9 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
}
private void updateProgress(float progress, long millis) {
if (timestamp != null) {
timestamp.setText(String.format(Locale.getDefault(), "%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(millis),
TimeUnit.MILLISECONDS.toSeconds(millis)));
if (duration != null && durationMillis > 0) {
long remainingSecs = TimeUnit.MILLISECONDS.toSeconds(durationMillis - millis);
duration.setText(getResources().getString(R.string.AudioView_duration, remainingSecs / 60, remainingSecs % 60));
}
if (smallView) {
@ -262,9 +263,6 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
this.downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
this.circleProgress.setBarColor(foregroundTint);
if (this.timestamp != null) {
this.timestamp.setTextColor(foregroundTint);
}
if (this.duration != null) {
this.duration.setTextColor(foregroundTint);
}
@ -372,7 +370,12 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis
private boolean wasPlaying;
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser && durationMillis > 0) {
float progressFloat = progress / (float) seekBar.getMax();
updateProgress(progressFloat, (long) (durationMillis * progressFloat));
}
}
@Override
public synchronized void onStartTrackingTouch(SeekBar seekBar) {

View file

@ -88,9 +88,8 @@ public final class WaveFormSeekBarView extends AppCompatSeekBar {
if (!Arrays.equals(data, this.data)) {
this.data = data;
this.dataSetTime = System.currentTimeMillis();
setWaveMode(data.length > 0);
invalidate();
}
setWaveMode(data.length > 0);
}
public void setWaveMode(boolean waveMode) {
@ -101,7 +100,9 @@ public final class WaveFormSeekBarView extends AppCompatSeekBar {
@Override
protected void onDraw(Canvas canvas) {
drawWave(canvas);
if (waveMode) {
drawWave(canvas);
}
super.onDraw(canvas);
}

View file

@ -643,7 +643,7 @@ public class Contact implements Parcelable {
private static Attachment attachmentFromUri(@Nullable Uri uri) {
if (uri == null) return null;
return new UriAttachment(uri, MediaUtil.IMAGE_JPEG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, false, false, null, null, null, null);
return new UriAttachment(uri, MediaUtil.IMAGE_JPEG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, false, false, null, null, null, null, null);
}
@Override

View file

@ -31,6 +31,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.bumptech.glide.Glide;
import com.fasterxml.jackson.annotation.JsonCreator;
@ -44,12 +45,14 @@ import org.json.JSONException;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.audio.AudioHash;
import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.MediaStream;
import org.thoughtcrime.securesms.mms.MmsException;
@ -120,7 +123,7 @@ public class AttachmentDatabase extends Database {
static final String HEIGHT = "height";
static final String CAPTION = "caption";
private static final String DATA_HASH = "data_hash";
static final String BLUR_HASH = "blur_hash";
static final String VISUAL_HASH = "blur_hash";
static final String TRANSFORM_PROPERTIES = "transform_properties";
static final String DISPLAY_ORDER = "display_order";
static final String UPLOAD_TIMESTAMP = "upload_timestamp";
@ -145,7 +148,7 @@ public class AttachmentDatabase extends Database {
THUMBNAIL_ASPECT_RATIO, UNIQUE_ID, DIGEST,
FAST_PREFLIGHT_ID, VOICE_NOTE, QUOTE, DATA_RANDOM,
THUMBNAIL_RANDOM, WIDTH, HEIGHT, CAPTION, STICKER_PACK_ID,
STICKER_PACK_KEY, STICKER_ID, DATA_HASH, BLUR_HASH,
STICKER_PACK_KEY, STICKER_ID, DATA_HASH, VISUAL_HASH,
TRANSFORM_PROPERTIES, TRANSFER_FILE, DISPLAY_ORDER,
UPLOAD_TIMESTAMP };
@ -182,7 +185,7 @@ public class AttachmentDatabase extends Database {
STICKER_PACK_KEY + " DEFAULT NULL, " +
STICKER_ID + " INTEGER DEFAULT -1, " +
DATA_HASH + " TEXT DEFAULT NULL, " +
BLUR_HASH + " TEXT DEFAULT NULL, " +
VISUAL_HASH + " TEXT DEFAULT NULL, " +
TRANSFORM_PROPERTIES + " TEXT DEFAULT NULL, " +
TRANSFER_FILE + " TEXT DEFAULT NULL, " +
DISPLAY_ORDER + " INTEGER DEFAULT 0, " +
@ -417,7 +420,7 @@ public class AttachmentDatabase extends Database {
values.put(WIDTH, 0);
values.put(HEIGHT, 0);
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
values.put(BLUR_HASH, (String) null);
values.put(VISUAL_HASH, (String) null);
values.put(CONTENT_TYPE, MediaUtil.VIEW_ONCE);
database.update(TABLE_NAME, values, MMS_ID + " = ?", new String[] {mmsId + ""});
@ -530,8 +533,9 @@ public class AttachmentDatabase extends Database {
values.put(DATA_HASH, dataInfo.hash);
}
if (placeholder != null && placeholder.getBlurHash() != null) {
values.put(BLUR_HASH, placeholder.getBlurHash().getHash());
String visualHashString = getVisualHashStringOrNull(placeholder);
if (visualHashString != null) {
values.put(VISUAL_HASH, visualHashString);
}
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
@ -555,9 +559,11 @@ public class AttachmentDatabase extends Database {
thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId, STANDARD_THUMB_TIME));
}
private static @Nullable String getBlurHashStringOrNull(@Nullable BlurHash blurHash) {
if (blurHash == null) return null;
return blurHash.getHash();
private static @Nullable String getVisualHashStringOrNull(@Nullable Attachment attachment) {
if (attachment == null) return null;
else if (attachment.getBlurHash() != null) return attachment.getBlurHash().getHash();
else if (attachment.getAudioHash() != null) return attachment.getAudioHash().getHash();
else return null;
}
public void copyAttachmentData(@NonNull AttachmentId sourceId, @NonNull AttachmentId destinationId)
@ -594,7 +600,7 @@ public class AttachmentDatabase extends Database {
contentValues.put(WIDTH, sourceAttachment.getWidth());
contentValues.put(HEIGHT, sourceAttachment.getHeight());
contentValues.put(CONTENT_TYPE, sourceAttachment.getContentType());
contentValues.put(BLUR_HASH, getBlurHashStringOrNull(sourceAttachment.getBlurHash()));
contentValues.put(VISUAL_HASH, getVisualHashStringOrNull(sourceAttachment));
database.update(TABLE_NAME, contentValues, PART_ID_WHERE, destinationId.toStrings());
}
@ -638,7 +644,7 @@ public class AttachmentDatabase extends Database {
values.put(NAME, attachment.getRelay());
values.put(SIZE, attachment.getSize());
values.put(FAST_PREFLIGHT_ID, attachment.getFastPreflightId());
values.put(BLUR_HASH, getBlurHashStringOrNull(attachment.getBlurHash()));
values.put(VISUAL_HASH, getVisualHashStringOrNull(attachment));
values.put(UPLOAD_TIMESTAMP, uploadTimestamp);
if (dataInfo != null && dataInfo.hash != null) {
@ -1099,11 +1105,12 @@ public class AttachmentDatabase extends Database {
JsonUtils.SaneJSONObject object = new JsonUtils.SaneJSONObject(array.getJSONObject(i));
if (!object.isNull(ROW_ID)) {
String contentType = object.getString(CONTENT_TYPE);
result.add(new DatabaseAttachment(new AttachmentId(object.getLong(ROW_ID), object.getLong(UNIQUE_ID)),
object.getLong(MMS_ID),
!TextUtils.isEmpty(object.getString(DATA)),
!TextUtils.isEmpty(object.getString(THUMBNAIL)),
object.getString(CONTENT_TYPE),
contentType,
object.getInt(TRANSFER_STATE),
object.getLong(SIZE),
object.getString(FILE_NAME),
@ -1123,7 +1130,8 @@ public class AttachmentDatabase extends Database {
object.getString(STICKER_PACK_KEY),
object.getInt(STICKER_ID))
: null,
BlurHash.parseOrNull(object.getString(BLUR_HASH)),
MediaUtil.isAudioType(contentType) ? null : BlurHash.parseOrNull(object.getString(VISUAL_HASH)),
MediaUtil.isAudioType(contentType) ? AudioHash.parseOrNull(object.getString(VISUAL_HASH)) : null,
TransformProperties.parse(object.getString(TRANSFORM_PROPERTIES)),
object.getInt(DISPLAY_ORDER),
object.getLong(UPLOAD_TIMESTAMP)));
@ -1132,12 +1140,13 @@ public class AttachmentDatabase extends Database {
return result;
} else {
String contentType = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE));
return Collections.singletonList(new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)),
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
!cursor.isNull(cursor.getColumnIndexOrThrow(DATA)),
!cursor.isNull(cursor.getColumnIndexOrThrow(THUMBNAIL)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)),
contentType,
cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)),
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)),
@ -1157,7 +1166,8 @@ public class AttachmentDatabase extends Database {
cursor.getString(cursor.getColumnIndexOrThrow(STICKER_PACK_KEY)),
cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)))
: null,
BlurHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(BLUR_HASH))),
MediaUtil.isAudioType(contentType) ? null : BlurHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))),
MediaUtil.isAudioType(contentType) ? AudioHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))) : null,
TransformProperties.parse(cursor.getString(cursor.getColumnIndexOrThrow(TRANSFORM_PROPERTIES))),
cursor.getInt(cursor.getColumnIndexOrThrow(DISPLAY_ORDER)),
cursor.getLong(cursor.getColumnIndexOrThrow(UPLOAD_TIMESTAMP))));
@ -1167,7 +1177,6 @@ public class AttachmentDatabase extends Database {
}
}
private AttachmentId insertAttachment(long mmsId, Attachment attachment, boolean quote)
throws MmsException
{
@ -1219,11 +1228,11 @@ public class AttachmentDatabase extends Database {
contentValues.put(CAPTION, attachment.getCaption());
contentValues.put(UPLOAD_TIMESTAMP, useTemplateUpload ? template.getUploadTimestamp() : attachment.getUploadTimestamp());
if (attachment.getTransformProperties().isVideoEdited()) {
contentValues.putNull(BLUR_HASH);
contentValues.putNull(VISUAL_HASH);
contentValues.put(TRANSFORM_PROPERTIES, attachment.getTransformProperties().serialize());
thumbnailTimeUs = Math.max(STANDARD_THUMB_TIME, attachment.getTransformProperties().videoTrimStartTimeUs);
} else {
contentValues.put(BLUR_HASH, getBlurHashStringOrNull(template.getBlurHash()));
contentValues.put(VISUAL_HASH, getVisualHashStringOrNull(template));
contentValues.put(TRANSFORM_PROPERTIES, template.getTransformProperties().serialize());
thumbnailTimeUs = STANDARD_THUMB_TIME;
}
@ -1330,6 +1339,21 @@ public class AttachmentDatabase extends Database {
}
}
@WorkerThread
public void writeAudioHash(@NonNull AttachmentId attachmentId, @Nullable AudioWaveFormData audioWaveForm) {
Log.i(TAG, "updating part audio wave form for #" + attachmentId);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues(1);
if (audioWaveForm != null) {
values.put(VISUAL_HASH, new AudioHash(audioWaveForm).getHash());
} else {
values.putNull(VISUAL_HASH);
}
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
}
@VisibleForTesting
class ThumbnailFetchCallable implements Callable<InputStream> {

View file

@ -43,7 +43,7 @@ public class MediaDatabase extends Database {
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_ID + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_KEY + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.BLUR_HASH + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VISUAL_HASH + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFORM_PROPERTIES + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CAPTION + ", "

View file

@ -218,7 +218,7 @@ public class MmsDatabase extends MessagingDatabase {
"'" + AttachmentDatabase.STICKER_PACK_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_ID+ ", " +
"'" + AttachmentDatabase.STICKER_PACK_KEY + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_KEY + ", " +
"'" + AttachmentDatabase.STICKER_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ", " +
"'" + AttachmentDatabase.BLUR_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.BLUR_HASH + ", " +
"'" + AttachmentDatabase.VISUAL_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VISUAL_HASH + ", " +
"'" + AttachmentDatabase.TRANSFORM_PROPERTIES + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFORM_PROPERTIES + ", " +
"'" + AttachmentDatabase.DISPLAY_ORDER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", " +
"'" + AttachmentDatabase.UPLOAD_TIMESTAMP + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UPLOAD_TIMESTAMP +

View file

@ -28,7 +28,6 @@ import net.sqlcipher.database.SQLiteQueryBuilder;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.libsignal.util.Pair;
@ -387,7 +386,7 @@ public class MmsSmsDatabase extends Database {
"'" + AttachmentDatabase.STICKER_PACK_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_ID + ", " +
"'" + AttachmentDatabase.STICKER_PACK_KEY + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_KEY + ", " +
"'" + AttachmentDatabase.STICKER_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ", " +
"'" + AttachmentDatabase.BLUR_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.BLUR_HASH + ", " +
"'" + AttachmentDatabase.VISUAL_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VISUAL_HASH + ", " +
"'" + AttachmentDatabase.TRANSFORM_PROPERTIES + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFORM_PROPERTIES + ", " +
"'" + AttachmentDatabase.DISPLAY_ORDER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", " +
"'" + AttachmentDatabase.UPLOAD_TIMESTAMP + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UPLOAD_TIMESTAMP +

View file

@ -138,7 +138,7 @@ final class GroupManagerV1 {
if (avatar != null) {
Uri avatarUri = BlobProvider.getInstance().forData(avatar).createForSingleUseInMemory();
avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null, null, null);
avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null, null, null, null);
}
OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, false, null, Collections.emptyList(), Collections.emptyList());

View file

@ -229,7 +229,7 @@ public class MmsDownloadJob extends BaseJob {
attachments.add(new UriAttachment(uri, Util.toIsoString(part.getContentType()),
AttachmentDatabase.TRANSFER_PROGRESS_DONE,
part.getData().length, name, false, false, null, null, null, null));
part.getData().length, name, false, false, null, null, null, null, null));
}
}
}

View file

@ -1634,6 +1634,7 @@ public final class PushProcessMessageJob extends BaseJob {
null,
stickerLocator,
null,
null,
null));
} else {
return Optional.of(PointerAttachment.forPointer(Optional.of(sticker.get().getAttachment()), stickerLocator).get());

View file

@ -176,6 +176,7 @@ public class LinkPreviewRepository {
null,
null,
null,
null,
null));
callback.onComplete(thumbnail);
@ -250,6 +251,7 @@ public class LinkPreviewRepository {
null,
null,
null,
null,
null));
callback.onComplete(Optional.of(new LinkPreview(packUrl, title, thumbnail)));

View file

@ -35,11 +35,11 @@ import org.thoughtcrime.securesms.util.ResUtil;
public class AudioSlide extends Slide {
public AudioSlide(Context context, Uri uri, long dataSize, boolean voiceNote) {
super(context, constructAttachmentFromUri(context, uri, MediaUtil.AUDIO_UNSPECIFIED, dataSize, 0, 0, false, null, null, null, null, voiceNote, false));
super(context, constructAttachmentFromUri(context, uri, MediaUtil.AUDIO_UNSPECIFIED, dataSize, 0, 0, false, null, null, null, null, null, voiceNote, false));
}
public AudioSlide(Context context, Uri uri, long dataSize, String contentType, boolean voiceNote) {
super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, 0, 0, null, null, voiceNote, false, null, null, null, null));
super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, 0, 0, null, null, voiceNote, false, null, null, null, null, null));
}
public AudioSlide(Context context, Attachment attachment) {

View file

@ -20,7 +20,7 @@ public class DocumentSlide extends Slide {
@NonNull String contentType, long size,
@Nullable String fileName)
{
super(context, constructAttachmentFromUri(context, uri, contentType, size, 0, 0, true, StorageUtil.getCleanFileName(fileName), null, null, null, false, false));
super(context, constructAttachmentFromUri(context, uri, contentType, size, 0, 0, true, StorageUtil.getCleanFileName(fileName), null, null, null, null, false, false));
}
@Override

View file

@ -20,7 +20,7 @@ public class GifSlide extends ImageSlide {
}
public GifSlide(Context context, Uri uri, long size, int width, int height, @Nullable String caption) {
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_GIF, size, width, height, true, null, caption, null, null, false, false));
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_GIF, size, width, height, true, null, caption, null, null, null, false, false));
}
@Override

View file

@ -42,7 +42,7 @@ public class ImageSlide extends Slide {
}
public ImageSlide(Context context, Uri uri, long size, int width, int height, @Nullable String caption, @Nullable BlurHash blurHash) {
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_JPEG, size, width, height, true, null, caption, null, blurHash, false, false));
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_JPEG, size, width, height, true, null, caption, null, blurHash, null, false, false));
}
@Override

View file

@ -26,6 +26,7 @@ import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
import org.thoughtcrime.securesms.audio.AudioHash;
import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.stickers.StickerLocator;
@ -155,10 +156,11 @@ public abstract class Slide {
@Nullable String caption,
@Nullable StickerLocator stickerLocator,
@Nullable BlurHash blurHash,
@Nullable AudioHash audioHash,
boolean voiceNote,
boolean quote)
{
return constructAttachmentFromUri(context, uri, defaultMime, size, width, height, hasThumbnail, fileName, caption, stickerLocator, blurHash, voiceNote, quote, null);
return constructAttachmentFromUri(context, uri, defaultMime, size, width, height, hasThumbnail, fileName, caption, stickerLocator, blurHash, audioHash, voiceNote, quote, null);
}
protected static Attachment constructAttachmentFromUri(@NonNull Context context,
@ -172,6 +174,7 @@ public abstract class Slide {
@Nullable String caption,
@Nullable StickerLocator stickerLocator,
@Nullable BlurHash blurHash,
@Nullable AudioHash audioHash,
boolean voiceNote,
boolean quote,
@Nullable AttachmentDatabase.TransformProperties transformProperties)
@ -192,6 +195,7 @@ public abstract class Slide {
caption,
stickerLocator,
blurHash,
audioHash,
transformProperties);
}

View file

@ -23,7 +23,7 @@ public class StickerSlide extends Slide {
}
public StickerSlide(Context context, Uri uri, long size, @NonNull StickerLocator stickerLocator) {
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_WEBP, size, WIDTH, HEIGHT, true, null, null, stickerLocator, null, false, false));
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_WEBP, size, WIDTH, HEIGHT, true, null, null, stickerLocator, null, null, false, false));
}
@Override

View file

@ -17,6 +17,6 @@ public class TextSlide extends Slide {
}
public TextSlide(@NonNull Context context, @NonNull Uri uri, @Nullable String filename, long size) {
super(context, constructAttachmentFromUri(context, uri, MediaUtil.LONG_TEXT, size, 0, 0, true, filename, null, null, null, false, false));
super(context, constructAttachmentFromUri(context, uri, MediaUtil.LONG_TEXT, size, 0, 0, true, filename, null, null, null, null, false, false));
}
}

View file

@ -37,7 +37,7 @@ public class VideoSlide extends Slide {
}
public VideoSlide(Context context, Uri uri, long dataSize, @Nullable String caption, @Nullable AttachmentDatabase.TransformProperties transformProperties) {
super(context, constructAttachmentFromUri(context, uri, MediaUtil.VIDEO_UNSPECIFIED, dataSize, 0, 0, MediaUtil.hasVideoThumbnail(uri), null, caption, null, null, false, false, transformProperties));
super(context, constructAttachmentFromUri(context, uri, MediaUtil.VIDEO_UNSPECIFIED, dataSize, 0, 0, MediaUtil.hasVideoThumbnail(uri), null, caption, null, null, null, false, false, transformProperties));
}
public VideoSlide(Context context, Attachment attachment) {

View file

@ -0,0 +1,39 @@
package org.thoughtcrime.securesms.util.concurrent;
import androidx.annotation.NonNull;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.Executor;
/**
* From https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html
*/
public final class SerialExecutor implements Executor {
private final Queue<Runnable> tasks = new ArrayDeque<>();
private final Executor executor;
private Runnable active;
public SerialExecutor(@NonNull Executor executor) {
this.executor = executor;
}
public synchronized void execute(final Runnable r) {
tasks.offer(() -> {
try {
r.run();
} finally {
scheduleNext();
}
});
if (active == null) {
scheduleNext();
}
}
private synchronized void scheduleNext() {
if ((active = tasks.poll()) != null) {
executor.execute(active);
}
}
}

View file

@ -41,3 +41,8 @@ message TemporalAuthCredentialResponse {
message TemporalAuthCredentialResponses {
repeated TemporalAuthCredentialResponse credentialResponse = 1;
}
message AudioWaveFormData {
int64 durationUs = 1;
bytes waveForm = 2;
}

View file

@ -4,58 +4,36 @@
tools:context="org.thoughtcrime.securesms.components.AudioView">
<LinearLayout
android:id="@+id/audio_widget_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:orientation="horizontal"
tools:background="#ff00ff">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<include layout="@layout/audio_view_circle" />
<include layout="@layout/audio_view_circle" />
<org.thoughtcrime.securesms.components.WaveFormSeekBarView
android:id="@+id/seek"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingStart="12dp"
android:paddingEnd="8dp"
tools:progress="50" />
<TextView
android:id="@+id/duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:fontFamily="sans-serif-light"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?conversation_item_sent_text_secondary_color"
android:textSize="@dimen/conversation_item_date_text_size"
android:visibility="gone"
tools:text="00:30"
tools:visibility="visible" />
</LinearLayout>
<org.thoughtcrime.securesms.components.WaveFormSeekBarView
android:id="@+id/seek"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:paddingStart="12dp"
android:paddingTop="10dp"
android:paddingEnd="8dp"
android:paddingBottom="10dp"
tools:progress="50" />
<TextView
android:id="@+id/timestamp"
android:id="@+id/duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="76dip"
android:autoLink="none"
android:layout_gravity="center_vertical"
android:fontFamily="sans-serif-light"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?conversation_item_sent_text_secondary_color"
android:textSize="@dimen/conversation_item_date_text_size"
android:visibility="gone"
tools:text="00:15"
tools:text="00:30"
tools:visibility="visible" />
</LinearLayout>

View file

@ -4,7 +4,6 @@
tools:context="org.thoughtcrime.securesms.components.AudioView">
<LinearLayout
android:id="@+id/audio_widget_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"

View file

@ -39,7 +39,6 @@
android:visibility="gone"
android:paddingTop="15dp"
android:paddingBottom="15dp"
app:widgetBackground="?conversation_item_bubble_background"
app:foregroundTintColor="@color/grey_500"
app:backgroundTintColor="?conversation_item_bubble_background"/>

View file

@ -336,7 +336,6 @@
</declare-styleable>
<declare-styleable name="AudioView">
<attr name="widgetBackground" format="color"/>
<attr name="foregroundTintColor" format="color" />
<attr name="backgroundTintColor" format="color" />
<attr name="waveformPlayedBarsColor" format="color" />