playback: add ability to play/shuffle selection
Add the ability to play or shuffle a selection. This finally allows "arbitrary" playback to be created from any combination of songs/albums/artist/genres, rather than just from pre-defined options. Resolves #313.
This commit is contained in:
parent
692839e8fe
commit
5988908b56
8 changed files with 50 additions and 42 deletions
|
@ -71,6 +71,14 @@ abstract class SelectionFragment<VB : ViewBinding> :
|
||||||
requireContext().showToast(R.string.lng_queue_added)
|
requireContext().showToast(R.string.lng_queue_added)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.action_selection_play -> {
|
||||||
|
playbackModel.play(selectionModel.consume())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.action_selection_shuffle -> {
|
||||||
|
playbackModel.shuffle(selectionModel.consume())
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ class MusicStore private constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a [Listener] from this instance, preventing it from recieving any further updates.
|
* Remove a [Listener] from this instance, preventing it from receiving any further updates.
|
||||||
* @param listener The [Listener] to remove. Does nothing if the [Listener] was never added in
|
* @param listener The [Listener] to remove. Does nothing if the [Listener] was never added in
|
||||||
* the first place.
|
* the first place.
|
||||||
* @see Listener
|
* @see Listener
|
||||||
|
|
|
@ -129,7 +129,6 @@ class Directory private constructor(val volume: StorageVolume, val relativePath:
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
data class MusicDirectories(val dirs: List<Directory>, val shouldInclude: Boolean)
|
data class MusicDirectories(val dirs: List<Directory>, val shouldInclude: Boolean)
|
||||||
// TODO: Unify include + exclude
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mime type of a file. Only intended for display.
|
* A mime type of a file. Only intended for display.
|
||||||
|
|
|
@ -196,7 +196,7 @@ val StorageVolume.isInternalCompat: Boolean
|
||||||
get() = isPrimaryCompat && isEmulatedCompat
|
get() = isPrimaryCompat && isEmulatedCompat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The unique identifier for this [StorageVolume], obtained in a version compatible manner Can be
|
* The unique identifier for this [StorageVolume], obtained in a version compatible manner. Can be
|
||||||
* null.
|
* null.
|
||||||
* @see StorageVolume.getUuid
|
* @see StorageVolume.getUuid
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -38,6 +38,7 @@ class PlaybackViewModel(application: Application) :
|
||||||
private val musicSettings = MusicSettings.from(application)
|
private val musicSettings = MusicSettings.from(application)
|
||||||
private val playbackSettings = PlaybackSettings.from(application)
|
private val playbackSettings = PlaybackSettings.from(application)
|
||||||
private val playbackManager = PlaybackStateManager.getInstance()
|
private val playbackManager = PlaybackStateManager.getInstance()
|
||||||
|
private val musicStore = MusicStore.getInstance()
|
||||||
private var lastPositionJob: Job? = null
|
private var lastPositionJob: Job? = null
|
||||||
|
|
||||||
private val _song = MutableStateFlow<Song?>(null)
|
private val _song = MutableStateFlow<Song?>(null)
|
||||||
|
@ -161,27 +162,13 @@ class PlaybackViewModel(application: Application) :
|
||||||
*/
|
*/
|
||||||
fun playFrom(song: Song, playbackMode: MusicMode) {
|
fun playFrom(song: Song, playbackMode: MusicMode) {
|
||||||
when (playbackMode) {
|
when (playbackMode) {
|
||||||
MusicMode.SONGS -> playFromAll(song)
|
MusicMode.SONGS -> playImpl(song, null)
|
||||||
MusicMode.ALBUMS -> playFromAlbum(song)
|
MusicMode.ALBUMS -> playImpl(song, song.album)
|
||||||
MusicMode.ARTISTS -> playFromArtist(song)
|
MusicMode.ARTISTS -> playFromArtist(song)
|
||||||
MusicMode.GENRES -> playFromGenre(song)
|
MusicMode.GENRES -> playFromGenre(song)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Play the given [Song] from all songs in the music library.
|
|
||||||
* @param song The [Song] to play.
|
|
||||||
*/
|
|
||||||
fun playFromAll(song: Song) {
|
|
||||||
playImpl(song, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Play a [Song] from it's [Album].
|
|
||||||
* @param song The [Song] to play.
|
|
||||||
*/
|
|
||||||
fun playFromAlbum(song: Song) = playImpl(song, song.album)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play a [Song] from one of it's [Artist]s.
|
* Play a [Song] from one of it's [Artist]s.
|
||||||
* @param song The [Song] to play.
|
* @param song The [Song] to play.
|
||||||
|
@ -250,6 +237,13 @@ class PlaybackViewModel(application: Application) :
|
||||||
*/
|
*/
|
||||||
fun play(genre: Genre) = playImpl(null, genre, false)
|
fun play(genre: Genre) = playImpl(null, genre, false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a [Music] selection.
|
||||||
|
* @param selection The selection to play.
|
||||||
|
*/
|
||||||
|
fun play(selection: List<Music>) =
|
||||||
|
playbackManager.play(null, selectionToSongs(selection), false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuffle an [Album].
|
* Shuffle an [Album].
|
||||||
* @param album The [Album] to shuffle.
|
* @param album The [Album] to shuffle.
|
||||||
|
@ -269,13 +263,11 @@ class PlaybackViewModel(application: Application) :
|
||||||
fun shuffle(genre: Genre) = playImpl(null, genre, true)
|
fun shuffle(genre: Genre) = playImpl(null, genre, true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the given [InternalPlayer.Action] to be completed eventually. This can be used to
|
* Shuffle a [Music] selection.
|
||||||
* enqueue a playback action at startup to then occur when the music library is fully loaded.
|
* @param selection The selection to shuffle.
|
||||||
* @param action The [InternalPlayer.Action] to perform eventually.
|
|
||||||
*/
|
*/
|
||||||
fun startAction(action: InternalPlayer.Action) {
|
fun shuffle(selection: List<Music>) =
|
||||||
playbackManager.startAction(action)
|
playbackManager.play(null, selectionToSongs(selection), true)
|
||||||
}
|
|
||||||
|
|
||||||
private fun playImpl(
|
private fun playImpl(
|
||||||
song: Song?,
|
song: Song?,
|
||||||
|
@ -285,6 +277,7 @@ class PlaybackViewModel(application: Application) :
|
||||||
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"
|
||||||
}
|
}
|
||||||
|
val library = musicStore.library ?: return
|
||||||
val sort =
|
val sort =
|
||||||
when (parent) {
|
when (parent) {
|
||||||
is Genre -> musicSettings.genreSongSort
|
is Genre -> musicSettings.genreSongSort
|
||||||
|
@ -292,7 +285,17 @@ class PlaybackViewModel(application: Application) :
|
||||||
is Album -> musicSettings.albumSongSort
|
is Album -> musicSettings.albumSongSort
|
||||||
null -> musicSettings.songSort
|
null -> musicSettings.songSort
|
||||||
}
|
}
|
||||||
playbackManager.play(song, parent, sort, shuffled)
|
val queue = sort.songs(parent?.songs ?: library.songs)
|
||||||
|
playbackManager.play(song, queue, shuffled)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the given [InternalPlayer.Action] to be completed eventually. This can be used to
|
||||||
|
* enqueue a playback action at startup to then occur when the music library is fully loaded.
|
||||||
|
* @param action The [InternalPlayer.Action] to perform eventually.
|
||||||
|
*/
|
||||||
|
fun startAction(action: InternalPlayer.Action) {
|
||||||
|
playbackManager.startAction(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- PLAYER FUNCTIONS ---
|
// --- PLAYER FUNCTIONS ---
|
||||||
|
|
|
@ -155,20 +155,21 @@ class PlaybackStateManager private constructor() {
|
||||||
/**
|
/**
|
||||||
* Start new playback.
|
* Start new playback.
|
||||||
* @param song A particular [Song] to play, or null to play the first [Song] in the new queue.
|
* @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 the entire [Library].
|
* @param parent The [MusicParent] to play from, or null if to play from the entire [Library].
|
||||||
* @param sort [Sort] to initially sort an ordered queue with.
|
* @param sort [Sort] to initially sort an ordered queue with.
|
||||||
* @param shuffled Whether to shuffle or not.
|
* @param shuffled Whether to shuffle or not.
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun play(song: Song?, parent: MusicParent?, sort: Sort, shuffled: Boolean) {
|
fun play(song: Song?, queue: List<Song>, shuffled: Boolean) {
|
||||||
val internalPlayer = internalPlayer ?: return
|
val internalPlayer = internalPlayer ?: return
|
||||||
val library = musicStore.library ?: return
|
val library = musicStore.library ?: return
|
||||||
// Set up parent and queue
|
// Set up parent and queue
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
queue.start(song, sort.songs(parent?.songs ?: library.songs), shuffled)
|
this.queue.start(song, queue, shuffled)
|
||||||
// Notify components of changes
|
// Notify components of changes
|
||||||
notifyNewPlayback()
|
notifyNewPlayback()
|
||||||
internalPlayer.loadSong(queue.currentSong, true)
|
internalPlayer.loadSong(this.queue.currentSong, true)
|
||||||
// Played something, so we are initialized now
|
// Played something, so we are initialized now
|
||||||
isInitialized = true
|
isInitialized = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -355,15 +355,14 @@ class PlaybackService :
|
||||||
}
|
}
|
||||||
// Shuffle all -> Start new playback from all songs
|
// Shuffle all -> Start new playback from all songs
|
||||||
is InternalPlayer.Action.ShuffleAll -> {
|
is InternalPlayer.Action.ShuffleAll -> {
|
||||||
playbackManager.play(null, null, musicSettings.songSort, true)
|
playbackManager.play(null, musicSettings.songSort.songs(library.songs), true)
|
||||||
}
|
}
|
||||||
// Open -> Try to find the Song for the given file and then play it from all songs
|
// Open -> Try to find the Song for the given file and then play it from all songs
|
||||||
is InternalPlayer.Action.Open -> {
|
is InternalPlayer.Action.Open -> {
|
||||||
library.findSongForUri(application, action.uri)?.let { song ->
|
library.findSongForUri(application, action.uri)?.let { song ->
|
||||||
playbackManager.play(
|
playbackManager.play(
|
||||||
song,
|
song,
|
||||||
null,
|
musicSettings.songSort.songs(library.songs),
|
||||||
musicSettings.songSort,
|
|
||||||
playbackManager.queue.isShuffled && playbackSettings.keepShuffle)
|
playbackManager.queue.isShuffled && playbackSettings.keepShuffle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,16 +8,14 @@
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="ifRoom"/>
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_selection_queue_add"
|
android:id="@+id/action_selection_queue_add"
|
||||||
android:icon="@drawable/ic_play_next"
|
|
||||||
android:title="@string/lbl_queue_add"
|
android:title="@string/lbl_queue_add"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
<!-- TOOD: Disabled until able to get queue system into shape -->
|
<item
|
||||||
<!-- <item-->
|
android:id="@+id/action_selection_play"
|
||||||
<!-- android:id="@+id/action_play_selection"-->
|
android:title="@string/lbl_play_selected"
|
||||||
<!-- android:title="@string/lbl_play_selected"-->
|
app:showAsAction="never"/>
|
||||||
<!-- app:showAsAction="never"/>-->
|
<item
|
||||||
<!-- <item-->
|
android:id="@+id/action_selection_shuffle"
|
||||||
<!-- android:id="@+id/action_shuffle_selection"-->
|
android:title="@string/lbl_shuffle_selected"
|
||||||
<!-- android:title="@string/lbl_shuffle_selected"-->
|
app:showAsAction="never"/>
|
||||||
<!-- app:showAsAction="never"/>-->
|
|
||||||
</menu>
|
</menu>
|
Loading…
Reference in a new issue