diff --git a/modules/core-downloader/src/main/java/tm/alashow/datmusic/downloader/AudioExtensions.kt b/modules/core-downloader/src/main/java/tm/alashow/datmusic/downloader/AudioExtensions.kt index b9483283..ddc2d08b 100644 --- a/modules/core-downloader/src/main/java/tm/alashow/datmusic/downloader/AudioExtensions.kt +++ b/modules/core-downloader/src/main/java/tm/alashow/datmusic/downloader/AudioExtensions.kt @@ -56,22 +56,24 @@ fun Audio.documentFile(parent: DocumentFile, songsGrouping: DownloadsSongsGroupi } fun Audio.artworkFromFile(context: Context): Bitmap? { - val downloadInfo = audioDownloadItem?.downloadInfo ?: return null - val metadataRetriever = MediaMetadataRetriever() - metadataRetriever.setDataSource(context, downloadInfo.fileUri) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - try { - return metadataRetriever.primaryImage - } catch (e: Exception) { - Timber.e(e) + try { + val downloadInfo = audioDownloadItem?.downloadInfo ?: return null + + val metadataRetriever = MediaMetadataRetriever() + metadataRetriever.setDataSource(context, downloadInfo.fileUri) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + try { + return metadataRetriever.primaryImage + } catch (e: Exception) { + Timber.e(e) + } } - } - try { val data = metadataRetriever.embeddedPicture if (data != null) { return BitmapFactory.decodeByteArray(data, 0, data.size) } + return null } catch (e: Exception) { Timber.e(e) } @@ -79,7 +81,12 @@ fun Audio.artworkFromFile(context: Context): Bitmap? { } fun AudioDownloadItem.audioHeader(context: Context): AudioHeader { - val mediaExtractor = MediaExtractor() - mediaExtractor.setDataSource(context, downloadInfo.fileUri, null) - return AudioHeader.from(this, mediaExtractor.getTrackFormat(0)) + try { + val mediaExtractor = MediaExtractor() + mediaExtractor.setDataSource(context, downloadInfo.fileUri, null) + return AudioHeader.from(this, mediaExtractor.getTrackFormat(0)) + } catch (e: Exception) { + Timber.e(e) + } + return AudioHeader() } diff --git a/modules/core-ui-playback/src/main/java/tm/alashow/datmusic/ui/playback/PlaybackSheet.kt b/modules/core-ui-playback/src/main/java/tm/alashow/datmusic/ui/playback/PlaybackSheet.kt index 6e740bbe..1acbd7b2 100644 --- a/modules/core-ui-playback/src/main/java/tm/alashow/datmusic/ui/playback/PlaybackSheet.kt +++ b/modules/core-ui-playback/src/main/java/tm/alashow/datmusic/ui/playback/PlaybackSheet.kt @@ -99,6 +99,7 @@ import tm.alashow.common.compose.LocalPlaybackConnection import tm.alashow.common.compose.LocalScaffoldState import tm.alashow.common.compose.rememberFlowWithLifecycle import tm.alashow.datmusic.data.repos.search.DatmusicSearchParams +import tm.alashow.datmusic.domain.entities.Audio import tm.alashow.datmusic.domain.entities.CoverImageSize import tm.alashow.datmusic.downloader.audioHeader import tm.alashow.datmusic.playback.NONE_PLAYBACK_STATE @@ -249,7 +250,7 @@ fun PlaybackSheetContent( if (playbackQueue.isValid) item { - PlaybackNowPlayingAudioInfo(playbackQueue = playbackQueue) + PlaybackAudioInfo(audio = playbackQueue.currentAudio) } playbackQueue( @@ -264,53 +265,63 @@ fun PlaybackSheetContent( } } -@OptIn(ExperimentalAnimationApi::class) -private fun LazyListScope.playbackQueue( +@Composable +private fun PlaybackSheetTopBar( playbackQueue: PlaybackQueue, - scrollToTop: Callback, - playbackConnection: PlaybackConnection, -) { - val lastIndex = playbackQueue.audiosList.size - val firstIndex = (playbackQueue.currentIndex + 1).coerceAtMost(lastIndex) - val queue = playbackQueue.audiosList.subList(firstIndex, lastIndex) - itemsIndexed(queue, key = { index, _ -> index }) { index, audio -> - val realPosition = firstIndex + index - AudioRow( - audio = audio, - imageSize = 40.dp, - onPlayAudio = { - playbackConnection.transportControls?.skipToQueueItem(realPosition.toLong()) - scrollToTop() - } - ) - } -} - -private fun LazyListScope.playbackNowPlayingWithControls( - nowPlaying: MediaMetadataCompat, - playbackState: PlaybackStateCompat, - contentColor: Color, onClose: Callback, + iconSize: Dp = 36.dp, + actionHandler: AudioActionHandler = LocalAudioActionHandler.current, ) { - item { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .padding(AppTheme.specs.paddingLarge) - ) { - PlaybackNowPlaying(nowPlaying = nowPlaying, onClose = onClose) - - PlaybackProgress( - playbackState = playbackState, - contentColor = contentColor - ) + val (expanded, setExpanded) = remember { mutableStateOf(false) } - PlaybackControls( - playbackState = playbackState, - contentColor = contentColor, - ) + TopAppBar( + elevation = 0.dp, + backgroundColor = Color.Transparent, + contentPadding = rememberInsetsPaddingValues(LocalWindowInsets.current.statusBars), + title = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .offset(x = -8.dp) // idk why this is needed for centering + ) { + val context = LocalContext.current + val queueTitle = QueueTitle.from(playbackQueue.title ?: "") + Text( + queueTitle.localizeType(context).uppercase(), + style = MaterialTheme.typography.overline.copy(fontWeight = FontWeight.Light), + maxLines = 1, + ) + Text( + queueTitle.localizeValue(context), style = MaterialTheme.typography.body1, + textAlign = TextAlign.Center, + overflow = TextOverflow.Ellipsis, + maxLines = 2, + ) + } + }, + navigationIcon = { + IconButton(onClick = onClose) { + Icon( + rememberVectorPainter(Icons.Default.KeyboardArrowDown), + modifier = Modifier.size(iconSize), + contentDescription = null, + ) + } + }, + actions = { + CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) { + AudioDropdownMenu( + expanded = expanded, + onExpandedChange = setExpanded, + actionLabels = currentPlayingMenuActionLabels, + ) { + if (playbackQueue.isValid) + actionHandler(AudioItemAction.from(it, playbackQueue.currentAudio)) + } + } } - } + ) } @Composable @@ -348,6 +359,33 @@ private fun PlaybackArtwork( } } +private fun LazyListScope.playbackNowPlayingWithControls( + nowPlaying: MediaMetadataCompat, + playbackState: PlaybackStateCompat, + contentColor: Color, + onClose: Callback, +) { + item { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .padding(AppTheme.specs.paddingLarge) + ) { + PlaybackNowPlaying(nowPlaying = nowPlaying, onClose = onClose) + + PlaybackProgress( + playbackState = playbackState, + contentColor = contentColor + ) + + PlaybackControls( + playbackState = playbackState, + contentColor = contentColor, + ) + } + } +} + @Composable private fun PlaybackNowPlaying( nowPlaying: MediaMetadataCompat, @@ -610,9 +648,9 @@ private fun PlaybackControls( } @Composable -private fun PlaybackNowPlayingAudioInfo(playbackQueue: PlaybackQueue) { +private fun PlaybackAudioInfo(audio: Audio) { val context = LocalContext.current - val dlItem = playbackQueue.currentAudio.audioDownloadItem + val dlItem = audio.audioDownloadItem if (dlItem != null) { val audiHeader = dlItem.audioHeader(context) Column( @@ -636,61 +674,24 @@ private fun PlaybackNowPlayingAudioInfo(playbackQueue: PlaybackQueue) { } } -@Composable -private fun PlaybackSheetTopBar( +@OptIn(ExperimentalAnimationApi::class) +private fun LazyListScope.playbackQueue( playbackQueue: PlaybackQueue, - onClose: Callback, - iconSize: Dp = 36.dp, - actionHandler: AudioActionHandler = LocalAudioActionHandler.current, + scrollToTop: Callback, + playbackConnection: PlaybackConnection, ) { - val (expanded, setExpanded) = remember { mutableStateOf(false) } - - TopAppBar( - elevation = 0.dp, - backgroundColor = Color.Transparent, - contentPadding = rememberInsetsPaddingValues(LocalWindowInsets.current.statusBars), - title = { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .fillMaxWidth() - .offset(x = -8.dp) // idk why this is needed for centering - ) { - val context = LocalContext.current - val queueTitle = QueueTitle.from(playbackQueue.title ?: "") - Text( - queueTitle.localizeType(context).uppercase(), - style = MaterialTheme.typography.overline.copy(fontWeight = FontWeight.Light), - maxLines = 1, - ) - Text( - queueTitle.localizeValue(context), style = MaterialTheme.typography.body1, - textAlign = TextAlign.Center, - overflow = TextOverflow.Ellipsis, - maxLines = 2, - ) - } - }, - navigationIcon = { - IconButton(onClick = onClose) { - Icon( - rememberVectorPainter(Icons.Default.KeyboardArrowDown), - modifier = Modifier.size(iconSize), - contentDescription = null, - ) - } - }, - actions = { - CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) { - AudioDropdownMenu( - expanded = expanded, - onExpandedChange = setExpanded, - actionLabels = currentPlayingMenuActionLabels, - ) { - if (playbackQueue.isValid) - actionHandler(AudioItemAction.from(it, playbackQueue.currentAudio)) - } + val lastIndex = playbackQueue.audiosList.size + val firstIndex = (playbackQueue.currentIndex + 1).coerceAtMost(lastIndex) + val queue = playbackQueue.audiosList.subList(firstIndex, lastIndex) + itemsIndexed(queue, key = { index, _ -> index }) { index, audio -> + val realPosition = firstIndex + index + AudioRow( + audio = audio, + imageSize = 40.dp, + onPlayAudio = { + playbackConnection.transportControls?.skipToQueueItem(realPosition.toLong()) + scrollToTop() } - } - ) + ) + } }