mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-17 23:42:04 +00:00
add fullscreen trailer player
This commit is contained in:
parent
d433a5cab4
commit
6cfb7fa4a9
3 changed files with 275 additions and 191 deletions
|
|
@ -75,7 +75,7 @@ import com.nuvio.app.features.details.components.DetailProductionSection
|
||||||
import com.nuvio.app.features.details.components.DetailSeriesContent
|
import com.nuvio.app.features.details.components.DetailSeriesContent
|
||||||
import com.nuvio.app.features.details.components.DetailTrailersSection
|
import com.nuvio.app.features.details.components.DetailTrailersSection
|
||||||
import com.nuvio.app.features.details.components.EpisodeWatchedActionSheet
|
import com.nuvio.app.features.details.components.EpisodeWatchedActionSheet
|
||||||
import com.nuvio.app.features.details.components.TrailerPlayerPopup
|
import com.nuvio.app.features.details.components.FullscreenTrailerPlayer
|
||||||
import com.nuvio.app.features.home.MetaPreview
|
import com.nuvio.app.features.home.MetaPreview
|
||||||
import com.nuvio.app.features.library.LibraryRepository
|
import com.nuvio.app.features.library.LibraryRepository
|
||||||
import com.nuvio.app.features.library.toLibraryItem
|
import com.nuvio.app.features.library.toLibraryItem
|
||||||
|
|
@ -822,7 +822,7 @@ fun MetaDetailsScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inAppTrailerPlaybackEnabled) {
|
if (inAppTrailerPlaybackEnabled) {
|
||||||
TrailerPlayerPopup(
|
FullscreenTrailerPlayer(
|
||||||
visible = selectedTrailer != null,
|
visible = selectedTrailer != null,
|
||||||
trailerTitle = selectedTrailer?.displayName ?: selectedTrailer?.name.orEmpty(),
|
trailerTitle = selectedTrailer?.displayName ?: selectedTrailer?.name.orEmpty(),
|
||||||
trailerType = selectedTrailer?.type.orEmpty(),
|
trailerType = selectedTrailer?.type.orEmpty(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,273 @@
|
||||||
|
package com.nuvio.app.features.details.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||||
|
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Close
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.pointer.PointerEventPass
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.nuvio.app.core.ui.PlatformBackHandler
|
||||||
|
import com.nuvio.app.features.player.EnterImmersivePlayerMode
|
||||||
|
import com.nuvio.app.features.player.LockPlayerToLandscape
|
||||||
|
import com.nuvio.app.features.player.PlatformPlayerSurface
|
||||||
|
import com.nuvio.app.features.player.PlayerResizeMode
|
||||||
|
import com.nuvio.app.features.trailer.TrailerPlaybackSource
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import nuvio.composeapp.generated.resources.Res
|
||||||
|
import nuvio.composeapp.generated.resources.action_retry
|
||||||
|
import nuvio.composeapp.generated.resources.detail_tab_trailer
|
||||||
|
import nuvio.composeapp.generated.resources.trailer_close
|
||||||
|
import nuvio.composeapp.generated.resources.trailer_unable_to_play
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
|
private const val TrailerControlsAutoHideMs = 3500L
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FullscreenTrailerPlayer(
|
||||||
|
visible: Boolean,
|
||||||
|
trailerTitle: String,
|
||||||
|
trailerType: String,
|
||||||
|
contentTitle: String,
|
||||||
|
playbackSource: TrailerPlaybackSource?,
|
||||||
|
isLoading: Boolean,
|
||||||
|
errorMessage: String?,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onRetry: (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
if (!visible) return
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnBackPress = true,
|
||||||
|
dismissOnClickOutside = false,
|
||||||
|
usePlatformDefaultWidth = false,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
FullscreenTrailerPlayerContent(
|
||||||
|
trailerTitle = trailerTitle,
|
||||||
|
trailerType = trailerType,
|
||||||
|
contentTitle = contentTitle,
|
||||||
|
playbackSource = playbackSource,
|
||||||
|
isLoading = isLoading,
|
||||||
|
errorMessage = errorMessage,
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
onRetry = onRetry,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FullscreenTrailerPlayerContent(
|
||||||
|
trailerTitle: String,
|
||||||
|
trailerType: String,
|
||||||
|
contentTitle: String,
|
||||||
|
playbackSource: TrailerPlaybackSource?,
|
||||||
|
isLoading: Boolean,
|
||||||
|
errorMessage: String?,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onRetry: (() -> Unit)?,
|
||||||
|
) {
|
||||||
|
LockPlayerToLandscape()
|
||||||
|
EnterImmersivePlayerMode()
|
||||||
|
PlatformBackHandler(enabled = true, onBack = onDismiss)
|
||||||
|
|
||||||
|
val headerType = trailerType.trim().ifBlank { stringResource(Res.string.detail_tab_trailer) }
|
||||||
|
val headerSubtitle = remember(trailerTitle, contentTitle, headerType) {
|
||||||
|
buildList {
|
||||||
|
if (trailerTitle.isNotBlank() && !trailerTitle.equals(headerType, ignoreCase = true)) {
|
||||||
|
add(trailerTitle)
|
||||||
|
}
|
||||||
|
if (contentTitle.isNotBlank()) {
|
||||||
|
add(contentTitle)
|
||||||
|
}
|
||||||
|
}.joinToString(separator = " • ")
|
||||||
|
}
|
||||||
|
|
||||||
|
var playerError by remember(playbackSource?.videoUrl, playbackSource?.audioUrl) {
|
||||||
|
mutableStateOf<String?>(null)
|
||||||
|
}
|
||||||
|
val activeError = errorMessage ?: playerError
|
||||||
|
|
||||||
|
var controlsVisible by remember { mutableStateOf(false) }
|
||||||
|
LaunchedEffect(controlsVisible, isLoading, activeError, playbackSource) {
|
||||||
|
if (!controlsVisible || isLoading || activeError != null || playbackSource == null) {
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
delay(TrailerControlsAutoHideMs)
|
||||||
|
controlsVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.Black)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
awaitEachGesture {
|
||||||
|
awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
|
||||||
|
controlsVisible = !controlsVisible
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
isLoading -> {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
color = Color.White,
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
activeError != null -> {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.padding(24.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.trailer_unable_to_play),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = Color.White,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = activeError,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.White.copy(alpha = 0.7f),
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
if (onRetry != null) {
|
||||||
|
TextButton(onClick = onRetry) {
|
||||||
|
Text(stringResource(Res.string.action_retry))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playbackSource != null -> {
|
||||||
|
PlatformPlayerSurface(
|
||||||
|
sourceUrl = playbackSource.videoUrl,
|
||||||
|
sourceAudioUrl = playbackSource.audioUrl,
|
||||||
|
useYoutubeChunkedPlayback = true,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
playWhenReady = true,
|
||||||
|
resizeMode = PlayerResizeMode.Fit,
|
||||||
|
useNativeController = true,
|
||||||
|
onControllerReady = {},
|
||||||
|
onSnapshot = {},
|
||||||
|
onError = { playerError = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = controlsVisible || isLoading || activeError != null,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
modifier = Modifier.align(Alignment.TopStart),
|
||||||
|
) {
|
||||||
|
TrailerTopBar(
|
||||||
|
title = headerType,
|
||||||
|
subtitle = headerSubtitle,
|
||||||
|
onClose = onDismiss,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TrailerTopBar(
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
Color.Black.copy(alpha = 0.55f),
|
||||||
|
shape = RoundedCornerShape(bottomStart = 18.dp, bottomEnd = 18.dp),
|
||||||
|
)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(Color.White.copy(alpha = 0.12f))
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null,
|
||||||
|
onClick = onClose,
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Close,
|
||||||
|
contentDescription = stringResource(Res.string.trailer_close),
|
||||||
|
tint = Color.White,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold),
|
||||||
|
color = Color.White,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
if (subtitle.isNotBlank()) {
|
||||||
|
Text(
|
||||||
|
text = subtitle,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.White.copy(alpha = 0.75f),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
package com.nuvio.app.features.details.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.rounded.Close
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import com.nuvio.app.core.ui.NuvioBottomSheetDivider
|
|
||||||
import com.nuvio.app.core.ui.NuvioModalBottomSheet
|
|
||||||
import com.nuvio.app.core.ui.dismissNuvioBottomSheet
|
|
||||||
import com.nuvio.app.core.ui.nuvioSafeBottomPadding
|
|
||||||
import com.nuvio.app.features.player.PlatformPlayerSurface
|
|
||||||
import com.nuvio.app.features.player.PlayerResizeMode
|
|
||||||
import com.nuvio.app.features.trailer.TrailerPlaybackSource
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import nuvio.composeapp.generated.resources.*
|
|
||||||
import org.jetbrains.compose.resources.stringResource
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun TrailerPlayerPopup(
|
|
||||||
visible: Boolean,
|
|
||||||
trailerTitle: String,
|
|
||||||
trailerType: String,
|
|
||||||
contentTitle: String,
|
|
||||||
playbackSource: TrailerPlaybackSource?,
|
|
||||||
isLoading: Boolean,
|
|
||||||
errorMessage: String?,
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
onRetry: (() -> Unit)? = null,
|
|
||||||
) {
|
|
||||||
if (!visible) return
|
|
||||||
|
|
||||||
val headerType = trailerType.trim().ifBlank { stringResource(Res.string.detail_tab_trailer) }
|
|
||||||
val headerSubtitle = buildList {
|
|
||||||
if (trailerTitle.isNotBlank() && !trailerTitle.equals(headerType, ignoreCase = true)) {
|
|
||||||
add(trailerTitle)
|
|
||||||
}
|
|
||||||
if (contentTitle.isNotBlank()) {
|
|
||||||
add(contentTitle)
|
|
||||||
}
|
|
||||||
}.joinToString(separator = " • ")
|
|
||||||
|
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
var playerError by remember(playbackSource?.videoUrl, playbackSource?.audioUrl) {
|
|
||||||
mutableStateOf<String?>(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
val activeError = errorMessage ?: playerError
|
|
||||||
|
|
||||||
val dismissSheet: () -> Unit = {
|
|
||||||
coroutineScope.launch {
|
|
||||||
dismissNuvioBottomSheet(sheetState = sheetState, onDismiss = onDismiss)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NuvioModalBottomSheet(
|
|
||||||
onDismissRequest = dismissSheet,
|
|
||||||
sheetState = sheetState,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.padding(bottom = nuvioSafeBottomPadding(14.dp)),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = headerType,
|
|
||||||
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.SemiBold),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
if (headerSubtitle.isNotBlank()) {
|
|
||||||
Text(
|
|
||||||
text = headerSubtitle,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(onClick = dismissSheet) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Close,
|
|
||||||
contentDescription = stringResource(Res.string.trailer_close),
|
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NuvioBottomSheetDivider()
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(18.dp))
|
|
||||||
.background(MaterialTheme.colorScheme.scrim)
|
|
||||||
.aspectRatio(16f / 9f),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
when {
|
|
||||||
isLoading -> {
|
|
||||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
|
||||||
}
|
|
||||||
|
|
||||||
activeError != null -> {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(Res.string.trailer_unable_to_play),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = activeError,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
maxLines = 3,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
if (onRetry != null) {
|
|
||||||
TextButton(onClick = onRetry) {
|
|
||||||
Text(stringResource(Res.string.action_retry))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
playbackSource != null -> {
|
|
||||||
PlatformPlayerSurface(
|
|
||||||
sourceUrl = playbackSource.videoUrl,
|
|
||||||
sourceAudioUrl = playbackSource.audioUrl,
|
|
||||||
useYoutubeChunkedPlayback = true,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
playWhenReady = true,
|
|
||||||
resizeMode = PlayerResizeMode.Fit,
|
|
||||||
useNativeController = true,
|
|
||||||
onControllerReady = {},
|
|
||||||
onSnapshot = {},
|
|
||||||
onError = { playerError = it },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue