From a6690e1bdefd732a3d4a2d3e64c8787dc3853aa2 Mon Sep 17 00:00:00 2001 From: Leonid Zavodnik Date: Sun, 22 Aug 2021 22:00:43 +0200 Subject: [PATCH] Update exoplayer version to v2.15 Fixes #11547 --- app/build.gradle | 6 +- .../securesms/BindableConversationItem.java | 7 +- .../voice/VoiceNoteMediaController.java | 67 +++-- ...ry.java => VoiceNoteMediaItemFactory.java} | 133 ++++++---- ...oiceNoteNotificationControlDispatcher.java | 34 --- .../voice/VoiceNoteNotificationManager.java | 49 ++-- .../voice/VoiceNotePlaybackController.kt | 22 +- .../voice/VoiceNotePlaybackPreparer.java | 228 +++++++++--------- .../voice/VoiceNotePlaybackService.java | 133 +++++----- .../components/voice/VoiceNotePlayer.kt | 38 +++ .../voice/VoiceNoteQueueDataAdapter.java | 93 ------- .../voice/VoiceNoteQueueNavigator.java | 23 +- .../conversation/ConversationAdapter.java | 16 +- .../conversation/ConversationFragment.java | 3 +- .../conversation/ConversationItem.java | 34 ++- .../conversation/ConversationUpdateItem.java | 7 +- .../securesms/giph/mp4/GiphyMp4Adapter.java | 6 +- .../giph/mp4/GiphyMp4ExoPlayerProvider.java | 36 ++- .../securesms/giph/mp4/GiphyMp4Fragment.java | 5 +- .../giph/mp4/GiphyMp4MediaSourceFactory.java | 35 --- .../securesms/giph/mp4/GiphyMp4Playable.java | 6 +- .../giph/mp4/GiphyMp4PlaybackPolicy.java | 2 +- .../mp4/GiphyMp4ProjectionPlayerHolder.java | 29 +-- .../giph/mp4/GiphyMp4ProjectionRecycler.java | 4 +- .../giph/mp4/GiphyMp4VideoPlayer.java | 17 +- .../giph/mp4/GiphyMp4ViewHolder.java | 17 +- .../MessageHeaderViewHolder.java | 18 +- .../securesms/video/VideoPlayer.java | 98 ++++---- .../video/exo/AttachmentDataSource.java | 8 +- .../exo/AttachmentDataSourceFactory.java | 2 +- .../exo/AttachmentMediaSourceFactory.java | 73 +++++- .../securesms/video/exo/BlobDataSource.java | 20 +- .../video/exo/ChunkedDataSource.java | 18 +- .../video/exo/ChunkedDataSourceFactory.java | 2 +- .../securesms/video/exo/PartDataSource.java | 6 +- app/src/main/res/values-ar/strings.xml | 4 +- app/src/main/res/values-az/strings.xml | 4 +- app/src/main/res/values-bg/strings.xml | 4 +- app/src/main/res/values-bn/strings.xml | 4 +- app/src/main/res/values-bs/strings.xml | 4 +- app/src/main/res/values-ca/strings.xml | 4 +- app/src/main/res/values-cs/strings.xml | 4 +- app/src/main/res/values-cy/strings.xml | 4 +- app/src/main/res/values-da/strings.xml | 4 +- app/src/main/res/values-de/strings.xml | 4 +- app/src/main/res/values-el/strings.xml | 4 +- app/src/main/res/values-eo/strings.xml | 4 +- app/src/main/res/values-es/strings.xml | 4 +- app/src/main/res/values-et/strings.xml | 4 +- app/src/main/res/values-eu/strings.xml | 4 +- app/src/main/res/values-fa/strings.xml | 4 +- app/src/main/res/values-fi/strings.xml | 4 +- app/src/main/res/values-fr/strings.xml | 4 +- app/src/main/res/values-gl/strings.xml | 4 +- app/src/main/res/values-gu/strings.xml | 4 +- app/src/main/res/values-hi/strings.xml | 4 +- app/src/main/res/values-hr/strings.xml | 4 +- app/src/main/res/values-hu/strings.xml | 4 +- app/src/main/res/values-in/strings.xml | 4 +- app/src/main/res/values-is/strings.xml | 4 +- app/src/main/res/values-it/strings.xml | 4 +- app/src/main/res/values-iw/strings.xml | 4 +- app/src/main/res/values-ja/strings.xml | 4 +- app/src/main/res/values-kab/strings.xml | 4 +- app/src/main/res/values-km/strings.xml | 2 +- app/src/main/res/values-kn/strings.xml | 4 +- app/src/main/res/values-ko/strings.xml | 4 +- app/src/main/res/values-ku/strings.xml | 4 +- app/src/main/res/values-lt/strings.xml | 4 +- app/src/main/res/values-lv/strings.xml | 4 +- app/src/main/res/values-mk/strings.xml | 4 +- app/src/main/res/values-ml/strings.xml | 4 +- app/src/main/res/values-mr/strings.xml | 4 +- app/src/main/res/values-ms/strings.xml | 4 +- app/src/main/res/values-my/strings.xml | 4 +- app/src/main/res/values-nb/strings.xml | 4 +- app/src/main/res/values-nl/strings.xml | 4 +- app/src/main/res/values-nn/strings.xml | 4 +- app/src/main/res/values-or/strings.xml | 4 +- app/src/main/res/values-pa/strings.xml | 4 +- app/src/main/res/values-pl/strings.xml | 4 +- app/src/main/res/values-pt-rBR/strings.xml | 4 +- app/src/main/res/values-pt/strings.xml | 4 +- app/src/main/res/values-ro/strings.xml | 4 +- app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values-sk/strings.xml | 4 +- app/src/main/res/values-sl/strings.xml | 4 +- app/src/main/res/values-sq/strings.xml | 4 +- app/src/main/res/values-sr/strings.xml | 4 +- app/src/main/res/values-sv/strings.xml | 4 +- app/src/main/res/values-sw/strings.xml | 4 +- app/src/main/res/values-ta/strings.xml | 4 +- app/src/main/res/values-te/strings.xml | 4 +- app/src/main/res/values-th/strings.xml | 4 +- app/src/main/res/values-tr/strings.xml | 4 +- app/src/main/res/values-ug/strings.xml | 4 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-ur/strings.xml | 4 +- app/src/main/res/values-vi/strings.xml | 4 +- app/src/main/res/values-yue/strings.xml | 4 +- app/src/main/res/values-zh-rCN/strings.xml | 4 +- app/src/main/res/values-zh-rHK/strings.xml | 4 +- app/src/main/res/values-zh-rTW/strings.xml | 4 +- app/src/main/res/values/strings.xml | 4 +- .../voice/VoiceNotePlaybackControllerTest.kt | 26 +- app/witness-verifications.gradle | 20 +- 106 files changed, 763 insertions(+), 850 deletions(-) rename app/src/main/java/org/thoughtcrime/securesms/components/voice/{VoiceNoteMediaDescriptionCompatFactory.java => VoiceNoteMediaItemFactory.java} (53%) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationControlDispatcher.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayer.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteQueueDataAdapter.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4MediaSourceFactory.java 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'],