Fixes race conditions when opening videos.
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user