playback: add playlist support

Add playlist support to the playback code.
This commit is contained in:
Alexander Capehart 2023-03-23 16:07:56 -06:00
parent 52e0620149
commit f388e492aa
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 66 additions and 30 deletions

View file

@ -3,17 +3,16 @@
## dev ## dev
#### What's New #### What's New
- Added support for `COMPILATION` and `ITUNESCOMPILATION` flags. - Added support for `COMPILATION` and `ITUNESCOMPILATION` flags
#### What's Improved #### What's Improved
- Accept `REPLAYGAIN_*` adjustment information on OPUS files alongside - Accept `REPLAYGAIN_*` adjustment information on OPUS files alongside
`R128_*` adjustments `R128_*` adjustments
- List updates are now consistent across the app - List updates are now consistent across the app
- Fixed jarring header update in detail view - Fixed jarring header update in detail view
- Search view now trims search queries - Searching now ignores punctuation and trailing whitespace
- Audio effect (equalizer) session is now broadcast when playing/pausing - Audio effect (equalizer) session is now broadcast when playing/pausing
rather than on start/stop rather than on start/stop
- Searching now ignores punctuation
- Numeric names are now logically sorted (i.e 7 before 15) - Numeric names are now logically sorted (i.e 7 before 15)
#### What's Fixed #### What's Fixed

View file

@ -26,7 +26,6 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
@ -114,16 +113,15 @@ class PlaylistDetailFragment :
return true return true
} }
// TODO: Handle
val currentPlaylist = unlikelyToBeNull(detailModel.currentPlaylist.value) val currentPlaylist = unlikelyToBeNull(detailModel.currentPlaylist.value)
return when (item.itemId) { return when (item.itemId) {
R.id.action_play_next -> { R.id.action_play_next -> {
// playbackModel.playNext(currentPlaylist) playbackModel.playNext(currentPlaylist)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
true true
} }
R.id.action_queue_add -> { R.id.action_queue_add -> {
// playbackModel.addToQueue(currentPlaylist) playbackModel.addToQueue(currentPlaylist)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
true true
} }
@ -131,12 +129,8 @@ class PlaylistDetailFragment :
} }
} }
override fun onClick(item: Song, viewHolder: RecyclerView.ViewHolder) {
// TODO: Handle
}
override fun onRealClick(item: Song) { override fun onRealClick(item: Song) {
// TODO: Handle playbackModel.playFromPlaylist(item, unlikelyToBeNull(detailModel.currentPlaylist.value))
} }
override fun onOpenMenu(item: Song, anchor: View) { override fun onOpenMenu(item: Song, anchor: View) {
@ -145,11 +139,11 @@ class PlaylistDetailFragment :
override fun onPlay() { override fun onPlay() {
// TODO: Handle // TODO: Handle
// playbackModel.play(unlikelyToBeNull(detailModel.currentPlaylist.value)) playbackModel.play(unlikelyToBeNull(detailModel.currentPlaylist.value))
} }
override fun onShuffle() { override fun onShuffle() {
// playbackModel.shuffle(unlikelyToBeNull(detailModel.currentPlaylist.value)) playbackModel.shuffle(unlikelyToBeNull(detailModel.currentPlaylist.value))
} }
override fun onOpenSortMenu(anchor: View) { override fun onOpenSortMenu(anchor: View) {

View file

@ -58,7 +58,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
*/ */
abstract fun onRealClick(item: T) abstract fun onRealClick(item: T)
override fun onClick(item: T, viewHolder: RecyclerView.ViewHolder) { final override fun onClick(item: T, viewHolder: RecyclerView.ViewHolder) {
if (selectionModel.selected.value.isNotEmpty()) { if (selectionModel.selected.value.isNotEmpty()) {
// Map clicking an item to selecting an item when items are already selected. // Map clicking an item to selecting an item when items are already selected.
selectionModel.select(item) selectionModel.select(item)
@ -68,7 +68,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
} }
} }
override fun onSelect(item: T) { final override fun onSelect(item: T) {
selectionModel.select(item) selectionModel.select(item)
} }
@ -222,26 +222,26 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
* *
* @param anchor The [View] to anchor the menu to. * @param anchor The [View] to anchor the menu to.
* @param menuRes The resource of the menu to load. * @param menuRes The resource of the menu to load.
* @param genre The [Playlist] to create the menu for. * @param playlist The [Playlist] to create the menu for.
*/ */
protected fun openMusicMenu(anchor: View, @MenuRes menuRes: Int, genre: Playlist) { protected fun openMusicMenu(anchor: View, @MenuRes menuRes: Int, playlist: Playlist) {
logD("Launching new genre menu: ${genre.rawName}") logD("Launching new playlist menu: ${playlist.rawName}")
openMusicMenuImpl(anchor, menuRes) { openMusicMenuImpl(anchor, menuRes) {
when (it.itemId) { when (it.itemId) {
R.id.action_play -> { R.id.action_play -> {
// playbackModel.play(genre) playbackModel.play(playlist)
} }
R.id.action_shuffle -> { R.id.action_shuffle -> {
// playbackModel.shuffle(genre) playbackModel.shuffle(playlist)
} }
R.id.action_play_next -> { R.id.action_play_next -> {
// playbackModel.playNext(genre) playbackModel.playNext(playlist)
// requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_queue_add -> { R.id.action_queue_add -> {
// playbackModel.addToQueue(genre) playbackModel.addToQueue(playlist)
// requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
else -> { else -> {
error("Unexpected menu item selected") error("Unexpected menu item selected")

View file

@ -168,6 +168,7 @@ constructor(
* - If [MusicMode.ALBUMS], the [Song] is played from it's [Album]. * - 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.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. * - 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 song The [Song] to play.
* @param playbackMode The [MusicMode] to play from. * @param playbackMode The [MusicMode] to play from.
@ -200,7 +201,7 @@ constructor(
} }
/** /**
* PLay a [Song] from one of it's [Genre]s. * Play a [Song] from one of it's [Genre]s.
* *
* @param song The [Song] to play. * @param song The [Song] to play.
* @param genre The [Genre] to play from. Must be linked to the [Song]. If null, the user will * @param genre The [Genre] to play from. Must be linked to the [Song]. If null, the user will
@ -216,6 +217,16 @@ constructor(
} }
} }
/**
* 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) {
playImpl(song, playlist)
}
/** /**
* Play an [Album]. * Play an [Album].
* *
@ -237,6 +248,13 @@ constructor(
*/ */
fun play(genre: Genre) = playImpl(null, genre, false) fun play(genre: Genre) = playImpl(null, genre, false)
/**
* Play a [Playlist].
*
* @param playlist The [Playlist] to play.
*/
fun play(playlist: Playlist) = playImpl(null, playlist, false)
/** /**
* Play a [Music] selection. * Play a [Music] selection.
* *
@ -260,12 +278,19 @@ constructor(
fun shuffle(artist: Artist) = playImpl(null, artist, true) fun shuffle(artist: Artist) = playImpl(null, artist, true)
/** /**
* Shuffle an [Genre]. * Shuffle a [Genre].
* *
* @param genre The [Genre] to shuffle. * @param genre The [Genre] to shuffle.
*/ */
fun shuffle(genre: Genre) = playImpl(null, genre, true) fun shuffle(genre: Genre) = playImpl(null, genre, true)
/**
* Shuffle a [Playlist].
*
* @param playlist The [Playlist] to shuffle.
*/
fun shuffle(playlist: Playlist) = playImpl(null, playlist, true)
/** /**
* Shuffle a [Music] selection. * Shuffle a [Music] selection.
* *
@ -292,7 +317,7 @@ constructor(
null -> musicSettings.songSort null -> musicSettings.songSort
} }
val songs = parent?.songs ?: deviceLibrary.songs val songs = parent?.songs ?: deviceLibrary.songs
val queue = sort?.songs(songs) ?: songs val queue = sort.songs(songs)
playbackManager.play(song, parent, queue, shuffled) playbackManager.play(song, parent, queue, shuffled)
} }
@ -365,6 +390,15 @@ constructor(
playbackManager.playNext(musicSettings.genreSongSort.songs(genre.songs)) playbackManager.playNext(musicSettings.genreSongSort.songs(genre.songs))
} }
/**
* Add a [Playlist] to the top of the queue.
*
* @param playlist The [Playlist] to add.
*/
fun playNext(playlist: Playlist) {
playbackManager.playNext(musicSettings.playlistSongSort.songs(playlist.songs))
}
/** /**
* Add a selection to the top of the queue. * Add a selection to the top of the queue.
* *
@ -410,6 +444,15 @@ constructor(
playbackManager.addToQueue(musicSettings.genreSongSort.songs(genre.songs)) playbackManager.addToQueue(musicSettings.genreSongSort.songs(genre.songs))
} }
/**
* Add a [Playlist] to the end of the queue.
*
* @param playlist The [Playlist] to add.
*/
fun addToQueue(playlist: Playlist) {
playbackManager.addToQueue(musicSettings.playlistSongSort.songs(playlist.songs))
}
/** /**
* Add a selection to the end of the queue. * Add a selection to the end of the queue.
* *
@ -494,7 +537,7 @@ constructor(
is Album -> musicSettings.albumSongSort.songs(it.songs) is Album -> musicSettings.albumSongSort.songs(it.songs)
is Artist -> musicSettings.artistSongSort.songs(it.songs) is Artist -> musicSettings.artistSongSort.songs(it.songs)
is Genre -> musicSettings.genreSongSort.songs(it.songs) is Genre -> musicSettings.genreSongSort.songs(it.songs)
is Playlist -> musicSettings.playlistSongSort?.songs(it.songs) ?: it.songs is Playlist -> musicSettings.playlistSongSort.songs(it.songs)
} }
} }
} }

View file

@ -50,7 +50,7 @@ open class FakeMusicSettings : MusicSettings {
set(_) = throw NotImplementedError() set(_) = throw NotImplementedError()
override var playlistSort: Sort override var playlistSort: Sort
get() = throw NotImplementedError() get() = throw NotImplementedError()
set(value) = throw NotImplementedError() set(_) = throw NotImplementedError()
override var albumSongSort: Sort override var albumSongSort: Sort
get() = throw NotImplementedError() get() = throw NotImplementedError()
set(_) = throw NotImplementedError() set(_) = throw NotImplementedError()