diff --git a/app/src/main/java/org/oxycblt/auxio/AuxioService.kt b/app/src/main/java/org/oxycblt/auxio/AuxioService.kt index 8178a8218..64121e6d1 100644 --- a/app/src/main/java/org/oxycblt/auxio/AuxioService.kt +++ b/app/src/main/java/org/oxycblt/auxio/AuxioService.kt @@ -26,14 +26,14 @@ import androidx.media3.session.MediaLibraryService import androidx.media3.session.MediaSession import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject -import org.oxycblt.auxio.music.service.IndexingServiceFragment +import org.oxycblt.auxio.music.service.IndexerServiceFragment import org.oxycblt.auxio.playback.service.MediaSessionServiceFragment @AndroidEntryPoint class AuxioService : MediaLibraryService(), ForegroundListener { @Inject lateinit var mediaSessionFragment: MediaSessionServiceFragment - @Inject lateinit var indexingFragment: IndexingServiceFragment + @Inject lateinit var indexingFragment: IndexerServiceFragment @SuppressLint("WrongConstant") override fun onCreate() { diff --git a/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt index 4e3083316..f38d00695 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt @@ -27,7 +27,7 @@ import javax.inject.Inject class CoverKeyer @Inject constructor() : Keyer> { override fun key(data: Collection, options: Options) = - "${data.map { it.perceptualHash }.hashCode()}" + "${data.map { it.key }.hashCode()}" } class CoverFetcher diff --git a/app/src/main/java/org/oxycblt/auxio/image/extractor/Cover.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/Cover.kt index 4595a0dcf..bf3cf97cf 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/extractor/Cover.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/Cover.kt @@ -22,23 +22,35 @@ import android.net.Uri import org.oxycblt.auxio.list.sort.Sort import org.oxycblt.auxio.music.Song -/** - * Bundle of [Uri] information used in [CoverExtractor] to ensure consistent [Uri] use when loading - * images. - * - * @param mediaStoreUri The album cover [Uri] obtained from MediaStore. - * @param song The [Uri] of the first song (by track) of the album, which can also be used to obtain - * an album cover. - * @author Alexander Capehart (OxygenCobalt) - */ -data class Cover(val perceptualHash: String?, val mediaStoreUri: Uri, val songUri: Uri) { +sealed interface Cover { + val key: String + val mediaStoreCoverUri: Uri + + /** + * The song has an embedded cover art we support, so we can operate with it on a per-song basis. + */ + data class Embedded(val songCoverUri: Uri, val songUri: Uri, val perceptualHash: String) : + Cover { + override val mediaStoreCoverUri = songCoverUri + override val key = perceptualHash + } + + /** + * We couldn't find any embedded cover art ourselves, but the android system might have some + * through a cover.jpg file or something similar. + */ + data class External(val albumCoverUri: Uri) : Cover { + override val mediaStoreCoverUri = albumCoverUri + override val key = albumCoverUri.toString() + } + companion object { private val FALLBACK_SORT = Sort(Sort.Mode.ByAlbum, Sort.Direction.ASCENDING) fun order(songs: Collection) = FALLBACK_SORT.songs(songs) .map { it.cover } - .groupBy { it.perceptualHash } + .groupBy { it.key } .entries .sortedByDescending { it.value.size } .map { it.value.first() } diff --git a/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt index 556b11445..d28979b6b 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt @@ -140,21 +140,27 @@ constructor( private suspend fun openCoverInputStream(cover: Cover) = try { - when (imageSettings.coverMode) { - CoverMode.OFF -> null - CoverMode.MEDIA_STORE -> extractMediaStoreCover(cover) - CoverMode.QUALITY -> extractQualityCover(cover) + when (cover) { + is Cover.Embedded -> + when (imageSettings.coverMode) { + CoverMode.OFF -> null + CoverMode.MEDIA_STORE -> extractMediaStoreCover(cover) + CoverMode.QUALITY -> extractQualityCover(cover) + } + is Cover.External -> { + extractMediaStoreCover(cover) + } } } catch (e: Exception) { logE("Unable to extract album cover due to an error: $e") null } - private suspend fun extractQualityCover(cover: Cover) = + private suspend fun extractQualityCover(cover: Cover.Embedded) = extractAospMetadataCover(cover) ?: extractExoplayerCover(cover) ?: extractMediaStoreCover(cover) - private fun extractAospMetadataCover(cover: Cover): InputStream? = + private fun extractAospMetadataCover(cover: Cover.Embedded): InputStream? = MediaMetadataRetriever().run { // This call is time-consuming but it also doesn't seem to hold up the main thread, // so it's probably fine not to wrap it.rmt @@ -166,7 +172,7 @@ constructor( embeddedPicture?.let { ByteArrayInputStream(it) }.also { release() } } - private suspend fun extractExoplayerCover(cover: Cover): InputStream? { + private suspend fun extractExoplayerCover(cover: Cover.Embedded): InputStream? { val tracks = MetadataRetriever.retrieveMetadata(mediaSourceFactory, MediaItem.fromUri(cover.songUri)) .asDeferred() @@ -186,7 +192,9 @@ constructor( private suspend fun extractMediaStoreCover(cover: Cover) = // Eliminate any chance that this blocking call might mess up the loading process - withContext(Dispatchers.IO) { context.contentResolver.openInputStream(cover.mediaStoreUri) } + withContext(Dispatchers.IO) { + context.contentResolver.openInputStream(cover.mediaStoreCoverUri) + } /** Derived from phonograph: https://github.com/kabouzeid/Phonograph */ private suspend fun createMosaic(streams: List, size: Size): FetchResult { diff --git a/app/src/main/java/org/oxycblt/auxio/list/recycler/MaterialDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/MaterialDragCallback.kt index 97eed4987..6ccf789b4 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/recycler/MaterialDragCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/MaterialDragCallback.kt @@ -64,7 +64,7 @@ abstract class MaterialDragCallback : ItemTouchHelper.Callback() { totalSize: Int, msSinceStartScroll: Long ): Int { - // Clamp the scroll speed to prevent thefrom freaking out + // Clamp the scroll speed to prevent the lists from freaking out // Adapted from NewPipe: https://github.com/TeamNewPipe/NewPipe val standardSpeed = super.interpolateOutOfBoundsScroll( diff --git a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt index 6604c3579..674c0cb75 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt @@ -29,8 +29,9 @@ import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.fs.MimeType +import org.oxycblt.auxio.music.fs.toAlbumCoverUri import org.oxycblt.auxio.music.fs.toAudioUri -import org.oxycblt.auxio.music.fs.toCoverUri +import org.oxycblt.auxio.music.fs.toSongCoverUri import org.oxycblt.auxio.music.info.Date import org.oxycblt.auxio.music.info.Disc import org.oxycblt.auxio.music.info.Name @@ -114,7 +115,18 @@ class SongImpl( get() = _genres override val cover = - Cover(rawSong.coverPerceptualHash, requireNotNull(rawSong.mediaStoreId).toCoverUri(), uri) + rawSong.coverPerceptualHash?.let { + // We were able to confirm that the song had a parsable cover and can be used on + // a per-song basis. Otherwise, just fall back to a per-album cover instead, as + // it implies either a cover.jpg pattern is used (likely) or ExoPlayer does not + // support the cover metadata of a given spec (unlikely). + Cover.Embedded( + requireNotNull(rawSong.mediaStoreId) { "Invalid raw ${rawSong.path}: No id" } + .toSongCoverUri(), + uri, + it) + } + ?: Cover.External(requireNotNull(rawSong.albumMediaStoreId).toAlbumCoverUri()) /** * The [RawAlbum] instances collated by the [Song]. This can be used to group [Song]s into an diff --git a/app/src/main/java/org/oxycblt/auxio/music/fs/StorageUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/fs/StorageUtil.kt index da61d613b..6cdc7df67 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/fs/StorageUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/fs/StorageUtil.kt @@ -102,13 +102,15 @@ fun Long.toAudioUri() = * @return An external storage image [Uri]. May not exist. * @see ContentUris.withAppendedId */ -fun Long.toCoverUri(): Uri = +fun Long.toSongCoverUri(): Uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.buildUpon().run { - appendPath(this@toCoverUri.toString()) + appendPath(this@toSongCoverUri.toString()) appendPath("albumart") build() } +fun Long.toAlbumCoverUri(): Uri = ContentUris.withAppendedId(externalCoversUri, this) + // --- STORAGEMANAGER UTILITIES --- // Largely derived from Material Files: https://github.com/zhanghai/MaterialFiles diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/IndexerComponent.kt b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/music/service/IndexerComponent.kt rename to app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt index 401b49145..6362def6b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/IndexerComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt @@ -1,6 +1,6 @@ /* * Copyright (c) 2024 Auxio Project - * IndexerComponent.kt is part of Auxio. + * IndexerServiceFragment.kt is part of Auxio. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,7 +35,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.util.getSystemServiceCompat import org.oxycblt.auxio.util.logD -class IndexingServiceFragment +class IndexerServiceFragment @Inject constructor( @ApplicationContext override val workerContext: Context, diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt b/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt index 776fed706..2e92157b2 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt @@ -74,7 +74,7 @@ fun Song.toMediaItem(context: Context, parent: MusicParent?): MediaItem { .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC) .setIsPlayable(true) .setIsBrowsable(false) - .setArtworkUri(album.cover.single.mediaStoreUri) + .setArtworkUri(album.cover.single.mediaStoreCoverUri) .setExtras( Bundle().apply { putString("uid", mediaSessionUID.toString()) @@ -105,7 +105,7 @@ fun Album.toMediaItem(context: Context): MediaItem { .setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM) .setIsPlayable(true) .setIsBrowsable(true) - .setArtworkUri(cover.single.mediaStoreUri) + .setArtworkUri(cover.single.mediaStoreCoverUri) .setExtras(Bundle().apply { putString("uid", mediaSessionUID.toString()) }) .build() return MediaItem.Builder() @@ -136,7 +136,7 @@ fun Artist.toMediaItem(context: Context): MediaItem { .setIsPlayable(true) .setIsBrowsable(true) .setGenre(genres.resolveNames(context)) - .setArtworkUri(cover.single.mediaStoreUri) + .setArtworkUri(cover.single.mediaStoreCoverUri) .setExtras(Bundle().apply { putString("uid", mediaSessionUID.toString()) }) .build() return MediaItem.Builder() @@ -159,7 +159,7 @@ fun Genre.toMediaItem(context: Context): MediaItem { .setMediaType(MediaMetadata.MEDIA_TYPE_GENRE) .setIsPlayable(true) .setIsBrowsable(true) - .setArtworkUri(cover.single.mediaStoreUri) + .setArtworkUri(cover.single.mediaStoreCoverUri) .setExtras(Bundle().apply { putString("uid", mediaSessionUID.toString()) }) .build() return MediaItem.Builder() @@ -182,7 +182,7 @@ fun Playlist.toMediaItem(context: Context): MediaItem { .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .setIsPlayable(true) .setIsBrowsable(true) - .setArtworkUri(cover?.single?.mediaStoreUri) + .setArtworkUri(cover?.single?.mediaStoreCoverUri) .setExtras(Bundle().apply { putString("uid", mediaSessionUID.toString()) }) .build() return MediaItem.Builder() diff --git a/media b/media index 6c77cfa13..1d58171e1 160000 --- a/media +++ b/media @@ -1 +1 @@ -Subproject commit 6c77cfa13c83bf2ae5188603d2c9a51ec4cb3ac3 +Subproject commit 1d58171e16107d73ec3c842319663a8a06bfd23a