playback: add scaffold for new playback mode
Add the scaffold for PlaySong, a new version of playback modes that - Supports playback of a song by itself, requested by #424. - Will make direct playback from the song menu feasible (given additional reworks) - Prevents the invalid state of playing a song by it's playlist, as the sealed interface implementation of PlaySong requires a Playlist to be provided to it's respective variant.
This commit is contained in:
parent
7d42d016f1
commit
7495a59ab1
7 changed files with 157 additions and 105 deletions
|
@ -101,8 +101,6 @@ object IntegerTable {
|
||||||
const val SORT_BY_TRACK = 0xA117
|
const val SORT_BY_TRACK = 0xA117
|
||||||
/** Sort.Mode.ByDateAdded */
|
/** Sort.Mode.ByDateAdded */
|
||||||
const val SORT_BY_DATE_ADDED = 0xA118
|
const val SORT_BY_DATE_ADDED = 0xA118
|
||||||
/** Sort.Mode.None */
|
|
||||||
const val SORT_BY_NONE = 0xA11F
|
|
||||||
/** ReplayGainMode.Off (No longer used but still reserved) */
|
/** ReplayGainMode.Off (No longer used but still reserved) */
|
||||||
// const val REPLAY_GAIN_MODE_OFF = 0xA110
|
// const val REPLAY_GAIN_MODE_OFF = 0xA110
|
||||||
/** ReplayGainMode.Track */
|
/** ReplayGainMode.Track */
|
||||||
|
@ -123,4 +121,16 @@ object IntegerTable {
|
||||||
const val COVER_MODE_MEDIA_STORE = 0xA11D
|
const val COVER_MODE_MEDIA_STORE = 0xA11D
|
||||||
/** CoverMode.Quality */
|
/** CoverMode.Quality */
|
||||||
const val COVER_MODE_QUALITY = 0xA11E
|
const val COVER_MODE_QUALITY = 0xA11E
|
||||||
|
/** PlaySong.Itself */
|
||||||
|
const val PLAY_SONG_ITSELF = 0xA11F
|
||||||
|
/** PlaySong.FromAll */
|
||||||
|
const val PLAY_SONG_FROM_ALL = 0xA120
|
||||||
|
/** PlaySong.FromAlbum */
|
||||||
|
const val PLAY_SONG_FROM_ALBUM = 0xA121
|
||||||
|
/** PlaySong.FromArtist */
|
||||||
|
const val PLAY_SONG_FROM_ARTIST = 0xA122
|
||||||
|
/** PlaySong.FromGenre */
|
||||||
|
const val PLAY_SONG_FROM_GENRE = 0xA123
|
||||||
|
/** PlaySong.FromPlaylist */
|
||||||
|
const val PLAY_SONG_FROM_PLAYLIST = 0xA124
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,8 +180,12 @@ class AlbumDetailFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRealClick(item: Song) {
|
override fun onRealClick(item: Song) {
|
||||||
// There can only be one album, so a null mode and an ALBUMS mode will function the same.
|
val mode = detailModel.playbackMode
|
||||||
playbackModel.playFrom(item, detailModel.playbackMode ?: MusicMode.ALBUMS)
|
if (mode != null) {
|
||||||
|
playbackModel.play(item, detailModel.playbackMode ?: MusicMode.ALBUMS)
|
||||||
|
} else {
|
||||||
|
playbackModel.playFromAlbum(item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenMenu(item: Song, anchor: View) {
|
override fun onOpenMenu(item: Song, anchor: View) {
|
||||||
|
|
|
@ -182,7 +182,7 @@ class ArtistDetailFragment :
|
||||||
is Song -> {
|
is Song -> {
|
||||||
val playbackMode = detailModel.playbackMode
|
val playbackMode = detailModel.playbackMode
|
||||||
if (playbackMode != null) {
|
if (playbackMode != null) {
|
||||||
playbackModel.playFrom(item, playbackMode)
|
playbackModel.play(item, playbackMode)
|
||||||
} else {
|
} else {
|
||||||
// When configured to play from the selected item, we already have an Artist
|
// When configured to play from the selected item, we already have an Artist
|
||||||
// to play from.
|
// to play from.
|
||||||
|
|
|
@ -180,7 +180,7 @@ class GenreDetailFragment :
|
||||||
is Song -> {
|
is Song -> {
|
||||||
val playbackMode = detailModel.playbackMode
|
val playbackMode = detailModel.playbackMode
|
||||||
if (playbackMode != null) {
|
if (playbackMode != null) {
|
||||||
playbackModel.playFrom(item, playbackMode)
|
playbackModel.play(item, playbackMode)
|
||||||
} else {
|
} else {
|
||||||
// When configured to play from the selected item, we already have an Genre
|
// When configured to play from the selected item, we already have an Genre
|
||||||
// to play from.
|
// to play from.
|
||||||
|
|
|
@ -137,7 +137,7 @@ class SongListFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRealClick(item: Song) {
|
override fun onRealClick(item: Song) {
|
||||||
playbackModel.playFrom(item, homeModel.playbackMode)
|
playbackModel.play(item, homeModel.playbackMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenMenu(item: Song, anchor: View) {
|
override fun onOpenMenu(item: Song, anchor: View) {
|
||||||
|
|
|
@ -180,32 +180,31 @@ constructor(
|
||||||
|
|
||||||
// --- PLAYING FUNCTIONS ---
|
// --- PLAYING FUNCTIONS ---
|
||||||
|
|
||||||
|
fun play(song: Song, playbackMode: MusicMode) {
|
||||||
|
play(song, PlaySong.fromPlaybackModeTemporary(playbackMode))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun play(song: Song, with: PlaySong) {
|
||||||
|
logD("Playing $song with $with")
|
||||||
|
playWithImpl(song, with, isImplicitlyShuffled())
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun playExplicit(song: Song, with: PlaySong) {
|
||||||
|
// playWithImpl(song, with, false)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun shuffleExplicit(song: Song, with: PlaySong) {
|
||||||
|
// playWithImpl(song, with, true)
|
||||||
|
// }
|
||||||
|
|
||||||
/** Shuffle all songs in the music library. */
|
/** Shuffle all songs in the music library. */
|
||||||
fun shuffleAll() {
|
fun shuffleAll() {
|
||||||
logD("Shuffling all songs")
|
logD("Shuffling all songs")
|
||||||
playImpl(null, null, true)
|
playFromAllImpl(null, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun playFromAlbum(song: Song) {
|
||||||
* Play a [Song] from the [MusicParent] outlined by the given [MusicMode].
|
playFromAlbumImpl(song, isImplicitlyShuffled())
|
||||||
* - If [MusicMode.SONGS], the [Song] is played from all songs.
|
|
||||||
* - If [MusicMode.ALBUMS], the [Song] is played from it's [Album].
|
|
||||||
* - If [MusicMode.ARTISTS], the [Song] is played from one of it's [Artist]s.
|
|
||||||
* - If [MusicMode.GENRES], the [Song] is played from one of it's [Genre]s.
|
|
||||||
* [MusicMode.PLAYLISTS] is disallowed here.
|
|
||||||
*
|
|
||||||
* @param song The [Song] to play.
|
|
||||||
* @param playbackMode The [MusicMode] to play from.
|
|
||||||
*/
|
|
||||||
fun playFrom(song: Song, playbackMode: MusicMode) {
|
|
||||||
logD("Playing $song from $playbackMode")
|
|
||||||
when (playbackMode) {
|
|
||||||
MusicMode.SONGS -> playImpl(song, null)
|
|
||||||
MusicMode.ALBUMS -> playImpl(song, song.album)
|
|
||||||
MusicMode.ARTISTS -> playFromArtist(song)
|
|
||||||
MusicMode.GENRES -> playFromGenre(song)
|
|
||||||
MusicMode.PLAYLISTS -> error("Playing from a playlist is not supported.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -216,16 +215,7 @@ constructor(
|
||||||
* be prompted on what artist to play. Defaults to null.
|
* be prompted on what artist to play. Defaults to null.
|
||||||
*/
|
*/
|
||||||
fun playFromArtist(song: Song, artist: Artist? = null) {
|
fun playFromArtist(song: Song, artist: Artist? = null) {
|
||||||
if (artist != null) {
|
playFromArtistImpl(song, artist, isImplicitlyShuffled())
|
||||||
logD("Playing $song from $artist")
|
|
||||||
playImpl(song, artist)
|
|
||||||
} else if (song.artists.size == 1) {
|
|
||||||
logD("$song has one artist, playing from it")
|
|
||||||
playImpl(song, song.artists[0])
|
|
||||||
} else {
|
|
||||||
logD("$song has multiple artists, showing choice dialog")
|
|
||||||
startPlaybackDecisionImpl(PlaybackDecision.PlayFromArtist(song))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -236,19 +226,77 @@ constructor(
|
||||||
* be prompted on what artist to play. Defaults to null.
|
* be prompted on what artist to play. Defaults to null.
|
||||||
*/
|
*/
|
||||||
fun playFromGenre(song: Song, genre: Genre? = null) {
|
fun playFromGenre(song: Song, genre: Genre? = null) {
|
||||||
if (genre != null) {
|
playFromGenreImpl(song, genre, isImplicitlyShuffled())
|
||||||
logD("Playing $song from $genre")
|
}
|
||||||
playImpl(song, genre)
|
|
||||||
} else if (song.genres.size == 1) {
|
/**
|
||||||
logD("$song has one genre, playing from it")
|
* Play a [Song] from one of it's [Playlist]s.
|
||||||
playImpl(song, song.genres[0])
|
*
|
||||||
} else {
|
* @param song The [Song] to play.
|
||||||
logD("$song has multiple genres, showing choice dialog")
|
* @param playlist The [Playlist] to play from. Must be linked to the [Song].
|
||||||
startPlaybackDecisionImpl(PlaybackDecision.PlayFromGenre(song))
|
*/
|
||||||
|
fun playFromPlaylist(song: Song, playlist: Playlist) {
|
||||||
|
playFromPlaylistImpl(song, playlist, isImplicitlyShuffled())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isImplicitlyShuffled() =
|
||||||
|
playbackManager.queue.isShuffled && playbackSettings.keepShuffle
|
||||||
|
|
||||||
|
private fun playWithImpl(song: Song, with: PlaySong, shuffled: Boolean) {
|
||||||
|
when (with) {
|
||||||
|
is PlaySong.ByItself -> playItselfImpl(song, shuffled)
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startPlaybackDecisionImpl(decision: PlaybackDecision) {
|
private fun playItselfImpl(song: Song, shuffled: Boolean) {
|
||||||
|
playImpl(song, listOf(song), shuffled)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playFromAllImpl(song: Song?, shuffled: Boolean) {
|
||||||
|
playImpl(song, null, shuffled)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
logD("$song has multiple artists, 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) {
|
||||||
|
logD("Playing $song from $playlist")
|
||||||
|
playImpl(song, playlist, shuffled)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startPlaybackDecision(decision: PlaybackDecision) {
|
||||||
val existing = _playbackDecision.flow.value
|
val existing = _playbackDecision.flow.value
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
logD("Already handling decision $existing, ignoring $decision")
|
logD("Already handling decision $existing, ignoring $decision")
|
||||||
|
@ -257,17 +305,6 @@ constructor(
|
||||||
_playbackDecision.put(decision)
|
_playbackDecision.put(decision)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PLay a [Song] from one of it's [Playlist]s.
|
|
||||||
*
|
|
||||||
* @param song The [Song] to play.
|
|
||||||
* @param playlist The [Playlist] to play from. Must be linked to the [Song].
|
|
||||||
*/
|
|
||||||
fun playFromPlaylist(song: Song, playlist: Playlist) {
|
|
||||||
logD("Playing $song from $playlist")
|
|
||||||
playImpl(song, playlist)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play an [Album].
|
* Play an [Album].
|
||||||
*
|
*
|
||||||
|
@ -278,46 +315,6 @@ constructor(
|
||||||
playImpl(null, album, false)
|
playImpl(null, album, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Play an [Artist].
|
|
||||||
*
|
|
||||||
* @param artist The [Artist] to play.
|
|
||||||
*/
|
|
||||||
fun play(artist: Artist) {
|
|
||||||
logD("Playing $artist")
|
|
||||||
playImpl(null, artist, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Play a [Genre].
|
|
||||||
*
|
|
||||||
* @param genre The [Genre] to play.
|
|
||||||
*/
|
|
||||||
fun play(genre: Genre) {
|
|
||||||
logD("Playing $genre")
|
|
||||||
playImpl(null, genre, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Play a [Playlist].
|
|
||||||
*
|
|
||||||
* @param playlist The [Playlist] to play.
|
|
||||||
*/
|
|
||||||
fun play(playlist: Playlist) {
|
|
||||||
logD("Playing $playlist")
|
|
||||||
playImpl(null, playlist, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Play a list of [Song]s.
|
|
||||||
*
|
|
||||||
* @param songs The [Song]s to play.
|
|
||||||
*/
|
|
||||||
fun play(songs: List<Song>) {
|
|
||||||
logD("Playing ${songs.size} songs")
|
|
||||||
playbackManager.play(null, null, songs, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuffle an [Album].
|
* Shuffle an [Album].
|
||||||
*
|
*
|
||||||
|
@ -328,6 +325,16 @@ constructor(
|
||||||
playImpl(null, album, true)
|
playImpl(null, album, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play an [Artist].
|
||||||
|
*
|
||||||
|
* @param artist The [Artist] to play.
|
||||||
|
*/
|
||||||
|
fun play(artist: Artist) {
|
||||||
|
logD("Playing $artist")
|
||||||
|
playImpl(null, artist, false)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuffle an [Artist].
|
* Shuffle an [Artist].
|
||||||
*
|
*
|
||||||
|
@ -338,6 +345,16 @@ constructor(
|
||||||
playImpl(null, artist, true)
|
playImpl(null, artist, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a [Genre].
|
||||||
|
*
|
||||||
|
* @param genre The [Genre] to play.
|
||||||
|
*/
|
||||||
|
fun play(genre: Genre) {
|
||||||
|
logD("Playing $genre")
|
||||||
|
playImpl(null, genre, false)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuffle a [Genre].
|
* Shuffle a [Genre].
|
||||||
*
|
*
|
||||||
|
@ -348,6 +365,16 @@ constructor(
|
||||||
playImpl(null, genre, true)
|
playImpl(null, genre, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a [Playlist].
|
||||||
|
*
|
||||||
|
* @param playlist The [Playlist] to play.
|
||||||
|
*/
|
||||||
|
fun play(playlist: Playlist) {
|
||||||
|
logD("Playing $playlist")
|
||||||
|
playImpl(null, playlist, false)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuffle a [Playlist].
|
* Shuffle a [Playlist].
|
||||||
*
|
*
|
||||||
|
@ -358,6 +385,16 @@ constructor(
|
||||||
playImpl(null, playlist, true)
|
playImpl(null, playlist, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a list of [Song]s.
|
||||||
|
*
|
||||||
|
* @param songs The [Song]s to play.
|
||||||
|
*/
|
||||||
|
fun play(songs: List<Song>) {
|
||||||
|
logD("Playing ${songs.size} songs")
|
||||||
|
playbackManager.play(null, null, songs, false)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuffle a list of [Song]s.
|
* Shuffle a list of [Song]s.
|
||||||
*
|
*
|
||||||
|
@ -368,11 +405,12 @@ constructor(
|
||||||
playbackManager.play(null, null, songs, true)
|
playbackManager.play(null, null, songs, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playImpl(
|
private fun playImpl(song: Song?, queue: List<Song>, shuffled: Boolean) {
|
||||||
song: Song?,
|
check(song == null || queue.contains(song)) { "Song to play not in queue" }
|
||||||
parent: MusicParent?,
|
playbackManager.play(song, null, queue, shuffled)
|
||||||
shuffled: Boolean = playbackManager.queue.isShuffled && playbackSettings.keepShuffle
|
}
|
||||||
) {
|
|
||||||
|
private fun playImpl(song: Song?, parent: MusicParent?, shuffled: Boolean) {
|
||||||
check(song == null || parent == null || parent.songs.contains(song)) {
|
check(song == null || parent == null || parent.songs.contains(song)) {
|
||||||
"Song to play not in parent"
|
"Song to play not in parent"
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,7 +174,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
||||||
|
|
||||||
override fun onRealClick(item: Music) {
|
override fun onRealClick(item: Music) {
|
||||||
when (item) {
|
when (item) {
|
||||||
is Song -> playbackModel.playFrom(item, searchModel.playbackMode)
|
is Song -> playbackModel.play(item, searchModel.playbackMode)
|
||||||
is Album -> detailModel.showAlbum(item)
|
is Album -> detailModel.showAlbum(item)
|
||||||
is Artist -> detailModel.showArtist(item)
|
is Artist -> detailModel.showArtist(item)
|
||||||
is Genre -> detailModel.showGenre(item)
|
is Genre -> detailModel.showGenre(item)
|
||||||
|
|
Loading…
Reference in a new issue