Untitled
unknown
kotlin
a year ago
13 kB
7
Indexable
Never
@AndroidEntryPoint class StoriesPlayerV2 : Fragment() { var player by mutableStateOf<MediaController?>(null) val viewModel by viewModels<StoriesPlayerViewModel2>() @Inject lateinit var controllerFuture: ListenableFuture<MediaController> override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { setContent { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) player?.let { PlayerScreen(Modifier.fillMaxWidth(1f), it) } } } } override fun onStart() { controllerFuture.addListener( { player = controllerFuture.get() lifecycleScope.launch { val newPlaylist = withContext(Dispatchers.IO) { if (!viewModel.initialized) { viewModel.initialized = true viewModel.newPlaylist() } else { listOf() } } if (newPlaylist.isNotEmpty()) { player?.setMediaItems(newPlaylist) player?.prepare() player?.seekTo(viewModel.listPosition, viewModel.lastPlayedPosition) player?.play() } } }, MoreExecutors.directExecutor() ) super.onStart() } override fun onStop() { super.onStop() player?.release() MediaController.releaseFuture(controllerFuture) } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun PlayerScreen(modifier: Modifier, player: MediaController) { var position by remember { mutableStateOf(0L) } var sliderPosition: Long? by remember { mutableStateOf(null) } var duration by remember { mutableStateOf(0L) } var mediaMetadataState by remember { mutableStateOf<MediaMetadata?>(player.mediaMetadata) } var currentMediaItem by remember { mutableStateOf(player.currentMediaItem) } var isPlayingState by remember { mutableStateOf(player.isPlaying) } var playbackState by remember { mutableStateOf(player.playbackState) } var openBottomSheet by rememberSaveable { mutableStateOf(false) } val scope = rememberCoroutineScope() DisposableEffect(key1 = player, effect = { val listener = object : Player.Listener { override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) { super.onMediaMetadataChanged(mediaMetadata) mediaMetadataState = mediaMetadata duration = 0 position = 0 } override fun onIsPlayingChanged(isPlaying: Boolean) { super.onIsPlayingChanged(isPlaying) isPlayingState = isPlaying } override fun onPlaybackStateChanged(newPlaybackState: Int) { super.onPlaybackStateChanged(newPlaybackState) playbackState = newPlaybackState } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { super.onMediaItemTransition(mediaItem, reason) // currentMediaItem = mediaItem } } player.addListener(listener) onDispose { player.removeListener(listener) } }) Column( modifier = modifier, verticalArrangement = Arrangement.SpaceBetween ) { IconButton(onClick = { activity?.finish() }) { Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "") } TrackInfo { mediaMetadataState } PlayerControls( player = player, isPlayingProvider = { isPlayingState }, onValueChangeFinished = { sliderPosition?.let { position = it player.seekTo(it) } sliderPosition = null }, onPositionChange = { sliderPosition = it.toLong() }, sliderPosition = { sliderPosition }, position = { position }, duration = { duration }, playbackState = { playbackState } ) { position = player.currentPosition duration = player.duration } ActionBar( modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp), isFavoriteProvider = { true }, onPlaylistClickedListener = { scope.launch { openBottomSheet = true } }, onFavoriteClickedListener = { scope.launch { // val session = viewModel.fetchSession(player.currentMediaItem?.mediaId) } }) } val skipPartiallyExpanded by remember { mutableStateOf(false) } val edgeToEdgeEnabled by remember { mutableStateOf(true) } val bottomSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = skipPartiallyExpanded ) if (openBottomSheet) { val windowInsets = if (edgeToEdgeEnabled) WindowInsets(0) else BottomSheetDefaults.windowInsets ModalBottomSheet( onDismissRequest = { openBottomSheet = false }, sheetState = bottomSheetState, windowInsets = windowInsets ) { LazyColumn { items(player.mediaItemCount) { val mediaItem = player.getMediaItemAt(it) ListItem( headlineContent = { Text("${mediaItem.mediaMetadata.title}") Text( text = "${mediaItem.mediaMetadata.description}", maxLines = 3, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodySmall ) }, leadingContent = { AsyncImage( model = mediaItem.mediaMetadata.artworkUri, contentDescription = "", modifier = Modifier.size(64.dp), ) }, modifier = Modifier.clickable { player.seekTo(it, 0L) scope.launch { bottomSheetState.hide() }.invokeOnCompletion { if (!bottomSheetState.isVisible) { openBottomSheet = false } } } ) } } Spacer(modifier = Modifier.height(48.dp)) } } } } @Composable private fun PlayerControls( modifier: Modifier = Modifier, player: MediaController, isPlayingProvider: () -> Boolean, sliderPosition: () -> Long?, position: () -> Long, onValueChangeFinished: () -> Unit, onPositionChange: (Float) -> Unit, duration: () -> Long, playbackState: () -> Int, onTick: () -> Unit, ) { Column { ProgressBar( modifier = Modifier.padding(16.dp), position = { (sliderPosition() ?: position()).toFloat() }, duration = { duration().toFloat() }, onPositionChange = onPositionChange, onValueChangeFinished = onValueChangeFinished, onTick = onTick, playbackState = playbackState ) Row(modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { IconButton(onClick = { player.seekToPreviousMediaItem() }) { Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "") } IconButton(onClick = { if (isPlayingProvider()) player.pause() else player.play() }) { Icon( imageVector = if (isPlayingProvider()) Icons.Filled.ArrowDropDown else Icons.Filled.PlayArrow, contentDescription = "" ) } IconButton(onClick = { player.seekToNextMediaItem() }) { Icon(imageVector = Icons.Filled.ArrowForward, contentDescription = "") } } } } @Composable private fun TrackInfo( modifier: Modifier = Modifier, mediaMetadata: () -> MediaMetadata? ) { Column( modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { var showShimmer by remember { mutableStateOf(true) } AsyncImage( model = mediaMetadata()?.artworkUri.toString(), contentDescription = "Album Art", modifier = Modifier .size(300.dp) .clip(RoundedCornerShape(2.dp)) .background(shimmerBrush(showShimmer = showShimmer)), onSuccess = { showShimmer = false }, contentScale = ContentScale.FillBounds ) Spacer(modifier = Modifier.height(32.dp)) Text(text = mediaMetadata()?.title.toString(), style = MaterialTheme.typography.titleLarge) if (mediaMetadata()?.artist != null) { Spacer(modifier = Modifier.height(16.dp)) Text(text = mediaMetadata()?.artist.toString()) } } } @Composable private fun ActionBar( modifier: Modifier = Modifier, onFavoriteClickedListener: () -> Unit = {}, onShareClickedListener: () -> Unit = {}, onPlaylistClickedListener: () -> Unit = {}, isFavoriteProvider: () -> Boolean, ) { Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = modifier.fillMaxWidth()) { IconButton(onClick = onFavoriteClickedListener) { Icon( imageVector = if (isFavoriteProvider()) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder, contentDescription = "" ) } IconButton(onClick = onShareClickedListener) { Icon(imageVector = Icons.Outlined.Share, contentDescription = "") } IconButton(onClick = { }) { Icon(imageVector = Icons.Outlined.FavoriteBorder, contentDescription = "") } IconButton(onClick = onPlaylistClickedListener) { Icon(imageVector = Icons.Outlined.FavoriteBorder, contentDescription = "") } } } @Composable private fun ProgressBar( modifier: Modifier = Modifier, position: () -> Float, onPositionChange: (Float) -> Unit, duration: () -> Float, onValueChangeFinished: () -> Unit, onTick: () -> Unit, playbackState: () -> Int ) { LaunchedEffect(key1 = playbackState(), block = { if (playbackState() == STATE_READY) { while (isActive) { onTick() delay(500) } } }) Column(modifier = modifier) { if (duration() < 0) return Slider( value = position(), onValueChange = onPositionChange, valueRange = 0f..duration(), onValueChangeFinished = onValueChangeFinished ) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth() ) { position().toLong().milliseconds.toComponents { hours, minutes, seconds, _ -> Text(text = "%02d:%02d".format(minutes, seconds)) } duration().toLong().milliseconds.toComponents { hours, minutes, seconds, _ -> Text(text = "%02d:%02d".format(minutes, seconds)) } } } }