Fixes race conditions when opening videos.

This commit is contained in:
Vitor Pamplona
2023-11-22 16:28:48 -05:00
parent 61f52ef62e
commit 3666a0b980
@@ -4,6 +4,7 @@ import android.content.Context
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.util.Log
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
@@ -25,6 +26,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
@@ -261,6 +263,18 @@ fun VideoViewInner(
} }
} }
@Immutable
sealed class MediaControllerState {
@Immutable
object NotStarted : MediaControllerState()
@Immutable
object Loading : MediaControllerState()
@Stable
class Loaded(val instance: MediaController) : MediaControllerState()
}
@Composable @Composable
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
fun GetVideoController( fun GetVideoController(
@@ -273,8 +287,13 @@ fun GetVideoController(
val context = LocalContext.current val context = LocalContext.current
val controller = remember(videoUri) { val controller = remember(videoUri) {
mutableStateOf<MediaController?>( val globalMutex = keepPlayingMutex
if (videoUri == keepPlayingMutex?.currentMediaItem?.mediaId) keepPlayingMutex else null mutableStateOf<MediaControllerState>(
if (videoUri == globalMutex?.currentMediaItem?.mediaId) {
MediaControllerState.Loaded(globalMutex)
} else {
MediaControllerState.NotStarted
}
) )
} }
@@ -294,7 +313,9 @@ fun GetVideoController(
DisposableEffect(key1 = videoUri) { DisposableEffect(key1 = videoUri) {
// If it is not null, the user might have come back from a playing video, like clicking on // If it is not null, the user might have come back from a playing video, like clicking on
// the notification of the video player. // the notification of the video player.
if (controller.value == null) { if (controller.value == MediaControllerState.NotStarted) {
controller.value = MediaControllerState.Loading
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
PlaybackClientController.prepareController( PlaybackClientController.prepareController(
uid, uid,
@@ -305,49 +326,28 @@ fun GetVideoController(
scope.launch(Dispatchers.Main) { scope.launch(Dispatchers.Main) {
// REQUIRED TO BE RUN IN THE MAIN THREAD // REQUIRED TO BE RUN IN THE MAIN THREAD
// checks again because of race conditions. val newState = MediaControllerState.Loaded(it)
if (controller.value == null) { // still prone to race conditions.
controller.value = it
if (!it.isPlaying) { if (!it.isPlaying) {
if (keepPlayingMutex?.isPlaying == true) { if (keepPlayingMutex?.isPlaying == true) {
// There is a video playing, start this one on mute. // There is a video playing, start this one on mute.
controller.value?.volume = 0f newState.instance.volume = 0f
} else { } else {
// There is no other video playing. Use the default mute state to // There is no other video playing. Use the default mute state to
// decide if sound is on or not. // decide if sound is on or not.
controller.value?.volume = if (defaultToStart) 0f else 1f newState.instance.volume = if (defaultToStart) 0f else 1f
} }
} }
controller.value?.setMediaItem(mediaItem.value) newState.instance.setMediaItem(mediaItem.value)
controller.value?.prepare() newState.instance.prepare()
} else if (controller.value != it) {
// discards the new controller because there is an existing one
it.stop()
it.release()
controller.value?.let { controller.value = newState
if (it.playbackState == Player.STATE_IDLE || it.playbackState == Player.STATE_ENDED) {
if (it.isPlaying) {
// There is a video playing, start this one on mute.
it.volume = 0f
} else {
// There is no other video playing. Use the default mute state to
// decide if sound is on or not.
it.volume = if (defaultToStart) 0f else 1f
}
it.setMediaItem(mediaItem.value)
it.prepare()
} }
} }
} }
} } else if (controller.value is MediaControllerState.Loaded) {
} (controller.value as? MediaControllerState.Loaded)?.instance?.let {
}
} else {
controller.value?.let {
scope.launch(Dispatchers.Main) { scope.launch(Dispatchers.Main) {
if (it.playbackState == Player.STATE_IDLE || it.playbackState == Player.STATE_ENDED) { if (it.playbackState == Player.STATE_IDLE || it.playbackState == Player.STATE_ENDED) {
if (it.isPlaying) { if (it.isPlaying) {
@@ -367,11 +367,15 @@ fun GetVideoController(
} }
onDispose { onDispose {
scope.launch(Dispatchers.Main) {
if (!keepPlaying.value) { if (!keepPlaying.value) {
// Stops and releases the media. // Stops and releases the media.
controller.value?.stop() (controller.value as? MediaControllerState.Loaded)?.instance?.let {
controller.value?.release() it.stop()
controller.value = null it.release()
controller.value = MediaControllerState.NotStarted
}
}
} }
} }
} }
@@ -384,7 +388,9 @@ fun GetVideoController(
// if the controller is null, restarts the controller with a new one // if the controller is null, restarts the controller with a new one
// if the controller is not null, just continue playing what the controller was playing // if the controller is not null, just continue playing what the controller was playing
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
if (controller.value == null) { if (controller.value == MediaControllerState.NotStarted) {
controller.value = MediaControllerState.Loading
PlaybackClientController.prepareController( PlaybackClientController.prepareController(
uid, uid,
videoUri, videoUri,
@@ -394,40 +400,41 @@ fun GetVideoController(
scope.launch(Dispatchers.Main) { scope.launch(Dispatchers.Main) {
// REQUIRED TO BE RUN IN THE MAIN THREAD // REQUIRED TO BE RUN IN THE MAIN THREAD
// checks again to make sure no other thread has created a controller. val newState = MediaControllerState.Loaded(it)
if (controller.value == null) {
controller.value = it
// checks again to make sure no other thread has created a controller.
if (!it.isPlaying) { if (!it.isPlaying) {
if (keepPlayingMutex?.isPlaying == true) { if (keepPlayingMutex?.isPlaying == true) {
// There is a video playing, start this one on mute. // There is a video playing, start this one on mute.
controller.value?.volume = 0f newState.instance.volume = 0f
} else { } else {
// There is no other video playing. Use the default mute state to // There is no other video playing. Use the default mute state to
// decide if sound is on or not. // decide if sound is on or not.
controller.value?.volume = newState.instance.volume =
if (defaultToStart) 0f else 1f if (defaultToStart) 0f else 1f
} }
} }
controller.value?.setMediaItem(mediaItem.value) newState.instance.setMediaItem(mediaItem.value)
controller.value?.prepare() newState.instance.prepare()
} else if (controller.value != it) {
// discards the new controller because there is an existing one controller.value = newState
it.stop()
it.release()
}
} }
} }
} }
} }
} }
if (event == Lifecycle.Event.ON_PAUSE) { if (event == Lifecycle.Event.ON_PAUSE) {
Log.d("PlaybackService", "Opening From Client - onPause ${controller.value} ")
scope.launch(Dispatchers.Main) {
if (!keepPlaying.value) { if (!keepPlaying.value) {
// Stops and releases the media. // Stops and releases the media.
controller.value?.stop() (controller.value as? MediaControllerState.Loaded)?.instance?.let {
controller.value?.release() it.stop()
controller.value = null it.release()
controller.value = MediaControllerState.NotStarted
}
}
} }
} }
} }
@@ -438,8 +445,8 @@ fun GetVideoController(
} }
} }
controller.value?.let { (controller.value as? MediaControllerState.Loaded) ?.let {
inner(it, keepPlaying) inner(it.instance, keepPlaying)
} }
} }