From 33eafa1d4d96659f2f5ea931bdc35050e280514d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 28 Mar 2025 14:47:34 +0100 Subject: [PATCH 1/6] Add video autoplay when opening the video from either the timeline or the media gallery --- .../impl/datasource/MediaGalleryDataSource.kt | 5 +++- .../mediaviewer/impl/local/LocalMediaView.kt | 2 ++ .../impl/local/audio/MediaAudioView.kt | 1 + .../player/MediaPlayerControllerState.kt | 1 + .../MediaPlayerControllerStateProvider.kt | 2 ++ .../impl/local/video/MediaVideoView.kt | 23 ++++++++++++++++--- .../impl/viewer/MediaViewerDataSource.kt | 6 ++--- .../impl/viewer/MediaViewerPresenter.kt | 1 + .../impl/viewer/MediaViewerState.kt | 1 + .../impl/viewer/MediaViewerStateProvider.kt | 2 ++ .../impl/viewer/MediaViewerView.kt | 12 ++++++++-- 11 files changed, 46 insertions(+), 10 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt index 4ad36d88277..4f6559db356 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn @@ -85,7 +86,9 @@ class TimelineMediaGalleryDataSource @Inject constructor( } }.flatMapLatest { timelineMediaItemsFactory.timelineItems - }.map { timelineItems -> + } + .distinctUntilChanged() + .map { timelineItems -> mediaItemsPostProcessor.process(mediaItems = timelineItems) }.map { mediaTimeline.orCache(it) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt index 10ac3a7c2af..bec02c7fd8c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt @@ -31,6 +31,7 @@ fun LocalMediaView( textFileViewer: TextFileViewer, modifier: Modifier = Modifier, isDisplayed: Boolean = true, + isUserSelected: Boolean = false, localMediaViewState: LocalMediaViewState = rememberLocalMediaViewState(), mediaInfo: MediaInfo? = localMedia?.info, ) { @@ -47,6 +48,7 @@ fun LocalMediaView( localMediaViewState = localMediaViewState, bottomPaddingInPixels = bottomPaddingInPixels, localMedia = localMedia, + autoplay = isUserSelected, modifier = modifier, ) mimeType == MimeTypes.PlainText -> TextFileView( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt index 684b96cb9f5..028a25999f0 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt @@ -111,6 +111,7 @@ private fun ExoPlayerMediaAudioView( MediaPlayerControllerState( isVisible = true, isPlaying = false, + isReady = false, progressInMillis = 0, durationInMillis = 0, canMute = false, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt index 349044439e4..6160b6758cc 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt @@ -12,6 +12,7 @@ import androidx.annotation.FloatRange data class MediaPlayerControllerState( val isVisible: Boolean, val isPlaying: Boolean, + val isReady: Boolean, val progressInMillis: Long, val durationInMillis: Long, val canMute: Boolean, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt index 88e4c2f7c77..2ce66a79e31 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt @@ -27,6 +27,7 @@ open class MediaPlayerControllerStateProvider : PreviewParameterProvider> { + internal fun dataFlow(): Flow> { return galleryDataSource.groupedMediaItemsFlow() .map { groupedItems -> when (groupedItems) { @@ -105,7 +103,7 @@ class MediaViewerDataSource( } } - private fun initialData(): PersistentList { + fun initialData(): PersistentList { val initialMediaItems = galleryDataSource.getLastData().dataOrNull()?.getItems(galleryMode).orEmpty() return buildMediaViewerPageList(initialMediaItems) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt index f8979845788..858e3e7916c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt @@ -148,6 +148,7 @@ class MediaViewerPresenter @AssistedInject constructor( } return MediaViewerState( + initiallySelectedEventId = inputs.eventId, listData = data.value, currentIndex = currentIndex.intValue, snackbarMessage = snackbarMessage, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt index 32c22b04706..5a0c4c23077 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetSta import kotlinx.collections.immutable.ImmutableList data class MediaViewerState( + val initiallySelectedEventId: EventId?, val listData: ImmutableList, val currentIndex: Int, val snackbarMessage: SnackbarMessage?, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt index 6686cd9fceb..324b0933251 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.media.aWaveForm +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.mediaviewer.api.MediaInfo @@ -202,6 +203,7 @@ fun aMediaViewerState( mediaBottomSheetState: MediaBottomSheetState = MediaBottomSheetState.Hidden, eventSink: (MediaViewerEvents) -> Unit = {}, ) = MediaViewerState( + initiallySelectedEventId = EventId("\$a:b"), listData = listData.toPersistentList(), currentIndex = currentIndex, snackbarMessage = null, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt index 501c4346744..a55846106b7 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt @@ -142,8 +142,13 @@ fun MediaViewerView( Box( modifier = Modifier.fillMaxSize() ) { + val isDisplayed = remember(pagerState.settledPage) { + // This 'item provider' lambda will be called when the data source changes with an outdated `settlePage` value + // So we need to update this value only when the `settledPage` value changes. It seems like a bug that needs to be fixed in Compose. + page == pagerState.settledPage + } MediaViewerPage( - isDisplayed = page == pagerState.settledPage, + isDisplayed = isDisplayed, showOverlay = showOverlay, bottomPaddingInPixels = bottomPaddingInPixels, data = dataForPage, @@ -157,7 +162,8 @@ fun MediaViewerView( }, onShowOverlayChange = { showOverlay = it - } + }, + isUserSelected = (state.listData[page] as? MediaViewerPageData.MediaViewerData)?.eventId == state.initiallySelectedEventId, ) // Bottom bar AnimatedVisibility(visible = showOverlay, enter = fadeIn(), exit = fadeOut()) { @@ -273,6 +279,7 @@ private fun MediaViewerPage( bottomPaddingInPixels: Int, data: MediaViewerPageData.MediaViewerData, textFileViewer: TextFileViewer, + isUserSelected: Boolean, onDismiss: () -> Unit, onRetry: () -> Unit, onDismissError: () -> Unit, @@ -328,6 +335,7 @@ private fun MediaViewerPage( currentOnShowOverlayChange(!currentShowOverlay) } }, + isUserSelected = isUserSelected, ) ThumbnailView( mediaInfo = data.mediaInfo, From aa3ac453b1582fde5e261d38827f91604d11e3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 31 Mar 2025 12:20:37 +0200 Subject: [PATCH 2/6] Fix lint issues --- .../mediaviewer/impl/datasource/MediaGalleryDataSource.kt | 8 ++++---- .../mediaviewer/impl/local/video/MediaVideoView.kt | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt index 4f6559db356..1dc81a11e39 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt @@ -89,10 +89,10 @@ class TimelineMediaGalleryDataSource @Inject constructor( } .distinctUntilChanged() .map { timelineItems -> - mediaItemsPostProcessor.process(mediaItems = timelineItems) - }.map { - mediaTimeline.orCache(it) - }.onEach { groupedMediaItems -> + val groupedItems = mediaItemsPostProcessor.process(mediaItems = timelineItems) + mediaTimeline.orCache(groupedItems) + } + .onEach { groupedMediaItems -> groupedMediaItemsFlow.emit(AsyncData.Success(groupedMediaItems)) } .onCompletion { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index 4c79a6e3317..49bf54bef75 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -177,7 +177,8 @@ private fun ExoPlayerMediaVideoView( } LaunchedEffect(autoplay, isDisplayed, mediaPlayerControllerState.isReady) { - if (autoplay && isDisplayed && mediaPlayerControllerState.isReady && !mediaPlayerControllerState.isPlaying) { + val isReadyAndPlaying = mediaPlayerControllerState.isReady && mediaPlayerControllerState.isPlaying + if (autoplay && isDisplayed && isReadyAndPlaying) { // When displayed, start autoplaying from the beginning exoPlayer.play() } else if (!isDisplayed && mediaPlayerControllerState.isPlaying) { From 8d9387737097535be8333191fcfffe86dbd038e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 31 Mar 2025 13:41:41 +0200 Subject: [PATCH 3/6] Revert changes to `MediaViewerDataSource` during experiments --- .../mediaviewer/impl/viewer/MediaViewerDataSource.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt index ab5b91c64d1..4bb37abf65f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.mediaviewer.impl.viewer +import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.State @@ -74,6 +75,7 @@ class MediaViewerDataSource( return remember { dataFlow() }.collectAsState(initialData()) } + @VisibleForTesting internal fun dataFlow(): Flow> { return galleryDataSource.groupedMediaItemsFlow() .map { groupedItems -> @@ -103,7 +105,7 @@ class MediaViewerDataSource( } } - fun initialData(): PersistentList { + private fun initialData(): PersistentList { val initialMediaItems = galleryDataSource.getLastData().dataOrNull()?.getItems(galleryMode).orEmpty() return buildMediaViewerPageList(initialMediaItems) From 96988e8025ae905361c26542a34af1ff549d33a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 31 Mar 2025 17:19:10 +0200 Subject: [PATCH 4/6] Fix check --- .../libraries/mediaviewer/impl/local/video/MediaVideoView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index 49bf54bef75..67306c78e15 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -177,8 +177,8 @@ private fun ExoPlayerMediaVideoView( } LaunchedEffect(autoplay, isDisplayed, mediaPlayerControllerState.isReady) { - val isReadyAndPlaying = mediaPlayerControllerState.isReady && mediaPlayerControllerState.isPlaying - if (autoplay && isDisplayed && isReadyAndPlaying) { + val isReadyAndNotPlaying = mediaPlayerControllerState.isReady && !mediaPlayerControllerState.isPlaying + if (autoplay && isDisplayed && isReadyAndNotPlaying) { // When displayed, start autoplaying from the beginning exoPlayer.play() } else if (!isDisplayed && mediaPlayerControllerState.isPlaying) { From 3f9ffb75f0c58428cafa9c6a8191b27371d31d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 31 Mar 2025 17:57:43 +0200 Subject: [PATCH 5/6] Only autoplay when the video is first displayed --- .../mediaviewer/impl/local/video/MediaVideoView.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index 67306c78e15..35c84145b5b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -176,11 +176,14 @@ private fun ExoPlayerMediaVideoView( } } - LaunchedEffect(autoplay, isDisplayed, mediaPlayerControllerState.isReady) { + var needsAutoPlay by remember { mutableStateOf(autoplay) } + + LaunchedEffect(needsAutoPlay, isDisplayed, mediaPlayerControllerState.isReady) { val isReadyAndNotPlaying = mediaPlayerControllerState.isReady && !mediaPlayerControllerState.isPlaying - if (autoplay && isDisplayed && isReadyAndNotPlaying) { + if (needsAutoPlay && isDisplayed && isReadyAndNotPlaying) { // When displayed, start autoplaying from the beginning exoPlayer.play() + needsAutoPlay = false } else if (!isDisplayed && mediaPlayerControllerState.isPlaying) { // If not displayed, make sure to pause the video exoPlayer.pause() From 41559906dba4afc0dfe7987ba3f04382c8991999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 1 Apr 2025 09:33:31 +0200 Subject: [PATCH 6/6] Remove going back to the beginning of the video when swiping to another one --- .../libraries/mediaviewer/impl/local/video/MediaVideoView.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index 35c84145b5b..ddd53fe8dd4 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -181,13 +181,12 @@ private fun ExoPlayerMediaVideoView( LaunchedEffect(needsAutoPlay, isDisplayed, mediaPlayerControllerState.isReady) { val isReadyAndNotPlaying = mediaPlayerControllerState.isReady && !mediaPlayerControllerState.isPlaying if (needsAutoPlay && isDisplayed && isReadyAndNotPlaying) { - // When displayed, start autoplaying from the beginning + // When displayed, start autoplaying exoPlayer.play() needsAutoPlay = false } else if (!isDisplayed && mediaPlayerControllerState.isPlaying) { // If not displayed, make sure to pause the video exoPlayer.pause() - exoPlayer.seekTo(0) } } if (localMedia?.uri != null) {