Pass through clip information to video player.
This commit is contained in:
parent
8c76cead58
commit
220931d3df
10 changed files with 61 additions and 41 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ object NotificationStateProvider {
|
|||
val parentRecord = conversationId.groupStoryId?.let {
|
||||
try {
|
||||
SignalDatabase.mms.getMessageRecord(it)
|
||||
} catch (e : NoSuchMessageException) {
|
||||
} catch (e: NoSuchMessageException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue