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:
parent
60367c45c3
commit
2bdbe212df
10 changed files with 186 additions and 158 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue