From 220931d3df9f25fbfc66dc54f1ab68b03089c85c Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Wed, 12 Oct 2022 14:49:27 -0300 Subject: [PATCH] Pass through clip information to video player. --- .../mp4/GiphyMp4ProjectionPlayerHolder.java | 8 ++-- .../giph/mp4/GiphyMp4VideoPlayer.java | 14 +++--- .../v2/NotificationStateProvider.kt | 2 +- .../thoughtcrime/securesms/stories/Stories.kt | 2 + .../stories/viewer/post/StoryPostFragment.kt | 2 +- .../stories/viewer/post/StoryPostState.kt | 5 ++- .../stories/viewer/post/StoryPostViewModel.kt | 10 ++++- .../stories/viewer/post/StoryVideoLoader.kt | 2 +- .../securesms/video/VideoPlayer.java | 44 +++++++++++-------- .../video/exo/SimpleExoPlayerPool.kt | 13 +++--- 10 files changed, 61 insertions(+), 41 deletions(-) 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 9638c5d12a..eeb8c75e8d 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 @@ -13,9 +13,9 @@ import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; +import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import org.signal.core.util.logging.Log; @@ -54,7 +54,7 @@ public final class GiphyMp4ProjectionPlayerHolder implements Player.Listener, De this.policyEnforcer = policyEnforcer; if (player.getExoPlayer() == null) { - SimpleExoPlayer fromPool = ApplicationDependencies.getExoPlayerPool().get(TAG); + ExoPlayer fromPool = ApplicationDependencies.getExoPlayerPool().get(TAG); if (fromPool == null) { Log.i(TAG, "Could not get exoplayer from pool."); @@ -75,7 +75,7 @@ public final class GiphyMp4ProjectionPlayerHolder implements Player.Listener, De this.mediaItem = null; this.policyEnforcer = null; - SimpleExoPlayer exoPlayer = player.getExoPlayer(); + ExoPlayer exoPlayer = player.getExoPlayer(); if (exoPlayer != null) { player.stop(); player.setExoPlayer(null); @@ -142,7 +142,7 @@ public final class GiphyMp4ProjectionPlayerHolder implements Player.Listener, De @Override public void onResume(@NonNull LifecycleOwner owner) { if (mediaItem != null) { - SimpleExoPlayer fromPool = ApplicationDependencies.getExoPlayerPool().get(TAG); + ExoPlayer fromPool = ApplicationDependencies.getExoPlayerPool().get(TAG); if (fromPool != null) { ExoPlayerKt.configureForGifPlayback(fromPool); fromPool.addListener(this); 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 914d521801..7d98324d92 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 @@ -13,8 +13,8 @@ import androidx.annotation.Nullable; import androidx.lifecycle.DefaultLifecycleObserver; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.PlayerView; @@ -31,10 +31,10 @@ public final class GiphyMp4VideoPlayer extends FrameLayout implements DefaultLif @SuppressWarnings("unused") private static final String TAG = Log.tag(GiphyMp4VideoPlayer.class); - private final PlayerView exoView; - private SimpleExoPlayer exoPlayer; - private CornerMask cornerMask; - private MediaItem mediaItem; + private final PlayerView exoView; + private ExoPlayer exoPlayer; + private CornerMask cornerMask; + private MediaItem mediaItem; public GiphyMp4VideoPlayer(Context context) { this(context, null); @@ -61,11 +61,11 @@ public final class GiphyMp4VideoPlayer extends FrameLayout implements DefaultLif } } - @Nullable SimpleExoPlayer getExoPlayer() { + @Nullable ExoPlayer getExoPlayer() { return exoPlayer; } - void setExoPlayer(@Nullable SimpleExoPlayer exoPlayer) { + void setExoPlayer(@Nullable ExoPlayer exoPlayer) { exoView.setPlayer(exoPlayer); this.exoPlayer = exoPlayer; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt index cd987b3024..0901216e59 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt @@ -42,7 +42,7 @@ object NotificationStateProvider { val parentRecord = conversationId.groupStoryId?.let { try { SignalDatabase.mms.getMessageRecord(it) - } catch (e : NoSuchMessageException) { + } catch (e: NoSuchMessageException) { null } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt index 91268be661..c268bb38f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt @@ -43,6 +43,7 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.math.max import kotlin.math.min +import kotlin.time.Duration.Companion.microseconds import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -368,6 +369,7 @@ object Stories { } private fun transformMedia(media: Media, transformProperties: AttachmentDatabase.TransformProperties): Media { + Log.d(TAG, "Transforming media clip: ${transformProperties.videoTrimStartTimeUs.microseconds.inWholeSeconds}s to ${transformProperties.videoTrimEndTimeUs.microseconds.inWholeSeconds}s") return Media( media.uri, media.mimeType, diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostFragment.kt index e77aa97287..5f39e4119a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostFragment.kt @@ -55,7 +55,7 @@ class StoryPostFragment : Fragment(R.layout.stories_post_fragment) { postViewModel.onPostContentChanged(it) } - disposables += postViewModel.state.subscribe { state -> + disposables += postViewModel.state.distinctUntilChanged().subscribe { state -> when (state) { is StoryPostState.None -> presentNone() is StoryPostState.TextPost -> presentTextPost(state) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostState.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostState.kt index 86f6bc822b..9201468142 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostState.kt @@ -5,6 +5,7 @@ import android.net.Uri import org.thoughtcrime.securesms.blurhash.BlurHash import org.thoughtcrime.securesms.database.model.databaseprotos.StoryTextPost import org.thoughtcrime.securesms.linkpreview.LinkPreview +import kotlin.time.Duration sealed class StoryPostState { data class TextPost( @@ -21,7 +22,9 @@ sealed class StoryPostState { data class VideoPost( val videoUri: Uri, - val size: Long + val size: Long, + val clipStart: Duration, + val clipEnd: Duration ) : StoryPostState() data class None(private val ts: Long = System.currentTimeMillis()) : StoryPostState() diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostViewModel.kt index 6d06eb6d4f..93d10ccb53 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryPostViewModel.kt @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.StoryTextPost import org.thoughtcrime.securesms.stories.viewer.page.StoryPost import org.thoughtcrime.securesms.util.Base64 import org.thoughtcrime.securesms.util.rx.RxStore +import kotlin.time.Duration.Companion.microseconds class StoryPostViewModel(private val repository: StoryTextPostRepository) : ViewModel() { @@ -38,7 +39,14 @@ class StoryPostViewModel(private val repository: StoryTextPostRepository) : View if (storyPostContent.uri == null) { store.update { StoryPostState.None() } } else if (storyPostContent.isVideo()) { - store.update { StoryPostState.VideoPost(videoUri = storyPostContent.uri, storyPostContent.attachment.size) } + store.update { + StoryPostState.VideoPost( + videoUri = storyPostContent.uri, + size = storyPostContent.attachment.size, + clipStart = storyPostContent.attachment.transformProperties.videoTrimStartTimeUs.microseconds, + clipEnd = storyPostContent.attachment.transformProperties.videoTrimEndTimeUs.microseconds + ) + } } else { store.update { StoryPostState.ImagePost(storyPostContent.uri, storyPostContent.attachment.blurHash) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryVideoLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryVideoLoader.kt index 6b7292b2bc..e722afeccf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryVideoLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/post/StoryVideoLoader.kt @@ -22,7 +22,7 @@ class StoryVideoLoader( fun load() { fragment.viewLifecycleOwner.lifecycle.addObserver(this) - videoPlayer.setVideoSource(VideoSlide(fragment.requireContext(), videoPost.videoUri, videoPost.size, false), true, TAG) + videoPlayer.setVideoSource(VideoSlide(fragment.requireContext(), videoPost.videoUri, videoPost.size, false), true, TAG, videoPost.clipStart.inWholeMilliseconds, videoPost.clipEnd.inWholeMilliseconds) } fun clear() { 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 b8e29b58e8..d39eec27c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java @@ -26,10 +26,12 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Tracks; import com.google.android.exoplayer2.source.ClippingMediaSource; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.MediaSource; @@ -56,7 +58,7 @@ public class VideoPlayer extends FrameLayout { private final PlayerControlView exoControls; private final DefaultMediaSourceFactory mediaSourceFactory; - private SimpleExoPlayer exoPlayer; + private ExoPlayer exoPlayer; private Window window; private PlayerStateCallback playerStateCallback; private PlayerPositionDiscontinuityCallback playerPositionDiscontinuityCallback; @@ -125,6 +127,10 @@ public class VideoPlayer extends FrameLayout { private MediaItem mediaItem; public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay, String poolTag) { + setVideoSource(videoSource, autoplay, poolTag, 0, 0); + } + + public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay, String poolTag, long clipStartMs, long clipEndMs) { if (exoPlayer == null) { exoPlayer = ApplicationDependencies.getExoPlayerPool().require(poolTag); exoPlayer.addListener(exoPlayerListener); @@ -136,7 +142,10 @@ public class VideoPlayer extends FrameLayout { } } - mediaItem = MediaItem.fromUri(Objects.requireNonNull(videoSource.getUri())); + mediaItem = MediaItem.fromUri(Objects.requireNonNull(videoSource.getUri())).buildUpon() + .setClippingConfiguration(getClippingConfiguration(clipStartMs, clipEndMs)) + .build(); + exoPlayer.setMediaItem(mediaItem); exoPlayer.prepare(); exoPlayer.setPlayWhenReady(autoplay); @@ -144,31 +153,22 @@ public class VideoPlayer extends FrameLayout { public void mute() { this.muted = true; - if (exoPlayer != null && exoPlayer.getAudioComponent() != null) { - exoPlayer.getAudioComponent().setVolume(0f); + if (exoPlayer != null) { + exoPlayer.setVolume(0f); } } public void unmute() { this.muted = false; - if (exoPlayer != null && exoPlayer.getAudioComponent() != null) { - exoPlayer.getAudioComponent().setVolume(1f); + if (exoPlayer != null) { + exoPlayer.setVolume(1f); } } public boolean hasAudioTrack() { if (exoPlayer != null) { - TrackGroupArray trackGroupArray = exoPlayer.getCurrentTrackGroups(); - if (trackGroupArray != null) { - for (int i = 0; i < trackGroupArray.length; i++) { - for (int j = 0; j < trackGroupArray.get(i).length; j++) { - String sampleMimeType = trackGroupArray.get(i).getFormat(j).sampleMimeType; - if (MediaUtil.isAudioType(sampleMimeType)) { - return true; - } - } - } - } + Tracks tracks = exoPlayer.getCurrentTracks(); + return tracks.containsType(C.TRACK_TYPE_AUDIO); } return false; @@ -317,6 +317,14 @@ public class VideoPlayer extends FrameLayout { } } + private @NonNull MediaItem.ClippingConfiguration getClippingConfiguration(long startMs, long endMs) { + return startMs != endMs ? new MediaItem.ClippingConfiguration.Builder() + .setStartPositionMs(startMs) + .setEndPositionMs(endMs) + .build() + : MediaItem.ClippingConfiguration.UNSET; + } + private class ExoPlayerListener implements Player.Listener { @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/SimpleExoPlayerPool.kt b/app/src/main/java/org/thoughtcrime/securesms/video/exo/SimpleExoPlayerPool.kt index 2ab3d46849..564b629c4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/SimpleExoPlayerPool.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/SimpleExoPlayerPool.kt @@ -3,11 +3,10 @@ package org.thoughtcrime.securesms.video.exo import android.content.Context import androidx.annotation.MainThread import com.google.android.exoplayer2.ExoPlayer -import com.google.android.exoplayer2.SimpleExoPlayer import com.google.android.exoplayer2.mediacodec.MediaCodecUtil import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException -import com.google.android.exoplayer2.source.MediaSourceFactory -import com.google.android.exoplayer2.source.ProgressiveMediaSource +import com.google.android.exoplayer2.source.DefaultMediaSourceFactory +import com.google.android.exoplayer2.source.MediaSource import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.util.MimeTypes import org.signal.core.util.logging.Log @@ -19,11 +18,11 @@ import org.thoughtcrime.securesms.util.DeviceProperties /** * ExoPlayerPool concrete instance which helps to manage a pool of SimpleExoPlayer objects */ -class SimpleExoPlayerPool(context: Context) : ExoPlayerPool(MAXIMUM_RESERVED_PLAYERS) { +class SimpleExoPlayerPool(context: Context) : ExoPlayerPool(MAXIMUM_RESERVED_PLAYERS) { private val context: Context = context.applicationContext private val okHttpClient = ApplicationDependencies.getOkHttpClient().newBuilder().proxySelector(ContentProxySelector()).build() private val dataSourceFactory: DataSource.Factory = SignalDataSource.Factory(ApplicationDependencies.getApplication(), okHttpClient, null) - private val mediaSourceFactory: MediaSourceFactory = ProgressiveMediaSource.Factory(dataSourceFactory) + private val mediaSourceFactory: MediaSource.Factory = DefaultMediaSourceFactory(dataSourceFactory) init { ApplicationDependencies.getAppForegroundObserver().addListener(this) @@ -57,8 +56,8 @@ class SimpleExoPlayerPool(context: Context) : ExoPlayerPool(MAX } @MainThread - override fun createPlayer(): SimpleExoPlayer { - return SimpleExoPlayer.Builder(context) + override fun createPlayer(): ExoPlayer { + return ExoPlayer.Builder(context) .setMediaSourceFactory(mediaSourceFactory) .build() }