playback: handle media item playback

This commit is contained in:
Alexander Capehart 2024-04-09 15:17:24 -06:00
parent 64b9557793
commit 7503accada
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 418 additions and 135 deletions

View file

@ -32,15 +32,16 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.persist.PersistenceRepository
import org.oxycblt.auxio.playback.state.DeferredPlayback
import org.oxycblt.auxio.playback.state.PlaybackCommand
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.Progression
import org.oxycblt.auxio.playback.state.QueueChange
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.playback.state.ShuffleMode
import org.oxycblt.auxio.util.Event
import org.oxycblt.auxio.util.MutableEvent
import org.oxycblt.auxio.util.logD
@ -59,8 +60,8 @@ constructor(
private val playbackManager: PlaybackStateManager,
private val playbackSettings: PlaybackSettings,
private val persistenceRepository: PersistenceRepository,
private val commandFactory: PlaybackCommand.Factory,
private val listSettings: ListSettings,
private val musicRepository: MusicRepository,
) : ViewModel(), PlaybackStateManager.Listener, PlaybackSettings.Listener {
private var lastPositionJob: Job? = null
@ -189,21 +190,21 @@ constructor(
fun play(song: Song, with: PlaySong) {
logD("Playing $song with $with")
playWithImpl(song, with, isImplicitlyShuffled())
playWithImpl(song, with, ShuffleMode.IMPLICIT)
}
fun playExplicit(song: Song, with: PlaySong) {
playWithImpl(song, with, false)
playWithImpl(song, with, ShuffleMode.OFF)
}
fun shuffleExplicit(song: Song, with: PlaySong) {
playWithImpl(song, with, true)
playWithImpl(song, with, ShuffleMode.ON)
}
/** Shuffle all songs in the music library. */
fun shuffleAll() {
logD("Shuffling all songs")
playFromAllImpl(null, true)
playFromAllImpl(null, ShuffleMode.ON)
}
/**
@ -214,7 +215,7 @@ constructor(
* be prompted on what artist to play. Defaults to null.
*/
fun playFromArtist(song: Song, artist: Artist? = null) {
playFromArtistImpl(song, artist, isImplicitlyShuffled())
playFromArtistImpl(song, artist, ShuffleMode.IMPLICIT)
}
/**
@ -225,63 +226,66 @@ constructor(
* be prompted on what artist to play. Defaults to null.
*/
fun playFromGenre(song: Song, genre: Genre? = null) {
playFromGenreImpl(song, genre, isImplicitlyShuffled())
playFromGenreImpl(song, genre, ShuffleMode.IMPLICIT)
}
private fun isImplicitlyShuffled() = playbackManager.isShuffled && playbackSettings.keepShuffle
private fun playWithImpl(song: Song, with: PlaySong, shuffled: Boolean) {
private fun playWithImpl(song: Song, with: PlaySong, shuffle: ShuffleMode) {
when (with) {
is PlaySong.FromAll -> playFromAllImpl(song, shuffled)
is PlaySong.FromAlbum -> playFromAlbumImpl(song, shuffled)
is PlaySong.FromArtist -> playFromArtistImpl(song, with.which, shuffled)
is PlaySong.FromGenre -> playFromGenreImpl(song, with.which, shuffled)
is PlaySong.FromPlaylist -> playFromPlaylistImpl(song, with.which, shuffled)
is PlaySong.ByItself -> playItselfImpl(song, shuffled)
is PlaySong.FromAll -> playFromAllImpl(song, shuffle)
is PlaySong.FromAlbum -> playFromAlbumImpl(song, shuffle)
is PlaySong.FromArtist -> playFromArtistImpl(song, with.which, shuffle)
is PlaySong.FromGenre -> playFromGenreImpl(song, with.which, shuffle)
is PlaySong.FromPlaylist -> playFromPlaylistImpl(song, with.which, shuffle)
is PlaySong.ByItself -> playItselfImpl(song, shuffle)
}
}
private fun playFromAllImpl(song: Song?, shuffled: Boolean) {
playImpl(song, null, shuffled)
private fun playItselfImpl(song: Song, shuffle: ShuffleMode) {
playbackManager.play(
requireNotNull(commandFactory.song(song, shuffle)) {
"Invalid playback parameters [$song $shuffle]"
})
}
private fun playFromAlbumImpl(song: Song, shuffled: Boolean) {
playImpl(song, song.album, shuffled)
}
private fun playFromArtistImpl(song: Song, artist: Artist?, shuffled: Boolean) {
if (artist != null) {
logD("Playing $song from $artist")
playImpl(song, artist, shuffled)
} else if (song.artists.size == 1) {
logD("$song has one artist, playing from it")
playImpl(song, song.artists[0], shuffled)
private fun playFromAllImpl(song: Song?, shuffle: ShuffleMode) {
val params =
if (song != null) {
commandFactory.songFromAll(song, shuffle)
} else {
logD("$song has multiple artists, showing choice dialog")
commandFactory.all(shuffle)
}
playImpl(params)
}
private fun playFromAlbumImpl(song: Song, shuffle: ShuffleMode) {
logD("Playing $song from album")
playImpl(commandFactory.songFromAlbum(song, shuffle))
}
private fun playFromArtistImpl(song: Song, artist: Artist?, shuffle: ShuffleMode) {
val params = commandFactory.songFromArtist(song, artist, shuffle)
if (params != null) {
playbackManager.play(params)
}
logD(
"Cannot use given artist parameter for $song [$artist from ${song.artists}], showing choice dialog")
startPlaybackDecision(PlaybackDecision.PlayFromArtist(song))
}
private fun playFromGenreImpl(song: Song, genre: Genre?, shuffle: ShuffleMode) {
val params = commandFactory.songFromGenre(song, genre, shuffle)
if (params != null) {
playbackManager.play(params)
}
logD(
"Cannot use given genre parameter for $song [$genre from ${song.genres}], showing choice dialog")
startPlaybackDecision(PlaybackDecision.PlayFromArtist(song))
}
private fun playFromGenreImpl(song: Song, genre: Genre?, shuffled: Boolean) {
if (genre != null) {
logD("Playing $song from $genre")
playImpl(song, genre, shuffled)
} else if (song.genres.size == 1) {
logD("$song has one genre, playing from it")
playImpl(song, song.genres[0], shuffled)
} else {
logD("$song has multiple genres, showing choice dialog")
startPlaybackDecision(PlaybackDecision.PlayFromGenre(song))
}
}
private fun playFromPlaylistImpl(song: Song, playlist: Playlist, shuffled: Boolean) {
private fun playFromPlaylistImpl(song: Song, playlist: Playlist, shuffle: ShuffleMode) {
logD("Playing $song from $playlist")
playImpl(song, playlist, shuffled)
}
private fun playItselfImpl(song: Song, shuffled: Boolean) {
playImpl(song, listOf(song), shuffled)
playImpl(commandFactory.songFromPlaylist(song, playlist, shuffle))
}
private fun startPlaybackDecision(decision: PlaybackDecision) {
@ -300,7 +304,7 @@ constructor(
*/
fun play(album: Album) {
logD("Playing $album")
playImpl(null, album, false)
playImpl(commandFactory.album(album, ShuffleMode.OFF))
}
/**
@ -310,7 +314,7 @@ constructor(
*/
fun shuffle(album: Album) {
logD("Shuffling $album")
playImpl(null, album, true)
playImpl(commandFactory.album(album, ShuffleMode.ON))
}
/**
@ -320,7 +324,7 @@ constructor(
*/
fun play(artist: Artist) {
logD("Playing $artist")
playImpl(null, artist, false)
playImpl(commandFactory.artist(artist, ShuffleMode.OFF))
}
/**
@ -330,7 +334,7 @@ constructor(
*/
fun shuffle(artist: Artist) {
logD("Shuffling $artist")
playImpl(null, artist, true)
playImpl(commandFactory.artist(artist, ShuffleMode.ON))
}
/**
@ -340,7 +344,7 @@ constructor(
*/
fun play(genre: Genre) {
logD("Playing $genre")
playImpl(null, genre, false)
playImpl(commandFactory.genre(genre, ShuffleMode.OFF))
}
/**
@ -350,7 +354,7 @@ constructor(
*/
fun shuffle(genre: Genre) {
logD("Shuffling $genre")
playImpl(null, genre, true)
playImpl(commandFactory.genre(genre, ShuffleMode.ON))
}
/**
@ -360,7 +364,7 @@ constructor(
*/
fun play(playlist: Playlist) {
logD("Playing $playlist")
playImpl(null, playlist, false)
playImpl(commandFactory.playlist(playlist, ShuffleMode.OFF))
}
/**
@ -370,7 +374,7 @@ constructor(
*/
fun shuffle(playlist: Playlist) {
logD("Shuffling $playlist")
playImpl(null, playlist, true)
playImpl(commandFactory.playlist(playlist, ShuffleMode.ON))
}
/**
@ -380,7 +384,7 @@ constructor(
*/
fun play(songs: List<Song>) {
logD("Playing ${songs.size} songs")
playbackManager.play(null, null, songs, false)
playImpl(commandFactory.songs(songs, ShuffleMode.OFF))
}
/**
@ -390,28 +394,11 @@ constructor(
*/
fun shuffle(songs: List<Song>) {
logD("Shuffling ${songs.size} songs")
playbackManager.play(null, null, songs, true)
playImpl(commandFactory.songs(songs, ShuffleMode.ON))
}
private fun playImpl(song: Song?, queue: List<Song>, shuffled: Boolean) {
check(song == null || queue.contains(song)) { "Song to play not in queue" }
playbackManager.play(song, null, queue, shuffled)
}
private fun playImpl(song: Song?, parent: MusicParent?, shuffled: Boolean) {
check(song == null || parent == null || parent.songs.contains(song)) {
"Song to play not in parent"
}
val deviceLibrary = musicRepository.deviceLibrary ?: return
val queue =
when (parent) {
is Genre -> listSettings.genreSongSort.songs(parent.songs)
is Artist -> listSettings.artistSongSort.songs(parent.songs)
is Album -> listSettings.albumSongSort.songs(parent.songs)
is Playlist -> parent.songs
null -> listSettings.songSort.songs(deviceLibrary.songs)
}
playbackManager.play(song, parent, queue, shuffled)
private fun playImpl(command: PlaybackCommand?) {
playbackManager.play(requireNotNull(command) { "Invalid playback parameters" })
}
/**
@ -617,49 +604,6 @@ constructor(
}
_openPanel.put(panel)
}
// --- SAVE/RESTORE FUNCTIONS ---
/**
* Force-save the current playback state.
*
* @param onDone Called when the save is completed with true if successful, and false otherwise.
*/
fun savePlaybackState(onDone: (Boolean) -> Unit) {
logD("Saving playback state")
viewModelScope.launch {
onDone(persistenceRepository.saveState(playbackManager.toSavedState()))
}
}
/**
* Clear the current playback state.
*
* @param onDone Called when the wipe is completed with true if successful, and false otherwise.
*/
fun wipePlaybackState(onDone: (Boolean) -> Unit) {
logD("Wiping playback state")
viewModelScope.launch { onDone(persistenceRepository.saveState(null)) }
}
/**
* Force-restore the current playback state.
*
* @param onDone Called when the restoration is completed with true if successful, and false
* otherwise.
*/
fun tryRestorePlaybackState(onDone: (Boolean) -> Unit) {
logD("Force-restoring playback state")
viewModelScope.launch {
val savedState = persistenceRepository.readState()
if (savedState != null) {
playbackManager.applySavedState(savedState, true)
onDone(true)
return@launch
}
onDone(false)
}
}
}
/**

View file

@ -0,0 +1,186 @@
/*
* Copyright (c) 2024 Auxio Project
* PlaybackCommand.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.playback.state
import javax.inject.Inject
import org.oxycblt.auxio.list.ListSettings
import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackSettings
/**
* @param song A particular [Song] to play, or null to play the first [Song] in the new queue.
* @param queue The queue of [Song]s to play from.
* @param parent The [MusicParent] to play from, or null if to play from an non-specific collection
* of "All [Song]s".
* @param shuffled Whether to shuffle or not.
*/
interface PlaybackCommand {
val song: Song?
val parent: MusicParent?
val queue: List<Song>
val shuffled: Boolean
interface Factory {
fun song(song: Song, shuffle: ShuffleMode): PlaybackCommand?
fun songFromAll(song: Song, shuffle: ShuffleMode): PlaybackCommand?
fun songFromAlbum(song: Song, shuffle: ShuffleMode): PlaybackCommand?
fun songFromArtist(song: Song, artist: Artist?, shuffle: ShuffleMode): PlaybackCommand?
fun songFromGenre(song: Song, genre: Genre?, shuffle: ShuffleMode): PlaybackCommand?
fun songFromPlaylist(song: Song, playlist: Playlist, shuffle: ShuffleMode): PlaybackCommand?
fun all(shuffle: ShuffleMode): PlaybackCommand?
fun songs(songs: List<Song>, shuffle: ShuffleMode): PlaybackCommand?
fun album(album: Album, shuffle: ShuffleMode): PlaybackCommand?
fun artist(artist: Artist, shuffle: ShuffleMode): PlaybackCommand?
fun genre(genre: Genre, shuffle: ShuffleMode): PlaybackCommand?
fun playlist(playlist: Playlist, shuffle: ShuffleMode): PlaybackCommand?
}
}
enum class ShuffleMode {
ON,
OFF,
IMPLICIT
}
class PlaybackCommandFactoryImpl
@Inject
constructor(
val playbackManager: PlaybackStateManager,
val playbackSettings: PlaybackSettings,
val listSettings: ListSettings,
val musicRepository: MusicRepository
) : PlaybackCommand.Factory {
data class PlaybackCommandImpl(
override val song: Song?,
override val parent: MusicParent?,
override val queue: List<Song>,
override val shuffled: Boolean
) : PlaybackCommand
override fun song(song: Song, shuffle: ShuffleMode) = newCommand(song, shuffle)
override fun songFromAll(song: Song, shuffle: ShuffleMode) = newCommand(song, shuffle)
override fun songFromAlbum(song: Song, shuffle: ShuffleMode) =
newCommand(song, song.album, listSettings.albumSongSort, shuffle)
override fun songFromArtist(song: Song, artist: Artist?, shuffle: ShuffleMode) =
newCommand(song, artist, song.artists, listSettings.artistSongSort, shuffle)
override fun songFromGenre(song: Song, genre: Genre?, shuffle: ShuffleMode) =
newCommand(song, genre, song.genres, listSettings.genreSongSort, shuffle)
override fun songFromPlaylist(song: Song, playlist: Playlist, shuffle: ShuffleMode) =
newCommand(song, playlist, playlist.songs, listSettings.playlistSort, shuffle)
override fun all(shuffle: ShuffleMode) = newCommand(null, shuffle)
override fun songs(songs: List<Song>, shuffle: ShuffleMode) =
newCommand(null, null, songs, shuffle)
override fun album(album: Album, shuffle: ShuffleMode) =
newCommand(null, album, listSettings.albumSongSort, shuffle)
override fun artist(artist: Artist, shuffle: ShuffleMode) =
newCommand(null, artist, listSettings.artistSongSort, shuffle)
override fun genre(genre: Genre, shuffle: ShuffleMode) =
newCommand(null, genre, listSettings.genreSongSort, shuffle)
override fun playlist(playlist: Playlist, shuffle: ShuffleMode) =
newCommand(null, playlist, playlist.songs, shuffle)
private fun <T : MusicParent> newCommand(
song: Song,
parent: T?,
parents: List<T>,
sort: Sort,
shuffle: ShuffleMode
): PlaybackCommand? {
return if (parent != null) {
newCommand(song, parent, sort, shuffle)
} else if (song.genres.size == 1) {
newCommand(song, parents.first(), sort, shuffle)
} else {
null
}
}
private fun newCommand(song: Song?, shuffle: ShuffleMode): PlaybackCommand? {
val deviceLibrary = musicRepository.deviceLibrary ?: return null
return newCommand(song, null, deviceLibrary.songs, listSettings.songSort, shuffle)
}
private fun newCommand(
song: Song?,
parent: MusicParent,
sort: Sort,
shuffle: ShuffleMode
): PlaybackCommand? {
val songs = sort.songs(parent.songs)
return newCommand(song, parent, songs, sort, shuffle)
}
private fun newCommand(
song: Song?,
parent: MusicParent?,
queue: Collection<Song>,
sort: Sort,
shuffle: ShuffleMode
): PlaybackCommand? {
if (queue.isEmpty() || song !in queue) {
return null
}
return newCommand(song, parent, sort.songs(queue), shuffle)
}
private fun newCommand(
song: Song?,
parent: MusicParent?,
queue: List<Song>,
shuffle: ShuffleMode
): PlaybackCommand {
return PlaybackCommandImpl(song, parent, queue, isShuffled(shuffle))
}
private fun isShuffled(shuffle: ShuffleMode) =
when (shuffle) {
ShuffleMode.ON -> true
ShuffleMode.OFF -> false
ShuffleMode.IMPLICIT -> playbackSettings.keepShuffle && playbackManager.isShuffled
}
}

View file

@ -111,13 +111,9 @@ interface PlaybackStateManager {
/**
* Start new playback.
*
* @param song A particular [Song] to play, or null to play the first [Song] in the new queue.
* @param queue The queue of [Song]s to play from.
* @param parent The [MusicParent] to play from, or null if to play from an non-specific
* collection of "All [Song]s".
* @param shuffled Whether to shuffle or not.
* @param command The parameters to start playback with.
*/
fun play(song: Song?, parent: MusicParent?, queue: List<Song>, shuffled: Boolean)
fun play(command: PlaybackCommand)
/**
* Go to the next [Song] in the queue. Will go to the first [Song] in the queue if there is no
@ -441,9 +437,13 @@ class PlaybackStateManagerImpl @Inject constructor() : PlaybackStateManager {
// --- PLAYING FUNCTIONS ---
@Synchronized
override fun play(song: Song?, parent: MusicParent?, queue: List<Song>, shuffled: Boolean) {
override fun play(command: PlaybackCommand) {
play(command.song, command.parent, command.queue, command.shuffled)
}
private fun play(song: Song?, parent: MusicParent?, queue: List<Song>, shuffled: Boolean) {
val stateHolder = stateHolder ?: return
logD("Playing $song from $parent in ${queue.size}-song queue [shuffled=$shuffled]")
logD("Playing ${song} from $parent in ${queue.size}-song queue [shuffled=${shuffled}]")
// Played something, so we are initialized now
isInitialized = true
stateHolder.newPlayback(queue, song, parent, shuffled)
@ -476,7 +476,7 @@ class PlaybackStateManagerImpl @Inject constructor() : PlaybackStateManager {
override fun playNext(songs: List<Song>) {
if (currentSong == null) {
logD("Nothing playing, short-circuiting to new playback")
play(songs[0], null, songs, false)
play(null, null, songs, false)
} else {
val stateHolder = stateHolder ?: return
logD("Adding ${songs.size} songs to start of queue")
@ -488,7 +488,7 @@ class PlaybackStateManagerImpl @Inject constructor() : PlaybackStateManager {
override fun addToQueue(songs: List<Song>) {
if (currentSong == null) {
logD("Nothing playing, short-circuiting to new playback")
play(songs[0], null, songs, false)
play(null, null, songs, false)
} else {
val stateHolder = stateHolder ?: return
logD("Adding ${songs.size} songs to end of queue")

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2024 Auxio Project
* PlaybackStateModule.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.playback.state
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface PlaybackStateModule {
@Binds fun playbackCommandFactory(factory: PlaybackCommandFactoryImpl): PlaybackCommand.Factory
}

View file

@ -101,11 +101,13 @@ import org.oxycblt.auxio.playback.persist.PersistenceRepository
import org.oxycblt.auxio.playback.replaygain.ReplayGainAudioProcessor
import org.oxycblt.auxio.playback.service.BetterShuffleOrder
import org.oxycblt.auxio.playback.state.DeferredPlayback
import org.oxycblt.auxio.playback.state.PlaybackCommand
import org.oxycblt.auxio.playback.state.PlaybackStateHolder
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.Progression
import org.oxycblt.auxio.playback.state.RawQueue
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.playback.state.ShuffleMode
import org.oxycblt.auxio.playback.state.StateAck
import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.getSystemServiceCompat
@ -137,6 +139,7 @@ class AuxioService :
private var currentIndexJob: Job? = null
@Inject lateinit var playbackManager: PlaybackStateManager
@Inject lateinit var commandFactory: PlaybackCommand.Factory
@Inject lateinit var playbackSettings: PlaybackSettings
@Inject lateinit var persistenceRepository: PersistenceRepository
@Inject lateinit var mediaSourceFactory: MediaSource.Factory
@ -518,7 +521,7 @@ class AuxioService :
}
override fun addToQueue(songs: List<Song>, ack: StateAck.AddToQueue) {
player.addMediaItems(songs.map { it.toMediaItem(this, null) })
player.addToQueue(songs)
playbackManager.ack(this, ack)
deferSave()
}
@ -556,17 +559,18 @@ class AuxioService :
is DeferredPlayback.ShuffleAll -> {
logD("Shuffling all tracks")
playbackManager.play(
null, null, listSettings.songSort.songs(deviceLibrary.songs), true)
requireNotNull(commandFactory.all(ShuffleMode.ON)) {
"Invalid playback parameters"
})
}
// Open -> Try to find the Song for the given file and then play it from all songs
is DeferredPlayback.Open -> {
logD("Opening specified file")
deviceLibrary.findSongForUri(workerContext, action.uri)?.let { song ->
playbackManager.play(
song,
null,
listSettings.songSort.songs(deviceLibrary.songs),
player.shuffleModeEnabled && playbackSettings.keepShuffle)
requireNotNull(commandFactory.song(song, ShuffleMode.IMPLICIT)) {
"Invalid playback parameters"
})
}
}
}
@ -735,6 +739,15 @@ class AuxioService :
return Futures.immediateFuture(result)
}
override fun onGetItem(
session: MediaLibrarySession,
browser: MediaSession.ControllerInfo,
mediaId: String
): ListenableFuture<LibraryResult<MediaItem>> {
// TODO
return super.onGetItem(session, browser, mediaId)
}
override fun onGetChildren(
session: MediaLibrarySession,
browser: MediaSession.ControllerInfo,
@ -815,6 +828,101 @@ class AuxioService :
}
}
override fun onSetMediaItems(
mediaSession: MediaSession,
controller: MediaSession.ControllerInfo,
mediaItems: MutableList<MediaItem>,
startIndex: Int,
startPositionMs: Long
): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
val deviceLibrary =
musicRepository.deviceLibrary
?: return Futures.immediateFailedFuture(Exception("Invalid state"))
val result =
if (mediaItems.size > 1) {
playMediaItemSelection(mediaItems, startIndex, deviceLibrary)
} else {
playSingleMediaItem(mediaItems.first(), deviceLibrary)
}
return if (result) {
// This will not actually do anything to the player, I patched that out
Futures.immediateFuture(
MediaSession.MediaItemsWithStartPosition(listOf(), C.INDEX_UNSET, C.TIME_UNSET))
} else {
Futures.immediateFailedFuture(Exception("Invalid state"))
}
}
private fun playMediaItemSelection(
mediaItems: List<MediaItem>,
startIndex: Int,
deviceLibrary: DeviceLibrary
): Boolean {
val targetSong = mediaItems.getOrNull(startIndex)?.toSong(deviceLibrary)
val songs = mediaItems.mapNotNull { it.toSong(deviceLibrary) }
var index = startIndex
if (targetSong != null) {
while (songs.getOrNull(index)?.uid != targetSong.uid) {
index--
}
}
playbackManager.play(commandFactory.songs(songs, ShuffleMode.OFF) ?: return false)
return true
}
private fun playSingleMediaItem(mediaItem: MediaItem, deviceLibrary: DeviceLibrary): Boolean {
val uid = ExternalUID.fromString(mediaItem.mediaId) ?: return false
val music: Music
var parent: MusicParent? = null
when (uid) {
is ExternalUID.Single -> {
music = musicRepository.find(uid.uid) ?: return false
}
is ExternalUID.Joined -> {
music = musicRepository.find(uid.childUid) ?: return false
parent = musicRepository.find(uid.parentUid) as? MusicParent ?: return false
}
else -> return false
}
val command =
when (music) {
is Song -> inferSongFromParentCommand(music, parent)
is Album -> commandFactory.album(music, ShuffleMode.OFF)
is Artist -> commandFactory.artist(music, ShuffleMode.OFF)
is Genre -> commandFactory.genre(music, ShuffleMode.OFF)
is Playlist -> commandFactory.playlist(music, ShuffleMode.OFF)
}
playbackManager.play(command ?: return false)
return true
}
private fun inferSongFromParentCommand(music: Song, parent: MusicParent?) =
when (parent) {
is Album -> commandFactory.songFromAlbum(music, ShuffleMode.IMPLICIT)
is Artist -> commandFactory.songFromArtist(music, parent, ShuffleMode.IMPLICIT)
?: commandFactory.songFromArtist(music, music.artists[0], ShuffleMode.IMPLICIT)
is Genre -> commandFactory.songFromGenre(music, parent, ShuffleMode.IMPLICIT)
?: commandFactory.songFromGenre(music, music.genres[0], ShuffleMode.IMPLICIT)
is Playlist -> commandFactory.songFromPlaylist(music, parent, ShuffleMode.IMPLICIT)
null -> commandFactory.songFromAll(music, ShuffleMode.IMPLICIT)
}
override fun onAddMediaItems(
mediaSession: MediaSession,
controller: MediaSession.ControllerInfo,
mediaItems: MutableList<MediaItem>
): ListenableFuture<MutableList<MediaItem>> {
val deviceLibrary =
musicRepository.deviceLibrary ?: return Futures.immediateFuture(mutableListOf())
val songs = mediaItems.mapNotNull { it.toSong(deviceLibrary) }
playbackManager.addToQueue(songs)
// This will not actually do anything to the player, I patched that out
return Futures.immediateFuture(mutableListOf())
}
override fun onCustomCommand(
session: MediaSession,
controller: MediaSession.ControllerInfo,
@ -1146,6 +1254,10 @@ class NeoPlayer(
}
}
fun addToQueue(songs: List<Song>) {
addMediaItems(songs.map { it.toMediaItem(context, null) })
}
fun move(from: Int, to: Int) {
val indices = unscrambleQueueIndices()
if (indices.isEmpty()) {
@ -1423,6 +1535,17 @@ private fun MediaItem.toSong(deviceLibrary: DeviceLibrary): Song? {
}
}
private fun MediaItem.toParent(deviceLibrary: DeviceLibrary): MusicParent? {
val uid = ExternalUID.fromString(mediaId) ?: return null
return when (uid) {
is ExternalUID.Joined -> {
deviceLibrary.findArtist(uid.parentUid)
}
is ExternalUID.Single -> null
is ExternalUID.Category -> null
}
}
class NeoBitmapLoader
@Inject
constructor(