musikr.cover: refactor cover
Instead of using a weird sealed class, instead go for a Cover/CoverCollection system instead that removes some implicit design dependence in musikr.
This commit is contained in:
parent
a24d102a00
commit
7768d98632
15 changed files with 205 additions and 76 deletions
|
@ -64,7 +64,7 @@ import org.oxycblt.musikr.Artist
|
|||
import org.oxycblt.musikr.Genre
|
||||
import org.oxycblt.musikr.Playlist
|
||||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
import org.oxycblt.musikr.cover.CoverCollection
|
||||
|
||||
/**
|
||||
* Auxio's extension of [ImageView] that enables cover art loading and playing indicator and
|
||||
|
@ -327,7 +327,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
*/
|
||||
fun bind(album: Album) =
|
||||
bindImpl(
|
||||
album.cover,
|
||||
album.covers,
|
||||
context.getString(R.string.desc_album_cover, album.name),
|
||||
R.drawable.ic_album_24)
|
||||
|
||||
|
@ -338,7 +338,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
*/
|
||||
fun bind(artist: Artist) =
|
||||
bindImpl(
|
||||
artist.cover,
|
||||
artist.covers,
|
||||
context.getString(R.string.desc_artist_image, artist.name),
|
||||
R.drawable.ic_artist_24)
|
||||
|
||||
|
@ -349,7 +349,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
*/
|
||||
fun bind(genre: Genre) =
|
||||
bindImpl(
|
||||
genre.cover,
|
||||
genre.covers,
|
||||
context.getString(R.string.desc_genre_image, genre.name),
|
||||
R.drawable.ic_genre_24)
|
||||
|
||||
|
@ -360,7 +360,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
*/
|
||||
fun bind(playlist: Playlist) =
|
||||
bindImpl(
|
||||
playlist.cover,
|
||||
playlist.covers,
|
||||
context.getString(R.string.desc_playlist_image, playlist.name),
|
||||
R.drawable.ic_playlist_24)
|
||||
|
||||
|
@ -372,9 +372,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
* @param errorRes The resource of the error drawable to use if the cover cannot be loaded.
|
||||
*/
|
||||
fun bind(songs: List<Song>, desc: String, @DrawableRes errorRes: Int) =
|
||||
bindImpl(Cover.multi(songs), desc, errorRes)
|
||||
bindImpl(CoverCollection.from(songs.mapNotNull { it.cover }), desc, errorRes)
|
||||
|
||||
private fun bindImpl(cover: Cover?, desc: String, @DrawableRes errorRes: Int) {
|
||||
private fun bindImpl(cover: Any?, desc: String, @DrawableRes errorRes: Int) {
|
||||
val request =
|
||||
ImageRequest.Builder(context)
|
||||
.data(cover)
|
||||
|
|
|
@ -36,14 +36,17 @@ class CoilModule {
|
|||
@Provides
|
||||
fun imageLoader(
|
||||
@ApplicationContext context: Context,
|
||||
keyer: CoverKeyer,
|
||||
factory: CoverFetcher.Factory
|
||||
coverKeyer: CoverKeyer,
|
||||
coverFactory: CoverFetcher.Factory,
|
||||
coverCollectionKeyer: CoverCollectionKeyer,
|
||||
coverCollectionFactory: CoverCollectionFetcher.Factory
|
||||
) =
|
||||
ImageLoader.Builder(context)
|
||||
.components {
|
||||
// Add fetchers for Music components to make them usable with ImageRequest
|
||||
add(keyer)
|
||||
add(factory)
|
||||
add(coverKeyer)
|
||||
add(coverFactory)
|
||||
add(coverCollectionKeyer)
|
||||
add(coverCollectionFactory)
|
||||
}
|
||||
// Use our own crossfade with error drawable support
|
||||
.transitionFactory(ErrorCrossfadeTransitionFactory())
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Auxio Project
|
||||
* Components.kt is part of Auxio.
|
||||
* Copyright (c) 2024 Auxio Project
|
||||
* CoverCollectionFetcher.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
|
||||
|
@ -31,7 +31,6 @@ import coil3.fetch.FetchResult
|
|||
import coil3.fetch.Fetcher
|
||||
import coil3.fetch.ImageFetchResult
|
||||
import coil3.fetch.SourceFetchResult
|
||||
import coil3.key.Keyer
|
||||
import coil3.request.Options
|
||||
import coil3.size.Dimension
|
||||
import coil3.size.Size
|
||||
|
@ -39,36 +38,24 @@ import coil3.size.pxOrElse
|
|||
import java.io.InputStream
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.withContext
|
||||
import okio.FileSystem
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
import org.oxycblt.musikr.cover.CoverCollection
|
||||
|
||||
class CoverKeyer @Inject constructor() : Keyer<Cover> {
|
||||
override fun key(data: Cover, options: Options) = "${data.id}&${options.size}"
|
||||
}
|
||||
|
||||
class CoverFetcher
|
||||
class CoverCollectionFetcher
|
||||
private constructor(
|
||||
private val context: Context,
|
||||
private val cover: Cover,
|
||||
private val covers: CoverCollection,
|
||||
private val size: Size,
|
||||
) : Fetcher {
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
val streams =
|
||||
when (val cover = cover) {
|
||||
is Cover.Single -> listOfNotNull(cover.open())
|
||||
is Cover.Multi ->
|
||||
buildList {
|
||||
for (single in cover.all) {
|
||||
single.open()?.let { add(it) }
|
||||
if (size == 4) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val streams = covers.covers.asFlow().mapNotNull { it.open() }.take(4).toList()
|
||||
// We don't immediately check for mosaic feasibility from album count alone, as that
|
||||
// does not factor in InputStreams failing to load. Instead, only check once we
|
||||
// definitely have image data to use.
|
||||
|
@ -79,6 +66,7 @@ private constructor(
|
|||
withContext(Dispatchers.IO) { streams.forEach(InputStream::close) }
|
||||
}
|
||||
}
|
||||
|
||||
// Not enough covers for a mosaic, take the first one (if that even exists)
|
||||
val first = streams.firstOrNull() ?: return null
|
||||
|
||||
|
@ -146,8 +134,8 @@ private constructor(
|
|||
return if (size.mod(2) > 0) size + 1 else size
|
||||
}
|
||||
|
||||
class Factory @Inject constructor() : Fetcher.Factory<Cover> {
|
||||
override fun create(data: Cover, options: Options, imageLoader: ImageLoader) =
|
||||
CoverFetcher(options.context, data, options.size)
|
||||
class Factory @Inject constructor() : Fetcher.Factory<CoverCollection> {
|
||||
override fun create(data: CoverCollection, options: Options, imageLoader: ImageLoader) =
|
||||
CoverCollectionFetcher(options.context, data, options.size)
|
||||
}
|
||||
}
|
110
app/src/main/java/org/oxycblt/auxio/image/coil/CoverFetcher.kt
Normal file
110
app/src/main/java/org/oxycblt/auxio/image/coil/CoverFetcher.kt
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Auxio Project
|
||||
* CoverFetcher.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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.image.coil
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import coil3.ImageLoader
|
||||
import coil3.asImage
|
||||
import coil3.decode.DataSource
|
||||
import coil3.decode.ImageSource
|
||||
import coil3.fetch.FetchResult
|
||||
import coil3.fetch.Fetcher
|
||||
import coil3.fetch.ImageFetchResult
|
||||
import coil3.fetch.SourceFetchResult
|
||||
import coil3.request.Options
|
||||
import coil3.size.Dimension
|
||||
import coil3.size.Size
|
||||
import coil3.size.pxOrElse
|
||||
import java.io.InputStream
|
||||
import javax.inject.Inject
|
||||
import okio.FileSystem
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
|
||||
class CoverFetcher private constructor(private val context: Context, private val cover: Cover) :
|
||||
Fetcher {
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
val stream = cover.open() ?: return null
|
||||
return SourceFetchResult(
|
||||
source = ImageSource(stream.source().buffer(), FileSystem.SYSTEM, null),
|
||||
mimeType = null,
|
||||
dataSource = DataSource.DISK)
|
||||
}
|
||||
|
||||
/** Derived from phonograph: https://github.com/kabouzeid/Phonograph */
|
||||
private suspend fun createMosaic(streams: List<InputStream>, size: Size): FetchResult {
|
||||
// Use whatever size coil gives us to create the mosaic.
|
||||
val mosaicSize = android.util.Size(size.width.mosaicSize(), size.height.mosaicSize())
|
||||
val mosaicFrameSize =
|
||||
Size(Dimension(mosaicSize.width / 2), Dimension(mosaicSize.height / 2))
|
||||
|
||||
val mosaicBitmap =
|
||||
Bitmap.createBitmap(mosaicSize.width, mosaicSize.height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(mosaicBitmap)
|
||||
|
||||
var x = 0
|
||||
var y = 0
|
||||
|
||||
// For each stream, create a bitmap scaled to 1/4th of the mosaics combined size
|
||||
// and place it on a corner of the canvas.
|
||||
for (stream in streams) {
|
||||
if (y == mosaicSize.height) {
|
||||
break
|
||||
}
|
||||
|
||||
// Crop the bitmap down to a square so it leaves no empty space
|
||||
// TODO: Work around this
|
||||
val bitmap =
|
||||
SquareCropTransformation.INSTANCE.transform(
|
||||
BitmapFactory.decodeStream(stream), mosaicFrameSize)
|
||||
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
|
||||
|
||||
x += bitmap.width
|
||||
if (x == mosaicSize.width) {
|
||||
x = 0
|
||||
y += bitmap.height
|
||||
}
|
||||
}
|
||||
|
||||
// It's way easier to map this into a drawable then try to serialize it into an
|
||||
// BufferedSource. Just make sure we mark it as "sampled" so Coil doesn't try to
|
||||
// load low-res mosaics into high-res ImageViews.
|
||||
return ImageFetchResult(
|
||||
image = mosaicBitmap.toDrawable(context.resources).asImage(),
|
||||
isSampled = true,
|
||||
dataSource = DataSource.DISK)
|
||||
}
|
||||
|
||||
private fun Dimension.mosaicSize(): Int {
|
||||
// Since we want the mosaic to be perfectly divisible into two, we need to round any
|
||||
// odd image sizes upwards to prevent the mosaic creation from failing.
|
||||
val size = pxOrElse { 512 }
|
||||
return if (size.mod(2) > 0) size + 1 else size
|
||||
}
|
||||
|
||||
class Factory @Inject constructor() : Fetcher.Factory<Cover> {
|
||||
override fun create(data: Cover, options: Options, imageLoader: ImageLoader) =
|
||||
CoverFetcher(options.context, data)
|
||||
}
|
||||
}
|
34
app/src/main/java/org/oxycblt/auxio/image/coil/Keyers.kt
Normal file
34
app/src/main/java/org/oxycblt/auxio/image/coil/Keyers.kt
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Auxio Project
|
||||
* Keyers.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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.image.coil
|
||||
|
||||
import coil3.key.Keyer
|
||||
import coil3.request.Options
|
||||
import javax.inject.Inject
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
import org.oxycblt.musikr.cover.CoverCollection
|
||||
|
||||
class CoverKeyer @Inject constructor() : Keyer<Cover> {
|
||||
override fun key(data: Cover, options: Options) = "${data.id}&${options.size}"
|
||||
}
|
||||
|
||||
class CoverCollectionKeyer @Inject constructor() : Keyer<CoverCollection> {
|
||||
override fun key(data: CoverCollection, options: Options) =
|
||||
"multi:${data.hashCode()}&${options.size}"
|
||||
}
|
|
@ -70,7 +70,7 @@ class MutableRevisionedStoredCovers(context: Context, private val revision: UUID
|
|||
}
|
||||
}
|
||||
|
||||
class RevisionedCover(private val revision: UUID, val inner: Cover.Single) : Cover.Single by inner {
|
||||
class RevisionedCover(private val revision: UUID, val inner: Cover) : Cover by inner {
|
||||
override val id: String
|
||||
get() = "${inner.id}@${revision}"
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.UUID
|
|||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
import org.oxycblt.musikr.cover.CoverCollection
|
||||
import org.oxycblt.musikr.fs.Format
|
||||
import org.oxycblt.musikr.fs.Path
|
||||
import org.oxycblt.musikr.tag.Date
|
||||
|
@ -276,7 +277,7 @@ interface Song : Music {
|
|||
/** The date the audio file was added to the device, as a unix epoch timestamp. */
|
||||
val dateAdded: Long
|
||||
/** Useful information to quickly obtain the album cover. */
|
||||
val cover: Cover.Single?
|
||||
val cover: Cover?
|
||||
/**
|
||||
* The parent [Album]. If the metadata did not specify an album, it's parent directory is used
|
||||
* instead.
|
||||
|
@ -310,7 +311,7 @@ interface Album : MusicParent {
|
|||
*/
|
||||
val releaseType: ReleaseType
|
||||
/** Cover information from album's songs. */
|
||||
val cover: Cover
|
||||
val covers: CoverCollection
|
||||
/** The duration of all songs in the album, in milliseconds. */
|
||||
val durationMs: Long
|
||||
/** The earliest date a song in this album was added, as a unix epoch timestamp. */
|
||||
|
@ -340,7 +341,7 @@ interface Artist : MusicParent {
|
|||
*/
|
||||
val durationMs: Long?
|
||||
/** Useful information to quickly obtain a (single) cover for a Genre. */
|
||||
val cover: Cover
|
||||
val covers: CoverCollection
|
||||
/** The [Genre]s of this artist. */
|
||||
val genres: List<Genre>
|
||||
}
|
||||
|
@ -356,7 +357,7 @@ interface Genre : MusicParent {
|
|||
/** The total duration of the songs in this genre, in milliseconds. */
|
||||
val durationMs: Long
|
||||
/** Useful information to quickly obtain a (single) cover for a Genre. */
|
||||
val cover: Cover
|
||||
val covers: CoverCollection
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -370,5 +371,5 @@ interface Playlist : MusicParent {
|
|||
/** The total duration of the songs in this genre, in milliseconds. */
|
||||
val durationMs: Long
|
||||
/** Useful information to quickly obtain a (single) cover for a Genre. */
|
||||
val cover: Cover
|
||||
val covers: CoverCollection
|
||||
}
|
||||
|
|
|
@ -19,29 +19,22 @@
|
|||
package org.oxycblt.musikr.cover
|
||||
|
||||
import java.io.InputStream
|
||||
import org.oxycblt.musikr.Song
|
||||
|
||||
sealed interface Cover {
|
||||
interface Cover {
|
||||
val id: String
|
||||
|
||||
interface Single : Cover {
|
||||
suspend fun open(): InputStream?
|
||||
}
|
||||
|
||||
class Multi(val all: List<Single>) : Cover {
|
||||
override val id = "multi@${all.hashCode()}"
|
||||
}
|
||||
suspend fun open(): InputStream?
|
||||
}
|
||||
|
||||
class CoverCollection private constructor(val covers: List<Cover>) {
|
||||
companion object {
|
||||
fun multi(songs: Collection<Song>) = order(songs).run { Multi(this) }
|
||||
|
||||
private fun order(songs: Collection<Song>) =
|
||||
songs
|
||||
.mapNotNull { it.cover }
|
||||
.groupBy { it.id }
|
||||
.entries
|
||||
.sortedByDescending { it.key }
|
||||
.sortedByDescending { it.value.size }
|
||||
.map { it.value.first() }
|
||||
fun from(covers: Collection<Cover>) =
|
||||
CoverCollection(
|
||||
covers
|
||||
.groupBy { it.id }
|
||||
.entries
|
||||
.sortedByDescending { it.key }
|
||||
.sortedByDescending { it.value.size }
|
||||
.map { it.value.first() })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ package org.oxycblt.musikr.cover
|
|||
import android.content.Context
|
||||
|
||||
interface StoredCovers {
|
||||
suspend fun obtain(id: String): Cover.Single?
|
||||
suspend fun obtain(id: String): Cover?
|
||||
|
||||
companion object {
|
||||
fun from(context: Context, path: String, format: CoverFormat): MutableStoredCovers =
|
||||
|
@ -30,7 +30,7 @@ interface StoredCovers {
|
|||
}
|
||||
|
||||
interface MutableStoredCovers : StoredCovers {
|
||||
suspend fun write(data: ByteArray): Cover.Single?
|
||||
suspend fun write(data: ByteArray): Cover?
|
||||
}
|
||||
|
||||
private class FileStoredCovers(
|
||||
|
@ -45,6 +45,6 @@ private class FileStoredCovers(
|
|||
}
|
||||
}
|
||||
|
||||
private class FileCover(override val id: String, private val coverFile: CoverFile) : Cover.Single {
|
||||
private class FileCover(override val id: String, private val coverFile: CoverFile) : Cover {
|
||||
override suspend fun open() = coverFile.open()
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import org.oxycblt.musikr.Album
|
|||
import org.oxycblt.musikr.Artist
|
||||
import org.oxycblt.musikr.Music
|
||||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
import org.oxycblt.musikr.cover.CoverCollection
|
||||
import org.oxycblt.musikr.tag.Date
|
||||
import org.oxycblt.musikr.tag.interpret.PreAlbum
|
||||
import org.oxycblt.musikr.util.update
|
||||
|
@ -56,7 +56,7 @@ internal class AlbumImpl(private val core: AlbumCore) : Album {
|
|||
override val releaseType = preAlbum.releaseType
|
||||
override val durationMs = core.songs.sumOf { it.durationMs }
|
||||
override val dateAdded = core.songs.minOf { it.dateAdded }
|
||||
override val cover = Cover.multi(core.songs)
|
||||
override val covers = CoverCollection.from(core.songs.mapNotNull { it.cover })
|
||||
override val dates: Date.Range? =
|
||||
core.songs.mapNotNull { it.date }.ifEmpty { null }?.run { Date.Range(min(), max()) }
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import org.oxycblt.musikr.Artist
|
|||
import org.oxycblt.musikr.Genre
|
||||
import org.oxycblt.musikr.Music
|
||||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
import org.oxycblt.musikr.cover.CoverCollection
|
||||
import org.oxycblt.musikr.tag.interpret.PreArtist
|
||||
import org.oxycblt.musikr.util.update
|
||||
|
||||
|
@ -55,7 +55,7 @@ internal class ArtistImpl(private val core: ArtistCore) : Artist {
|
|||
get() = core.resolveGenres().toList()
|
||||
|
||||
override val durationMs = core.songs.sumOf { it.durationMs }
|
||||
override val cover = Cover.multi(core.songs)
|
||||
override val covers = CoverCollection.from(core.songs.mapNotNull { it.cover })
|
||||
|
||||
private val hashCode =
|
||||
31 * (31 * uid.hashCode() + core.preArtist.hashCode()) * core.songs.hashCode()
|
||||
|
|
|
@ -22,7 +22,7 @@ import org.oxycblt.musikr.Artist
|
|||
import org.oxycblt.musikr.Genre
|
||||
import org.oxycblt.musikr.Music
|
||||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
import org.oxycblt.musikr.cover.CoverCollection
|
||||
import org.oxycblt.musikr.tag.interpret.PreGenre
|
||||
import org.oxycblt.musikr.util.update
|
||||
|
||||
|
@ -44,7 +44,7 @@ internal class GenreImpl(private val core: GenreCore) : Genre {
|
|||
override val songs = core.songs
|
||||
override val artists = core.artists
|
||||
override val durationMs = core.songs.sumOf { it.durationMs }
|
||||
override val cover = Cover.multi(core.songs)
|
||||
override val covers = CoverCollection.from(core.songs.mapNotNull { it.cover })
|
||||
|
||||
private val hashCode = 31 * (31 * uid.hashCode() + core.preGenre.hashCode()) + songs.hashCode()
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ package org.oxycblt.musikr.model
|
|||
|
||||
import org.oxycblt.musikr.Playlist
|
||||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
import org.oxycblt.musikr.cover.CoverCollection
|
||||
import org.oxycblt.musikr.playlist.interpret.PrePlaylistInfo
|
||||
import org.oxycblt.musikr.tag.Name
|
||||
|
||||
|
@ -33,7 +33,7 @@ internal class PlaylistImpl(val core: PlaylistCore) : Playlist {
|
|||
override val uid = core.prePlaylist.handle.uid
|
||||
override val name: Name.Known = core.prePlaylist.name
|
||||
override val durationMs = core.songs.sumOf { it.durationMs }
|
||||
override val cover = Cover.multi(core.songs)
|
||||
override val covers = CoverCollection.from(core.songs.mapNotNull { it.cover })
|
||||
override val songs = core.songs
|
||||
|
||||
private var hashCode =
|
||||
|
|
|
@ -163,7 +163,7 @@ internal data class RawSong(
|
|||
val file: DeviceFile,
|
||||
val properties: Properties,
|
||||
val tags: ParsedTags,
|
||||
val cover: Cover.Single?
|
||||
val cover: Cover?
|
||||
)
|
||||
|
||||
internal sealed interface ExtractedMusic {
|
||||
|
|
|
@ -48,7 +48,7 @@ internal data class PreSong(
|
|||
val replayGainAdjustment: ReplayGainAdjustment,
|
||||
val lastModified: Long,
|
||||
val dateAdded: Long,
|
||||
val cover: Cover.Single?,
|
||||
val cover: Cover?,
|
||||
val preAlbum: PreAlbum,
|
||||
val preArtists: List<PreArtist>,
|
||||
val preGenres: List<PreGenre>
|
||||
|
|
Loading…
Reference in a new issue