diff --git a/app/build.gradle b/app/build.gradle
index 289060fb22..2cb1df0a9c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -443,9 +443,9 @@ dependencies {
implementation 'com.google.android.gms:play-services-maps:16.1.0'
implementation 'com.google.android.gms:play-services-auth:16.0.1'
- implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
- implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
- implementation 'com.google.android.exoplayer:extension-mediasession:2.9.1'
+ implementation 'com.google.android.exoplayer:exoplayer-core:2.15.0'
+ implementation 'com.google.android.exoplayer:exoplayer-ui:2.15.0'
+ implementation 'com.google.android.exoplayer:extension-mediasession:2.15.0'
implementation 'org.conscrypt:conscrypt-android:2.0.0'
implementation 'org.signal:aesgcmprovider:0.0.3'
diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java
index b638a8dba7..59b3cbd179 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java
@@ -1,8 +1,6 @@
package org.thoughtcrime.securesms;
-import android.graphics.Point;
import android.net.Uri;
-import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
@@ -12,7 +10,6 @@ import androidx.lifecycle.Observer;
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.contactshare.Contact;
-import org.thoughtcrime.securesms.conversation.ConversationItem;
import org.thoughtcrime.securesms.conversation.ConversationMessage;
import org.thoughtcrime.securesms.conversation.colors.Colorizable;
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
@@ -29,7 +26,6 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.stickers.StickerLocator;
-import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.List;
@@ -49,11 +45,10 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
boolean pulseMention,
boolean hasWallpaper,
boolean isMessageRequestAccepted,
- @NonNull AttachmentMediaSourceFactory attachmentMediaSourceFactory,
boolean canPlayInline,
@NonNull Colorizer colorizer);
- ConversationMessage getConversationMessage();
+ @NonNull ConversationMessage getConversationMessage();
void setEventListener(@Nullable EventListener listener);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java
index 2b232e79b6..61d2443860 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java
@@ -2,15 +2,12 @@ package org.thoughtcrime.securesms.components.voice;
import android.content.ComponentName;
import android.media.AudioManager;
-import android.media.session.PlaybackState;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.RemoteException;
import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
@@ -45,9 +42,9 @@ import java.util.Objects;
*/
public class VoiceNoteMediaController implements DefaultLifecycleObserver {
- public static final String EXTRA_THREAD_ID = "voice.note.thread_id";
- public static final String EXTRA_MESSAGE_ID = "voice.note.message_id";
- public static final String EXTRA_PROGRESS = "voice.note.playhead";
+ public static final String EXTRA_THREAD_ID = "voice.note.thread_id";
+ public static final String EXTRA_MESSAGE_ID = "voice.note.message_id";
+ public static final String EXTRA_PROGRESS = "voice.note.playhead";
public static final String EXTRA_PLAY_SINGLE = "voice.note.play.single";
private static final String TAG = Log.tag(VoiceNoteMediaController.class);
@@ -77,7 +74,7 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
LiveRecipient threadRecipient = Recipient.live(message.getThreadRecipientId());
LiveData name = LiveDataUtil.combineLatest(sender.getLiveDataResolved(),
threadRecipient.getLiveDataResolved(),
- (s, t) -> VoiceNoteMediaDescriptionCompatFactory.getTitle(activity, s, t, null));
+ (s, t) -> VoiceNoteMediaItemFactory.getTitle(activity, s, t, null));
return Transformations.map(name, displayName -> Optional.of(
new VoiceNotePlayerView.State(
@@ -262,32 +259,28 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
private final class ConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
@Override
public void onConnected() {
- try {
- MediaSessionCompat.Token token = mediaBrowser.getSessionToken();
- MediaControllerCompat mediaController = new MediaControllerCompat(activity, token);
+ MediaSessionCompat.Token token = mediaBrowser.getSessionToken();
+ MediaControllerCompat mediaController = new MediaControllerCompat(activity, token);
- MediaControllerCompat.setMediaController(activity, mediaController);
+ MediaControllerCompat.setMediaController(activity, mediaController);
- MediaMetadataCompat mediaMetadataCompat = mediaController.getMetadata();
- if (canExtractPlaybackInformationFromMetadata(mediaMetadataCompat)) {
- VoiceNotePlaybackState newState = extractStateFromMetadata(mediaController, mediaMetadataCompat, null);
+ MediaMetadataCompat mediaMetadataCompat = mediaController.getMetadata();
+ if (canExtractPlaybackInformationFromMetadata(mediaMetadataCompat)) {
+ VoiceNotePlaybackState newState = extractStateFromMetadata(mediaController, mediaMetadataCompat, null);
- if (newState != null) {
- voiceNotePlaybackState.postValue(newState);
- } else {
- voiceNotePlaybackState.postValue(VoiceNotePlaybackState.NONE);
- }
+ if (newState != null) {
+ voiceNotePlaybackState.postValue(newState);
+ } else {
+ voiceNotePlaybackState.postValue(VoiceNotePlaybackState.NONE);
}
-
- cleanUpOldProximityWakeLockManager();
- voiceNoteProximityWakeLockManager = new VoiceNoteProximityWakeLockManager(activity, mediaController);
-
- mediaController.registerCallback(mediaControllerCompatCallback);
-
- mediaControllerCompatCallback.onPlaybackStateChanged(mediaController.getPlaybackState());
- } catch (RemoteException e) {
- Log.w(TAG, "onConnected: Failed to set media controller", e);
}
+
+ cleanUpOldProximityWakeLockManager();
+ voiceNoteProximityWakeLockManager = new VoiceNoteProximityWakeLockManager(activity, mediaController);
+
+ mediaController.registerCallback(mediaControllerCompatCallback);
+
+ mediaControllerCompatCallback.onPlaybackStateChanged(mediaController.getPlaybackState());
}
@Override
@@ -312,8 +305,8 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
}
private static boolean canExtractPlaybackInformationFromMetadata(@Nullable MediaMetadataCompat mediaMetadataCompat) {
- return mediaMetadataCompat != null &&
- mediaMetadataCompat.getDescription() != null &&
+ return mediaMetadataCompat != null &&
+ mediaMetadataCompat.getDescription() != null &&
mediaMetadataCompat.getDescription().getMediaUri() != null;
}
@@ -322,7 +315,7 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
@Nullable VoiceNotePlaybackState previousState)
{
Uri mediaUri = Objects.requireNonNull(mediaMetadataCompat.getDescription().getMediaUri());
- boolean autoReset = Objects.equals(mediaUri, VoiceNotePlaybackPreparer.NEXT_URI) || Objects.equals(mediaUri, VoiceNotePlaybackPreparer.END_URI);
+ boolean autoReset = Objects.equals(mediaUri, VoiceNoteMediaItemFactory.NEXT_URI) || Objects.equals(mediaUri, VoiceNoteMediaItemFactory.END_URI);
long position = mediaController.getPlaybackState().getPosition();
long duration = mediaMetadataCompat.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
Bundle extras = mediaController.getExtras();
@@ -384,17 +377,17 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
long timestamp = -1L;
if (mediaExtras != null) {
- messageId = mediaExtras.getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_MESSAGE_ID, -1L);
- messagePosition = mediaExtras.getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_MESSAGE_POSITION, -1L);
- threadId = mediaExtras.getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_THREAD_ID, -1L);
- timestamp = mediaExtras.getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_MESSAGE_TIMESTAMP, -1L);
+ messageId = mediaExtras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_ID, -1L);
+ messagePosition = mediaExtras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_POSITION, -1L);
+ threadId = mediaExtras.getLong(VoiceNoteMediaItemFactory.EXTRA_THREAD_ID, -1L);
+ timestamp = mediaExtras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_TIMESTAMP, -1L);
- String serializedSenderId = mediaExtras.getString(VoiceNoteMediaDescriptionCompatFactory.EXTRA_INDIVIDUAL_RECIPIENT_ID);
+ String serializedSenderId = mediaExtras.getString(VoiceNoteMediaItemFactory.EXTRA_INDIVIDUAL_RECIPIENT_ID);
if (serializedSenderId != null) {
senderId = RecipientId.from(serializedSenderId);
}
- String serializedThreadRecipientId = mediaExtras.getString(VoiceNoteMediaDescriptionCompatFactory.EXTRA_THREAD_RECIPIENT_ID);
+ String serializedThreadRecipientId = mediaExtras.getString(VoiceNoteMediaItemFactory.EXTRA_THREAD_RECIPIENT_ID);
if (serializedThreadRecipientId != null) {
threadRecipientId = RecipientId.from(serializedThreadRecipientId);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaDescriptionCompatFactory.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaItemFactory.java
similarity index 53%
rename from app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaDescriptionCompatFactory.java
rename to app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaItemFactory.java
index 67903ab3c4..4139c1d1cc 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaDescriptionCompatFactory.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaItemFactory.java
@@ -9,6 +9,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
+
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@@ -24,9 +27,9 @@ import java.util.Locale;
import java.util.Objects;
/**
- * Factory responsible for building out MediaDescriptionCompat objects for voice notes.
+ * Factory responsible for building out MediaItem objects for voice notes.
*/
-class VoiceNoteMediaDescriptionCompatFactory {
+class VoiceNoteMediaItemFactory {
public static final String EXTRA_MESSAGE_POSITION = "voice.note.extra.MESSAGE_POSITION";
public static final String EXTRA_THREAD_RECIPIENT_ID = "voice.note.extra.RECIPIENT_ID";
@@ -37,13 +40,16 @@ class VoiceNoteMediaDescriptionCompatFactory {
public static final String EXTRA_MESSAGE_ID = "voice.note.extra.MESSAGE_ID";
public static final String EXTRA_MESSAGE_TIMESTAMP = "voice.note.extra.MESSAGE_TIMESTAMP";
- private static final String TAG = Log.tag(VoiceNoteMediaDescriptionCompatFactory.class);
+ public static final Uri NEXT_URI = Uri.parse("file:///android_asset/sounds/state-change_confirm-down.ogg");
+ public static final Uri END_URI = Uri.parse("file:///android_asset/sounds/state-change_confirm-up.ogg");
- private VoiceNoteMediaDescriptionCompatFactory() {}
+ private static final String TAG = Log.tag(VoiceNoteMediaItemFactory.class);
- static MediaDescriptionCompat buildMediaDescription(@NonNull Context context,
- long threadId,
- @NonNull Uri draftUri)
+ private VoiceNoteMediaItemFactory() {}
+
+ static MediaItem buildMediaItem(@NonNull Context context,
+ long threadId,
+ @NonNull Uri draftUri)
{
Recipient threadRecipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
@@ -51,28 +57,28 @@ class VoiceNoteMediaDescriptionCompatFactory {
threadRecipient = Recipient.UNKNOWN;
}
- return buildMediaDescription(context,
- threadRecipient,
- Recipient.self(),
- Recipient.self(),
- 0,
- threadId,
- -1,
- System.currentTimeMillis(),
- draftUri);
+ return buildMediaItem(context,
+ threadRecipient,
+ Recipient.self(),
+ Recipient.self(),
+ 0,
+ threadId,
+ -1,
+ System.currentTimeMillis(),
+ draftUri);
}
/**
- * Build out a MediaDescriptionCompat for a given voice note. Expects to be run
+ * Build out a MediaItem for a given voice note. Expects to be run
* on a background thread.
*
* @param context Context.
* @param messageRecord The MessageRecord of the given voice note.
- * @return A MediaDescriptionCompat with all the details the service expects.
+ * @return A MediaItem with all the details the service expects.
*/
@WorkerThread
- @Nullable static MediaDescriptionCompat buildMediaDescription(@NonNull Context context,
- @NonNull MessageRecord messageRecord)
+ @Nullable static MediaItem buildMediaItem(@NonNull Context context,
+ @NonNull MessageRecord messageRecord)
{
int startingPosition = DatabaseFactory.getMmsSmsDatabase(context)
.getMessagePositionInConversation(messageRecord.getThreadId(),
@@ -95,26 +101,26 @@ class VoiceNoteMediaDescriptionCompatFactory {
return null;
}
- return buildMediaDescription(context,
- threadRecipient,
- avatarRecipient,
- sender,
- startingPosition,
- messageRecord.getThreadId(),
- messageRecord.getId(),
- messageRecord.getDateReceived(),
- uri);
+ return buildMediaItem(context,
+ threadRecipient,
+ avatarRecipient,
+ sender,
+ startingPosition,
+ messageRecord.getThreadId(),
+ messageRecord.getId(),
+ messageRecord.getDateReceived(),
+ uri);
}
- private static MediaDescriptionCompat buildMediaDescription(@NonNull Context context,
- @NonNull Recipient threadRecipient,
- @NonNull Recipient avatarRecipient,
- @NonNull Recipient sender,
- int startingPosition,
- long threadId,
- long messageId,
- long dateReceived,
- @NonNull Uri audioUri)
+ private static MediaItem buildMediaItem(@NonNull Context context,
+ @NonNull Recipient threadRecipient,
+ @NonNull Recipient avatarRecipient,
+ @NonNull Recipient sender,
+ int startingPosition,
+ long threadId,
+ long messageId,
+ long dateReceived,
+ @NonNull Uri audioUri)
{
Bundle extras = new Bundle();
extras.putString(EXTRA_THREAD_RECIPIENT_ID, threadRecipient.getId().serialize());
@@ -132,17 +138,28 @@ class VoiceNoteMediaDescriptionCompatFactory {
String subtitle = null;
if (preference.isDisplayContact()) {
- subtitle = context.getString(R.string.VoiceNoteMediaDescriptionCompatFactory__voice_message,
+ subtitle = context.getString(R.string.VoiceNoteMediaItemFactory__voice_message,
DateUtils.formatDateWithoutDayOfWeek(Locale.getDefault(),
dateReceived));
}
- return new MediaDescriptionCompat.Builder()
- .setMediaUri(audioUri)
- .setTitle(title)
- .setSubtitle(subtitle)
- .setExtras(extras)
- .build();
+ return new MediaItem.Builder()
+ .setUri(audioUri)
+ .setMediaMetadata(
+ new MediaMetadata.Builder()
+ .setTitle(title)
+ .setSubtitle(subtitle)
+ .setExtras(extras)
+ .build()
+ )
+ .setTag(
+ new MediaDescriptionCompat.Builder()
+ .setMediaUri(audioUri)
+ .setTitle(title)
+ .setSubtitle(subtitle)
+ .setExtras(extras)
+ .build())
+ .build();
}
public static @NonNull String getTitle(@NonNull Context context, @NonNull Recipient sender, @NonNull Recipient threadRecipient, @Nullable NotificationPrivacyPreference notificationPrivacyPreference) {
@@ -154,7 +171,7 @@ class VoiceNoteMediaDescriptionCompatFactory {
}
if (preference.isDisplayContact() && threadRecipient.isGroup()) {
- return context.getString(R.string.VoiceNoteMediaDescriptionCompatFactory__s_to_s,
+ return context.getString(R.string.VoiceNoteMediaItemFactory__s_to_s,
sender.getDisplayName(context),
threadRecipient.getDisplayName(context));
} else if (preference.isDisplayContact()) {
@@ -163,4 +180,28 @@ class VoiceNoteMediaDescriptionCompatFactory {
return context.getString(R.string.MessageNotifier_signal_message);
}
}
+
+ public static MediaItem buildNextVoiceNoteMediaItem(@NonNull MediaItem source) {
+ return cloneMediaItem(source, "next", NEXT_URI);
+ }
+
+ public static MediaItem buildEndVoiceNoteMediaItem(@NonNull MediaItem source) {
+ return cloneMediaItem(source, "end", END_URI);
+ }
+
+ private static MediaItem cloneMediaItem(MediaItem source, String mediaId, Uri uri) {
+ MediaDescriptionCompat description = source.playbackProperties != null ? (MediaDescriptionCompat) source.playbackProperties.tag : null;
+ return source.buildUpon()
+ .setMediaId(mediaId)
+ .setUri(uri)
+ .setTag(
+ description != null ?
+ new MediaDescriptionCompat.Builder()
+ .setMediaUri(uri)
+ .setTitle(description.getTitle())
+ .setSubtitle(description.getSubtitle())
+ .setExtras(description.getExtras())
+ .build() : null)
+ .build();
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationControlDispatcher.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationControlDispatcher.java
deleted file mode 100644
index 8301a98ca2..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationControlDispatcher.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.thoughtcrime.securesms.components.voice;
-
-import androidx.annotation.NonNull;
-
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.DefaultControlDispatcher;
-import com.google.android.exoplayer2.Player;
-
-public class VoiceNoteNotificationControlDispatcher extends DefaultControlDispatcher {
-
- private final VoiceNoteQueueDataAdapter dataAdapter;
-
- public VoiceNoteNotificationControlDispatcher(@NonNull VoiceNoteQueueDataAdapter dataAdapter) {
- this.dataAdapter = dataAdapter;
- }
-
- @Override
- public boolean dispatchSeekTo(Player player, int windowIndex, long positionMs) {
- boolean isQueueToneIndex = windowIndex % 2 == 1;
- boolean isSeekingToStart = positionMs == C.TIME_UNSET;
-
- if (isQueueToneIndex && isSeekingToStart) {
- int nextVoiceNoteWindowIndex = player.getCurrentWindowIndex() < windowIndex ? windowIndex + 1 : windowIndex - 1;
-
- if (dataAdapter.size() <= nextVoiceNoteWindowIndex) {
- return super.dispatchSeekTo(player, windowIndex, positionMs);
- } else {
- return super.dispatchSeekTo(player, nextVoiceNoteWindowIndex, positionMs);
- }
- } else {
- return super.dispatchSeekTo(player, windowIndex, positionMs);
- }
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationManager.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationManager.java
index 0fda3aed71..65d62d1d4b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationManager.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationManager.java
@@ -4,7 +4,6 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
-import android.os.RemoteException;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
@@ -16,17 +15,13 @@ import com.google.android.exoplayer2.ui.PlayerNotificationManager;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.color.MaterialColor;
-import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
-import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.AvatarUtil;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.Objects;
@@ -40,30 +35,22 @@ class VoiceNoteNotificationManager {
VoiceNoteNotificationManager(@NonNull Context context,
@NonNull MediaSessionCompat.Token token,
- @NonNull PlayerNotificationManager.NotificationListener listener,
- @NonNull VoiceNoteQueueDataAdapter dataAdapter)
+ @NonNull PlayerNotificationManager.NotificationListener listener)
{
- this.context = context;
-
- try {
- controller = new MediaControllerCompat(context, token);
- } catch (RemoteException e) {
- throw new IllegalArgumentException("Could not create a controller with given token");
- }
-
- notificationManager = PlayerNotificationManager.createWithNotificationChannel(context,
- NotificationChannels.VOICE_NOTES,
- R.string.NotificationChannel_voice_notes,
- NOW_PLAYING_NOTIFICATION_ID,
- new DescriptionAdapter());
+ this.context = context;
+ controller = new MediaControllerCompat(context, token);
+ notificationManager = new PlayerNotificationManager.Builder(context, NOW_PLAYING_NOTIFICATION_ID, NotificationChannels.VOICE_NOTES)
+ .setChannelNameResourceId(R.string.NotificationChannel_voice_notes)
+ .setMediaDescriptionAdapter(new DescriptionAdapter())
+ .setNotificationListener(listener)
+ .build();
notificationManager.setMediaSessionToken(token);
notificationManager.setSmallIcon(R.drawable.ic_notification);
- notificationManager.setRewindIncrementMs(0);
- notificationManager.setFastForwardIncrementMs(0);
- notificationManager.setNotificationListener(listener);
notificationManager.setColorized(true);
- notificationManager.setControlDispatcher(new VoiceNoteNotificationControlDispatcher(dataAdapter));
+ notificationManager.setUseFastForwardAction(false);
+ notificationManager.setUseRewindAction(false);
+ notificationManager.setUseStopAction(true);
}
public void hideNotification() {
@@ -90,18 +77,20 @@ class VoiceNoteNotificationManager {
@Override
public @Nullable PendingIntent createCurrentContentIntent(Player player) {
- if (!hasMetadata()) return null;
+ if (!hasMetadata()) {
+ return null;
+ }
- String serializedRecipientId = controller.getMetadata().getString(VoiceNoteMediaDescriptionCompatFactory.EXTRA_THREAD_RECIPIENT_ID);
+ String serializedRecipientId = controller.getMetadata().getString(VoiceNoteMediaItemFactory.EXTRA_THREAD_RECIPIENT_ID);
if (serializedRecipientId == null) {
return null;
}
RecipientId recipientId = RecipientId.from(serializedRecipientId);
- int startingPosition = (int) controller.getMetadata().getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_MESSAGE_POSITION);
- long threadId = controller.getMetadata().getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_THREAD_ID);
+ int startingPosition = (int) controller.getMetadata().getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_POSITION);
+ long threadId = controller.getMetadata().getLong(VoiceNoteMediaItemFactory.EXTRA_THREAD_ID);
- int color = (int) controller.getMetadata().getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_COLOR);
+ int color = (int) controller.getMetadata().getLong(VoiceNoteMediaItemFactory.EXTRA_COLOR);
if (color == 0) {
color = ChatColorsPalette.UNKNOWN_CONTACT.asSingleColor();
@@ -138,7 +127,7 @@ class VoiceNoteNotificationManager {
return null;
}
- String serializedRecipientId = controller.getMetadata().getString(VoiceNoteMediaDescriptionCompatFactory.EXTRA_AVATAR_RECIPIENT_ID);
+ String serializedRecipientId = controller.getMetadata().getString(VoiceNoteMediaItemFactory.EXTRA_AVATAR_RECIPIENT_ID);
if (serializedRecipientId == null) {
return null;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackController.kt b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackController.kt
index af016e0d98..238e57ecee 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackController.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackController.kt
@@ -4,29 +4,31 @@ import android.media.AudioManager
import android.os.Bundle
import android.os.ResultReceiver
import com.google.android.exoplayer2.C
+import com.google.android.exoplayer2.ControlDispatcher
import com.google.android.exoplayer2.PlaybackParameters
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.audio.AudioAttributes
-import com.google.android.exoplayer2.ext.mediasession.DefaultPlaybackController
+import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.util.Util
-class VoiceNotePlaybackController(private val voiceNotePlaybackParameters: VoiceNotePlaybackParameters) : DefaultPlaybackController() {
+class VoiceNotePlaybackController(
+ private val player: SimpleExoPlayer,
+ private val voiceNotePlaybackParameters: VoiceNotePlaybackParameters
+) : MediaSessionConnector.CommandReceiver {
- override fun getCommands(): Array {
- return arrayOf(VoiceNotePlaybackService.ACTION_NEXT_PLAYBACK_SPEED, VoiceNotePlaybackService.ACTION_SET_AUDIO_STREAM)
- }
-
- override fun onCommand(player: Player, command: String, extras: Bundle?, cb: ResultReceiver?) {
+ @Suppress("deprecation")
+ override fun onCommand(p: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?): Boolean {
if (command == VoiceNotePlaybackService.ACTION_NEXT_PLAYBACK_SPEED) {
val speed = extras?.getFloat(VoiceNotePlaybackService.ACTION_NEXT_PLAYBACK_SPEED, 1f) ?: 1f
player.playbackParameters = PlaybackParameters(speed)
voiceNotePlaybackParameters.setSpeed(speed)
+ return true
} else if (command == VoiceNotePlaybackService.ACTION_SET_AUDIO_STREAM) {
val newStreamType: Int = extras?.getInt(VoiceNotePlaybackService.ACTION_SET_AUDIO_STREAM, AudioManager.STREAM_MUSIC) ?: AudioManager.STREAM_MUSIC
- val currentStreamType = Util.getStreamTypeForAudioUsage((player as SimpleExoPlayer).audioAttributes.usage)
+ val currentStreamType = Util.getStreamTypeForAudioUsage(player.audioAttributes.usage)
if (newStreamType != currentStreamType) {
val attributes = when (newStreamType) {
AudioManager.STREAM_MUSIC -> AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_MUSIC).setUsage(C.USAGE_MEDIA).build()
@@ -35,12 +37,14 @@ class VoiceNotePlaybackController(private val voiceNotePlaybackParameters: Voice
}
player.playWhenReady = false
- player.audioAttributes = attributes
+ player.setAudioAttributes(attributes, false)
if (newStreamType == AudioManager.STREAM_VOICE_CALL) {
player.playWhenReady = true
}
}
+ return true
}
+ return false
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackPreparer.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackPreparer.java
index bc97372d64..511db5310c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackPreparer.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackPreparer.java
@@ -4,7 +4,6 @@ import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
-import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.widget.Toast;
@@ -14,11 +13,12 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream;
+import com.google.android.exoplayer2.ControlDispatcher;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
-import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
@@ -26,12 +26,9 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.model.MessageRecord;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.MessageRecordUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
-import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
import java.util.Collections;
import java.util.List;
@@ -49,30 +46,19 @@ final class VoiceNotePlaybackPreparer implements MediaSessionConnector.PlaybackP
private static final Executor EXECUTOR = Executors.newSingleThreadExecutor();
private static final long LIMIT = 5;
- public static final Uri NEXT_URI = Uri.parse("file:///android_asset/sounds/state-change_confirm-down.ogg");
- public static final Uri END_URI = Uri.parse("file:///android_asset/sounds/state-change_confirm-up.ogg");
-
- private final Context context;
- private final SimpleExoPlayer player;
- private final VoiceNoteQueueDataAdapter queueDataAdapter;
- private final AttachmentMediaSourceFactory mediaSourceFactory;
- private final ConcatenatingMediaSource dataSource;
+ private final Context context;
+ private final Player player;
private final VoiceNotePlaybackParameters voiceNotePlaybackParameters;
private boolean canLoadMore;
private Uri latestUri = Uri.EMPTY;
VoiceNotePlaybackPreparer(@NonNull Context context,
- @NonNull SimpleExoPlayer player,
- @NonNull VoiceNoteQueueDataAdapter queueDataAdapter,
- @NonNull AttachmentMediaSourceFactory mediaSourceFactory,
+ @NonNull Player player,
@NonNull VoiceNotePlaybackParameters voiceNotePlaybackParameters)
{
- this.context = context;
- this.player = player;
- this.queueDataAdapter = queueDataAdapter;
- this.mediaSourceFactory = mediaSourceFactory;
- this.dataSource = new ConcatenatingMediaSource();
+ this.context = context;
+ this.player = player;
this.voiceNotePlaybackParameters = voiceNotePlaybackParameters;
}
@@ -82,23 +68,26 @@ final class VoiceNotePlaybackPreparer implements MediaSessionConnector.PlaybackP
}
@Override
- public void onPrepare() {
+ public void onPrepare(boolean playWhenReady) {
throw new UnsupportedOperationException("VoiceNotePlaybackPreparer does not support onPrepare");
}
@Override
- public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+ public void onPrepareFromMediaId(@NonNull String mediaId, boolean playWhenReady, @Nullable Bundle extras) {
throw new UnsupportedOperationException("VoiceNotePlaybackPreparer does not support onPrepareFromMediaId");
}
@Override
- public void onPrepareFromSearch(String query, Bundle extras) {
+ public void onPrepareFromSearch(@NonNull String query, boolean playWhenReady, @Nullable Bundle extras) {
throw new UnsupportedOperationException("VoiceNotePlaybackPreparer does not support onPrepareFromSearch");
}
@Override
- public void onPrepareFromUri(final Uri uri, Bundle extras) {
+ public void onPrepareFromUri(@NonNull Uri uri, boolean playWhenReady, @Nullable Bundle extras) {
Log.d(TAG, "onPrepareFromUri: " + uri);
+ if (extras == null) {
+ return;
+ }
long messageId = extras.getLong(VoiceNoteMediaController.EXTRA_MESSAGE_ID);
long threadId = extras.getLong(VoiceNoteMediaController.EXTRA_THREAD_ID);
@@ -112,26 +101,25 @@ final class VoiceNotePlaybackPreparer implements MediaSessionConnector.PlaybackP
() -> {
if (singlePlayback) {
if (messageId != -1) {
- return loadMediaDescriptionForSinglePlayback(messageId);
+ return loadMediaItemsForSinglePlayback(messageId);
} else {
- return loadMediaDescriptionForDraftPlayback(threadId, uri);
+ return loadMediaItemsForDraftPlayback(threadId, uri);
}
} else {
- return loadMediaDescriptionsForConsecutivePlayback(messageId);
+ return loadMediaItemsForConsecutivePlayback(messageId);
}
},
- descriptions -> {
- queueDataAdapter.clear();
- dataSource.clear();
+ mediaItems -> {
+ player.clearMediaItems();
- if (Util.hasItems(descriptions) && Objects.equals(latestUri, uri)) {
- applyDescriptionsToQueue(descriptions);
+ if (Util.hasItems(mediaItems) && Objects.equals(latestUri, uri)) {
+ applyDescriptionsToQueue(mediaItems);
- int window = Math.max(0, queueDataAdapter.indexOf(uri));
+ int window = Math.max(0, indexOfPlayerMediaItemByUri(uri));
- player.addListener(new Player.EventListener() {
+ player.addListener(new Player.Listener() {
@Override
- public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int reason) {
+ public void onTimelineChanged(@NonNull Timeline timeline, int reason) {
if (timeline.getWindowCount() >= window) {
player.setPlayWhenReady(false);
player.setPlaybackParameters(voiceNotePlaybackParameters.getParameters());
@@ -142,102 +130,91 @@ final class VoiceNotePlaybackPreparer implements MediaSessionConnector.PlaybackP
}
});
- player.prepare(dataSource);
+ player.prepare();
canLoadMore = !singlePlayback;
} else if (Objects.equals(latestUri, uri)) {
Log.w(TAG, "Requested playback but no voice notes could be found.");
- ThreadUtil.postToMain(() -> Toast.makeText(context, R.string.VoiceNotePlaybackPreparer__failed_to_play_voice_message, Toast.LENGTH_SHORT)
- .show());
+ ThreadUtil.postToMain(() -> {
+ Toast.makeText(context, R.string.VoiceNotePlaybackPreparer__failed_to_play_voice_message, Toast.LENGTH_SHORT)
+ .show();
+ });
}
});
}
- @Override
- public String[] getCommands() {
- return new String[0];
- }
-
- @Override
- public void onCommand(Player player, String command, Bundle extras, ResultReceiver cb) {
- }
-
@MainThread
- private void applyDescriptionsToQueue(@NonNull List descriptions) {
- for (MediaDescriptionCompat description : descriptions) {
- int holderIndex = queueDataAdapter.indexOf(description.getMediaUri());
- MediaDescriptionCompat next = createNextClone(description);
- int currentIndex = player.getCurrentWindowIndex();
+ private void applyDescriptionsToQueue(@NonNull List mediaItems) {
+ for (MediaItem mediaItem : mediaItems) {
+ MediaItem.PlaybackProperties playbackProperties = mediaItem.playbackProperties;
+ if (playbackProperties == null) {
+ continue;
+ }
+ int holderIndex = indexOfPlayerMediaItemByUri(playbackProperties.uri);
+ MediaItem next = VoiceNoteMediaItemFactory.buildNextVoiceNoteMediaItem(mediaItem);
+ int currentIndex = player.getCurrentWindowIndex();
if (holderIndex != -1) {
- queueDataAdapter.remove(holderIndex);
-
- if (!queueDataAdapter.isEmpty()) {
- queueDataAdapter.remove(holderIndex);
- }
-
- queueDataAdapter.add(holderIndex, createNextClone(description));
- queueDataAdapter.add(holderIndex, description);
-
if (currentIndex != holderIndex) {
- dataSource.removeMediaSource(holderIndex);
- dataSource.addMediaSource(holderIndex, mediaSourceFactory.createMediaSource(description));
+ player.removeMediaItem(holderIndex);
+ player.addMediaItem(holderIndex, mediaItem);
}
if (currentIndex != holderIndex + 1) {
- if (dataSource.getSize() > 1) {
- dataSource.removeMediaSource(holderIndex + 1);
+ if (player.getMediaItemCount() > 1) {
+ player.removeMediaItem(holderIndex + 1);
}
- dataSource.addMediaSource(holderIndex + 1, mediaSourceFactory.createMediaSource(next));
+ player.addMediaItem(holderIndex + 1, next);
}
} else {
- int insertLocation = queueDataAdapter.indexAfter(description);
+ int insertLocation = indexAfter(mediaItem);
- queueDataAdapter.add(insertLocation, next);
- queueDataAdapter.add(insertLocation, description);
-
- dataSource.addMediaSource(insertLocation, mediaSourceFactory.createMediaSource(next));
- dataSource.addMediaSource(insertLocation, mediaSourceFactory.createMediaSource(description));
+ player.addMediaItem(insertLocation, next);
+ player.addMediaItem(insertLocation, mediaItem);
}
}
- int lastIndex = queueDataAdapter.size() - 1;
- MediaDescriptionCompat last = queueDataAdapter.getMediaDescription(lastIndex);
+ int itemsCount = player.getMediaItemCount();
+ if (itemsCount > 0) {
+ int lastIndex = itemsCount - 1;
+ MediaItem last = player.getMediaItemAt(lastIndex);
- if (Objects.equals(last.getMediaUri(), NEXT_URI)) {
- queueDataAdapter.remove(lastIndex);
- dataSource.removeMediaSource(lastIndex);
+ if (last.playbackProperties != null &&
+ Objects.equals(last.playbackProperties.uri, VoiceNoteMediaItemFactory.NEXT_URI))
+ {
+ player.removeMediaItem(lastIndex);
- if (queueDataAdapter.size() > 1) {
- MediaDescriptionCompat end = createEndClone(last);
+ if (player.getMediaItemCount() > 1) {
+ MediaItem end = VoiceNoteMediaItemFactory.buildEndVoiceNoteMediaItem(last);
- queueDataAdapter.add(lastIndex, end);
- dataSource.addMediaSource(lastIndex, mediaSourceFactory.createMediaSource(end));
+ player.addMediaItem(lastIndex, end);
+ }
}
}
+ }
- if (queueDataAdapter.size() != dataSource.getSize()) {
- throw new IllegalStateException("QueueDataAdapter and DataSource size inconsistency.");
+ private int indexOfPlayerMediaItemByUri(@NonNull Uri uri) {
+ for (int i = 0; i < player.getMediaItemCount(); i++) {
+ MediaItem.PlaybackProperties playbackProperties = player.getMediaItemAt(i).playbackProperties;
+ if (playbackProperties != null && playbackProperties.uri.equals(uri)) {
+ return i;
+ }
}
+ return -1;
}
- private @NonNull MediaDescriptionCompat createEndClone(@NonNull MediaDescriptionCompat source) {
- return buildUpon(source).setMediaId("end").setMediaUri(END_URI).build();
- }
+ private int indexAfter(@NonNull MediaItem target) {
+ int size = player.getMediaItemCount();
+ long targetMessageId = target.mediaMetadata.extras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_ID);
+ for (int i = 0; i < size; i++) {
+ MediaMetadata mediaMetadata = player.getMediaItemAt(i).mediaMetadata;
+ long messageId = mediaMetadata.extras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_ID);
- private @NonNull MediaDescriptionCompat createNextClone(@NonNull MediaDescriptionCompat source) {
- return buildUpon(source).setMediaId("next").setMediaUri(NEXT_URI).build();
- }
-
- private @NonNull MediaDescriptionCompat.Builder buildUpon(@NonNull MediaDescriptionCompat source) {
- return new MediaDescriptionCompat.Builder()
- .setSubtitle(source.getSubtitle())
- .setDescription(source.getDescription())
- .setTitle(source.getTitle())
- .setIconUri(source.getIconUri())
- .setIconBitmap(source.getIconBitmap())
- .setMediaId(source.getMediaId())
- .setExtras(source.getExtras());
+ if (messageId > targetMessageId) {
+ return i;
+ }
+ }
+ return size;
}
public void loadMoreVoiceNotes() {
@@ -245,36 +222,37 @@ final class VoiceNotePlaybackPreparer implements MediaSessionConnector.PlaybackP
return;
}
- MediaDescriptionCompat mediaDescriptionCompat = queueDataAdapter.getMediaDescription(player.getCurrentWindowIndex());
- if (Objects.equals(mediaDescriptionCompat, VoiceNoteQueueDataAdapter.EMPTY)) {
+ MediaItem currentMediaItem = player.getCurrentMediaItem();
+ if (currentMediaItem == null) {
return;
}
- long messageId = mediaDescriptionCompat.getExtras().getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_MESSAGE_ID);
+ long messageId = currentMediaItem.mediaMetadata.extras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_ID);
SimpleTask.run(EXECUTOR,
- () -> loadMediaDescriptionsForConsecutivePlayback(messageId),
- descriptions -> {
- if (Util.hasItems(descriptions) && canLoadMore) {
- applyDescriptionsToQueue(descriptions);
+ () -> loadMediaItemsForConsecutivePlayback(messageId),
+ mediaItems -> {
+ if (Util.hasItems(mediaItems) && canLoadMore) {
+ applyDescriptionsToQueue(mediaItems);
}
});
}
- private @NonNull List loadMediaDescriptionForSinglePlayback(long messageId) {
+ private @NonNull List loadMediaItemsForSinglePlayback(long messageId) {
try {
- MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(context).getMessageRecord(messageId);
+ MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(context)
+ .getMessageRecord(messageId);
if (!MessageRecordUtil.hasAudio(messageRecord)) {
Log.w(TAG, "Message does not contain audio.");
return Collections.emptyList();
}
- MediaDescriptionCompat mediaDescriptionCompat = VoiceNoteMediaDescriptionCompatFactory.buildMediaDescription(context ,messageRecord);
- if (mediaDescriptionCompat == null) {
+ MediaItem mediaItem = VoiceNoteMediaItemFactory.buildMediaItem(context, messageRecord);
+ if (mediaItem == null) {
return Collections.emptyList();
} else {
- return Collections.singletonList(mediaDescriptionCompat);
+ return Collections.singletonList(mediaItem);
}
} catch (NoSuchMessageException e) {
Log.w(TAG, "Could not find message.", e);
@@ -282,17 +260,20 @@ final class VoiceNotePlaybackPreparer implements MediaSessionConnector.PlaybackP
}
}
- private @NonNull List loadMediaDescriptionForDraftPlayback(long threadId, @NonNull Uri draftUri) {
- return Collections.singletonList(VoiceNoteMediaDescriptionCompatFactory.buildMediaDescription(context, threadId, draftUri));
+ private @NonNull List loadMediaItemsForDraftPlayback(long threadId, @NonNull Uri draftUri) {
+ return Collections
+ .singletonList(VoiceNoteMediaItemFactory.buildMediaItem(context, threadId, draftUri));
}
@WorkerThread
- private @NonNull List loadMediaDescriptionsForConsecutivePlayback(long messageId) {
+ private @NonNull List loadMediaItemsForConsecutivePlayback(long messageId) {
try {
- List recordsAfter = DatabaseFactory.getMmsSmsDatabase(context).getMessagesAfterVoiceNoteInclusive(messageId, LIMIT);
+ List recordsAfter = DatabaseFactory.getMmsSmsDatabase(context)
+ .getMessagesAfterVoiceNoteInclusive(messageId, LIMIT);
return buildFilteredMessageRecordList(recordsAfter).stream()
- .map(record -> VoiceNoteMediaDescriptionCompatFactory.buildMediaDescription(context, record))
+ .map(record -> VoiceNoteMediaItemFactory
+ .buildMediaItem(context, record))
.filter(Objects::nonNull)
.collect(Collectors.toList());
} catch (NoSuchMessageException e) {
@@ -306,4 +287,15 @@ final class VoiceNotePlaybackPreparer implements MediaSessionConnector.PlaybackP
.takeWhile(MessageRecordUtil::hasAudio)
.toList();
}
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public boolean onCommand(@NonNull Player player,
+ @NonNull ControlDispatcher controlDispatcher,
+ @NonNull String command,
+ @Nullable Bundle extras,
+ @Nullable ResultReceiver cb)
+ {
+ return false;
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java
index e5bb930f3a..f6fccfd3cc 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java
@@ -6,11 +6,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Process;
-import android.os.RemoteException;
import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
@@ -21,22 +20,15 @@ import androidx.core.content.ContextCompat;
import androidx.media.MediaBrowserServiceCompat;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.DefaultLoadControl;
-import com.google.android.exoplayer2.DefaultRenderersFactory;
-import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.ExoPlayerFactory;
-import com.google.android.exoplayer2.LoadControl;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
-import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.model.MessageId;
@@ -45,7 +37,6 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceViewedUpdateJob;
import org.thoughtcrime.securesms.jobs.SendViewedReceiptJob;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.service.KeyCachingService;
-import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
import java.util.Collections;
import java.util.List;
@@ -70,56 +61,38 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
private MediaSessionCompat mediaSession;
private MediaSessionConnector mediaSessionConnector;
- private PlaybackStateCompat.Builder stateBuilder;
- private SimpleExoPlayer player;
+ private VoiceNotePlayer player;
private BecomingNoisyReceiver becomingNoisyReceiver;
private KeyClearedReceiver keyClearedReceiver;
private VoiceNoteNotificationManager voiceNoteNotificationManager;
- private VoiceNoteQueueDataAdapter queueDataAdapter;
private VoiceNotePlaybackPreparer voiceNotePlaybackPreparer;
private boolean isForegroundService;
private VoiceNotePlaybackParameters voiceNotePlaybackParameters;
- private final LoadControl loadControl = new DefaultLoadControl.Builder()
- .setBufferDurationsMs(Integer.MAX_VALUE,
- Integer.MAX_VALUE,
- Integer.MAX_VALUE,
- Integer.MAX_VALUE)
- .createDefaultLoadControl();
-
@Override
public void onCreate() {
super.onCreate();
mediaSession = new MediaSessionCompat(this, TAG);
voiceNotePlaybackParameters = new VoiceNotePlaybackParameters(mediaSession);
- stateBuilder = new PlaybackStateCompat.Builder()
- .setActions(SUPPORTED_ACTIONS)
- .addCustomAction(ACTION_NEXT_PLAYBACK_SPEED, "speed", R.drawable.ic_toggle_24);
- mediaSessionConnector = new MediaSessionConnector(mediaSession, new VoiceNotePlaybackController(voiceNotePlaybackParameters));
+ mediaSessionConnector = new MediaSessionConnector(mediaSession);
becomingNoisyReceiver = new BecomingNoisyReceiver(this, mediaSession.getSessionToken());
keyClearedReceiver = new KeyClearedReceiver(this, mediaSession.getSessionToken());
- player = ExoPlayerFactory.newSimpleInstance(this, new DefaultRenderersFactory(this), new DefaultTrackSelector(), loadControl);
- queueDataAdapter = new VoiceNoteQueueDataAdapter();
+ player = new VoiceNotePlayer(this);
voiceNoteNotificationManager = new VoiceNoteNotificationManager(this,
mediaSession.getSessionToken(),
- new VoiceNoteNotificationManagerListener(),
- queueDataAdapter);
-
- AttachmentMediaSourceFactory mediaSourceFactory = new AttachmentMediaSourceFactory(this);
-
- voiceNotePlaybackPreparer = new VoiceNotePlaybackPreparer(this, player, queueDataAdapter, mediaSourceFactory, voiceNotePlaybackParameters);
-
- mediaSession.setPlaybackState(stateBuilder.build());
+ new VoiceNoteNotificationManagerListener());
+ voiceNotePlaybackPreparer = new VoiceNotePlaybackPreparer(this, player, voiceNotePlaybackParameters);
player.addListener(new VoiceNotePlayerEventListener());
- player.setAudioAttributes(new AudioAttributes.Builder()
- .setContentType(C.CONTENT_TYPE_SPEECH)
- .setUsage(C.USAGE_MEDIA)
- .build(), true);
- mediaSessionConnector.setPlayer(player, voiceNotePlaybackPreparer);
- mediaSessionConnector.setQueueNavigator(new VoiceNoteQueueNavigator(mediaSession, queueDataAdapter));
+ mediaSessionConnector.setPlayer(player);
+ mediaSessionConnector.setEnabledPlaybackActions(SUPPORTED_ACTIONS);
+ mediaSessionConnector.setPlaybackPreparer(voiceNotePlaybackPreparer);
+ mediaSessionConnector.setQueueNavigator(new VoiceNoteQueueNavigator(mediaSession));
+
+ VoiceNotePlaybackController voiceNotePlaybackController = new VoiceNotePlaybackController(player.getInternalPlayer(), voiceNotePlaybackParameters);
+ mediaSessionConnector.registerCustomCommandReceiver(voiceNotePlaybackController);
setSessionToken(mediaSession.getSessionToken());
@@ -131,7 +104,8 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
public void onTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
- player.stop(true);
+ player.stop();
+ player.clearMediaItems();
}
@Override
@@ -158,10 +132,19 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
result.sendResult(Collections.emptyList());
}
- private class VoiceNotePlayerEventListener implements Player.EventListener {
+ private class VoiceNotePlayerEventListener implements Player.Listener {
@Override
- public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
+ public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
+ onPlaybackStateChanged(playWhenReady, player.getPlaybackState());
+ }
+
+ @Override
+ public void onPlaybackStateChanged(int playbackState) {
+ onPlaybackStateChanged(player.getPlayWhenReady(), playbackState);
+ }
+
+ private void onPlaybackStateChanged(boolean playWhenReady, int playbackState) {
switch (playbackState) {
case Player.STATE_BUFFERING:
case Player.STATE_READY:
@@ -169,6 +152,7 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
if (!playWhenReady) {
stopForeground(false);
+ isForegroundService = false;
becomingNoisyReceiver.unregister();
} else {
sendViewedReceiptForCurrentWindowIndex();
@@ -182,30 +166,34 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
}
@Override
- public void onPositionDiscontinuity(int reason) {
- int currentWindowIndex = player.getCurrentWindowIndex();
+ public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) {
+ int currentWindowIndex = newPosition.windowIndex;
if (currentWindowIndex == C.INDEX_UNSET) {
return;
}
- if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) {
+ if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
sendViewedReceiptForCurrentWindowIndex();
- MediaDescriptionCompat mediaDescriptionCompat = queueDataAdapter.getMediaDescription(currentWindowIndex);
- Log.d(TAG, "onPositionDiscontinuity: current window uri: " + mediaDescriptionCompat.getMediaUri());
+ MediaItem currentMediaItem = player.getCurrentMediaItem();
+ if (currentMediaItem != null && currentMediaItem.playbackProperties != null) {
+ Log.d(TAG, "onPositionDiscontinuity: current window uri: " + currentMediaItem.playbackProperties.uri);
+ }
PlaybackParameters playbackParameters = getPlaybackParametersForWindowPosition(currentWindowIndex);
final float speed = playbackParameters != null ? playbackParameters.speed : 1f;
if (speed != player.getPlaybackParameters().speed) {
player.setPlayWhenReady(false);
- player.setPlaybackParameters(playbackParameters);
+ if (playbackParameters != null) {
+ player.setPlaybackParameters(playbackParameters);
+ }
player.seekTo(currentWindowIndex, 1);
player.setPlayWhenReady(true);
}
}
boolean isWithinThreshold = currentWindowIndex < LOAD_MORE_THRESHOLD ||
- currentWindowIndex + LOAD_MORE_THRESHOLD >= queueDataAdapter.size();
+ currentWindowIndex + LOAD_MORE_THRESHOLD >= player.getMediaItemCount();
if (isWithinThreshold && currentWindowIndex % 2 == 0) {
voiceNotePlaybackPreparer.loadMoreVoiceNotes();
@@ -213,7 +201,7 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
}
@Override
- public void onPlayerError(ExoPlaybackException error) {
+ public void onPlayerError(@NonNull PlaybackException error) {
Log.w(TAG, "ExoPlayer error occurred:", error);
}
}
@@ -236,16 +224,23 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
player.getCurrentWindowIndex() != C.INDEX_UNSET)
{
- final MediaDescriptionCompat descriptionCompat = queueDataAdapter.getMediaDescription(player.getCurrentWindowIndex());
+ MediaItem currentMediaItem = player.getCurrentMediaItem();
+ if (currentMediaItem == null || currentMediaItem.playbackProperties == null) {
+ return;
+ }
- if (!descriptionCompat.getMediaUri().getScheme().equals("content")) {
+ Uri mediaUri = currentMediaItem.playbackProperties.uri;
+ if (!mediaUri.getScheme().equals("content")) {
return;
}
SignalExecutors.BOUNDED.execute(() -> {
- Bundle extras = descriptionCompat.getExtras();
- long messageId = extras.getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_MESSAGE_ID);
- RecipientId recipientId = RecipientId.from(extras.getString(VoiceNoteMediaDescriptionCompatFactory.EXTRA_INDIVIDUAL_RECIPIENT_ID));
+ Bundle extras = currentMediaItem.mediaMetadata.extras;
+ if (extras == null) {
+ return;
+ }
+ long messageId = extras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_ID);
+ RecipientId recipientId = RecipientId.from(extras.getString(VoiceNoteMediaItemFactory.EXTRA_INDIVIDUAL_RECIPIENT_ID));
MessageDatabase messageDatabase = DatabaseFactory.getMmsDatabase(this);
MessageDatabase.MarkedMessageInfo markedMessageInfo = messageDatabase.setIncomingMessageViewed(messageId);
@@ -264,8 +259,8 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
private class VoiceNoteNotificationManagerListener implements PlayerNotificationManager.NotificationListener {
@Override
- public void onNotificationStarted(int notificationId, Notification notification) {
- if (!isForegroundService) {
+ public void onNotificationPosted(int notificationId, Notification notification, boolean ongoing) {
+ if (ongoing && !isForegroundService) {
ContextCompat.startForegroundService(getApplicationContext(), new Intent(getApplicationContext(), VoiceNotePlaybackService.class));
startForeground(notificationId, notification);
isForegroundService = true;
@@ -273,7 +268,7 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
}
@Override
- public void onNotificationCancelled(int notificationId) {
+ public void onNotificationCancelled(int notificationId, boolean dismissedByUser) {
stopForeground(true);
isForegroundService = false;
stopSelf();
@@ -292,12 +287,8 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
private boolean registered;
private KeyClearedReceiver(@NonNull Context context, @NonNull MediaSessionCompat.Token token) {
- this.context = context;
- try {
- this.controller = new MediaControllerCompat(context, token);
- } catch (RemoteException e) {
- throw new IllegalArgumentException("Failed to create controller from token", e);
- }
+ this.context = context;
+ this.controller = new MediaControllerCompat(context, token);
}
void register() {
@@ -332,12 +323,8 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
private boolean registered;
private BecomingNoisyReceiver(Context context, MediaSessionCompat.Token token) {
- this.context = context;
- try {
- this.controller = new MediaControllerCompat(context, token);
- } catch (RemoteException e) {
- throw new IllegalArgumentException("Failed to create controller from token", e);
- }
+ this.context = context;
+ this.controller = new MediaControllerCompat(context, token);
}
void register() {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayer.kt b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayer.kt
new file mode 100644
index 0000000000..c65740a28e
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayer.kt
@@ -0,0 +1,38 @@
+package org.thoughtcrime.securesms.components.voice
+
+import android.content.Context
+import com.google.android.exoplayer2.C
+import com.google.android.exoplayer2.DefaultLoadControl
+import com.google.android.exoplayer2.ForwardingPlayer
+import com.google.android.exoplayer2.SimpleExoPlayer
+import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory
+
+class VoiceNotePlayer @JvmOverloads constructor(
+ context: Context,
+ val internalPlayer: SimpleExoPlayer = SimpleExoPlayer.Builder(context)
+ .setMediaSourceFactory(AttachmentMediaSourceFactory(context))
+ .setLoadControl(
+ DefaultLoadControl.Builder()
+ .setBufferDurationsMs(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE)
+ .build()
+ ).build()
+) : ForwardingPlayer(internalPlayer) {
+
+ override fun seekTo(windowIndex: Int, positionMs: Long) {
+ super.seekTo(windowIndex, positionMs)
+
+ val isQueueToneIndex = windowIndex % 2 == 1
+ val isSeekingToStart = positionMs == C.TIME_UNSET
+
+ return if (isQueueToneIndex && isSeekingToStart) {
+ val nextVoiceNoteWindowIndex = if (currentWindowIndex < windowIndex) windowIndex + 1 else windowIndex - 1
+ if (mediaItemCount <= nextVoiceNoteWindowIndex) {
+ super.seekTo(windowIndex, positionMs)
+ } else {
+ super.seekTo(nextVoiceNoteWindowIndex, positionMs)
+ }
+ } else {
+ super.seekTo(windowIndex, positionMs)
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteQueueDataAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteQueueDataAdapter.java
deleted file mode 100644
index 9827fd79eb..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteQueueDataAdapter.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package org.thoughtcrime.securesms.components.voice;
-
-import android.net.Uri;
-import android.support.v4.media.MediaDescriptionCompat;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-
-import com.google.android.exoplayer2.ext.mediasession.TimelineQueueEditor;
-
-import org.signal.core.util.logging.Log;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * DataAdapter which maintains the current queue of MediaDescriptionCompat objects.
- */
-@MainThread
-final class VoiceNoteQueueDataAdapter implements TimelineQueueEditor.QueueDataAdapter {
-
- private static final String TAG = Log.tag(VoiceNoteQueueDataAdapter.class);
-
- public static final MediaDescriptionCompat EMPTY = new MediaDescriptionCompat.Builder().build();
-
- private final List descriptions = new LinkedList<>();
-
- @Override
- public MediaDescriptionCompat getMediaDescription(int position) {
- if (descriptions.size() <= position) {
- Log.i(TAG, "getMediaDescription: Returning EMPTY MediaDescriptionCompat for index " + position);
- return EMPTY;
- }
-
- return descriptions.get(position);
- }
-
- @Override
- public void add(int position, MediaDescriptionCompat description) {
- descriptions.add(position, description);
- }
-
- @Override
- public void remove(int position) {
- descriptions.remove(position);
- }
-
- @Override
- public void move(int from, int to) {
- MediaDescriptionCompat description = descriptions.remove(from);
- descriptions.add(to, description);
- }
-
- int size() {
- return descriptions.size();
- }
-
- int indexOf(@NonNull Uri uri) {
- for (int i = 0; i < descriptions.size(); i++) {
- if (Objects.equals(uri, descriptions.get(i).getMediaUri())) {
- return i;
- }
- }
-
- return -1;
- }
-
- int indexAfter(@NonNull MediaDescriptionCompat target) {
- if (isEmpty()) {
- return 0;
- }
-
- long targetMessageId = target.getExtras().getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_MESSAGE_ID);
- for (int i = 0; i < descriptions.size(); i++) {
- long descriptionMessageId = descriptions.get(i).getExtras().getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_MESSAGE_ID);
-
- if (descriptionMessageId > targetMessageId) {
- return i;
- }
- }
-
- return descriptions.size();
- }
-
- boolean isEmpty() {
- return descriptions.isEmpty();
- }
-
- void clear() {
- descriptions.clear();
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteQueueNavigator.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteQueueNavigator.java
index b4b86ab13b..20b254ce9a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteQueueNavigator.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteQueueNavigator.java
@@ -5,24 +5,33 @@ import android.support.v4.media.session.MediaSessionCompat;
import androidx.annotation.NonNull;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.ext.mediasession.TimelineQueueEditor;
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator;
/**
* Navigator to help support seek forward and back.
*/
final class VoiceNoteQueueNavigator extends TimelineQueueNavigator {
+ private static final MediaDescriptionCompat EMPTY = new MediaDescriptionCompat.Builder().build();
- private final TimelineQueueEditor.QueueDataAdapter queueDataAdapter;
-
- public VoiceNoteQueueNavigator(@NonNull MediaSessionCompat mediaSession, @NonNull TimelineQueueEditor.QueueDataAdapter queueDataAdapter) {
+ public VoiceNoteQueueNavigator(@NonNull MediaSessionCompat mediaSession) {
super(mediaSession);
- this.queueDataAdapter = queueDataAdapter;
}
@Override
- public MediaDescriptionCompat getMediaDescription(Player player, int windowIndex) {
- return queueDataAdapter.getMediaDescription(windowIndex);
+ public @NonNull MediaDescriptionCompat getMediaDescription(@NonNull Player player, int windowIndex) {
+ MediaItem mediaItem = windowIndex >= 0 && windowIndex < player.getMediaItemCount() ? player.getMediaItemAt(windowIndex) : null;
+
+ if (mediaItem == null || mediaItem.playbackProperties == null) {
+ return EMPTY;
+ }
+
+ MediaDescriptionCompat mediaDescriptionCompat = (MediaDescriptionCompat) mediaItem.playbackProperties.tag;
+ if (mediaDescriptionCompat == null) {
+ return EMPTY;
+ }
+
+ return mediaDescriptionCompat;
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java
index 75da60b2fb..70576477cf 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java
@@ -37,7 +37,7 @@ import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
-import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.MediaItem;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
@@ -59,9 +59,7 @@ import org.thoughtcrime.securesms.util.MessageRecordUtil;
import org.thoughtcrime.securesms.util.Projection;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.ThemeUtil;
-import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
-import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
import org.whispersystems.libsignal.util.guava.Optional;
import java.security.MessageDigest;
@@ -119,7 +117,6 @@ public class ConversationAdapter
private final Set releasedFastRecords;
private final Calendar calendar;
private final MessageDigest digest;
- private final AttachmentMediaSourceFactory attachmentMediaSourceFactory;
private String searchQuery;
private ConversationMessage recordToPulse;
@@ -137,7 +134,6 @@ public class ConversationAdapter
@NonNull Locale locale,
@Nullable ItemClickListener clickListener,
@NonNull Recipient recipient,
- @NonNull AttachmentMediaSourceFactory attachmentMediaSourceFactory,
@NonNull Colorizer colorizer)
{
super(new DiffUtil.ItemCallback() {
@@ -166,7 +162,6 @@ public class ConversationAdapter
this.digest = getMessageDigestOrThrow();
this.hasWallpaper = recipient.hasWallpaper();
this.isMessageRequestAccepted = true;
- this.attachmentMediaSourceFactory = attachmentMediaSourceFactory;
this.colorizer = colorizer;
setHasStableIds(true);
@@ -301,7 +296,6 @@ public class ConversationAdapter
conversationMessage == recordToPulse,
hasWallpaper,
isMessageRequestAccepted,
- attachmentMediaSourceFactory,
conversationMessage == inlineContent,
colorizer);
@@ -701,8 +695,8 @@ public class ConversationAdapter
}
@Override
- public @Nullable MediaSource getMediaSource() {
- return getBindable().getMediaSource();
+ public @Nullable MediaItem getMediaItem() {
+ return getBindable().getMediaItem();
}
@Override
@@ -710,8 +704,8 @@ public class ConversationAdapter
return getBindable().getPlaybackPolicyEnforcer();
}
- @NonNull
- public @Override Projection getGiphyMp4PlayableProjection(@NonNull ViewGroup recyclerView) {
+ @Override
+ public @NonNull Projection getGiphyMp4PlayableProjection(@NonNull ViewGroup recyclerView) {
return getBindable().getGiphyMp4PlayableProjection(recyclerView);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
index 7a2814196d..485bd9f357 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
@@ -163,7 +163,6 @@ import org.thoughtcrime.securesms.util.WindowUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.views.AdaptiveActionsToolbar;
-import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
import java.io.IOException;
@@ -695,7 +694,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
}
Log.d(TAG, "Initializing adapter for " + recipient.getId());
- ConversationAdapter adapter = new ConversationAdapter(requireContext(), this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get(), new AttachmentMediaSourceFactory(requireContext()), colorizer);
+ ConversationAdapter adapter = new ConversationAdapter(requireContext(), this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get(), colorizer);
adapter.setPagingController(conversationViewModel.getPagingController());
list.setAdapter(adapter);
setInlineDateDecoration(adapter);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java
index e577886aee..3884bd802e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java
@@ -16,8 +16,6 @@
*/
package org.thoughtcrime.securesms.conversation;
-import static org.thoughtcrime.securesms.util.ThemeUtil.isDarkTheme;
-
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
@@ -62,10 +60,9 @@ import androidx.core.text.util.LinkifyCompat;
import androidx.lifecycle.LifecycleOwner;
import com.annimon.stream.Stream;
-import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.MediaItem;
import com.google.common.collect.Sets;
-import org.jetbrains.annotations.NotNull;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.BindableConversationItem;
import org.thoughtcrime.securesms.MediaPreviewActivity;
@@ -128,13 +125,13 @@ import org.thoughtcrime.securesms.util.MessageRecordUtil;
import org.thoughtcrime.securesms.util.Projection;
import org.thoughtcrime.securesms.util.SearchUtil;
import org.thoughtcrime.securesms.util.StringUtil;
+import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.UrlClickHandler;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VibrateUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.NullableStub;
import org.thoughtcrime.securesms.util.views.Stub;
-import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.ArrayList;
@@ -218,7 +215,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
private final Context context;
- private MediaSource mediaSource;
+ private MediaItem mediaItem;
private boolean canPlayContent;
private Projection.Corners bodyBubbleCorners;
private Colorizer colorizer;
@@ -286,7 +283,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
boolean pulse,
boolean hasWallpaper,
boolean isMessageRequestAccepted,
- @NonNull AttachmentMediaSourceFactory attachmentMediaSourceFactory,
boolean allowedToPlayInline,
@NonNull Colorizer colorizer)
{
@@ -307,7 +303,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
this.groupThread = conversationRecipient.isGroup();
this.recipient = messageRecord.getIndividualRecipient().live();
this.canPlayContent = false;
- this.mediaSource = null;
+ this.mediaItem = null;
this.colorizer = colorizer;
this.recipient.observeForever(this);
@@ -315,7 +311,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
setGutterSizes(messageRecord, groupThread);
setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
- setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, groupThread, hasWallpaper, isMessageRequestAccepted, attachmentMediaSourceFactory, allowedToPlayInline);
+ setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, groupThread, hasWallpaper, isMessageRequestAccepted, allowedToPlayInline);
setBodyText(messageRecord, searchQuery, isMessageRequestAccepted);
setBubbleState(messageRecord, messageRecord.getRecipient(), hasWallpaper, colorizer);
setInteractionState(conversationMessage, pulse);
@@ -612,7 +608,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
}
@Override
- public ConversationMessage getConversationMessage() {
+ public @NonNull ConversationMessage getConversationMessage() {
return conversationMessage;
}
@@ -852,7 +848,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (messageRecord.isOutgoing()) {
bodyText.setMentionBackgroundTint(ContextCompat.getColor(context, R.color.transparent_black_25));
} else {
- bodyText.setMentionBackgroundTint(ContextCompat.getColor(context, isDarkTheme(context) ? R.color.core_grey_60 : R.color.core_grey_20));
+ bodyText.setMentionBackgroundTint(ContextCompat.getColor(context, ThemeUtil.isDarkTheme(context) ? R.color.core_grey_60 : R.color.core_grey_20));
}
bodyText.setText(StringUtil.trim(styledText));
@@ -866,7 +862,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
boolean isGroupThread,
boolean hasWallpaper,
boolean messageRequestAccepted,
- @Nullable AttachmentMediaSourceFactory attachmentMediaSourceFactory,
boolean allowedToPlayInline)
{
boolean showControls = !messageRecord.isFailed();
@@ -1073,8 +1068,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
footer.setVisibility(VISIBLE);
- if (attachmentMediaSourceFactory != null &&
- thumbnailSlides.size() == 1 &&
+ if (thumbnailSlides.size() == 1 &&
thumbnailSlides.get(0).isVideoGif() &&
thumbnailSlides.get(0) instanceof VideoSlide)
{
@@ -1082,9 +1076,9 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
Uri uri = thumbnailSlides.get(0).getUri();
if (uri != null) {
- mediaSource = attachmentMediaSourceFactory.createMediaSource(uri);
+ mediaItem = MediaItem.fromUri(uri);
} else {
- mediaSource = null;
+ mediaItem = null;
}
}
@@ -1675,8 +1669,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
}
@Override
- public @Nullable MediaSource getMediaSource() {
- return mediaSource;
+ public @Nullable MediaItem getMediaItem() {
+ return mediaItem;
}
@Override
@@ -1882,7 +1876,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
public void onClick(final View v, final Slide slide) {
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
performClick();
- } else if (!canPlayContent && mediaSource != null && eventListener != null) {
+ } else if (!canPlayContent && mediaItem != null && eventListener != null) {
eventListener.onPlayInlineContent(conversationMessage);
} else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
Intent intent = new Intent(context, MediaPreviewActivity.class);
@@ -1960,7 +1954,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
private final class TouchDelegateChangedListener implements ConversationItemFooter.OnTouchDelegateChangedListener {
@Override
- public void onTouchDelegateChanged(@NonNull @NotNull Rect delegateRect, @NonNull @NotNull View delegateView) {
+ public void onTouchDelegateChanged(@NonNull Rect delegateRect, @NonNull View delegateView) {
offsetDescendantRectToMyCoords(footer, delegateRect);
setTouchDelegate(new TouchDelegate(delegateRect, delegateView));
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java
index dd338609ab..851086ce50 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java
@@ -2,11 +2,9 @@ package org.thoughtcrime.securesms.conversation;
import android.content.Context;
import android.content.res.ColorStateList;
-import android.graphics.Point;
import android.text.Spannable;
import android.text.SpannableString;
import android.util.AttributeSet;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -27,7 +25,6 @@ import org.thoughtcrime.securesms.BindableConversationItem;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.VerifyIdentityActivity;
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
-import org.thoughtcrime.securesms.conversation.mutiselect.Multiselect;
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSettingsDialog;
import org.thoughtcrime.securesms.database.model.IdentityRecord;
@@ -48,7 +45,6 @@ import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
-import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Collection;
@@ -116,7 +112,6 @@ public final class ConversationUpdateItem extends FrameLayout
boolean pulseMention,
boolean hasWallpaper,
boolean isMessageRequestAccepted,
- @NonNull AttachmentMediaSourceFactory attachmentMediaSourceFactory,
boolean allowedToPlayInline,
@NonNull Colorizer colorizer)
{
@@ -131,7 +126,7 @@ public final class ConversationUpdateItem extends FrameLayout
}
@Override
- public ConversationMessage getConversationMessage() {
+ public @NonNull ConversationMessage getConversationMessage() {
return conversationMessage;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Adapter.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Adapter.java
index 2c52380006..e163014ca0 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Adapter.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Adapter.java
@@ -23,15 +23,13 @@ import java.util.Objects;
final class GiphyMp4Adapter extends ListAdapter {
private final Callback listener;
- private final GiphyMp4MediaSourceFactory mediaSourceFactory;
private PagingController pagingController;
- public GiphyMp4Adapter(@NonNull GiphyMp4MediaSourceFactory mediaSourceFactory, @Nullable Callback listener) {
+ public GiphyMp4Adapter(@Nullable Callback listener) {
super(new GiphyImageDiffUtilCallback());
this.listener = listener;
- this.mediaSourceFactory = mediaSourceFactory;
}
@Override
@@ -39,7 +37,7 @@ final class GiphyMp4Adapter extends ListAdapter
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.giphy_mp4, parent, false);
- return new GiphyMp4ViewHolder(itemView, listener, mediaSourceFactory);
+ return new GiphyMp4ViewHolder(itemView, listener);
}
@Override
diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ExoPlayerProvider.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ExoPlayerProvider.java
index ccaa03d872..10488a8fd6 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ExoPlayerProvider.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ExoPlayerProvider.java
@@ -6,39 +6,37 @@ import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.lifecycle.DefaultLifecycleObserver;
-import com.google.android.exoplayer2.DefaultLoadControl;
-import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlayer;
-import com.google.android.exoplayer2.ExoPlayerFactory;
-import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
-import com.google.android.exoplayer2.trackselection.TrackSelection;
-import com.google.android.exoplayer2.trackselection.TrackSelector;
+import com.google.android.exoplayer2.source.MediaSourceFactory;
+import com.google.android.exoplayer2.source.ProgressiveMediaSource;
+import com.google.android.exoplayer2.upstream.DataSource;
+
+import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
+import org.thoughtcrime.securesms.net.ContentProxySelector;
+import org.thoughtcrime.securesms.video.exo.ChunkedDataSourceFactory;
+
+import okhttp3.OkHttpClient;
/**
* Provider which creates ExoPlayer instances for displaying Giphy content.
*/
final class GiphyMp4ExoPlayerProvider implements DefaultLifecycleObserver {
- private final Context context;
- private final TrackSelection.Factory videoTrackSelectionFactory;
- private final DefaultRenderersFactory renderersFactory;
- private final TrackSelector trackSelector;
- private final LoadControl loadControl;
+ private final Context context;
+ private final OkHttpClient okHttpClient = ApplicationDependencies.getOkHttpClient().newBuilder().proxySelector(new ContentProxySelector()).build();
+ private final DataSource.Factory dataSourceFactory = new ChunkedDataSourceFactory(okHttpClient, null);
+ private final MediaSourceFactory mediaSourceFactory = new ProgressiveMediaSource.Factory(dataSourceFactory);
GiphyMp4ExoPlayerProvider(@NonNull Context context) {
- this.context = context;
- this.videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
- this.renderersFactory = new DefaultRenderersFactory(context);
- this.trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
- this.loadControl = new DefaultLoadControl();
+ this.context = context;
}
@MainThread final @NonNull ExoPlayer create() {
- SimpleExoPlayer exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector, loadControl);
+ SimpleExoPlayer exoPlayer = new SimpleExoPlayer.Builder(context)
+ .setMediaSourceFactory(mediaSourceFactory)
+ .build();
exoPlayer.setRepeatMode(Player.REPEAT_MODE_ALL);
exoPlayer.setVolume(0f);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Fragment.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Fragment.java
index 5a0c80d602..d77121a324 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Fragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Fragment.java
@@ -14,8 +14,6 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.net.ContentProxySelector;
import java.util.List;
@@ -48,8 +46,7 @@ public class GiphyMp4Fragment extends Fragment {
ContentLoadingProgressBar progressBar = view.findViewById(R.id.content_loading);
TextView nothingFound = view.findViewById(R.id.nothing_found);
GiphyMp4ViewModel viewModel = ViewModelProviders.of(requireActivity(), new GiphyMp4ViewModel.Factory(isForMms)).get(GiphyMp4ViewModel.class);
- GiphyMp4MediaSourceFactory mediaSourceFactory = new GiphyMp4MediaSourceFactory(ApplicationDependencies.getOkHttpClient().newBuilder().proxySelector(new ContentProxySelector()).build());
- GiphyMp4Adapter adapter = new GiphyMp4Adapter(mediaSourceFactory, viewModel::saveToBlob);
+ GiphyMp4Adapter adapter = new GiphyMp4Adapter(viewModel::saveToBlob);
List holders = GiphyMp4ProjectionPlayerHolder.injectVideoViews(requireContext(),
getViewLifecycleOwner().getLifecycle(),
frameLayout,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4MediaSourceFactory.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4MediaSourceFactory.java
deleted file mode 100644
index 89bc7f952e..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4MediaSourceFactory.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.thoughtcrime.securesms.giph.mp4;
-
-import android.net.Uri;
-
-import androidx.annotation.NonNull;
-
-import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
-import com.google.android.exoplayer2.extractor.ExtractorsFactory;
-import com.google.android.exoplayer2.source.ExtractorMediaSource;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.upstream.DataSource;
-
-import org.thoughtcrime.securesms.video.exo.ChunkedDataSourceFactory;
-
-import okhttp3.OkHttpClient;
-
-/**
- * Factory which creates MediaSource objects for given Giphy URIs
- */
-final class GiphyMp4MediaSourceFactory {
-
- private final DataSource.Factory dataSourceFactory;
- private final ExtractorsFactory extractorsFactory;
- private final ExtractorMediaSource.Factory extractorMediaSourceFactory;
-
- GiphyMp4MediaSourceFactory(@NonNull OkHttpClient okHttpClient) {
- dataSourceFactory = new ChunkedDataSourceFactory(okHttpClient, null);
- extractorsFactory = new DefaultExtractorsFactory();
- extractorMediaSourceFactory = new ExtractorMediaSource.Factory(dataSourceFactory).setExtractorsFactory(extractorsFactory);
- }
-
- @NonNull MediaSource create(@NonNull Uri uri) {
- return extractorMediaSourceFactory.createMediaSource(uri);
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Playable.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Playable.java
index e651fa43bc..98cb2b5eb7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Playable.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4Playable.java
@@ -5,7 +5,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.MediaItem;
import org.thoughtcrime.securesms.util.Projection;
@@ -23,9 +23,9 @@ public interface GiphyMp4Playable {
void hideProjectionArea();
/**
- * @return The MediaSource to play back in the given VideoPlayer
+ * @return The MediaItem to play back in the given VideoPlayer
*/
- default @Nullable MediaSource getMediaSource() {
+ default @Nullable MediaItem getMediaItem() {
return null;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4PlaybackPolicy.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4PlaybackPolicy.java
index 92805ab75c..39421d566c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4PlaybackPolicy.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4PlaybackPolicy.java
@@ -49,7 +49,7 @@ public final class GiphyMp4PlaybackPolicy {
int maxInstances = 0;
try {
- MediaCodecInfo info = MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false);
+ MediaCodecInfo info = MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false, false);
if (info != null && info.getMaxSupportedInstances() > 0) {
maxInstances = (int) (info.getMaxSupportedInstances() * ratio);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionPlayerHolder.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionPlayerHolder.java
index 8dc77eae5b..a16e17cf0b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionPlayerHolder.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionPlayerHolder.java
@@ -11,13 +11,11 @@ import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
-import org.signal.glide.Log;
import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.components.CornerMask;
import org.thoughtcrime.securesms.util.Projection;
import java.util.ArrayList;
@@ -26,12 +24,12 @@ import java.util.List;
/**
* Object which holds on to an injected video player.
*/
-public final class GiphyMp4ProjectionPlayerHolder implements Player.EventListener {
+public final class GiphyMp4ProjectionPlayerHolder implements Player.Listener {
private final FrameLayout container;
private final GiphyMp4VideoPlayer player;
private Runnable onPlaybackReady;
- private MediaSource mediaSource;
+ private MediaItem mediaItem;
private GiphyMp4PlaybackPolicyEnforcer policyEnforcer;
private GiphyMp4ProjectionPlayerHolder(@NonNull FrameLayout container, @NonNull GiphyMp4VideoPlayer player) {
@@ -43,22 +41,22 @@ public final class GiphyMp4ProjectionPlayerHolder implements Player.EventListene
return container;
}
- public void playContent(@NonNull MediaSource mediaSource, @Nullable GiphyMp4PlaybackPolicyEnforcer policyEnforcer) {
- this.mediaSource = mediaSource;
+ public void playContent(@NonNull MediaItem mediaItem, @Nullable GiphyMp4PlaybackPolicyEnforcer policyEnforcer) {
+ this.mediaItem = mediaItem;
this.policyEnforcer = policyEnforcer;
- player.setVideoSource(mediaSource);
+ player.setVideoItem(mediaItem);
player.play();
}
public void clearMedia() {
- this.mediaSource = null;
+ this.mediaItem = null;
this.policyEnforcer = null;
player.stop();
}
- public @Nullable MediaSource getMediaSource() {
- return mediaSource;
+ public @Nullable MediaItem getMediaItem() {
+ return mediaItem;
}
public void setOnPlaybackReady(@Nullable Runnable onPlaybackReady) {
@@ -74,7 +72,7 @@ public final class GiphyMp4ProjectionPlayerHolder implements Player.EventListene
}
@Override
- public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
+ public void onPlaybackStateChanged(int playbackState) {
if (playbackState == Player.STATE_READY) {
if (onPlaybackReady != null) {
if (policyEnforcer != null) {
@@ -86,8 +84,11 @@ public final class GiphyMp4ProjectionPlayerHolder implements Player.EventListene
}
@Override
- public void onPositionDiscontinuity(int reason) {
- if (policyEnforcer != null && reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) {
+ public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition,
+ @NonNull Player.PositionInfo newPosition,
+ int reason)
+ {
+ if (policyEnforcer != null && reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
if (policyEnforcer.endPlayback()) {
player.stop();
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionRecycler.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionRecycler.java
index 9a241e3099..54d496593a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionRecycler.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionRecycler.java
@@ -108,13 +108,13 @@ public final class GiphyMp4ProjectionRecycler implements GiphyMp4PlaybackControl
}
private void startPlayback(@NonNull GiphyMp4ProjectionPlayerHolder holder, @NonNull GiphyMp4Playable giphyMp4Playable) {
- if (!Objects.equals(holder.getMediaSource(), giphyMp4Playable.getMediaSource())) {
+ if (!Objects.equals(holder.getMediaItem(), giphyMp4Playable.getMediaItem())) {
holder.setOnPlaybackReady(null);
giphyMp4Playable.showProjectionArea();
holder.show();
holder.setOnPlaybackReady(giphyMp4Playable::hideProjectionArea);
- holder.playContent(giphyMp4Playable.getMediaSource(), giphyMp4Playable.getPlaybackPolicyEnforcer());
+ holder.playContent(giphyMp4Playable.getMediaItem(), giphyMp4Playable.getPlaybackPolicyEnforcer());
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4VideoPlayer.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4VideoPlayer.java
index 6ff56539a5..fd88fd5d39 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4VideoPlayer.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4VideoPlayer.java
@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.giph.mp4;
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.util.AttributeSet;
import android.widget.FrameLayout;
@@ -13,7 +12,7 @@ import androidx.lifecycle.LifecycleOwner;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
-import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerView;
@@ -56,7 +55,8 @@ public final class GiphyMp4VideoPlayer extends FrameLayout implements DefaultLif
super.onDetachedFromWindow();
}
- @Override protected void dispatchDraw(Canvas canvas) {
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (cornerMask != null) {
@@ -69,8 +69,9 @@ public final class GiphyMp4VideoPlayer extends FrameLayout implements DefaultLif
this.exoPlayer = exoPlayer;
}
- void setVideoSource(@NonNull MediaSource mediaSource) {
- exoPlayer.prepare(mediaSource);
+ void setVideoItem(@NonNull MediaItem mediaItem) {
+ exoPlayer.setMediaItem(mediaItem);
+ exoPlayer.prepare();
}
void setCorners(@Nullable Projection.Corners corners) {
@@ -91,7 +92,8 @@ public final class GiphyMp4VideoPlayer extends FrameLayout implements DefaultLif
void stop() {
if (exoPlayer != null) {
- exoPlayer.stop(true);
+ exoPlayer.stop();
+ exoPlayer.clearMediaItems();
}
}
@@ -107,7 +109,8 @@ public final class GiphyMp4VideoPlayer extends FrameLayout implements DefaultLif
exoView.setResizeMode(resizeMode);
}
- @Override public void onDestroy(@NonNull LifecycleOwner owner) {
+ @Override
+ public void onDestroy(@NonNull LifecycleOwner owner) {
if (exoPlayer != null) {
exoPlayer.release();
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ViewHolder.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ViewHolder.java
index 79bb4470c7..f7bf602881 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ViewHolder.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ViewHolder.java
@@ -13,7 +13,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
-import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import org.thoughtcrime.securesms.R;
@@ -36,28 +36,25 @@ final class GiphyMp4ViewHolder extends RecyclerView.ViewHolder implements GiphyM
private final ImageView stillImage;
private final GiphyMp4Adapter.Callback listener;
private final Drawable placeholder;
- private final GiphyMp4MediaSourceFactory mediaSourceFactory;
- private float aspectRatio;
- private MediaSource mediaSource;
+ private float aspectRatio;
+ private MediaItem mediaItem;
GiphyMp4ViewHolder(@NonNull View itemView,
- @Nullable GiphyMp4Adapter.Callback listener,
- @NonNull GiphyMp4MediaSourceFactory mediaSourceFactory)
+ @Nullable GiphyMp4Adapter.Callback listener)
{
super(itemView);
this.container = itemView.findViewById(R.id.container);
this.listener = listener;
this.stillImage = itemView.findViewById(R.id.still_image);
this.placeholder = new ColorDrawable(Util.getRandomElement(ChatColorsPalette.Names.getAll()).getColor(itemView.getContext()));
- this.mediaSourceFactory = mediaSourceFactory;
container.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH);
}
void onBind(@NonNull GiphyImage giphyImage) {
aspectRatio = giphyImage.getGifAspectRatio();
- mediaSource = mediaSourceFactory.create(Uri.parse(giphyImage.getMp4PreviewUrl()));
+ mediaItem = MediaItem.fromUri(Uri.parse(giphyImage.getMp4PreviewUrl()));
container.setAspectRatio(aspectRatio);
@@ -77,8 +74,8 @@ final class GiphyMp4ViewHolder extends RecyclerView.ViewHolder implements GiphyM
}
@Override
- public @NonNull MediaSource getMediaSource() {
- return mediaSource;
+ public @NonNull MediaItem getMediaItem() {
+ return mediaItem;
}
@Override
diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageHeaderViewHolder.java b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageHeaderViewHolder.java
index 6519d06c81..3c5d9d9d91 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageHeaderViewHolder.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageHeaderViewHolder.java
@@ -17,9 +17,8 @@ import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.RecyclerView;
import com.annimon.stream.Stream;
-import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.MediaItem;
-import org.jetbrains.annotations.NotNull;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.R;
@@ -31,12 +30,11 @@ import org.thoughtcrime.securesms.conversation.colors.Colorizer;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Playable;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer;
-import org.thoughtcrime.securesms.util.Projection;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.ExpirationUtil;
-import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
+import org.thoughtcrime.securesms.util.Projection;
import org.whispersystems.libsignal.util.guava.Optional;
import java.sql.Date;
@@ -119,7 +117,6 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
false,
false,
false,
- new AttachmentMediaSourceFactory(conversationItem.getContext()),
true,
colorizer);
}
@@ -238,8 +235,8 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
}
@Override
- public @Nullable MediaSource getMediaSource() {
- return conversationItem.getMediaSource();
+ public @Nullable MediaItem getMediaItem() {
+ return conversationItem.getMediaItem();
}
@Override
@@ -252,12 +249,13 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
return conversationItem.getGiphyMp4PlayableProjection(recyclerview);
}
- @Override public
- boolean canPlayContent() {
+ @Override
+ public boolean canPlayContent() {
return conversationItem.canPlayContent();
}
- @NotNull @Override public List getColorizerProjections() {
+ @Override
+ public @NonNull List getColorizerProjections() {
List projections = conversationItem.getColorizerProjections();
updateProjections();
return projections;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java
index 65a1441796..582f5917f7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java
@@ -26,21 +26,11 @@ import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.DefaultLoadControl;
-import com.google.android.exoplayer2.DefaultRenderersFactory;
-import com.google.android.exoplayer2.ExoPlayerFactory;
-import com.google.android.exoplayer2.LoadControl;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
-import com.google.android.exoplayer2.extractor.ExtractorsFactory;
-import com.google.android.exoplayer2.source.ClippingMediaSource;
-import com.google.android.exoplayer2.source.ExtractorMediaSource;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
-import com.google.android.exoplayer2.trackselection.TrackSelection;
-import com.google.android.exoplayer2.trackselection.TrackSelector;
+import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
+import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.PlayerView;
@@ -51,6 +41,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.video.exo.AttachmentDataSourceFactory;
+import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class VideoPlayer extends FrameLayout {
@@ -87,21 +78,30 @@ public class VideoPlayer extends FrameLayout {
this.exoControls.setShowTimeoutMs(-1);
}
- private CreateMediaSource createMediaSource;
+ private MediaItem mediaItem;
public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay) {
- Context context = getContext();
- DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(context);
- TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
- TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
- LoadControl loadControl = new DefaultLoadControl();
+ Context context = getContext();
if (exoPlayer == null) {
- exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector, loadControl);
+ DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(context, "GenericUserAgent", null);
+ AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(context, defaultDataSourceFactory, null);
+ MediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(attachmentDataSourceFactory);
+
+ exoPlayer = new SimpleExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build();
exoPlayer.addListener(new ExoPlayerListener(this, window, playerStateCallback, playerPositionDiscontinuityCallback));
- exoPlayer.addListener(new Player.EventListener() {
+ exoPlayer.addListener(new Player.Listener() {
@Override
- public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
+ public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
+ onPlaybackStateChanged(playWhenReady, exoPlayer.getPlaybackState());
+ }
+
+ @Override
+ public void onPlaybackStateChanged(int playbackState) {
+ onPlaybackStateChanged(exoPlayer.getPlayWhenReady(), playbackState);
+ }
+
+ private void onPlaybackStateChanged(boolean playWhenReady, int playbackState) {
if (playerCallback != null) {
switch (playbackState) {
case Player.STATE_READY:
@@ -118,15 +118,9 @@ public class VideoPlayer extends FrameLayout {
exoControls.setPlayer(exoPlayer);
}
- DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(context, "GenericUserAgent", null);
- AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(context, defaultDataSourceFactory, null);
- ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
-
- createMediaSource = () -> new ExtractorMediaSource.Factory(attachmentDataSourceFactory)
- .setExtractorsFactory(extractorsFactory)
- .createMediaSource(videoSource.getUri());
-
- exoPlayer.prepare(createMediaSource.create());
+ mediaItem = MediaItem.fromUri(Objects.requireNonNull(videoSource.getUri()));
+ exoPlayer.setMediaItem(mediaItem);
+ exoPlayer.prepare();
exoPlayer.setPlayWhenReady(autoplay);
}
@@ -151,10 +145,7 @@ public class VideoPlayer extends FrameLayout {
}
public @Nullable View getControlView() {
- if (this.exoControls != null) {
- return this.exoControls;
- }
- return null;
+ return this.exoControls;
}
public void cleanup() {
@@ -198,9 +189,13 @@ public class VideoPlayer extends FrameLayout {
}
public void clip(long fromUs, long toUs, boolean playWhenReady) {
- if (this.exoPlayer != null && createMediaSource != null) {
- MediaSource clippedMediaSource = new ClippingMediaSource(createMediaSource.create(), fromUs, toUs);
- exoPlayer.prepare(clippedMediaSource);
+ if (this.exoPlayer != null && mediaItem != null) {
+ MediaItem clippedMediaItem = mediaItem.buildUpon()
+ .setClipStartPositionMs(TimeUnit.MICROSECONDS.toMillis(fromUs))
+ .setClipEndPositionMs(TimeUnit.MICROSECONDS.toMillis(toUs))
+ .build();
+ exoPlayer.setMediaItem(clippedMediaItem);
+ exoPlayer.prepare();
exoPlayer.setPlayWhenReady(playWhenReady);
clipped = true;
clippedStartUs = fromUs;
@@ -208,9 +203,10 @@ public class VideoPlayer extends FrameLayout {
}
public void removeClip(boolean playWhenReady) {
- if (exoPlayer != null && createMediaSource != null) {
+ if (exoPlayer != null && mediaItem != null) {
if (clipped) {
- exoPlayer.prepare(createMediaSource.create());
+ exoPlayer.setMediaItem(mediaItem);
+ exoPlayer.prepare();
clipped = false;
clippedStartUs = 0;
}
@@ -246,7 +242,7 @@ public class VideoPlayer extends FrameLayout {
}
}
- private static class ExoPlayerListener implements Player.EventListener {
+ private static class ExoPlayerListener implements Player.Listener {
private final VideoPlayer videoPlayer;
private final Window window;
private final PlayerStateCallback playerStateCallback;
@@ -264,7 +260,16 @@ public class VideoPlayer extends FrameLayout {
}
@Override
- public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
+ public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
+ onPlaybackStateChanged(playWhenReady, videoPlayer.exoPlayer.getPlaybackState());
+ }
+
+ @Override
+ public void onPlaybackStateChanged(int playbackState) {
+ onPlaybackStateChanged(videoPlayer.exoPlayer.getPlayWhenReady(), playbackState);
+ }
+
+ private void onPlaybackStateChanged(boolean playWhenReady, int playbackState) {
switch (playbackState) {
case Player.STATE_IDLE:
case Player.STATE_BUFFERING:
@@ -289,7 +294,10 @@ public class VideoPlayer extends FrameLayout {
}
@Override
- public void onPositionDiscontinuity(int reason) {
+ public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition,
+ @NonNull Player.PositionInfo newPosition,
+ int reason)
+ {
if (playerPositionDiscontinuityCallback != null) {
playerPositionDiscontinuityCallback.onPositionDiscontinuity(videoPlayer, reason);
}
@@ -314,8 +322,4 @@ public class VideoPlayer extends FrameLayout {
void onStopped();
}
-
- private interface CreateMediaSource {
- MediaSource create();
- }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java
index 2a55e58630..d965835d33 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java
@@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.video.exo;
import android.net.Uri;
+import androidx.annotation.NonNull;
+
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
@@ -34,7 +36,7 @@ public class AttachmentDataSource implements DataSource {
}
@Override
- public void addTransferListener(TransferListener transferListener) {
+ public void addTransferListener(@NonNull TransferListener transferListener) {
}
@Override
@@ -47,7 +49,7 @@ public class AttachmentDataSource implements DataSource {
}
@Override
- public int read(byte[] buffer, int offset, int readLength) throws IOException {
+ public int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException {
return dataSource.read(buffer, offset, readLength);
}
@@ -57,7 +59,7 @@ public class AttachmentDataSource implements DataSource {
}
@Override
- public Map> getResponseHeaders() {
+ public @NonNull Map> getResponseHeaders() {
return Collections.emptyMap();
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java
index 2e3e5e2ea3..9bd861b4cc 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java
@@ -27,7 +27,7 @@ public class AttachmentDataSourceFactory implements DataSource.Factory {
}
@Override
- public AttachmentDataSource createDataSource() {
+ public @NonNull AttachmentDataSource createDataSource() {
return new AttachmentDataSource(defaultDataSourceFactory.createDataSource(),
new PartDataSource(context, listener),
new BlobDataSource(context, listener));
diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentMediaSourceFactory.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentMediaSourceFactory.java
index 7b3cbf8501..90376d758f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentMediaSourceFactory.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentMediaSourceFactory.java
@@ -5,27 +5,38 @@ import android.net.Uri;
import android.support.v4.media.MediaDescriptionCompat;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
-import com.google.android.exoplayer2.source.ExtractorMediaSource;
+import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.MediaSourceFactory;
+import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
+import com.google.android.exoplayer2.upstream.HttpDataSource;
+import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
+
+import java.util.List;
/**
* This class is responsible for creating a MediaSource object for a given Uri, using AttachmentDataSourceFactory
*/
-public final class AttachmentMediaSourceFactory {
+@SuppressWarnings("deprecation")
+public final class AttachmentMediaSourceFactory implements MediaSourceFactory {
- private final ExtractorMediaSource.Factory extractorMediaSourceFactory;
+ private final ProgressiveMediaSource.Factory progressiveMediaSourceFactory;
public AttachmentMediaSourceFactory(@NonNull Context context) {
DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(context, "GenericUserAgent", null);
AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(context, defaultDataSourceFactory, null);
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
- extractorMediaSourceFactory = new ExtractorMediaSource.Factory(attachmentDataSourceFactory)
- .setExtractorsFactory(extractorsFactory);
+ progressiveMediaSourceFactory = new ProgressiveMediaSource.Factory(attachmentDataSourceFactory, extractorsFactory);
}
/**
@@ -36,13 +47,53 @@ public final class AttachmentMediaSourceFactory {
* @return A preparable MediaSource
*/
public @NonNull MediaSource createMediaSource(MediaDescriptionCompat description) {
- return createMediaSource(description.getMediaUri());
+ return progressiveMediaSourceFactory.createMediaSource(
+ new MediaItem.Builder().setUri(description.getMediaUri()).setTag(description).build()
+ );
}
- /**
- * Creates a MediaSource for a given Uri
- */
- public @NonNull MediaSource createMediaSource(Uri uri) {
- return extractorMediaSourceFactory.createMediaSource(uri);
+ @Override
+ public MediaSourceFactory setStreamKeys(@Nullable List streamKeys) {
+ return progressiveMediaSourceFactory.setStreamKeys(streamKeys);
+ }
+
+ @Override
+ public MediaSourceFactory setDrmSessionManagerProvider(@Nullable DrmSessionManagerProvider drmSessionManagerProvider) {
+ return progressiveMediaSourceFactory.setDrmSessionManagerProvider(drmSessionManagerProvider);
+ }
+
+ @Override
+ public MediaSourceFactory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
+ return progressiveMediaSourceFactory.setDrmSessionManager(drmSessionManager);
+ }
+
+ @Override
+ public MediaSourceFactory setDrmHttpDataSourceFactory(@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
+ return progressiveMediaSourceFactory.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
+ }
+
+ @Override
+ public MediaSourceFactory setDrmUserAgent(@Nullable String userAgent) {
+ return progressiveMediaSourceFactory.setDrmUserAgent(userAgent);
+ }
+
+ @Override
+ public MediaSourceFactory setLoadErrorHandlingPolicy(@Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
+ return progressiveMediaSourceFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy);
+ }
+
+ @Override
+ public int[] getSupportedTypes() {
+ return new int[] { C.TYPE_OTHER };
+ }
+
+ @Override
+ public MediaSource createMediaSource(MediaItem mediaItem) {
+ return progressiveMediaSourceFactory.createMediaSource(mediaItem);
+ }
+
+ @Override
+ public MediaSource createMediaSource(Uri uri) {
+ return progressiveMediaSourceFactory.createMediaSource(uri);
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/BlobDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/BlobDataSource.java
index b45fb38d22..c07ebb6d39 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/BlobDataSource.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/BlobDataSource.java
@@ -25,7 +25,7 @@ public class BlobDataSource implements DataSource {
private final @NonNull Context context;
private final @Nullable TransferListener listener;
- private Uri uri;
+ private DataSpec dataSpec;
private InputStream inputStream;
BlobDataSource(@NonNull Context context, @Nullable TransferListener listener) {
@@ -34,21 +34,21 @@ public class BlobDataSource implements DataSource {
}
@Override
- public void addTransferListener(TransferListener transferListener) {
+ public void addTransferListener(@NonNull TransferListener transferListener) {
}
@Override
public long open(DataSpec dataSpec) throws IOException {
- this.uri = dataSpec.uri;
- this.inputStream = BlobProvider.getInstance().getStream(context, uri, dataSpec.position);
+ this.dataSpec = dataSpec;
+ this.inputStream = BlobProvider.getInstance().getStream(context, dataSpec.uri, dataSpec.position);
if (listener != null) {
listener.onTransferStart(this, dataSpec, false);
}
- long size = unwrapLong(BlobProvider.getFileSize(uri));
+ long size = unwrapLong(BlobProvider.getFileSize(dataSpec.uri));
if (size == 0) {
- size = BlobProvider.getInstance().calculateFileSize(context, uri);
+ size = BlobProvider.getInstance().calculateFileSize(context, dataSpec.uri);
}
if (size - dataSpec.position <= 0) throw new EOFException("No more data");
@@ -61,11 +61,11 @@ public class BlobDataSource implements DataSource {
}
@Override
- public int read(byte[] buffer, int offset, int readLength) throws IOException {
+ public int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException {
int read = inputStream.read(buffer, offset, readLength);
if (read > 0 && listener != null) {
- listener.onBytesTransferred(this, null, false, read);
+ listener.onBytesTransferred(this, dataSpec, false, read);
}
return read;
@@ -73,11 +73,11 @@ public class BlobDataSource implements DataSource {
@Override
public Uri getUri() {
- return uri;
+ return dataSpec.uri;
}
@Override
- public Map> getResponseHeaders() {
+ public @NonNull Map> getResponseHeaders() {
return Collections.emptyMap();
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSource.java
index 78621f87e4..45da165972 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSource.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSource.java
@@ -28,7 +28,7 @@ public class ChunkedDataSource implements DataSource {
private final OkHttpClient okHttpClient;
private final TransferListener transferListener;
- private Uri uri;
+ private DataSpec dataSpec;
private volatile InputStream inputStream;
private volatile Exception exception;
@@ -38,12 +38,12 @@ public class ChunkedDataSource implements DataSource {
}
@Override
- public void addTransferListener(TransferListener transferListener) {
+ public void addTransferListener(@NonNull TransferListener transferListener) {
}
@Override
- public long open(DataSpec dataSpec) throws IOException {
- this.uri = dataSpec.uri;
+ public long open(@NonNull DataSpec dataSpec) throws IOException {
+ this.dataSpec = dataSpec;
this.exception = null;
if (inputStream != null) {
@@ -55,7 +55,7 @@ public class ChunkedDataSource implements DataSource {
CountDownLatch countDownLatch = new CountDownLatch(1);
ChunkedDataFetcher fetcher = new ChunkedDataFetcher(okHttpClient);
- fetcher.fetch(this.uri.toString(), dataSpec.length, new ChunkedDataFetcher.Callback() {
+ fetcher.fetch(this.dataSpec.uri.toString(), dataSpec.length, new ChunkedDataFetcher.Callback() {
@Override
public void onSuccess(InputStream stream) {
inputStream = stream;
@@ -87,7 +87,7 @@ public class ChunkedDataSource implements DataSource {
transferListener.onTransferStart(this, dataSpec, false);
}
- if ( dataSpec.length != C.LENGTH_UNSET && dataSpec.length - dataSpec.position <= 0) {
+ if (dataSpec.length != C.LENGTH_UNSET && dataSpec.length - dataSpec.position <= 0) {
throw new EOFException("No more data");
}
@@ -95,11 +95,11 @@ public class ChunkedDataSource implements DataSource {
}
@Override
- public int read(byte[] buffer, int offset, int readLength) throws IOException {
+ public int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException {
int read = inputStream.read(buffer, offset, readLength);
if (read > 0 && transferListener != null) {
- transferListener.onBytesTransferred(this, null, false, read);
+ transferListener.onBytesTransferred(this, dataSpec, false, read);
}
return read;
@@ -107,7 +107,7 @@ public class ChunkedDataSource implements DataSource {
@Override
public @Nullable Uri getUri() {
- return uri;
+ return dataSpec.uri;
}
@Override
diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSourceFactory.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSourceFactory.java
index 603e533c6a..f6a01c9c7f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSourceFactory.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSourceFactory.java
@@ -20,7 +20,7 @@ public class ChunkedDataSourceFactory implements DataSource.Factory {
@Override
- public DataSource createDataSource() {
+ public @NonNull DataSource createDataSource() {
return new ChunkedDataSource(okHttpClient, listener);
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java
index ca208f4bef..22bf62a7de 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java
@@ -37,7 +37,7 @@ public class PartDataSource implements DataSource {
}
@Override
- public void addTransferListener(TransferListener transferListener) {
+ public void addTransferListener(@NonNull TransferListener transferListener) {
}
@Override
@@ -62,7 +62,7 @@ public class PartDataSource implements DataSource {
}
@Override
- public int read(byte[] buffer, int offset, int readLength) throws IOException {
+ public int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException {
int read = inputSteam.read(buffer, offset, readLength);
if (read > 0 && listener != null) {
@@ -78,7 +78,7 @@ public class PartDataSource implements DataSource {
}
@Override
- public Map> getResponseHeaders() {
+ public @NonNull Map> getResponseHeaders() {
return Collections.emptyMap();
}
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 0cb7915b2b..29b7815c19 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -2993,8 +2993,8 @@
فشل تشغيل الرسالة الصوتية
- رسالة صوتية · %1$s
- %1$s إلى %2$s
+ رسالة صوتية · %1$s
+ %1$s إلى %2$s
%1$s/%2$s
تم حظر \"%1$s\".
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index c902e39585..9fb2f79cd1 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -2715,8 +2715,8 @@
Səsli mesaj oxudulmadı
- Səsli mesaj · %1$s
- %1$s, %2$s
+ Səsli mesaj · %1$s
+ %1$s, %2$s
%1$s/%2$s
\"%1$s\" əngəlləndi.
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index 853f2c25b5..67ab8ee9ff 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -2613,8 +2613,8 @@
Грешка при възпроизвеждане на гласово съобщение
- Гласово съобщение · %1$s
- %1$s до %2$s
+ Гласово съобщение · %1$s
+ %1$s до %2$s
%1$s/%2$s
\"%1$s\" беше блокиран.
diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml
index f7befe85a5..bf768a82f4 100644
--- a/app/src/main/res/values-bn/strings.xml
+++ b/app/src/main/res/values-bn/strings.xml
@@ -2674,8 +2674,8 @@
ভয়েস বার্তা চালাতে ব্যর্থ
- ভয়েস বার্তা। %1$s
- %1$s থেকে %2$s
+ ভয়েস বার্তা। %1$s
+ %1$s থেকে %2$s
%1$s/%2$s
\"%1$s\" কে ব্লক করা হয়েছে।
diff --git a/app/src/main/res/values-bs/strings.xml b/app/src/main/res/values-bs/strings.xml
index 9fd06ab528..2970331091 100644
--- a/app/src/main/res/values-bs/strings.xml
+++ b/app/src/main/res/values-bs/strings.xml
@@ -2803,8 +2803,8 @@ prenijeli račun na svoj novi Android uređaj.
Neuspjelo puštanje glasovne poruke
- Glasovna poruka · %1$s
- %1$s do %2$s
+ Glasovna poruka · %1$s
+ %1$s do %2$s
%1$s/%2$s
\"%1$s\" je blokiran/a.
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index c342af479d..d3143c0e02 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -2709,8 +2709,8 @@ S\'ha rebut un missatge d\'intercanvi de claus per a una versió del protocol no
Ha fallat reproduir el missatge de veu.
- Missatge de veu · %1$s
- %1$s a %2$s
+ Missatge de veu · %1$s
+ %1$s a %2$s
%1$s/%2$s
S\'ha blocat %1$s.
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 587a13215d..7813671751 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -2880,8 +2880,8 @@ Obdržen požadavek na výměnu klíčů pro neplatnou verzi protokolu.
Nepodařilo ze přehrát zvukovou zprávu
- Hlasová zpráva · %1$s
- %1$s pro %2$s
+ Hlasová zpráva · %1$s
+ %1$s pro %2$s
%1$s/%2$s
\"%1$s\" byl zablokován.
diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml
index aff492d3ba..ad46f7f47f 100644
--- a/app/src/main/res/values-cy/strings.xml
+++ b/app/src/main/res/values-cy/strings.xml
@@ -2883,8 +2883,8 @@ Send neges heb ei ddiogelu?
Methu chware neges llais
- Neges llais · %1$s
- %1$s i %2$s
+ Neges llais · %1$s
+ %1$s i %2$s
%1$s/%2$s
Mae \"%1$s\" wedi\'i rwystro.
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index f57613c6ea..bd52360864 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -2658,8 +2658,8 @@ Modtog en nøgle besked, for en ugyldig protokol-version
Linket er ikke aktivt i øjeblikket
- Stemme besked · %1$s
- %1$s til %2$s
+ Stemme besked · %1$s
+ %1$s til %2$s
%1$s/%2$s
\"%1$s\" er blevet blokeret
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 768ca1b792..ec6405501d 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -2704,8 +2704,8 @@ Schlüsselaustausch-Nachricht für eine ungültige Protokollversion empfangen
Sprachnachricht konnte nicht wiedergegeben werden
- Sprachnachricht · %1$s
- %1$s an %2$s
+ Sprachnachricht · %1$s
+ %1$s an %2$s
%1$s/%2$s
%1$s wurde blockiert.
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 42ce0d57b8..d221427772 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -2618,8 +2618,8 @@
Αποτυχία αναπαραγωγής μηνύματος φωνής
- Μήνυμα φωνής · %1$s
- %1$s προς %2$s
+ Μήνυμα φωνής · %1$s
+ %1$s προς %2$s
%1$s/%2$s
Ο/Η \"%1$s\" έχει φραγεί
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index 7692a843f2..c6dda87dd3 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -2659,8 +2659,8 @@ Ricevis mesaĝon pri interŝanĝo de ŝlosiloj por nevalida protokola versio.
La ligilo nun ne estas aktiva
- Voĉa mesaĝo · %1$s
- %1$s al %2$s
+ Voĉa mesaĝo · %1$s
+ %1$s al %2$s
%1$s/%2$s
„%1$s“ estis blokita.
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 946c8d9219..91683f436b 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -2715,8 +2715,8 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del
Fallo al reproducir nota de voz
- Nota de voz · %1$s
- %1$s de %2$s
+ Nota de voz · %1$s
+ %1$s de %2$s
%1$s/%2$s
Se ha bloqueado a %1$s
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index 5e9228beb7..ae7e15afc5 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -2568,8 +2568,8 @@
See link ei ole hetkel aktiivne
- Häälsõnum · %1$s
- %1$s saajale %2$s
+ Häälsõnum · %1$s
+ %1$s saajale %2$s
%1$s/%2$s
\"%1$s\" on blokitud
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index 2b4cfc0e59..d6b3f943b6 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -2397,8 +2397,8 @@ Gakoaren elkar-trukeraro mezua jaso da protokoloaren bertsio baliogabe baterako.
Esteka hau ez dago indarrean
- Ahots-mezua · %1$s
- %1$s tik %2$s ra
+ Ahots-mezua · %1$s
+ %1$s tik %2$s ra
%1$s/%2$s
\"%1$s\" blokeatua izan da.
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 8f9675c7a5..971bf8f2af 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -2713,8 +2713,8 @@
پخش پیام صوتی ناموفق بود
- پیام صوتی · %1$s
- %1$s به %2$s
+ پیام صوتی · %1$s
+ %1$s به %2$s
%1$s/%2$s
«%1$s» مسدود شد.
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index e7a9dd5216..c7f9badd8c 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -2681,8 +2681,8 @@ Vastaanotetiin avaintenvaihtoviesti, joka kuuluu väärälle protokollaversiolle
Ääniviestin toisto epäonnistui
- Ääniviesti · %1$s
- %1$s henkilölle %2$s
+ Ääniviesti · %1$s
+ %1$s henkilölle %2$s
%1$s/%2$s
\"%1$s\" on nyt estetty.
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 320326a1ad..21e486d81d 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -2706,8 +2706,8 @@
Échec de lecture du message vocal
- Message vocal – %1$s
- %1$s à %2$s
+ Message vocal – %1$s
+ %1$s à %2$s
%1$s/%2$s
« %1$s » a été bloqué.
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 6f8e0d2b4c..e549f4ef8c 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -2527,8 +2527,8 @@
Fallou a reprodución da mensaxe
- Mensaxe de voz · %1$s
- %1$s a %2$s
+ Mensaxe de voz · %1$s
+ %1$s a %2$s
%1$s/%2$s
\"%1$s\" foi bloqueado.
diff --git a/app/src/main/res/values-gu/strings.xml b/app/src/main/res/values-gu/strings.xml
index ab8c6a4b1d..fbaeb42809 100644
--- a/app/src/main/res/values-gu/strings.xml
+++ b/app/src/main/res/values-gu/strings.xml
@@ -2674,8 +2674,8 @@
વૉઈસ મેસેજ ચલાવવામાં નિષ્ફળ
- વૉઈસ મેસેજ . %1$s
- %1$sતરફ%2$s
+ વૉઈસ મેસેજ . %1$s
+ %1$sતરફ%2$s
%1$s/%2$s
\"%1$s\" બ્લૉક કરવામાં આવ્યો છે
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index 89d4b35122..7e1507e5a8 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -2674,8 +2674,8 @@
वॉइस संदेश चलाने में विफल रहा
- ऑडियो संदेश · %1$s
- %1$sसे%2$s
+ ऑडियो संदेश · %1$s
+ %1$sसे%2$s
%1$s/%2$s
\"%1$s\" को ब्लॉक किया गया है
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 9b363499f5..bb4883a593 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -2759,8 +2759,8 @@ broj telefona
Reprodukcija glasovne poruke nije uspjela
- Glasovna poruka: %1$s
- %1$s za %2$s
+ Glasovna poruka: %1$s
+ %1$s za %2$s
%1$s/%2$s
\"%1$s\" je blokiran/a.
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 15f99fcf5c..ddf555ab93 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -2669,8 +2669,8 @@ Kulcs-csere üzenet érkezett érvénytelen protokoll verzióhoz.
Hangüzenet lejátszása sikertelen
- Hangüzenet · %1$s
- %1$s (címzett: %2$s)
+ Hangüzenet · %1$s
+ %1$s (címzett: %2$s)
%1$s/%2$s
\"%1$s\" letiltva.
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 63a4cfdb3a..270e34aee1 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -2597,8 +2597,8 @@ Menerima pesan pertukaran kunci untuk versi protokol yang tidak valid.
Gagal memutar pesan suara
- Pesan suara. %1$s
- %1$s hingga %2$s
+ Pesan suara. %1$s
+ %1$s hingga %2$s
%1$s/%2$s
\"%1$s\" telah diblokir.
diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml
index a558ccde86..12413d6d61 100644
--- a/app/src/main/res/values-is/strings.xml
+++ b/app/src/main/res/values-is/strings.xml
@@ -2717,8 +2717,8 @@ PIN-númerið þitt. Ef þú manst ekki PIN-númerið, geturðu endursannvottað
Mistókst að spila talskilaboð
- Talskilaboð - %1$s
- %1$s til %2$s
+ Talskilaboð - %1$s
+ %1$s til %2$s
%1$s/%2$s
\"%1$s\" hefur verið útilokaður.
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 0645ec07c0..6df0a8bfe5 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -2715,8 +2715,8 @@
Impossibile riprodurre il messaggio vocale
- Messaggio vocale · %1$s
- %1$s a %2$s
+ Messaggio vocale · %1$s
+ %1$s a %2$s
%1$s/%2$s
\"%1$s\" è stato bloccato.
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index 786476c2b2..addf12619e 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -2881,8 +2881,8 @@
נכשל בניגון הודעה קולית
- הודעה קולית · %1$s
- %1$s אל %2$s
+ הודעה קולית · %1$s
+ %1$s אל %2$s
%1$s/%2$s
\"%1$s\" נחסם.
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 1aeea59439..8f2b1cdf2d 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -2622,8 +2622,8 @@
音声メッセージを再生できませんでした。
- 音声メッセージ · %1$s
- %1$s から %2$s
+ 音声メッセージ · %1$s
+ %1$s から %2$s
%1$s/%2$s
「%1$s」はブロックされました。
diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml
index d7877918d3..adad34fdc0 100644
--- a/app/src/main/res/values-kab/strings.xml
+++ b/app/src/main/res/values-kab/strings.xml
@@ -2506,8 +2506,8 @@ i d-tefkiḍ (%s) ur yeɣti ara.
Asaɣ ur ermid ara akka tura
- Izen ameslaw. %1$s
- %1$s ɣer %2$s
+ Izen ameslaw. %1$s
+ %1$s ɣer %2$s
%1$s/%2$s
\"%1$s\" yettusewḥel.
diff --git a/app/src/main/res/values-km/strings.xml b/app/src/main/res/values-km/strings.xml
index 5343492e10..f0f984f9ec 100644
--- a/app/src/main/res/values-km/strings.xml
+++ b/app/src/main/res/values-km/strings.xml
@@ -2047,7 +2047,7 @@
បានចម្លងទៅ clipboard
- %1$s ទៅ%2$s
+ %1$s ទៅ%2$s
លុបចេញ
diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml
index 05925743b5..e1ce34d27a 100644
--- a/app/src/main/res/values-kn/strings.xml
+++ b/app/src/main/res/values-kn/strings.xml
@@ -2677,8 +2677,8 @@
ಧ್ವನಿ ಸಂದೇಶವನ್ನು ಪ್ಲೇ ಮಾಡಲು ವಿಫಲವಾಗಿದೆ
- ಧ್ವನಿ ಸಂದೇಶ . %1$s
- %1$s ಗೆ %2$s
+ ಧ್ವನಿ ಸಂದೇಶ . %1$s
+ %1$s ಗೆ %2$s
%1$s/%2$s/
\"%1$s\" ರನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ.
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 85a0d9ed9c..5d85f62643 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -2609,8 +2609,8 @@
음성 메시지를 재생하지 못했습니다.
- 음성 메시지 %1$s
- %1$s~%2$s
+ 음성 메시지 %1$s
+ %1$s~%2$s
%1$s/%2$s
\'%1$s\' 님이 차단되었습니다.
diff --git a/app/src/main/res/values-ku/strings.xml b/app/src/main/res/values-ku/strings.xml
index 450f294911..99a9270517 100644
--- a/app/src/main/res/values-ku/strings.xml
+++ b/app/src/main/res/values-ku/strings.xml
@@ -2267,8 +2267,8 @@
Ev girêdan niha ne çalak e
- Peyama dengî · %1$s
- %1$s --> %2$s
+ Peyama dengî · %1$s
+ %1$s --> %2$s
%1$s/%2$s
\"%1$s\" hat astengkirin.
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index ae5561d534..af9a6b7d7a 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -2881,8 +2881,8 @@
Nepavyko atkurti balso žinutės
- Balso žinutė · %1$s
- %1$s naudotojui %2$s
+ Balso žinutė · %1$s
+ %1$s naudotojui %2$s
%1$s/%2$s
Naudotojas „%1$s“ užblokuotas.
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
index fe9d2db613..7f85ea93cd 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -2760,8 +2760,8 @@ Saņemts nederīgas protokola versijas atslēgas apmaiņas ziņojums.
Neizdevās atskaņot balss ziņu
- Balss ziņa · %1$s
- %1$s %2$s
+ Balss ziņa · %1$s
+ %1$s %2$s
%1$s/%2$s
\"%1$s\" ir bloķēts.
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index 8df6605255..9c834a93d4 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -2641,8 +2641,8 @@
Не успеа да се пушти гласовната порака
- Гласовна порака · %1$s
- %1$s до %2$s
+ Гласовна порака · %1$s
+ %1$s до %2$s
%1$s/%2$s
„%1$s“ е блокиран/а.
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index 03f023aa18..44e99045b2 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -2675,8 +2675,8 @@
ശബ്ദ സന്ദേശം പ്ലേ ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു
- വോയിസ് മെസേജ് - %1$s
- %1$s %2$s-നോട്
+ വോയിസ് മെസേജ് - %1$s
+ %1$s %2$s-നോട്
%1$s/%2$s
\"%1$s\" - നെ തടഞ്ഞു
diff --git a/app/src/main/res/values-mr/strings.xml b/app/src/main/res/values-mr/strings.xml
index a08b2b6a58..3625e7ad26 100644
--- a/app/src/main/res/values-mr/strings.xml
+++ b/app/src/main/res/values-mr/strings.xml
@@ -2677,8 +2677,8 @@
व्हॉईस संदेश प्ले करण्यात अयशस्वी
- व्हॉईस संदेश · %1$s
- %1$s ते %2$s
+ व्हॉईस संदेश · %1$s
+ %1$s ते %2$s
%1$s/%2$s
\"%1$s\" ब्लॉक केले गेले आहे.
diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml
index 085b495ccf..d70bed5e39 100644
--- a/app/src/main/res/values-ms/strings.xml
+++ b/app/src/main/res/values-ms/strings.xml
@@ -2594,8 +2594,8 @@ Menerima mesej pertukaran kunci untuk versi protokol yang tidak sah.
Gagal memainkan mesej suara
- Mesej suara · %1$s
- %1$s kepada %2$s
+ Mesej suara · %1$s
+ %1$s kepada %2$s
%1$s/%2$s
\"%1$s\" telah disekat.
diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml
index a6efd7420d..9d0de2a5f5 100644
--- a/app/src/main/res/values-my/strings.xml
+++ b/app/src/main/res/values-my/strings.xml
@@ -2527,8 +2527,8 @@
၎င်းအဖွဲ့လင့်ခ်သည် အသက်မဝင်ပါ
- အသံ မက်ဆေ့ချ် · %1$s
- %1$sမှ %2$sသို့
+ အသံ မက်ဆေ့ချ် · %1$s
+ %1$sမှ %2$sသို့
%1$s/%2$s
\"%1$s\" ကို ပိတ်ပယ်ထားပါသည်
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index 5579459272..e6a2836fd9 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -2596,8 +2596,8 @@ Mottok nøkkelutvekslingsmelding for ugyldig protokollversion.
Denne lenken er ikke aktiv
- Lydmelding · %1$s
- %1$s til %2$s
+ Lydmelding · %1$s
+ %1$s til %2$s
%1$s/%2$s
\"%1$s\" har blitt blokkert.
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index c5bea8e1dd..0a185ad248 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -2719,8 +2719,8 @@ Tot slot moet Signal de telefoonstatus kunnen lezen om te voorkomen dat Signal-o
Het afspelen van het audiobericht is mislukt
- Audiobericht · %1$s
- %1$s naar %2$s
+ Audiobericht · %1$s
+ %1$s naar %2$s
%1$s/%2$s
“%1$s” is geblokkeerd.
diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml
index 72c14a3ebc..0fb561d098 100644
--- a/app/src/main/res/values-nn/strings.xml
+++ b/app/src/main/res/values-nn/strings.xml
@@ -2721,8 +2721,8 @@ Kom i gang ved å senda ei melding til ein venn.
Klarte ikkje spela av talemelding
- Talemelding · %1$s
- %1$s til %2$s
+ Talemelding · %1$s
+ %1$s til %2$s
%1$s/%2$s
«%1$s» er blokkert.
diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml
index 23c494c91c..e6de5bc4bf 100644
--- a/app/src/main/res/values-or/strings.xml
+++ b/app/src/main/res/values-or/strings.xml
@@ -2440,8 +2440,8 @@
ଲିଙ୍କ୍ ବର୍ତ୍ତମାନ ସକ୍ରିୟ ନାହିଁ
- ଧ୍ୱନି-ବାର୍ତ୍ତା %1$s
- %1$s ରୁ %2$sକୁ
+ ଧ୍ୱନି-ବାର୍ତ୍ତା %1$s
+ %1$s ରୁ %2$sକୁ
%1$s/%2$s
\"%1$s\" ଙ୍କୁ ଅବରୋଧ କରାଗଲା
diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml
index 2bcdf7bf3a..a0e1a64496 100644
--- a/app/src/main/res/values-pa/strings.xml
+++ b/app/src/main/res/values-pa/strings.xml
@@ -2682,8 +2682,8 @@
ਅਵਾਜ਼ ਵਾਲੇ ਸੁਨੇਹਾ ਸੁਣਾਉਣਾ ਅਸਫਲ ਰਿਹਾ
- ਆਵਾਜ਼ ਸੁਨੇਹਾ · %1$s
- %1$s ਤੋਂ %2$s
+ ਆਵਾਜ਼ ਸੁਨੇਹਾ · %1$s
+ %1$s ਤੋਂ %2$s
%1$s/%2$s
\"%1$s\" ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਈ ਹੈ।
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index c2e1c36cd1..9259b2639f 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -2873,8 +2873,8 @@ Otrzymano wiadomość wymiany klucz dla niepoprawnej wersji protokołu.
Nie udało się odtworzyć wiadomości głosowej
- Wiadomość głosowa · %1$s
- %1$s do %2$s
+ Wiadomość głosowa · %1$s
+ %1$s do %2$s
%1$s/%2$s
Zablokowano \"%1$s\".
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 50c20afbb5..d7716695e9 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -2715,8 +2715,8 @@
Falha ao reproduzir a mensagem de voz
- Mensagem de voz · %1$s
- %1$s para %2$s
+ Mensagem de voz · %1$s
+ %1$s para %2$s
%1$s/%2$s
\"%1$s\" foi bloqueado.
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 5c9afacfc1..54c89c758f 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -2714,8 +2714,8 @@
Falha ao reproduzir a mensagem de voz
- Mensagem de voz · %1$s
- %1$s para %2$s
+ Mensagem de voz · %1$s
+ %1$s para %2$s
%1$s/%2$s
\"%1$s\" foi bloqueado(a).
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 18918ee845..03dc6187e1 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -2795,8 +2795,8 @@ Am primit mesajul conform căruia schimbul de chei a avut loc pentru o versiune
Mesajul vocal nu a putut fi redat
- Mesaj vocal · %1$s
- %1$s la %2$s
+ Mesaj vocal · %1$s
+ %1$s la %2$s
%1$s/%2$s
\"%1$s\" a fost blocat.
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index da35b24dcb..5065ad8bd2 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -2874,8 +2874,8 @@
Не удалось воспроизвести голосовое сообщение
- Голосовое сообщение · %1$s
- %1$s в %2$s
+ Голосовое сообщение · %1$s
+ %1$s в %2$s
%1$s/%2$s
«%1$s» заблокирован(-а).
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 72420448a9..67451b4b36 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -2880,8 +2880,8 @@ Bola prijatá správa výmeny kľúčov s neplatnou verziou protokolu.
Nepodarilo sa prehrať hlasovú správu
- Hlasová správa · %1$s
- od %1$s do %2$s
+ Hlasová správa · %1$s
+ od %1$s do %2$s
%1$s/%2$s
\"%1$s\" bol/a zablokovaný/á.
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index 220fad493a..8fe2cd718b 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -2790,8 +2790,8 @@ Prejeto sporočilo za izmenjavo ključev za napačno različico protokola.
Glasovnega sporočila ni bilo mogoče predvajati
- Glasovno sporočilo · %1$s
- %1$s do %2$s
+ Glasovno sporočilo · %1$s
+ %1$s do %2$s
%1$s/%2$s
Uporabnik/ca \"%1$s\" je bil/a blokiran/a.
diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml
index f8fe45b6cd..090c201d6a 100644
--- a/app/src/main/res/values-sq/strings.xml
+++ b/app/src/main/res/values-sq/strings.xml
@@ -2719,8 +2719,8 @@ spastrohet dhe krejt lënda do të fshihet.
S’u arrit të luhej mesazh zanor
- Mesazh zanor · %1$s
- %1$s për %2$s
+ Mesazh zanor · %1$s
+ %1$s për %2$s
%1$s/%2$s
\"%1$s\" u bllokua.
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index b99e256a27..c923c528aa 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -2797,8 +2797,8 @@
Гласовна порука није успешно учитана
- Гласовна порука · %1$s
- %1$s за %2$s
+ Гласовна порука · %1$s
+ %1$s за %2$s
%1$s/%2$s
„%1$s“ је блокиран/на.
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index f6aa1ce380..d422df3586 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -2708,8 +2708,8 @@ Tog emot meddelande för nyckelutbyte för ogiltig protokollversion.
Det gick inte att spela röstmeddelande
- Röstmeddelande · %1$s
- %1$s till %2$s
+ Röstmeddelande · %1$s
+ %1$s till %2$s
%1$s/%2$s
\"%1$s\" har blockerats.
diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml
index 8bbd5c9d8a..351d3a6a34 100644
--- a/app/src/main/res/values-sw/strings.xml
+++ b/app/src/main/res/values-sw/strings.xml
@@ -2677,8 +2677,8 @@ Funga ufikiaji wa Signal na ufungaji wa Android au alama za vidole
Imeshindwa kucheza ujumbe wa sauti
- Ujumbe wa sauti · %1$s
- %1$shadi %2$s
+ Ujumbe wa sauti · %1$s
+ %1$shadi %2$s
%1$s/%2$s
\"%1$s\" amezuiwa.
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
index 4097f672b4..b753dc95f3 100644
--- a/app/src/main/res/values-ta/strings.xml
+++ b/app/src/main/res/values-ta/strings.xml
@@ -2685,8 +2685,8 @@
தோல்வி குரல் செய்தியை இயக்கு
- குரல் செய்தி:%1$s
- %1$s to %2$s
+ குரல் செய்தி:%1$s
+ %1$s to %2$s
%1$s/%2$s
\"%1$s\"தடுக்கப்பட்டார்
diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml
index bc0b1c9d4d..92e563b144 100644
--- a/app/src/main/res/values-te/strings.xml
+++ b/app/src/main/res/values-te/strings.xml
@@ -2449,8 +2449,8 @@
లింక్ ప్రస్తుతం సక్రియంగా లేదు
- వాయిస్ సందేశం · %1$s
- %1$sకు%2$s
+ వాయిస్ సందేశం · %1$s
+ %1$sకు%2$s
%1$s/%2$s
\"%1$s\" బ్లాక్ చేయబడింది.
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
index a44d3dc479..31da63174e 100644
--- a/app/src/main/res/values-th/strings.xml
+++ b/app/src/main/res/values-th/strings.xml
@@ -2601,8 +2601,8 @@
การเล่นข้อความเสียงล้มเหลว
- ข้อความเสียง %1$s
- %1$s ถึง %2$s
+ ข้อความเสียง %1$s
+ %1$s ถึง %2$s
%1$s/%2$s
\"%1$s\" ถูกปิดกั้นแล้ว
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 7c8ea9f146..3103b8732e 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -2713,8 +2713,8 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
Sesli ileti oynatılamadı
- Sesli ileti · %1$s
- %1$s --> %2$s
+ Sesli ileti · %1$s
+ %1$s --> %2$s
%1$s/%2$s
\"%1$s\" engellendi.
diff --git a/app/src/main/res/values-ug/strings.xml b/app/src/main/res/values-ug/strings.xml
index a38f08859f..b4892a1dab 100644
--- a/app/src/main/res/values-ug/strings.xml
+++ b/app/src/main/res/values-ug/strings.xml
@@ -2682,8 +2682,8 @@
ئۈن ئۇچۇرنى قويالمىدى
- ئاۋازلىق ئۇچۇر· %1$s
- %1$s دىن%2$s غىچە
+ ئاۋازلىق ئۇچۇر· %1$s
+ %1$s دىن%2$s غىچە
%1$s/%2$s
\"%1$s\" چەكلىنىپ قالدى.
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 1d2159245a..0c338f62e8 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -2610,7 +2610,7 @@
Скопійовано до буфера обміну
- %1$s до %2$s
+ %1$s до %2$s
Заблоковано \"%1$s\"
Розблоковано \"%1$s\"
diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml
index 73212f5958..82a8f9b0b2 100644
--- a/app/src/main/res/values-ur/strings.xml
+++ b/app/src/main/res/values-ur/strings.xml
@@ -2668,8 +2668,8 @@
آڈیو پیغام چلانے میں ناکام
- آڈیو پیغام · %1$s
- %1$sسے%2$s
+ آڈیو پیغام · %1$s
+ %1$sسے%2$s
%1$s/%2$s
\"%1$s\" کو مسدود کردیا گیا ہے۔
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 3d641924e9..687448a0ab 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -2584,8 +2584,8 @@ Nhận thông tin trao đổi mã khóa về phiên bản giao thức không h
Không thể phát tin nhắn thoại
- Tin nhắn thoại · %1$s
- %1$s đến %2$s
+ Tin nhắn thoại · %1$s
+ %1$s đến %2$s
%1$s/%2$s
\"%1$s\" đã bị chặn.
diff --git a/app/src/main/res/values-yue/strings.xml b/app/src/main/res/values-yue/strings.xml
index 91a97b9d90..5f43fc36ba 100644
--- a/app/src/main/res/values-yue/strings.xml
+++ b/app/src/main/res/values-yue/strings.xml
@@ -2632,8 +2632,8 @@
播唔到語音訊息
- 語音訊息 · %1$s
- %1$s 畀 %2$s
+ 語音訊息 · %1$s
+ %1$s 畀 %2$s
%1$s/%2$s
已封鎖「%1$s」。
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 3a22c00669..4d5981d988 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -2591,8 +2591,8 @@
无法播放语音消息
- 语音消息 · %1$s
- %1$s 至 %2$s
+ 语音消息 · %1$s
+ %1$s 至 %2$s
%1$s/%2$s
已屏蔽“%1$s”。
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index 15ff06b18e..aa38bf539e 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -2631,8 +2631,8 @@
無法播放語音訊息
- 語音訊息 · %1$s
- 由 %1$s 發給 %2$s
+ 語音訊息 · %1$s
+ 由 %1$s 發給 %2$s
%1$s/%2$s
「%1$s」已封鎖。
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index e86bd92189..05a0b590e6 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -2628,8 +2628,8 @@
無法播放語音留言
- 語音訊息 · %1$s
- %1$s 傳送給 %2$s
+ 語音訊息 · %1$s
+ %1$s 傳送給 %2$s
%1$s/%2$s
\"%1$s\"已經被封鎖。
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ef806e2561..be726fd362 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3230,8 +3230,8 @@
Failed to play voice message
- Voice message · %1$s
- %1$s to %2$s
+ Voice message · %1$s
+ %1$s to %2$s
%1$s/%2$s
diff --git a/app/src/test/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackControllerTest.kt b/app/src/test/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackControllerTest.kt
index 56400bc29c..52564a5947 100644
--- a/app/src/test/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackControllerTest.kt
+++ b/app/src/test/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackControllerTest.kt
@@ -5,9 +5,9 @@ import android.media.AudioManager
import android.os.Bundle
import android.support.v4.media.session.MediaSessionCompat
import com.google.android.exoplayer2.C
+import com.google.android.exoplayer2.ControlDispatcher
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.audio.AudioAttributes
-import org.junit.Assert.assertArrayEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
@@ -25,15 +25,11 @@ class VoiceNotePlaybackControllerTest {
private val mediaSessionCompat = mock(MediaSessionCompat::class.java)
private val playbackParameters = VoiceNotePlaybackParameters(mediaSessionCompat)
- private val testSubject = VoiceNotePlaybackController(playbackParameters)
private val mediaAudioAttributes = AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_MUSIC).setUsage(C.USAGE_MEDIA).build()
private val callAudioAttributes = AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_SPEECH).setUsage(C.USAGE_VOICE_COMMUNICATION).build()
+ private val controlDispatcher = mock(ControlDispatcher::class.java)
private val player: SimpleExoPlayer = mock(SimpleExoPlayer::class.java)
-
- @Test
- fun `When I getCommands, then I expect PLAYBACK_SPEED and AUDIO_STREAM`() {
- assertArrayEquals(arrayOf(VoiceNotePlaybackService.ACTION_NEXT_PLAYBACK_SPEED, VoiceNotePlaybackService.ACTION_SET_AUDIO_STREAM), testSubject.commands)
- }
+ private val testSubject = VoiceNotePlaybackController(player, playbackParameters)
@Test
fun `Given stream is media, When I onCommand for voice, then I expect the stream to switch to voice and continue playback`() {
@@ -45,11 +41,11 @@ class VoiceNotePlaybackControllerTest {
val expected = callAudioAttributes
// WHEN
- testSubject.onCommand(player, command, extras, null)
+ testSubject.onCommand(player, controlDispatcher, command, extras, null)
// THEN
verify(player).playWhenReady = false
- verify(player).audioAttributes = expected
+ verify(player).setAudioAttributes(expected, false)
verify(player).playWhenReady = true
}
@@ -63,11 +59,11 @@ class VoiceNotePlaybackControllerTest {
val expected = mediaAudioAttributes
// WHEN
- testSubject.onCommand(player, command, extras, null)
+ testSubject.onCommand(player, controlDispatcher, command, extras, null)
// THEN
verify(player).playWhenReady = false
- verify(player).audioAttributes = expected
+ verify(player).setAudioAttributes(expected, false)
verify(player, Mockito.never()).playWhenReady = true
}
@@ -80,11 +76,11 @@ class VoiceNotePlaybackControllerTest {
val extras = Bundle().apply { putInt(VoiceNotePlaybackService.ACTION_SET_AUDIO_STREAM, AudioManager.STREAM_VOICE_CALL) }
// WHEN
- testSubject.onCommand(player, command, extras, null)
+ testSubject.onCommand(player, controlDispatcher, command, extras, null)
// THEN
verify(player, Mockito.never()).playWhenReady = anyBoolean()
- verify(player, Mockito.never()).audioAttributes = any()
+ verify(player, Mockito.never()).setAudioAttributes(any(), anyBoolean())
}
@Test
@@ -96,10 +92,10 @@ class VoiceNotePlaybackControllerTest {
val extras = Bundle().apply { putInt(VoiceNotePlaybackService.ACTION_SET_AUDIO_STREAM, AudioManager.STREAM_MUSIC) }
// WHEN
- testSubject.onCommand(player, command, extras, null)
+ testSubject.onCommand(player, controlDispatcher, command, extras, null)
// THEN
verify(player, Mockito.never()).playWhenReady = anyBoolean()
- verify(player, Mockito.never()).audioAttributes = any()
+ verify(player, Mockito.never()).setAudioAttributes(any(), anyBoolean())
}
}
diff --git a/app/witness-verifications.gradle b/app/witness-verifications.gradle
index 84eb6b674e..5bc8071416 100644
--- a/app/witness-verifications.gradle
+++ b/app/witness-verifications.gradle
@@ -171,8 +171,8 @@ dependencyVerification {
['androidx.localbroadcastmanager:localbroadcastmanager:1.0.0',
'e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8'],
- ['androidx.media:media:1.0.0',
- 'b23b527b2bac870c4a7451e6982d7132e413e88d7f27dbeb1fc7640a720cd9ee'],
+ ['androidx.media:media:1.3.1',
+ '24c6ca0c281b27dec054a09ae584028266683274ca8f53b230470ff404a7d3f4'],
['androidx.multidex:multidex:2.0.1',
'42dd32ff9f97f85771b82a20003a8d70f68ab7b4ba328964312ce0732693db09'],
@@ -195,8 +195,8 @@ dependencyVerification {
['androidx.print:print:1.0.0',
'1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd'],
- ['androidx.recyclerview:recyclerview:1.1.0',
- 'f0d2b5a67d0a91ee1b1c73ef2b636a81f3563925ddd15a1d4e1c41ec28de7a4f'],
+ ['androidx.recyclerview:recyclerview:1.2.1',
+ 'a1ea0329ee6d938305dfd0f8ce5c48dea2aac14e5606d23e7fb60afcfb655d6e'],
['androidx.savedstate:savedstate-ktx:1.1.0',
'e44d61347463b0fafeeb649cbcc3d7109b2eb5e11d1522e986105170cdebbf68'],
@@ -297,14 +297,14 @@ dependencyVerification {
['com.google.android.datatransport:transport-runtime:3.0.0',
'691bd6ee2bf93182d29cb8a3a6f735098337e6f35d7fc9c8a79f17fdae604894'],
- ['com.google.android.exoplayer:exoplayer-core:2.9.1',
- 'b6ab34abac36bc2bc6934b7a50008162feca2c0fde91aaf1e8c1c22f2c16e2c0'],
+ ['com.google.android.exoplayer:exoplayer-core:2.15.0',
+ '8ae0c0027108181739e8a008377c8eb7fb93c4e04e3efdb5846662dd1ca9eba2'],
- ['com.google.android.exoplayer:exoplayer-ui:2.9.1',
- '7a942afcc402ff01e9bf48e8d3942850986710f06562d50a1408aaf04a683151'],
+ ['com.google.android.exoplayer:exoplayer-ui:2.15.0',
+ '9e1869798de1ce16d9feb4fd97097f11c7cd54a2018c91d22aafd793f6a8dadf'],
- ['com.google.android.exoplayer:extension-mediasession:2.9.1',
- 'a7dff433b41b714c6584d925f1438ca890eaa16d66a47c3fb9ce88b39dcf3d52'],
+ ['com.google.android.exoplayer:extension-mediasession:2.15.0',
+ 'f7e304c4f38d113e164642478f4f96ad952cc0fa2ab27f2be4a0978587f0eeb3'],
['com.google.android.gms:play-services-auth-api-phone:16.0.0',
'19365818b9ceb048ef48db12b5ffadd5eb86dbeb2c7c7b823bfdd89c665f42e5'],