Pass through clip information to video player.

This commit is contained in:
Alex Hart 2022-10-12 14:49:27 -03:00 committed by Greyson Parrelli
parent 8c76cead58
commit 220931d3df
10 changed files with 61 additions and 41 deletions

View file

@ -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);

View file

@ -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;
@ -32,7 +32,7 @@ public final class GiphyMp4VideoPlayer extends FrameLayout implements DefaultLif
private static final String TAG = Log.tag(GiphyMp4VideoPlayer.class);
private final PlayerView exoView;
private SimpleExoPlayer exoPlayer;
private ExoPlayer exoPlayer;
private CornerMask cornerMask;
private MediaItem mediaItem;
@ -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;
}

View file

@ -42,7 +42,7 @@ object NotificationStateProvider {
val parentRecord = conversationId.groupStoryId?.let {
try {
SignalDatabase.mms.getMessageRecord(it)
} catch (e : NoSuchMessageException) {
} catch (e: NoSuchMessageException) {
null
}
}

View file

@ -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,

View file

@ -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)

View file

@ -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()

View file

@ -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) }
}

View file

@ -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() {

View file

@ -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

View file

@ -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<SimpleExoPlayer>(MAXIMUM_RESERVED_PLAYERS) {
class SimpleExoPlayerPool(context: Context) : ExoPlayerPool<ExoPlayer>(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<SimpleExoPlayer>(MAX
}
@MainThread
override fun createPlayer(): SimpleExoPlayer {
return SimpleExoPlayer.Builder(context)
override fun createPlayer(): ExoPlayer {
return ExoPlayer.Builder(context)
.setMediaSourceFactory(mediaSourceFactory)
.build()
}