From 457013d0471e2f85727bc37308b502138ac2f098 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Tue, 6 Sep 2022 13:12:25 -0600 Subject: [PATCH] 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. --- CHANGELOG.md | 7 +++- .../auxio/detail/AlbumDetailFragment.kt | 8 +++-- .../auxio/detail/ArtistDetailFragment.kt | 12 +++++-- .../auxio/detail/GenreDetailFragment.kt | 14 ++++---- .../auxio/playback/PlaybackViewModel.kt | 32 ++++++++++++++++++- .../auxio/playback/state/InternalPlayer.kt | 7 +++- .../auxio/playback/state/PlaybackMode.kt | 5 ++- .../playback/state/PlaybackStateManager.kt | 15 ++------- .../playback/system/MediaSessionComponent.kt | 3 -- .../java/org/oxycblt/auxio/util/LogUtil.kt | 24 ++++++++------ .../org/oxycblt/auxio/util/PrimitiveUtil.kt | 4 +-- 11 files changed, 86 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a256610b..4cc629e2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index a1ddf6b68..f85a3cd25 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -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) + } } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index 501d218ff..3f6ef330a 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -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) } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index 541186601..f59d5472b 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -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)) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 9bf54a183..3b4f284c3 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -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) } /** diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/InternalPlayer.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/InternalPlayer.kt index 67008b7be..bdd4db3a0 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/InternalPlayer.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/InternalPlayer.kt @@ -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()) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt index 194f32e23..f2028acf7 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt @@ -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 } - } } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index bb959e3ff..65b11c44a 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -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) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt index f0a853bd2..8159cd4b0 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt @@ -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) : diff --git a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt index eb48b2bb1..09e50b6eb 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt @@ -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 */ diff --git a/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt index 0c798d5bb..b9a81aa16 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt @@ -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. */