mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-26 17:52:29 +01:00
Makes a cache for Media Items
This commit is contained in:
parent
b45f9bd460
commit
cd32c4db72
@ -57,7 +57,6 @@ import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
@ -91,6 +90,8 @@ import androidx.media3.session.MediaController
|
||||
import androidx.media3.ui.AspectRatioFrameLayout
|
||||
import androidx.media3.ui.PlayerView
|
||||
import com.linc.audiowaveform.infiniteLinearGradient
|
||||
import com.vitorpamplona.amethyst.commons.compose.GenericBaseCache
|
||||
import com.vitorpamplona.amethyst.commons.compose.produceCachedState
|
||||
import com.vitorpamplona.amethyst.service.playback.PlaybackClientController
|
||||
import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.LyricsIcon
|
||||
@ -328,6 +329,43 @@ fun VideoViewInner(
|
||||
}
|
||||
}
|
||||
|
||||
val mediaItemCache = MediaItemCache()
|
||||
|
||||
@Immutable
|
||||
data class MediaItemData(
|
||||
val videoUri: String,
|
||||
val authorName: String? = null,
|
||||
val title: String? = null,
|
||||
val artworkUri: String? = null,
|
||||
)
|
||||
|
||||
class MediaItemCache() : GenericBaseCache<MediaItemData, MediaItem>(20) {
|
||||
override suspend fun compute(data: MediaItemData): MediaItem? {
|
||||
return MediaItem.Builder()
|
||||
.setMediaId(data.videoUri)
|
||||
.setUri(data.videoUri)
|
||||
.setMediaMetadata(
|
||||
MediaMetadata.Builder()
|
||||
.setArtist(data.authorName?.ifBlank { null })
|
||||
.setTitle(data.title?.ifBlank { null } ?: data.videoUri)
|
||||
.setArtworkUri(
|
||||
try {
|
||||
if (data.artworkUri != null) {
|
||||
Uri.parse(data.artworkUri)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
null
|
||||
},
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GetMediaItem(
|
||||
videoUri: String,
|
||||
@ -336,51 +374,15 @@ fun GetMediaItem(
|
||||
authorName: String?,
|
||||
inner: @Composable (State<MediaItem>) -> Unit,
|
||||
) {
|
||||
val mediaItem =
|
||||
produceState<MediaItem?>(
|
||||
initialValue = null,
|
||||
key1 = videoUri,
|
||||
) {
|
||||
this.value =
|
||||
MediaItem.Builder()
|
||||
.setMediaId(videoUri)
|
||||
.setUri(videoUri)
|
||||
.setMediaMetadata(
|
||||
MediaMetadata.Builder()
|
||||
.setArtist(authorName?.ifBlank { null })
|
||||
.setTitle(title?.ifBlank { null } ?: videoUri)
|
||||
.setArtworkUri(
|
||||
try {
|
||||
if (artworkUri != null) {
|
||||
Uri.parse(artworkUri)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
null
|
||||
},
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
val data = remember(videoUri) { MediaItemData(videoUri, title, artworkUri, authorName) }
|
||||
val mediaItem by produceCachedState(cache = mediaItemCache, key = data)
|
||||
|
||||
mediaItem.value?.let {
|
||||
mediaItem?.let {
|
||||
val myState = remember(videoUri) { mutableStateOf(it) }
|
||||
inner(myState)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class MediaControllerState {
|
||||
@Immutable object NotStarted : MediaControllerState()
|
||||
|
||||
@Immutable object Loading : MediaControllerState()
|
||||
|
||||
@Stable class Loaded(val instance: MediaController) : MediaControllerState()
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(UnstableApi::class)
|
||||
fun GetVideoController(
|
||||
@ -394,12 +396,11 @@ fun GetVideoController(
|
||||
|
||||
val controller =
|
||||
remember(videoUri) {
|
||||
val globalMutex = keepPlayingMutex
|
||||
mutableStateOf<MediaControllerState>(
|
||||
if (videoUri == globalMutex?.currentMediaItem?.mediaId) {
|
||||
MediaControllerState.Loaded(globalMutex)
|
||||
mutableStateOf(
|
||||
if (videoUri == keepPlayingMutex?.currentMediaItem?.mediaId) {
|
||||
keepPlayingMutex
|
||||
} else {
|
||||
MediaControllerState.NotStarted
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -407,7 +408,7 @@ fun GetVideoController(
|
||||
val keepPlaying =
|
||||
remember(videoUri) {
|
||||
mutableStateOf<Boolean>(
|
||||
keepPlayingMutex != null && controller.value == keepPlayingMutex,
|
||||
keepPlayingMutex != null && controller == keepPlayingMutex,
|
||||
)
|
||||
}
|
||||
|
||||
@ -419,9 +420,7 @@ fun GetVideoController(
|
||||
DisposableEffect(key1 = videoUri) {
|
||||
// If it is not null, the user might have come back from a playing video, like clicking on
|
||||
// the notification of the video player.
|
||||
if (controller.value == MediaControllerState.NotStarted) {
|
||||
controller.value = MediaControllerState.Loading
|
||||
|
||||
if (controller.value == null) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Log.d("PlaybackService", "Preparing Video $videoUri ")
|
||||
PlaybackClientController.prepareController(
|
||||
@ -432,29 +431,27 @@ fun GetVideoController(
|
||||
) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
// REQUIRED TO BE RUN IN THE MAIN THREAD
|
||||
|
||||
val newState = MediaControllerState.Loaded(it)
|
||||
|
||||
if (!it.isPlaying) {
|
||||
if (keepPlayingMutex?.isPlaying == true) {
|
||||
// There is a video playing, start this one on mute.
|
||||
newState.instance.volume = 0f
|
||||
it.volume = 0f
|
||||
} else {
|
||||
// There is no other video playing. Use the default mute state to
|
||||
// decide if sound is on or not.
|
||||
newState.instance.volume = if (defaultToStart) 0f else 1f
|
||||
it.volume = if (defaultToStart) 0f else 1f
|
||||
}
|
||||
}
|
||||
|
||||
newState.instance.setMediaItem(mediaItem.value)
|
||||
newState.instance.prepare()
|
||||
it.setMediaItem(mediaItem.value)
|
||||
it.prepare()
|
||||
|
||||
controller.value = newState
|
||||
controller.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (controller.value is MediaControllerState.Loaded) {
|
||||
(controller.value as? MediaControllerState.Loaded)?.instance?.let {
|
||||
} else {
|
||||
// has been loaded. prepare to play
|
||||
controller.value?.let {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
if (it.playbackState == Player.STATE_IDLE || it.playbackState == Player.STATE_ENDED) {
|
||||
if (it.isPlaying) {
|
||||
@ -466,7 +463,10 @@ fun GetVideoController(
|
||||
it.volume = if (defaultToStart) 0f else 1f
|
||||
}
|
||||
|
||||
it.setMediaItem(mediaItem.value)
|
||||
if (mediaItem.value != it.currentMediaItem) {
|
||||
it.setMediaItem(mediaItem.value)
|
||||
}
|
||||
|
||||
it.prepare()
|
||||
}
|
||||
}
|
||||
@ -477,11 +477,11 @@ fun GetVideoController(
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
if (!keepPlaying.value) {
|
||||
// Stops and releases the media.
|
||||
(controller.value as? MediaControllerState.Loaded)?.instance?.let {
|
||||
controller.value?.let {
|
||||
it.stop()
|
||||
it.release()
|
||||
Log.d("PlaybackService", "Releasing Video $videoUri ")
|
||||
controller.value = MediaControllerState.NotStarted
|
||||
controller.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -496,12 +496,9 @@ fun GetVideoController(
|
||||
if (event == Lifecycle.Event.ON_RESUME) {
|
||||
// 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
|
||||
scope.launch(Dispatchers.IO) {
|
||||
if (controller.value == MediaControllerState.NotStarted) {
|
||||
controller.value = MediaControllerState.Loading
|
||||
|
||||
if (controller.value == null) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Log.d("PlaybackService", "Preparing Video from Resume $videoUri ")
|
||||
|
||||
PlaybackClientController.prepareController(
|
||||
uid,
|
||||
videoUri,
|
||||
@ -510,25 +507,22 @@ fun GetVideoController(
|
||||
) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
// REQUIRED TO BE RUN IN THE MAIN THREAD
|
||||
|
||||
val newState = MediaControllerState.Loaded(it)
|
||||
|
||||
// checks again to make sure no other thread has created a controller.
|
||||
if (!it.isPlaying) {
|
||||
if (keepPlayingMutex?.isPlaying == true) {
|
||||
// There is a video playing, start this one on mute.
|
||||
newState.instance.volume = 0f
|
||||
it.volume = 0f
|
||||
} else {
|
||||
// There is no other video playing. Use the default mute state to
|
||||
// decide if sound is on or not.
|
||||
newState.instance.volume = if (defaultToStart) 0f else 1f
|
||||
it.volume = if (defaultToStart) 0f else 1f
|
||||
}
|
||||
}
|
||||
|
||||
newState.instance.setMediaItem(mediaItem.value)
|
||||
newState.instance.prepare()
|
||||
it.setMediaItem(mediaItem.value)
|
||||
it.prepare()
|
||||
|
||||
controller.value = newState
|
||||
controller.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -538,11 +532,11 @@ fun GetVideoController(
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
if (!keepPlaying.value) {
|
||||
// Stops and releases the media.
|
||||
(controller.value as? MediaControllerState.Loaded)?.instance?.let {
|
||||
controller.value?.let {
|
||||
Log.d("PlaybackService", "Releasing Video from Pause $videoUri ")
|
||||
it.stop()
|
||||
it.release()
|
||||
controller.value = MediaControllerState.NotStarted
|
||||
controller.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -553,7 +547,9 @@ fun GetVideoController(
|
||||
onDispose { lifeCycleOwner.lifecycle.removeObserver(observer) }
|
||||
}
|
||||
|
||||
(controller.value as? MediaControllerState.Loaded)?.let { inner(it.instance, keepPlaying) }
|
||||
controller.value?.let {
|
||||
inner(it, keepPlaying)
|
||||
}
|
||||
}
|
||||
|
||||
// background playing mutex.
|
||||
|
@ -35,6 +35,17 @@ fun <K, V> produceCachedState(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <K, V> produceCachedState(
|
||||
cache: CachedState<K, V>,
|
||||
key: String,
|
||||
updateValue: K,
|
||||
): State<V?> {
|
||||
return produceState(initialValue = cache.cached(updateValue), key1 = key) {
|
||||
value = cache.update(updateValue)
|
||||
}
|
||||
}
|
||||
|
||||
interface CachedState<K, V> {
|
||||
fun cached(k: K): V?
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user