playback: decouple fields and callback

Decouple the callback notifying code and the field code. This makes
code more reasonable for future changes.
This commit is contained in:
OxygenCobalt 2022-04-29 12:07:14 -06:00
parent 60367c45c3
commit 2bdbe212df
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
10 changed files with 186 additions and 158 deletions

View file

@ -13,7 +13,7 @@
- Made the layout of album songs more similar to other songs
#### Dev/Meta
- Updated translations [Konstantin Tutsch -> German, cccClyde -> Chinese]
- Updated translations [Konstantin Tutsch -> German, cccClyde -> Chinese, Gsset -> Russian]
- Switched to spotless and ktfmt instead of ktlint
- Migrated constants to centralized table
- Introduced new RecyclerView framework

View file

@ -115,9 +115,9 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0)
}
override fun isAutoMirrored(): Boolean = true
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
override fun setAlpha(alpha: Int) {}
override fun setColorFilter(colorFilter: ColorFilter?) {}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
private fun updatePath() {
val r = bounds.height().toFloat() / 2
@ -168,6 +168,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0)
}
companion object {
// Cache sqrt(2) for faster calculations
private const val SQRT2 = 1.4142135623730950488f
}
}

View file

@ -135,11 +135,11 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
removeCallbacks(hideThumbRunnable)
showScrollbar()
showPopup()
onDragListener?.onFastScrollStart()
listener?.onFastScrollStart()
} else {
postAutoHideScrollbar()
hidePopup()
onDragListener?.onFastScrollStop()
listener?.onFastScrollStop()
}
}
@ -161,7 +161,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
* A listener for when a drag event occurs. The value will be true if a drag has begun, and
* false if a drag ended.
*/
var onDragListener: OnFastScrollListener? = null
var listener: OnFastScrollListener? = null
init {
overlay.add(thumbView)

View file

@ -52,7 +52,7 @@ abstract class HomeListFragment<T : Item> :
override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) {
setupRecycler(binding.homeRecycler)
binding.homeRecycler.popupProvider = this
binding.homeRecycler.onDragListener = this
binding.homeRecycler.listener = this
}
override fun onDestroyBinding(binding: FragmentHomeListBinding) {
@ -60,7 +60,7 @@ abstract class HomeListFragment<T : Item> :
binding.homeRecycler.apply {
adapter = null
popupProvider = null
onDragListener = null
listener = null
}
}

View file

@ -314,13 +314,13 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
private fun restorePlaybackState() {
logD("Attempting to restore playback state")
onSongUpdate(playbackManager.song)
onPositionUpdate(playbackManager.position)
onParentUpdate(playbackManager.parent)
onQueueUpdate(playbackManager.queue, playbackManager.index)
onPlayingUpdate(playbackManager.isPlaying)
onShuffleUpdate(playbackManager.isShuffling)
onLoopUpdate(playbackManager.loopMode)
onSongChanged(playbackManager.song)
onPositionChanged(playbackManager.position)
onParentChanged(playbackManager.parent)
onQueueChanged(playbackManager.queue, playbackManager.index)
onPlayingChanged(playbackManager.isPlaying)
onShuffleChanged(playbackManager.isShuffling)
onLoopModeChanged(playbackManager.loopMode)
}
// --- OVERRIDES ---
@ -329,31 +329,31 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
playbackManager.removeCallback(this)
}
override fun onSongUpdate(song: Song?) {
override fun onSongChanged(song: Song?) {
mSong.value = song
}
override fun onParentUpdate(parent: MusicParent?) {
override fun onParentChanged(parent: MusicParent?) {
mParent.value = parent
}
override fun onPositionUpdate(position: Long) {
override fun onPositionChanged(position: Long) {
mPositionSeconds.value = position / 1000
}
override fun onQueueUpdate(queue: List<Song>, index: Int) {
override fun onQueueChanged(queue: List<Song>, index: Int) {
mNextUp.value = queue.slice(index.inc() until queue.size)
}
override fun onPlayingUpdate(isPlaying: Boolean) {
override fun onPlayingChanged(isPlaying: Boolean) {
mIsPlaying.value = isPlaying
}
override fun onShuffleUpdate(isShuffling: Boolean) {
override fun onShuffleChanged(isShuffling: Boolean) {
mIsShuffling.value = isShuffling
}
override fun onLoopUpdate(loopMode: LoopMode) {
override fun onLoopModeChanged(loopMode: LoopMode) {
mLoopMode.value = loopMode
}
}

View file

@ -48,42 +48,17 @@ class PlaybackStateManager private constructor() {
// Playback
private var mSong: Song? = null
set(value) {
field = value
callbacks.forEach { it.onSongUpdate(value) }
}
private var mPosition: Long = 0
set(value) {
field = value
callbacks.forEach { it.onPositionUpdate(value) }
}
private var mParent: MusicParent? = null
set(value) {
field = value
callbacks.forEach { it.onParentUpdate(value) }
}
// Queue
private var mQueue = mutableListOf<Song>()
private var mIndex = 0
// Status
// State
private var mIsPlaying = false
set(value) {
field = value
callbacks.forEach { it.onPlayingUpdate(value) }
}
private var mPosition: Long = 0
private var mIsShuffling = false
set(value) {
field = value
callbacks.forEach { it.onShuffleUpdate(value) }
}
private var mLoopMode = LoopMode.NONE
set(value) {
field = value
callbacks.forEach { it.onLoopUpdate(value) }
}
private var mIsRestored = false
private var mHasPlayed = false
@ -94,24 +69,26 @@ class PlaybackStateManager private constructor() {
/** The parent the queue is based on, null if all_songs */
val parent: MusicParent?
get() = mParent
/** The current playback progress */
val position: Long
get() = mPosition
/** The current queue determined by [parent] and [playbackMode] */
/** The current queue determined by [parent] */
val queue: List<Song>
get() = mQueue
/** The current position in the queue */
val index: Int
get() = mIndex
/** Whether playback is paused or not */
val isPlaying: Boolean
get() = mIsPlaying
/** Whether the queue is shuffled */
val isShuffling: Boolean
get() = mIsShuffling
/** The current playback progress */
val position: Long
get() = mPosition
/** The current [LoopMode] */
val loopMode: LoopMode
get() = mLoopMode
/** Whether the queue is shuffled */
val isShuffling: Boolean
get() = mIsShuffling
/** Whether this instance has already been restored */
val isRestored: Boolean
get() = mIsRestored
@ -165,9 +142,11 @@ class PlaybackStateManager private constructor() {
}
}
notifyParentChanged()
updatePlayback(song)
// Keep shuffle on, if enabled
setShuffling(settingsManager.keepShuffle && mIsShuffling, keepSong = true)
setShuffling(settingsManager.keepShuffle && isShuffling, keepSong = true)
}
/**
@ -178,17 +157,19 @@ class PlaybackStateManager private constructor() {
logD("Playing ${parent.rawName}")
mParent = parent
notifyParentChanged()
mIndex = 0
mQueue =
when (parent) {
is Album -> {
mQueue = parent.songs.toMutableList()
parent.songs.toMutableList()
}
is Artist -> {
mQueue = parent.songs.toMutableList()
parent.songs.toMutableList()
}
is Genre -> {
mQueue = parent.songs.toMutableList()
parent.songs.toMutableList()
}
}
@ -211,6 +192,8 @@ class PlaybackStateManager private constructor() {
private fun updatePlayback(song: Song, shouldPlay: Boolean = true) {
mSong = song
mPosition = 0
notifySongChanged()
notifyPositionChanged()
setPlaying(shouldPlay)
}
@ -225,10 +208,10 @@ class PlaybackStateManager private constructor() {
updatePlayback(mQueue[mIndex])
} else {
mIndex = 0
updatePlayback(mQueue[mIndex], shouldPlay = mLoopMode == LoopMode.ALL)
updatePlayback(mQueue[mIndex], shouldPlay = loopMode == LoopMode.ALL)
}
pushQueueUpdate()
notifyQueueChanged()
}
/** Go to the previous song, doing any checks that are needed. */
@ -243,7 +226,7 @@ class PlaybackStateManager private constructor() {
}
updatePlayback(mQueue[mIndex])
pushQueueUpdate()
notifyQueueChanged()
}
}
@ -258,7 +241,7 @@ class PlaybackStateManager private constructor() {
logD("Removing item ${mQueue[index].rawName}")
mQueue.removeAt(index)
pushQueueUpdate()
notifyQueueChanged()
return true
}
@ -271,7 +254,7 @@ class PlaybackStateManager private constructor() {
logD("Moving item $from to position $to")
mQueue.add(to, mQueue.removeAt(from))
pushQueueUpdate()
notifyQueueChanged()
return true
}
@ -282,7 +265,7 @@ class PlaybackStateManager private constructor() {
}
mQueue.add(mIndex + 1, song)
pushQueueUpdate()
notifyQueueChanged()
}
/** Add a list of [songs] to the top of the queue. */
@ -292,24 +275,19 @@ class PlaybackStateManager private constructor() {
}
mQueue.addAll(mIndex + 1, songs)
pushQueueUpdate()
notifyQueueChanged()
}
/** Add a [song] to the end of the queue. */
fun addToQueue(song: Song) {
mQueue.add(song)
pushQueueUpdate()
notifyQueueChanged()
}
/** Add a list of [songs] to the end of the queue. */
fun addToQueue(songs: List<Song>) {
mQueue.addAll(songs)
pushQueueUpdate()
}
/** Force any callbacks to receive a queue update. */
private fun pushQueueUpdate() {
callbacks.forEach { it.onQueueUpdate(mQueue, mIndex) }
notifyQueueChanged()
}
// --- SHUFFLE FUNCTIONS ---
@ -320,6 +298,7 @@ class PlaybackStateManager private constructor() {
*/
fun setShuffling(shuffled: Boolean, keepSong: Boolean) {
mIsShuffling = shuffled
notifyShufflingChanged()
if (mIsShuffling) {
genShuffle(keepSong)
@ -348,7 +327,7 @@ class PlaybackStateManager private constructor() {
mSong = mQueue[0]
}
pushQueueUpdate()
notifyQueueChanged()
}
/**
@ -359,20 +338,20 @@ class PlaybackStateManager private constructor() {
val library = musicStore.library ?: return
val lastSong = mSong
val parent = parent
mQueue =
when (parent) {
null -> settingsManager.libSongSort.songs(library.songs).toMutableList()
is Album -> settingsManager.detailAlbumSort.album(mParent as Album).toMutableList()
is Artist ->
settingsManager.detailArtistSort.artist(mParent as Artist).toMutableList()
is Genre -> settingsManager.detailGenreSort.genre(mParent as Genre).toMutableList()
is Album -> settingsManager.detailAlbumSort.album(parent).toMutableList()
is Artist -> settingsManager.detailArtistSort.artist(parent).toMutableList()
is Genre -> settingsManager.detailGenreSort.genre(parent).toMutableList()
}
if (keepSong) {
mIndex = mQueue.indexOf(lastSong)
}
pushQueueUpdate()
notifyQueueChanged()
}
// --- STATE FUNCTIONS ---
@ -385,6 +364,10 @@ class PlaybackStateManager private constructor() {
}
mIsPlaying = playing
for (callback in callbacks) {
callback.onPlayingChanged(playing)
}
}
}
@ -393,11 +376,12 @@ class PlaybackStateManager private constructor() {
* @param position The new position in millis.
* @see seekTo
*/
fun setPosition(position: Long) {
fun synchronizePosition(position: Long) {
mSong?.let { song ->
// Don't accept any bugged positions that are over the duration of the song.
if (position <= song.duration) {
mPosition = position
notifyPositionChanged()
}
}
}
@ -409,6 +393,7 @@ class PlaybackStateManager private constructor() {
*/
fun seekTo(position: Long) {
mPosition = position
notifyPositionChanged()
callbacks.forEach { it.onSeek(position) }
}
@ -427,6 +412,7 @@ class PlaybackStateManager private constructor() {
/** Set the [LoopMode] to [mode]. */
fun setLoopMode(mode: LoopMode) {
mLoopMode = mode
notifyLoopModeChanged()
}
/** Mark whether this instance has played or not */
@ -463,13 +449,13 @@ class PlaybackStateManager private constructor() {
database.writeState(
PlaybackStateDatabase.SavedState(
mSong,
mPosition,
mParent,
mIndex,
song,
position,
parent,
index,
playbackMode,
mIsShuffling,
mLoopMode,
isShuffling,
loopMode,
))
database.writeQueue(mQueue)
@ -503,7 +489,7 @@ class PlaybackStateManager private constructor() {
if (playbackState != null) {
unpackFromPlaybackState(playbackState)
mQueue = queue
pushQueueUpdate()
notifyQueueChanged()
doParentSanityCheck(playbackState.playbackMode)
doIndexSanityCheck()
}
@ -515,8 +501,6 @@ class PlaybackStateManager private constructor() {
/** Unpack a [playbackState] into this instance. */
private fun unpackFromPlaybackState(playbackState: PlaybackStateDatabase.SavedState) {
// Turn the simplified information from PlaybackState into usable data.
// Do queue setup first
mParent = playbackState.parent
mIndex = playbackState.queueIndex
@ -526,7 +510,11 @@ class PlaybackStateManager private constructor() {
mLoopMode = playbackState.loopMode
mIsShuffling = playbackState.isShuffling
notifySongChanged()
notifyParentChanged()
seekTo(playbackState.position)
notifyShufflingChanged()
notifyLoopModeChanged()
}
/** Do a sanity check to make sure the parent was not lost in the restore process. */
@ -542,6 +530,8 @@ class PlaybackStateManager private constructor() {
PlaybackMode.IN_ARTIST -> mQueue.firstOrNull()?.album?.artist
PlaybackMode.IN_GENRE -> mQueue.firstOrNull()?.genre
}
notifyParentChanged()
}
}
@ -554,7 +544,7 @@ class PlaybackStateManager private constructor() {
if (correctedIndex > -1) {
logD("Correcting malformed index to $correctedIndex")
mIndex = correctedIndex
pushQueueUpdate()
notifyQueueChanged()
}
}
}
@ -591,19 +581,57 @@ class PlaybackStateManager private constructor() {
}
}
// --- CALLBACKS ---
private fun notifySongChanged() {
for (callback in callbacks) {
callback.onSongChanged(song)
}
}
private fun notifyParentChanged() {
for (callback in callbacks) {
callback.onParentChanged(parent)
}
}
private fun notifyPositionChanged() {
for (callback in callbacks) {
callback.onPositionChanged(position)
}
}
private fun notifyLoopModeChanged() {
for (callback in callbacks) {
callback.onLoopModeChanged(loopMode)
}
}
private fun notifyShufflingChanged() {
for (callback in callbacks) {
callback.onShuffleChanged(isShuffling)
}
}
/** Force any callbacks to receive a queue update. */
private fun notifyQueueChanged() {
for (callback in callbacks) {
callback.onQueueChanged(mQueue, mIndex)
}
}
/**
* The interface for receiving updates from [PlaybackStateManager]. Add the callback to
* [PlaybackStateManager] using [addCallback], remove them on destruction with [removeCallback].
*/
interface Callback {
fun onSongUpdate(song: Song?) {}
fun onParentUpdate(parent: MusicParent?) {}
fun onPositionUpdate(position: Long) {}
fun onQueueUpdate(queue: List<Song>, index: Int) {}
fun onModeUpdate(mode: PlaybackMode) {}
fun onPlayingUpdate(isPlaying: Boolean) {}
fun onShuffleUpdate(isShuffling: Boolean) {}
fun onLoopUpdate(loopMode: LoopMode) {}
fun onSongChanged(song: Song?) {}
fun onParentChanged(parent: MusicParent?) {}
fun onPositionChanged(position: Long) {}
fun onQueueChanged(queue: List<Song>, index: Int) {}
fun onPlayingChanged(isPlaying: Boolean) {}
fun onShuffleChanged(isShuffling: Boolean) {}
fun onLoopModeChanged(loopMode: LoopMode) {}
fun onSeek(position: Long) {}
}

View file

@ -127,7 +127,7 @@ class PlaybackService :
positionScope.launch {
while (true) {
playbackManager.setPosition(player.currentPosition)
playbackManager.synchronizePosition(player.currentPosition)
delay(POS_POLL_INTERVAL)
}
}
@ -235,7 +235,7 @@ class PlaybackService :
reason: Int
) {
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
playbackManager.setPosition(player.currentPosition)
playbackManager.synchronizePosition(player.currentPosition)
}
}
@ -258,7 +258,7 @@ class PlaybackService :
// --- PLAYBACK STATE CALLBACK OVERRIDES ---
override fun onSongUpdate(song: Song?) {
override fun onSongChanged(song: Song?) {
if (song != null) {
logD("Setting player to ${song.rawName}")
player.setMediaItem(MediaItem.fromUri(song.uri))
@ -273,25 +273,25 @@ class PlaybackService :
stopForegroundAndNotification()
}
override fun onParentUpdate(parent: MusicParent?) {
override fun onParentChanged(parent: MusicParent?) {
notification.setParent(parent)
startForegroundOrNotify()
}
override fun onPlayingUpdate(isPlaying: Boolean) {
override fun onPlayingChanged(isPlaying: Boolean) {
player.playWhenReady = isPlaying
notification.setPlaying(isPlaying)
startForegroundOrNotify()
}
override fun onLoopUpdate(loopMode: LoopMode) {
override fun onLoopModeChanged(loopMode: LoopMode) {
if (!settingsManager.useAltNotifAction) {
notification.setLoop(loopMode)
startForegroundOrNotify()
}
}
override fun onShuffleUpdate(isShuffling: Boolean) {
override fun onShuffleChanged(isShuffling: Boolean) {
if (settingsManager.useAltNotifAction) {
notification.setShuffle(isShuffling)
startForegroundOrNotify()
@ -306,7 +306,7 @@ class PlaybackService :
override fun onColorizeNotifUpdate(doColorize: Boolean) {
playbackManager.song?.let { song ->
connector.onSongUpdate(song)
connector.onSongChanged(song)
notification.setMetadata(song, ::startForegroundOrNotify)
}
}
@ -323,7 +323,7 @@ class PlaybackService :
override fun onShowCoverUpdate(showCovers: Boolean) {
playbackManager.song?.let { song ->
connector.onSongUpdate(song)
connector.onSongChanged(song)
notification.setMetadata(song, ::startForegroundOrNotify)
}
}
@ -376,11 +376,11 @@ class PlaybackService :
logD("Restoring the service state")
// Re-call existing callbacks with the current values to restore everything
onParentUpdate(playbackManager.parent)
onPlayingUpdate(playbackManager.isPlaying)
onShuffleUpdate(playbackManager.isShuffling)
onLoopUpdate(playbackManager.loopMode)
onSongUpdate(playbackManager.song)
onParentChanged(playbackManager.parent)
onPlayingChanged(playbackManager.isPlaying)
onShuffleChanged(playbackManager.isShuffling)
onLoopModeChanged(playbackManager.loopMode)
onSongChanged(playbackManager.song)
onSeek(playbackManager.position)
// Notify other classes that rely on this service to also update.

View file

@ -47,8 +47,8 @@ class PlaybackSessionConnector(
playbackManager.addCallback(this)
player.addListener(this)
onSongUpdate(playbackManager.song)
onPlayingUpdate(playbackManager.isPlaying)
onSongChanged(playbackManager.song)
onPlayingChanged(playbackManager.isPlaying)
}
fun release() {
@ -108,7 +108,7 @@ class PlaybackSessionConnector(
// --- PLAYBACKSTATEMANAGER CALLBACKS ---
override fun onSongUpdate(song: Song?) {
override fun onSongChanged(song: Song?) {
if (song == null) {
mediaSession.setMetadata(emptyMetadata)
return
@ -137,7 +137,7 @@ class PlaybackSessionConnector(
}
}
override fun onPlayingUpdate(isPlaying: Boolean) {
override fun onPlayingChanged(isPlaying: Boolean) {
invalidateSessionState()
}

View file

@ -64,41 +64,39 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
* Vanilla Music's implementation.
*/
fun applyReplayGain(metadata: Metadata?) {
if (metadata == null) {
logW("No metadata could be extracted from this track")
if (metadata == null || settingsManager.replayGainMode == ReplayGainMode.OFF) {
logW(
"Not applying replaygain [" +
"metadata: ${metadata != null}, " +
"enabled: ${settingsManager.replayGainMode == ReplayGainMode.OFF}]")
volume = 1f
return
}
// ReplayGain is configurable, so determine what to do based off of the mode.
val useAlbumGain: (Gain) -> Boolean =
when (settingsManager.replayGainMode) {
ReplayGainMode.OFF -> {
logD("ReplayGain is off")
volume = 1f
return
}
// User wants track gain to be preferred. Default to album gain only if there
// is no track gain.
ReplayGainMode.TRACK -> { gain -> gain.track == 0f }
// User wants album gain to be preferred. Default to track gain only if there
// is no album gain.
ReplayGainMode.ALBUM -> { gain -> gain.album != 0f }
// User wants album gain to be used when in an album, track gain otherwise.
ReplayGainMode.DYNAMIC -> { _ ->
playbackManager.parent is Album &&
playbackManager.song?.album == playbackManager.parent
}
}
val gain = parseReplayGain(metadata)
val adjust =
if (gain != null) {
if (useAlbumGain(gain)) {
// ReplayGain is configurable, so determine what to do based off of the mode.
val useAlbumGain =
when (settingsManager.replayGainMode) {
ReplayGainMode.OFF -> error("Unreachable")
// User wants track gain to be preferred. Default to album gain only if
// there is no track gain.
ReplayGainMode.TRACK -> gain.track == 0f
// User wants album gain to be preferred. Default to track gain only if
// here is no album gain.
ReplayGainMode.ALBUM -> gain.album != 0f
// User wants album gain to be used when in an album, track gain otherwise.
ReplayGainMode.DYNAMIC ->
playbackManager.parent is Album &&
playbackManager.song?.album?.id == playbackManager.parent?.id
}
if (useAlbumGain) {
logD("Using album gain")
gain.album
} else {
@ -177,6 +175,7 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
}
private fun parseReplayGainFloat(raw: String): Float {
// Grok a float from a ReplayGain tag by removing everything that is not 0-9, , or -.
return try {
raw.replace(Regex("[^0-9.-]"), "").toFloat()
} catch (e: Exception) {
@ -211,11 +210,11 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
}
} else {
for (i in position until limit step 2) {
var sample = inputBuffer.getLeShort(i)
// Clamp the values to the minimum and maximum values possible for the
// encoding. This prevents issues where samples amplified beyond 1 << 16
// will end up becoming truncated during the conversion to a short,
// Ensure we clamp the values to the minimum and maximum values possible
// for the encoding. This prevents issues where samples amplified beyond
// 1 << 16 will end up becoming truncated during the conversion to a short,
// resulting in popping.
var sample = inputBuffer.getLeShort(i)
sample =
(sample * volume)
.toInt()

View file

@ -61,19 +61,19 @@ class WidgetController(private val context: Context) :
// --- PLAYBACKSTATEMANAGER CALLBACKS ---
override fun onSongUpdate(song: Song?) {
override fun onSongChanged(song: Song?) {
widget.update(context, playbackManager)
}
override fun onPlayingUpdate(isPlaying: Boolean) {
override fun onPlayingChanged(isPlaying: Boolean) {
widget.update(context, playbackManager)
}
override fun onShuffleUpdate(isShuffling: Boolean) {
override fun onShuffleChanged(isShuffling: Boolean) {
widget.update(context, playbackManager)
}
override fun onLoopUpdate(loopMode: LoopMode) {
override fun onLoopModeChanged(loopMode: LoopMode) {
widget.update(context, playbackManager)
}