Назад к задачамПолучайте помощь с лайвкодингом в реальном времени с Sobes Copilot
Junior — Middle+
6
Обзор кода экрана видеоплеера, построенного с помощью Jetpack Compose
Компании, где спрашивали:
Salmon
Условие задачи
Необходимо выполнить ревью реализации экрана видеоплеера, написанного на Jetpack Compose.
sealed interface VideoPlayerState {
data object Empty : VideoPlayerState
data object Loading : VideoPlayerState
data class Content(
val currentVideoUrl: String,
val videoList: List<Video>
) : VideoPlayerState }
@Composable
fun VideoPlayerScreen(
category: String,
viewModel: VideoPlayerViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val currentState = uiState
val context = LocalContext.current
val isPreview = LocalInspectionMode.current
LaunchedEffect(Unit) {
viewModel.getVideo(category) }
if (isPreview) {
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(3f / 4f),
contentAlignment = Alignment.Center
) {
Text("ExoPlayer Placeholder", color = Color.White) }
} else {
when (currentState) {
is VideoPlayerState.Content -> {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
ExoPlayer(url = currentState.currentVideoUrl)
BasicButton(
modifier = Modifier
.padding(50.dp)
.align(Alignment.CenterHorizontally),
onClick = { },
text = "Continue" )
BasicButton(
modifier = Modifier
.padding(50.dp)
.align(Alignment.CenterHorizontally),
onClick = {
val fileName = "video_${System.currentTimeMillis()}.mp4"
saveVideoToStorage(context, currentState.currentVideoUrl, fileName)
},
text = "Save Video"
)
NavItem(
videoList = currentState.videoList,
onItemSelected = { viewModel.selectVideo(it) }
) } }
is VideoPlayerState.Loading -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator() } }
is VideoPlayerState.Empty -> Unit
} } }
@OptIn(UnstableApi::class)
@Composable
fun ExoPlayer(
url: String,
modifier: Modifier = Modifier,
viewModel: PlayerViewModel = hiltViewModel(),
listener: Player.Listener? = null
) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val exoPlayer by viewModel.playerFlow.collectAsState()
if (listener != null) {
exoPlayer!!.addListener(listener)
}
LaunchedEffect(url) {
viewModel.setVideoUrl(url)
}
DisposableEffect(lifecycleOwner) {
val lifecycleObserver = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_PAUSE -> viewModel.savePosition()
Lifecycle.Event.ON_RESUME -> exoPlayer?.seekTo(viewModel.currentPosition)
else -> Unit
}
}
lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
onDispose {
lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
}
}
AndroidView(
factory = {
PlayerView(context).apply {
resizeMode = RESIZE_MODE_ZOOM
player = exoPlayer
}
},
update = { it.player = exoPlayer },
modifier = modifier.aspectRatio(16f / 9f)
)
}
suspend fun saveVideoToStorage(
context: Context,
videoUrl: String,
fileName: String
): Uri? {
// TODO: реализация сохранения в External Storage
return null
}