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 - Made the layout of album songs more similar to other songs
#### Dev/Meta #### 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 - Switched to spotless and ktfmt instead of ktlint
- Migrated constants to centralized table - Migrated constants to centralized table
- Introduced new RecyclerView framework - 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 isAutoMirrored(): Boolean = true
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
override fun setAlpha(alpha: Int) {} override fun setAlpha(alpha: Int) {}
override fun setColorFilter(colorFilter: ColorFilter?) {} override fun setColorFilter(colorFilter: ColorFilter?) {}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
private fun updatePath() { private fun updatePath() {
val r = bounds.height().toFloat() / 2 val r = bounds.height().toFloat() / 2
@ -168,6 +168,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0)
} }
companion object { companion object {
// Cache sqrt(2) for faster calculations
private const val SQRT2 = 1.4142135623730950488f private const val SQRT2 = 1.4142135623730950488f
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -64,41 +64,39 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
* Vanilla Music's implementation. * Vanilla Music's implementation.
*/ */
fun applyReplayGain(metadata: Metadata?) { fun applyReplayGain(metadata: Metadata?) {
if (metadata == null) { if (metadata == null || settingsManager.replayGainMode == ReplayGainMode.OFF) {
logW("No metadata could be extracted from this track") logW(
"Not applying replaygain [" +
"metadata: ${metadata != null}, " +
"enabled: ${settingsManager.replayGainMode == ReplayGainMode.OFF}]")
volume = 1f volume = 1f
return 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 gain = parseReplayGain(metadata)
val adjust = val adjust =
if (gain != null) { 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") logD("Using album gain")
gain.album gain.album
} else { } else {
@ -177,6 +175,7 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
} }
private fun parseReplayGainFloat(raw: String): Float { private fun parseReplayGainFloat(raw: String): Float {
// Grok a float from a ReplayGain tag by removing everything that is not 0-9, , or -.
return try { return try {
raw.replace(Regex("[^0-9.-]"), "").toFloat() raw.replace(Regex("[^0-9.-]"), "").toFloat()
} catch (e: Exception) { } catch (e: Exception) {
@ -211,11 +210,11 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
} }
} else { } else {
for (i in position until limit step 2) { for (i in position until limit step 2) {
var sample = inputBuffer.getLeShort(i) // Ensure we clamp the values to the minimum and maximum values possible
// Clamp the values to the minimum and maximum values possible for the // for the encoding. This prevents issues where samples amplified beyond
// encoding. This prevents issues where samples amplified beyond 1 << 16 // 1 << 16 will end up becoming truncated during the conversion to a short,
// will end up becoming truncated during the conversion to a short,
// resulting in popping. // resulting in popping.
var sample = inputBuffer.getLeShort(i)
sample = sample =
(sample * volume) (sample * volume)
.toInt() .toInt()

View file

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