all: cleanup

This commit is contained in:
Alexander Capehart 2024-08-27 16:48:56 -06:00
parent 924e3d1801
commit f0dda6c43e
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
9 changed files with 134 additions and 242 deletions

View file

@ -36,7 +36,8 @@ import org.oxycblt.auxio.music.service.MusicServiceFragment
import org.oxycblt.auxio.playback.service.PlaybackServiceFragment
@AndroidEntryPoint
class AuxioService : MediaBrowserServiceCompat(), ForegroundListener, MusicServiceFragment.Invalidator {
class AuxioService :
MediaBrowserServiceCompat(), ForegroundListener, MusicServiceFragment.Invalidator {
@Inject lateinit var playbackFragment: PlaybackServiceFragment
@Inject lateinit var musicFragment: MusicServiceFragment
@ -88,10 +89,8 @@ class AuxioService : MediaBrowserServiceCompat(), ForegroundListener, MusicServi
musicFragment.getItem(itemId, result)
}
override fun onLoadChildren(
parentId: String,
result: Result<MutableList<MediaItem>>
) = throw NotImplementedError()
override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaItem>>) =
throw NotImplementedError()
override fun onLoadChildren(
parentId: String,
@ -99,12 +98,7 @@ class AuxioService : MediaBrowserServiceCompat(), ForegroundListener, MusicServi
options: Bundle
) = musicFragment.getChildren(parentId, result)
override fun onSearch(
query: String,
extras: Bundle?,
result: Result<MutableList<MediaItem>>
) {
override fun onSearch(query: String, extras: Bundle?, result: Result<MutableList<MediaItem>>) {
musicFragment.search(query, result)
}

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024 Auxio Project
* IndexerServiceFragment.kt is part of Auxio.
* Indexer.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
@ -28,7 +28,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.ForegroundListener
import org.oxycblt.auxio.ForegroundServiceNotification
import org.oxycblt.auxio.music.IndexingState
import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicSettings

View file

@ -78,16 +78,17 @@ sealed interface MediaSessionUID {
}
return when (parts[0]) {
ID_CATEGORY ->
CategoryItem(when (parts[1]) {
Category.ROOT.id -> Category.ROOT
Category.MORE.id -> Category.MORE
Category.SONGS.id -> Category.SONGS
Category.ALBUMS.id -> Category.ALBUMS
Category.ARTISTS.id -> Category.ARTISTS
Category.GENRES.id -> Category.GENRES
Category.PLAYLISTS.id -> Category.PLAYLISTS
else -> return null
})
CategoryItem(
when (parts[1]) {
Category.ROOT.id -> Category.ROOT
Category.MORE.id -> Category.MORE
Category.SONGS.id -> Category.SONGS
Category.ALBUMS.id -> Category.ALBUMS
Category.ARTISTS.id -> Category.ARTISTS
Category.GENRES.id -> Category.GENRES
Category.PLAYLISTS.id -> Category.PLAYLISTS
else -> return null
})
ID_ITEM -> {
val uids = parts[1].split(">", limit = 2)
if (uids.size == 1) {
@ -107,7 +108,8 @@ sealed interface MediaSessionUID {
typealias Sugar = Bundle.(Context) -> Unit
fun header(@StringRes nameRes: Int): Sugar = {
putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, it.getString(nameRes))
putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, it.getString(nameRes))
}
fun Category.toMediaItem(context: Context): MediaItem {
@ -119,10 +121,11 @@ fun Category.toMediaItem(context: Context): MediaItem {
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM)
}
val mediaSessionUID = MediaSessionUID.CategoryItem(this)
val description = MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(context.getString(nameRes))
.setExtras(extras)
val description =
MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(context.getString(nameRes))
.setExtras(extras)
if (bitmapRes != null) {
val bitmap = BitmapFactory.decodeResource(context.resources, bitmapRes)
description.setIconBitmap(bitmap)
@ -130,7 +133,11 @@ fun Category.toMediaItem(context: Context): MediaItem {
return MediaItem(description.build(), MediaItem.FLAG_BROWSABLE)
}
fun Song.toMediaItem(context: Context, parent: MusicParent? = null, vararg sugar: Sugar): MediaItem {
fun Song.toMediaItem(
context: Context,
parent: MusicParent? = null,
vararg sugar: Sugar
): MediaItem {
val mediaSessionUID =
if (parent == null) {
MediaSessionUID.SingleItem(uid)
@ -138,88 +145,97 @@ fun Song.toMediaItem(context: Context, parent: MusicParent? = null, vararg sugar
MediaSessionUID.ChildItem(parent.uid, uid)
}
val extras = Bundle().apply { sugar.forEach { this.it(context) } }
val description = MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(name.resolve(context))
.setSubtitle(artists.resolveNames(context))
.setDescription(album.name.resolve(context))
.setIconUri(album.cover.single.mediaStoreCoverUri)
.setMediaUri(uri)
.setExtras(extras)
.build()
val description =
MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(name.resolve(context))
.setSubtitle(artists.resolveNames(context))
.setDescription(album.name.resolve(context))
.setIconUri(album.cover.single.mediaStoreCoverUri)
.setMediaUri(uri)
.setExtras(extras)
.build()
return MediaItem(description, MediaItem.FLAG_PLAYABLE)
}
fun Album.toMediaItem(context: Context, parent: MusicParent? = null, vararg sugar: Sugar): MediaItem {
fun Album.toMediaItem(
context: Context,
parent: MusicParent? = null,
vararg sugar: Sugar
): MediaItem {
val mediaSessionUID =
if (parent == null) {
MediaSessionUID.SingleItem(uid)
} else {
MediaSessionUID.ChildItem(parent.uid, uid)
}
val description = MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(name.resolve(context))
.setSubtitle(artists.resolveNames(context))
.setIconUri(cover.single.mediaStoreCoverUri)
.build()
val description =
MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(name.resolve(context))
.setSubtitle(artists.resolveNames(context))
.setIconUri(cover.single.mediaStoreCoverUri)
.build()
return MediaItem(description, MediaItem.FLAG_BROWSABLE)
}
fun Artist.toMediaItem(context: Context, vararg sugar: Sugar): MediaItem {
val mediaSessionUID = MediaSessionUID.SingleItem(uid)
val counts =
context.getString(
R.string.fmt_two,
if (explicitAlbums.isNotEmpty()) {
context.getPlural(R.plurals.fmt_album_count, explicitAlbums.size)
} else {
context.getString(R.string.def_album_count)
},
if (songs.isNotEmpty()) {
context.getPlural(R.plurals.fmt_song_count, songs.size)
} else {
context.getString(R.string.def_song_count)
})
val description = MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(name.resolve(context))
.setSubtitle(counts)
.setIconUri(cover.single.mediaStoreCoverUri)
.build()
context.getString(
R.string.fmt_two,
if (explicitAlbums.isNotEmpty()) {
context.getPlural(R.plurals.fmt_album_count, explicitAlbums.size)
} else {
context.getString(R.string.def_album_count)
},
if (songs.isNotEmpty()) {
context.getPlural(R.plurals.fmt_song_count, songs.size)
} else {
context.getString(R.string.def_song_count)
})
val description =
MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(name.resolve(context))
.setSubtitle(counts)
.setIconUri(cover.single.mediaStoreCoverUri)
.build()
return MediaItem(description, MediaItem.FLAG_BROWSABLE)
}
fun Genre.toMediaItem(context: Context, vararg sugar: Sugar): MediaItem {
val mediaSessionUID = MediaSessionUID.SingleItem(uid)
val counts =
if (songs.isNotEmpty()) {
context.getPlural(R.plurals.fmt_song_count, songs.size)
} else {
context.getString(R.string.def_song_count)
}
val description = MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(name.resolve(context))
.setSubtitle(counts)
.setIconUri(cover.single.mediaStoreCoverUri)
.build()
if (songs.isNotEmpty()) {
context.getPlural(R.plurals.fmt_song_count, songs.size)
} else {
context.getString(R.string.def_song_count)
}
val description =
MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(name.resolve(context))
.setSubtitle(counts)
.setIconUri(cover.single.mediaStoreCoverUri)
.build()
return MediaItem(description, MediaItem.FLAG_BROWSABLE)
}
fun Playlist.toMediaItem(context: Context, vararg sugar: Sugar): MediaItem {
val mediaSessionUID = MediaSessionUID.SingleItem(uid)
val counts =
if (songs.isNotEmpty()) {
context.getPlural(R.plurals.fmt_song_count, songs.size)
} else {
context.getString(R.string.def_song_count)
}
val description = MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(name.resolve(context))
.setSubtitle(counts)
.setIconUri(cover?.single?.mediaStoreCoverUri)
.build()
if (songs.isNotEmpty()) {
context.getPlural(R.plurals.fmt_song_count, songs.size)
} else {
context.getString(R.string.def_song_count)
}
val description =
MediaDescriptionCompat.Builder()
.setMediaId(mediaSessionUID.toString())
.setTitle(name.resolve(context))
.setSubtitle(counts)
.setIconUri(cover?.single?.mediaStoreCoverUri)
.build()
return MediaItem(description, MediaItem.FLAG_BROWSABLE)
}

View file

@ -19,16 +19,9 @@
package org.oxycblt.auxio.music.service
import android.content.Context
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat.MediaItem
import androidx.annotation.StringRes
import androidx.media.utils.MediaConstants
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import javax.inject.Inject
import org.oxycblt.auxio.R
import org.oxycblt.auxio.list.ListSettings
import org.oxycblt.auxio.list.sort.Sort
@ -41,9 +34,6 @@ import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.device.DeviceLibrary
import org.oxycblt.auxio.music.user.UserLibrary
import org.oxycblt.auxio.search.SearchEngine
import javax.inject.Inject
import kotlin.math.min
class MusicBrowser
@Inject
@ -112,10 +102,8 @@ constructor(
is MediaSessionUID.CategoryItem -> return uid.category.toMediaItem(context)
is MediaSessionUID.SingleItem ->
musicRepository.find(uid.uid)?.let { musicRepository.find(it.uid) }
is MediaSessionUID.ChildItem ->
musicRepository.find(uid.childUid)?.let { musicRepository.find(it.uid) }
null -> null
}
?: return null
@ -147,44 +135,33 @@ constructor(
return when (val mediaSessionUID = MediaSessionUID.fromString(id)) {
is MediaSessionUID.CategoryItem -> {
when (mediaSessionUID.category) {
Category.ROOT ->
Category.IMPORTANT.map { it.toMediaItem(context) }
Category.ROOT -> Category.IMPORTANT.map { it.toMediaItem(context) }
Category.MORE -> TODO()
Category.SONGS ->
listSettings.songSort.songs(deviceLibrary.songs).map {
it.toMediaItem(context, null)
}
Category.ALBUMS ->
listSettings.albumSort.albums(deviceLibrary.albums).map {
it.toMediaItem(context)
}
Category.ARTISTS ->
listSettings.artistSort.artists(deviceLibrary.artists).map {
it.toMediaItem(context)
}
Category.GENRES ->
listSettings.genreSort.genres(deviceLibrary.genres).map {
it.toMediaItem(context)
}
Category.PLAYLISTS ->
userLibrary.playlists.map { it.toMediaItem(context) }
Category.PLAYLISTS -> userLibrary.playlists.map { it.toMediaItem(context) }
}
}
is MediaSessionUID.SingleItem -> {
getChildMediaItems(mediaSessionUID.uid)
}
is MediaSessionUID.ChildItem -> {
getChildMediaItems(mediaSessionUID.childUid)
}
null -> {
return null
}
@ -195,118 +172,28 @@ constructor(
return when (val item = musicRepository.find(uid)) {
is Album -> {
val songs = listSettings.albumSongSort.songs(item.songs)
songs.map { it.toMediaItem(context, item, header(R.string.lbl_songs))}
songs.map { it.toMediaItem(context, item, header(R.string.lbl_songs)) }
}
is Artist -> {
val albums = ARTIST_ALBUMS_SORT.albums(item.explicitAlbums + item.implicitAlbums)
val songs = listSettings.artistSongSort.songs(item.songs)
albums.map { it.toMediaItem(context, null, header(R.string.lbl_songs)) } +
songs.map { it.toMediaItem(context, item, header(R.string.lbl_songs)) }
songs.map { it.toMediaItem(context, item, header(R.string.lbl_songs)) }
}
is Genre -> {
val artists = GENRE_ARTISTS_SORT.artists(item.artists)
val songs = listSettings.genreSongSort.songs(item.songs)
artists.map { it.toMediaItem(context, header(R.string.lbl_songs)) } +
songs.map { it.toMediaItem(context, null, header(R.string.lbl_songs)) }
songs.map { it.toMediaItem(context, null, header(R.string.lbl_songs)) }
}
is Playlist -> {
item.songs.map { it.toMediaItem(context, null, header(R.string.lbl_songs)) }
}
is Song,
null -> return null
}
}
// suspend fun prepareSearch(query: String, controller: String) {
// searchSubscribers[controller] = query
// val existing = searchResults[query]
// if (existing == null) {
// val new = searchTo(query)
// searchResults[query] = new
// new.await()
// } else {
// val items = existing.await()
// invalidator?.invalidate(controller, query, items.count())
// }
// }
//
// suspend fun getSearchResult(
// query: String,
// page: Int,
// pageSize: Int,
// ): List<MediaItem>? {
// val deferred = searchResults[query] ?: searchTo(query).also { searchResults[query] = it }
// return deferred.await().concat().paginate(page, pageSize)
// }
//
// private fun SearchEngine.Items.concat(): MutableList<MediaItem> {
// val music = mutableListOf<MediaItem>()
// if (songs != null) {
// music.addAll(songs.map { it.toMediaItem(context, null) })
// }
// if (albums != null) {
// music.addAll(albums.map { it.toMediaItem(context) })
// }
// if (artists != null) {
// music.addAll(artists.map { it.toMediaItem(context) })
// }
// if (genres != null) {
// music.addAll(genres.map { it.toMediaItem(context) })
// }
// if (playlists != null) {
// music.addAll(playlists.map { it.toMediaItem(context) })
// }
// return music
// }
//
// private fun SearchEngine.Items.count(): Int {
// var count = 0
// if (songs != null) {
// count += songs.size
// }
// if (albums != null) {
// count += albums.size
// }
// if (artists != null) {
// count += artists.size
// }
// if (genres != null) {
// count += genres.size
// }
// if (playlists != null) {
// count += playlists.size
// }
// return count
// }
//
// private fun searchTo(query: String) =
// searchScope.async {
// if (query.isEmpty()) {
// return@async SearchEngine.Items()
// }
// val deviceLibrary = musicRepository.deviceLibrary ?: return@async SearchEngine.Items()
// val userLibrary = musicRepository.userLibrary ?: return@async SearchEngine.Items()
// val items =
// SearchEngine.Items(
// deviceLibrary.songs,
// deviceLibrary.albums,
// deviceLibrary.artists,
// deviceLibrary.genres,
// userLibrary.playlists
// )
// val results = searchEngine.search(items, query)
// for (entry in searchSubscribers.entries) {
// if (entry.value == query) {
// invalidator?.invalidate(entry.key, query, results.count())
// }
// }
// results
// }
private companion object {
// TODO: Rely on detail item gen logic?
val ARTIST_ALBUMS_SORT = Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING)

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024 Auxio Project
* IndexerServiceFragment.kt is part of Auxio.
* MusicServiceFragment.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
@ -23,6 +23,7 @@ import android.support.v4.media.MediaBrowserCompat.MediaItem
import androidx.media.MediaBrowserServiceCompat
import androidx.media.MediaBrowserServiceCompat.BrowserRoot
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -35,7 +36,6 @@ import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.search.SearchEngine
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
import javax.inject.Inject
class MusicServiceFragment
@Inject
@ -76,7 +76,6 @@ constructor(
invalidator = null
}
override fun invalidateMusic(ids: Set<String>) {
ids.forEach { mediaId ->
requireNotNull(invalidator) { "Invalidator not available" }.invalidateMusic(mediaId)
@ -131,8 +130,7 @@ constructor(
fun getChildren(
mediaId: String,
result: MediaBrowserServiceCompat.Result<MutableList<MediaItem>>
) =
result.dispatch { musicBrowser.getChildren(mediaId)?.toMutableList() }
) = result.dispatch { musicBrowser.getChildren(mediaId)?.toMutableList() }
fun search(query: String, result: MediaBrowserServiceCompat.Result<MutableList<MediaItem>>) =
result.dispatchAsync {
@ -148,12 +146,10 @@ constructor(
deviceLibrary.albums,
deviceLibrary.artists,
deviceLibrary.genres,
userLibrary.playlists
)
userLibrary.playlists)
searchEngine.search(items, query).concat()
}
private fun SearchEngine.Items.concat(): MutableList<MediaItem> {
val music = mutableListOf<MediaItem>()
if (songs != null) {

View file

@ -46,7 +46,6 @@ import org.oxycblt.auxio.image.ImageSettings
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.service.toMediaItem
import org.oxycblt.auxio.playback.PlaybackSettings
import org.oxycblt.auxio.playback.persist.PersistenceRepository
import org.oxycblt.auxio.playback.replaygain.ReplayGainAudioProcessor
@ -141,10 +140,7 @@ class ExoPlaybackStateHolder(
} else {
emptyList()
}
return RawQueue(
heap.mapNotNull { it.song },
shuffledMapping,
player.currentMediaItemIndex)
return RawQueue(heap.mapNotNull { it.song }, shuffledMapping, player.currentMediaItemIndex)
}
override fun handleDeferred(action: DeferredPlayback): Boolean {
@ -533,12 +529,10 @@ class ExoPlaybackStateHolder(
currentSaveJob = saveScope.launch { block() }
}
private fun Song.buildMediaItem() = MediaItem.Builder()
.setUri(uri)
.setTag(this)
.build()
private fun Song.buildMediaItem() = MediaItem.Builder().setUri(uri).setTag(this).build()
private val MediaItem.song: Song? get() = this.localConfiguration?.tag as? Song?
private val MediaItem.song: Song?
get() = this.localConfiguration?.tag as? Song?
private fun Player.unscrambleQueueIndices(): List<Int> {
val timeline = currentTimeline

View file

@ -88,14 +88,20 @@ private constructor(
constructor(
private val playbackManager: PlaybackStateManager,
private val playbackSettings: PlaybackSettings,
private val commandFactory: PlaybackCommand.Factory,
private val commandFactory: PlaybackCommand.Factory,
private val musicRepository: MusicRepository,
private val bitmapProvider: BitmapProvider,
private val imageSettings: ImageSettings
) {
fun create(context: Context) =
MediaSessionHolder(
context, playbackManager, playbackSettings, commandFactory, musicRepository, bitmapProvider, imageSettings)
context,
playbackManager,
playbackSettings,
commandFactory,
musicRepository,
bitmapProvider,
imageSettings)
}
private val mediaSession =
@ -234,11 +240,13 @@ private constructor(
super.onAddQueueItem(description)
val deviceLibrary = musicRepository.deviceLibrary ?: return
val uid = MediaSessionUID.fromString(description.mediaId ?: return) ?: return
val song = when (uid) {
is MediaSessionUID.SingleItem -> deviceLibrary.findSong(uid.uid)
is MediaSessionUID.ChildItem -> deviceLibrary.findSong(uid.childUid)
else -> null
} ?: return
val song =
when (uid) {
is MediaSessionUID.SingleItem -> deviceLibrary.findSong(uid.uid)
is MediaSessionUID.ChildItem -> deviceLibrary.findSong(uid.childUid)
else -> null
}
?: return
playbackManager.addToQueue(song)
}
@ -246,11 +254,13 @@ private constructor(
super.onRemoveQueueItem(description)
val deviceLibrary = musicRepository.deviceLibrary ?: return
val uid = MediaSessionUID.fromString(description.mediaId ?: return) ?: return
val song = when (uid) {
is MediaSessionUID.SingleItem -> deviceLibrary.findSong(uid.uid)
is MediaSessionUID.ChildItem -> deviceLibrary.findSong(uid.childUid)
else -> null
} ?: return
val song =
when (uid) {
is MediaSessionUID.SingleItem -> deviceLibrary.findSong(uid.uid)
is MediaSessionUID.ChildItem -> deviceLibrary.findSong(uid.childUid)
else -> null
}
?: return
val queueIndex = playbackManager.queue.indexOf(song)
if (queueIndex > -1) {
playbackManager.removeQueueItem(queueIndex)

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024 Auxio Project
* MediaSessionServiceFragment.kt is part of Auxio.
* PlaybackServiceFragment.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
@ -109,8 +109,6 @@ constructor(
foregroundListener?.updateForeground(ForegroundListener.Change.MEDIA_SESSION)
}
// override fun onGetLibraryRoot(
// session: MediaLibrarySession,
// browser: MediaSession.ControllerInfo,

View file

@ -61,9 +61,7 @@ interface SearchEngine {
val artists: Collection<Artist>? = null,
val genres: Collection<Genre>? = null,
val playlists: Collection<Playlist>? = null
) {
}
) {}
}
class SearchEngineImpl @Inject constructor(@ApplicationContext private val context: Context) :