playback: add framework for multi-parent playback
Add some functions to eventually enable multi-parent playback. PlaybackMode is still used in some places, however will steadily be phased out hopefully.
This commit is contained in:
parent
e5d7cdc340
commit
457013d047
11 changed files with 86 additions and 45 deletions
|
@ -3,7 +3,12 @@
|
|||
## dev
|
||||
|
||||
#### What's Fixed
|
||||
- Fixed issue wher the scroll popup would not display correctly in landscape mode [#230]
|
||||
- Fixed issue where the scroll popup would not display correctly in landscape mode [#230]
|
||||
- Fixed issue where the playback progress would continue in the notification even if
|
||||
audio focus was lost
|
||||
|
||||
#### Dev/Meta
|
||||
- Completed migration to reactive playback system
|
||||
|
||||
## 2.6.3
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ import org.oxycblt.auxio.music.Artist
|
|||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
||||
|
@ -122,7 +121,12 @@ class AlbumDetailFragment :
|
|||
|
||||
override fun onItemClick(item: Item) {
|
||||
if (item is Song) {
|
||||
playbackModel.play(item, settings.detailPlaybackMode ?: PlaybackMode.IN_ALBUM)
|
||||
val playbackMode = settings.detailPlaybackMode
|
||||
if (playbackMode != null) {
|
||||
playbackModel.play(item, playbackMode)
|
||||
} else {
|
||||
playbackModel.playFromAlbum(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@ import org.oxycblt.auxio.music.Artist
|
|||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
||||
|
@ -112,9 +111,16 @@ class ArtistDetailFragment :
|
|||
}
|
||||
|
||||
override fun onItemClick(item: Item) {
|
||||
|
||||
when (item) {
|
||||
is Song ->
|
||||
playbackModel.play(item, settings.detailPlaybackMode ?: PlaybackMode.IN_ARTIST)
|
||||
is Song -> {
|
||||
val playbackMode = settings.detailPlaybackMode
|
||||
if (playbackMode != null) {
|
||||
playbackModel.play(item, playbackMode)
|
||||
} else {
|
||||
playbackModel.playFromArtist(item)
|
||||
}
|
||||
}
|
||||
is Album -> navModel.exploreNavigateTo(item)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ import org.oxycblt.auxio.music.Genre
|
|||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
||||
|
@ -113,12 +112,13 @@ class GenreDetailFragment :
|
|||
}
|
||||
|
||||
override fun onItemClick(item: Item) {
|
||||
when (item) {
|
||||
is Song ->
|
||||
playbackModel.play(item, settings.detailPlaybackMode ?: PlaybackMode.IN_GENRE)
|
||||
is Album ->
|
||||
findNavController()
|
||||
.navigate(ArtistDetailFragmentDirections.actionShowAlbum(item.id))
|
||||
check(item is Song)
|
||||
|
||||
val playbackMode = settings.detailPlaybackMode
|
||||
if (playbackMode != null) {
|
||||
playbackModel.play(item, playbackMode)
|
||||
} else {
|
||||
playbackModel.playFromGenre(item, unlikelyToBeNull(detailModel.currentGenre.value))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,37 @@ class PlaybackViewModel(application: Application) :
|
|||
|
||||
/** Play a [song] with the [mode] specified, */
|
||||
fun play(song: Song, mode: PlaybackMode) {
|
||||
playbackManager.play(song, mode, settings)
|
||||
// TODO: Remove this function when selection is implemented
|
||||
|
||||
val parent =
|
||||
when (mode) {
|
||||
PlaybackMode.IN_ALBUM -> song.album
|
||||
PlaybackMode.IN_ARTIST -> song.album.artist
|
||||
PlaybackMode.IN_GENRE -> song.genres.maxBy { it.songs.size }
|
||||
PlaybackMode.ALL_SONGS -> null
|
||||
}
|
||||
|
||||
playbackManager.play(song, parent, settings)
|
||||
}
|
||||
|
||||
/** Play a song from it's album. */
|
||||
fun playFromAlbum(song: Song) {
|
||||
playbackManager.play(song, song.album, settings)
|
||||
}
|
||||
|
||||
/** Play a song from it's artist. */
|
||||
fun playFromArtist(song: Song) {
|
||||
playbackManager.play(song, song.album.artist, settings)
|
||||
}
|
||||
|
||||
/** Play a song from the specific genre that contains the song. */
|
||||
fun playFromGenre(song: Song, genre: Genre) {
|
||||
if (!genre.songs.contains(song)) {
|
||||
logE("Genre does not contain song, not playing")
|
||||
return
|
||||
}
|
||||
|
||||
playbackManager.play(song, genre, settings)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -78,6 +78,8 @@ interface InternalPlayer {
|
|||
/** Load this state into the analogous [PlaybackStateCompat.Builder]. */
|
||||
fun intoPlaybackState(builder: PlaybackStateCompat.Builder): PlaybackStateCompat.Builder =
|
||||
builder.setState(
|
||||
// State represents the user's preference, not the actual player state.
|
||||
// Doing this produces a better experience in the media control UI.
|
||||
if (isPlaying) {
|
||||
PlaybackStateCompat.STATE_PLAYING
|
||||
} else {
|
||||
|
@ -92,6 +94,9 @@ interface InternalPlayer {
|
|||
},
|
||||
creationTime)
|
||||
|
||||
// Equality ignores the creation time to prevent functionally
|
||||
// identical states from being equal.
|
||||
|
||||
override fun equals(other: Any?) =
|
||||
other is State &&
|
||||
isPlaying == other.isPlaying &&
|
||||
|
@ -109,9 +114,9 @@ interface InternalPlayer {
|
|||
/** Create a new instance of this state. */
|
||||
fun new(isPlaying: Boolean, isAdvancing: Boolean, positionMs: Long) =
|
||||
State(
|
||||
isPlaying,
|
||||
// Minor sanity check: Make sure that advancing can't occur if the
|
||||
// main playing value is paused.
|
||||
isPlaying,
|
||||
isPlaying && isAdvancing,
|
||||
positionMs,
|
||||
SystemClock.elapsedRealtime())
|
||||
|
|
|
@ -51,14 +51,13 @@ enum class PlaybackMode {
|
|||
* Get a [PlaybackMode] for an int [constant]
|
||||
* @return The mode, null if there isn't one for this.
|
||||
*/
|
||||
fun fromInt(constant: Int): PlaybackMode? {
|
||||
return when (constant) {
|
||||
fun fromInt(constant: Int) =
|
||||
when (constant) {
|
||||
IntegerTable.PLAYBACK_MODE_ALL_SONGS -> ALL_SONGS
|
||||
IntegerTable.PLAYBACK_MODE_IN_ALBUM -> IN_ALBUM
|
||||
IntegerTable.PLAYBACK_MODE_IN_ARTIST -> IN_ARTIST
|
||||
IntegerTable.PLAYBACK_MODE_IN_GENRE -> IN_GENRE
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,22 +144,13 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
// --- PLAYING FUNCTIONS ---
|
||||
|
||||
/** Play a [song]. */
|
||||
/** Play a song from a parent that contains the song. */
|
||||
@Synchronized
|
||||
fun play(song: Song, playbackMode: PlaybackMode, settings: Settings) {
|
||||
fun play(song: Song, parent: MusicParent?, settings: Settings) {
|
||||
val internalPlayer = internalPlayer ?: return
|
||||
val library = musicStore.library ?: return
|
||||
|
||||
parent =
|
||||
when (playbackMode) {
|
||||
PlaybackMode.ALL_SONGS -> null
|
||||
PlaybackMode.IN_ALBUM -> song.album
|
||||
PlaybackMode.IN_ARTIST -> song.album.artist
|
||||
PlaybackMode.IN_GENRE ->
|
||||
song.genres.maxBy {
|
||||
it.songs.size
|
||||
} // TODO: Stopgap measure until I can rework this and add selection
|
||||
}
|
||||
this.parent = parent
|
||||
|
||||
applyNewQueue(library, settings, settings.keepShuffle && isShuffled, song)
|
||||
|
||||
|
|
|
@ -54,9 +54,6 @@ import org.oxycblt.auxio.util.logD
|
|||
* while also keeping in mind the absurd rate limiting system in place just to have a sort-of
|
||||
* coherent state. And even then it will break if you skip too much.
|
||||
*
|
||||
* Google, please replace this API. No, don't paper it over with even more broken abstractions.
|
||||
* Replace it. Please.
|
||||
*
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class MediaSessionComponent(private val context: Context, private val callback: Callback) :
|
||||
|
|
|
@ -51,27 +51,31 @@ private val Any.autoTag: String
|
|||
|
||||
/**
|
||||
* I know that this will not stop you, but consider what you are doing with your life, plagiarizers.
|
||||
*
|
||||
* Do you want to live a fulfilling existence on this planet? Or do you want to spend your life
|
||||
* taking work others did and making it objectively worse so you could arbitrage a fraction of a
|
||||
* penny on every AdMob impression you get? You could do so many great things if you simply had the
|
||||
* courage to come up with an idea of your own. If you still want to go on, I guess the only thing I
|
||||
* can say is this:
|
||||
* penny on every AdMob impression you get?
|
||||
*
|
||||
* JUNE 1989 TIANAMEN SQUARE PROTESTS AND MASSACRE 六四事件
|
||||
* You could do so many great things if you simply had the courage to come up with an idea of your
|
||||
* own.
|
||||
*
|
||||
* 2022 RUSSIAN INVASION OF UKRAINE Вторжение России на Украину
|
||||
* If you still want to go on, I guess the only thing I can say is this:
|
||||
*
|
||||
* WOMEN'S RIGHTS IN THE ISLAMIC REPUBLIC OF IRAN حقوق زنان در ایران
|
||||
* JUNE 1989 TIANAMEN SQUARE PROTESTS AND MASSACRE / 六四事件
|
||||
*
|
||||
* UYGHUR GENOCIDE/XINJIANG INTERNMENT CAMPS 新疆种族灭绝指控/新疆再教育營
|
||||
* 2022 RUSSIAN INVASION OF UKRAINE / ВТОРЖЕНИЕ РОССИИ НА УКРАИНУ
|
||||
*
|
||||
* WOMEN'S RIGHTS IN THE ISLAMIC REPUBLIC OF IRAN / حقوق زنان در ایران
|
||||
*
|
||||
* UYGHUR GENOCIDE/XINJIANG INTERNMENT CAMPS / 新疆种族灭绝指控/新疆再教育營
|
||||
*
|
||||
* KASHMIR INDEPENDENCE MOVEMENT
|
||||
*
|
||||
* FREE TIBET 西藏自由
|
||||
* FREE TIBET / 西藏自由
|
||||
*
|
||||
* 1915-1916 ARMENIAN GENOCIDE Ermeni Kırımı
|
||||
* 1915-1916 ARMENIAN GENOCIDE / ERMENI KIRIMI
|
||||
*
|
||||
* 2018 TORTURE AND ASSASSINATION OF JAMAL KHASHOGGI مقتل جمال خاشقجي
|
||||
* 2018 TORTURE AND ASSASSINATION OF JAMAL KHASHOGGI / مقتل جمال خاشقجي
|
||||
*
|
||||
* UNITED ARAB EMIRATES ENSLAVED MIGRANT WORKERS
|
||||
*/
|
||||
|
|
|
@ -53,10 +53,10 @@ fun Long.msToDs() = floorDiv(100)
|
|||
/** Converts a long in milliseconds to a long in seconds */
|
||||
fun Long.msToSecs() = floorDiv(1000)
|
||||
|
||||
/** Converts a long in deciseconds to a long in milliseconds. */
|
||||
/** Converts a long in deci-seconds to a long in milliseconds. */
|
||||
fun Long.dsToMs() = times(100)
|
||||
|
||||
/** Converts a long in deciseconds to a long in seconds. */
|
||||
/** Converts a long in deci-seconds to a long in seconds. */
|
||||
fun Long.dsToSecs() = floorDiv(10)
|
||||
|
||||
/** Converts a long in seconds to a long in milliseconds. */
|
||||
|
|
Loading…
Reference in a new issue