@@ -10,12 +10,14 @@ package io.element.android.libraries.mediaviewer.impl.local.video
10
10
import android.annotation.SuppressLint
11
11
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
12
12
import android.widget.FrameLayout
13
+ import androidx.annotation.OptIn
13
14
import androidx.compose.foundation.background
14
15
import androidx.compose.foundation.layout.Box
15
16
import androidx.compose.foundation.layout.fillMaxSize
16
17
import androidx.compose.foundation.layout.fillMaxWidth
17
18
import androidx.compose.foundation.layout.padding
18
19
import androidx.compose.runtime.Composable
20
+ import androidx.compose.runtime.DisposableEffect
19
21
import androidx.compose.runtime.LaunchedEffect
20
22
import androidx.compose.runtime.derivedStateOf
21
23
import androidx.compose.runtime.getValue
@@ -33,6 +35,7 @@ import androidx.media3.common.MediaItem
33
35
import androidx.media3.common.Player
34
36
import androidx.media3.common.Player.STATE_READY
35
37
import androidx.media3.common.Timeline
38
+ import androidx.media3.common.util.UnstableApi
36
39
import androidx.media3.exoplayer.ExoPlayer
37
40
import androidx.media3.ui.AspectRatioFrameLayout
38
41
import androidx.media3.ui.PlayerView
@@ -55,6 +58,7 @@ import io.element.android.libraries.mediaviewer.impl.local.player.togglePlay
55
58
import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState
56
59
import kotlinx.coroutines.delay
57
60
import me.saket.telephoto.zoomable.zoomable
61
+ import timber.log.Timber
58
62
import kotlin.time.Duration.Companion.seconds
59
63
60
64
@SuppressLint(" UnsafeOptInUsageError" )
@@ -165,35 +169,6 @@ private fun ExoPlayerMediaVideoView(
165
169
}
166
170
}
167
171
168
- LaunchedEffect (exoPlayer.isPlaying) {
169
- if (exoPlayer.isPlaying) {
170
- while (true ) {
171
- mediaPlayerControllerState = mediaPlayerControllerState.copy(
172
- progressInMillis = exoPlayer.currentPosition,
173
- )
174
- delay(200 )
175
- }
176
- } else {
177
- // Ensure we render the final state
178
- mediaPlayerControllerState = mediaPlayerControllerState.copy(
179
- progressInMillis = exoPlayer.currentPosition,
180
- )
181
- }
182
- }
183
-
184
- var needsAutoPlay by remember { mutableStateOf(autoplay) }
185
-
186
- LaunchedEffect (needsAutoPlay, isDisplayed, mediaPlayerControllerState.isReady) {
187
- val isReadyAndNotPlaying = mediaPlayerControllerState.isReady && ! mediaPlayerControllerState.isPlaying
188
- if (needsAutoPlay && isDisplayed && isReadyAndNotPlaying) {
189
- // When displayed, start autoplaying
190
- exoPlayer.play()
191
- needsAutoPlay = false
192
- } else if (! isDisplayed && mediaPlayerControllerState.isPlaying) {
193
- // If not displayed, make sure to pause the video
194
- exoPlayer.pause()
195
- }
196
- }
197
172
if (localMedia?.uri != null ) {
198
173
LaunchedEffect (localMedia.uri) {
199
174
val mediaItem = MediaItem .fromUri(localMedia.uri)
@@ -263,16 +238,72 @@ private fun ExoPlayerMediaVideoView(
263
238
)
264
239
}
265
240
266
- OnLifecycleEvent { _, event ->
267
- when (event) {
268
- Lifecycle .Event .ON_CREATE -> exoPlayer.addListener(playerListener)
269
- Lifecycle .Event .ON_RESUME -> exoPlayer.prepare()
270
- Lifecycle .Event .ON_PAUSE -> exoPlayer.pause()
271
- Lifecycle .Event .ON_DESTROY -> {
272
- exoPlayer.release()
241
+ LaunchedEffect (exoPlayer.isPlaying) {
242
+ if (exoPlayer.isPlaying) {
243
+ while (true ) {
244
+ mediaPlayerControllerState = mediaPlayerControllerState.copy(
245
+ progressInMillis = exoPlayer.currentPosition,
246
+ )
247
+ delay(200 )
248
+ }
249
+ } else {
250
+ // Ensure we render the final state
251
+ mediaPlayerControllerState = mediaPlayerControllerState.copy(
252
+ progressInMillis = exoPlayer.currentPosition,
253
+ )
254
+ }
255
+ }
256
+
257
+ ExoPlayerLifecycleHelper (
258
+ exoPlayer = exoPlayer,
259
+ autoplay = autoplay,
260
+ isDisplayed = isDisplayed,
261
+ playerListener = playerListener,
262
+ mediaPlayerControllerState = mediaPlayerControllerState,
263
+ )
264
+ }
265
+
266
+ @OptIn(UnstableApi ::class )
267
+ @Composable
268
+ private fun ExoPlayerLifecycleHelper (
269
+ exoPlayer : ExoPlayer ,
270
+ autoplay : Boolean ,
271
+ isDisplayed : Boolean ,
272
+ playerListener : Player .Listener ,
273
+ mediaPlayerControllerState : MediaPlayerControllerState ,
274
+ ) {
275
+ // Prepare and release the exoPlayer with the composable lifecycle
276
+ DisposableEffect (Unit ) {
277
+ Timber .d(" ExoPlayerMediaVideoView DisposableEffect: initializing exoPlayer" )
278
+ exoPlayer.addListener(playerListener)
279
+ exoPlayer.prepare()
280
+
281
+ onDispose {
282
+ Timber .d(" Disposing exoplayer" )
283
+ if (! exoPlayer.isReleased) {
273
284
exoPlayer.removeListener(playerListener)
285
+ exoPlayer.release()
274
286
}
275
- else -> Unit
287
+ }
288
+ }
289
+
290
+ var needsAutoPlay by remember { mutableStateOf(autoplay) }
291
+ LaunchedEffect (needsAutoPlay, isDisplayed, mediaPlayerControllerState.isReady) {
292
+ val isReadyAndNotPlaying = mediaPlayerControllerState.isReady && ! mediaPlayerControllerState.isPlaying
293
+ if (needsAutoPlay && isDisplayed && isReadyAndNotPlaying) {
294
+ // When displayed, start autoplaying
295
+ exoPlayer.play()
296
+ needsAutoPlay = false
297
+ } else if (! isDisplayed && mediaPlayerControllerState.isPlaying) {
298
+ // If not displayed, make sure to pause the video
299
+ exoPlayer.pause()
300
+ }
301
+ }
302
+
303
+ // Pause playback when lifecycle is paused
304
+ OnLifecycleEvent { _, event ->
305
+ if (event == Lifecycle .Event .ON_PAUSE && exoPlayer.isPlaying) {
306
+ exoPlayer.pause()
276
307
}
277
308
}
278
309
}
0 commit comments