Skip to content

Commit 9f59db8

Browse files
authored
Media viewer: release the ExoPlayers when the hosting composables are disposed (#5351)
* Media viewer: release the `ExoPlayers` when the hosting composables are disposed * Restore missing `removeListener` call
1 parent d9e7d74 commit 9f59db8

File tree

1 file changed

+68
-37
lines changed
  • libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video

1 file changed

+68
-37
lines changed

libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ package io.element.android.libraries.mediaviewer.impl.local.video
1010
import android.annotation.SuppressLint
1111
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
1212
import android.widget.FrameLayout
13+
import androidx.annotation.OptIn
1314
import androidx.compose.foundation.background
1415
import androidx.compose.foundation.layout.Box
1516
import androidx.compose.foundation.layout.fillMaxSize
1617
import androidx.compose.foundation.layout.fillMaxWidth
1718
import androidx.compose.foundation.layout.padding
1819
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.DisposableEffect
1921
import androidx.compose.runtime.LaunchedEffect
2022
import androidx.compose.runtime.derivedStateOf
2123
import androidx.compose.runtime.getValue
@@ -33,6 +35,7 @@ import androidx.media3.common.MediaItem
3335
import androidx.media3.common.Player
3436
import androidx.media3.common.Player.STATE_READY
3537
import androidx.media3.common.Timeline
38+
import androidx.media3.common.util.UnstableApi
3639
import androidx.media3.exoplayer.ExoPlayer
3740
import androidx.media3.ui.AspectRatioFrameLayout
3841
import androidx.media3.ui.PlayerView
@@ -55,6 +58,7 @@ import io.element.android.libraries.mediaviewer.impl.local.player.togglePlay
5558
import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState
5659
import kotlinx.coroutines.delay
5760
import me.saket.telephoto.zoomable.zoomable
61+
import timber.log.Timber
5862
import kotlin.time.Duration.Companion.seconds
5963

6064
@SuppressLint("UnsafeOptInUsageError")
@@ -165,35 +169,6 @@ private fun ExoPlayerMediaVideoView(
165169
}
166170
}
167171

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-
}
197172
if (localMedia?.uri != null) {
198173
LaunchedEffect(localMedia.uri) {
199174
val mediaItem = MediaItem.fromUri(localMedia.uri)
@@ -263,16 +238,72 @@ private fun ExoPlayerMediaVideoView(
263238
)
264239
}
265240

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) {
273284
exoPlayer.removeListener(playerListener)
285+
exoPlayer.release()
274286
}
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()
276307
}
277308
}
278309
}

0 commit comments

Comments
 (0)