From 43e01e839d1c8010681bf1d70b97e4d53c8a6d32 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Mon, 27 Dec 2021 18:34:43 -0700 Subject: [PATCH] playback: misc changes I just spent 5 days trying to implement gapless playback using ExoPlayer and Hopium. That's 5 days I'm never getting back. Heres what I did add in the process though. --- .../auxio/playback/PlaybackFragment.kt | 4 +- .../auxio/playback/PlaybackViewModel.kt | 48 +++++++------------ .../auxio/playback/queue/QueueFragment.kt | 2 +- .../playback/state/PlaybackStateManager.kt | 41 +++++++--------- .../auxio/playback/system/PlaybackService.kt | 38 +-------------- info/ADDITIONS.md | 15 +++--- 6 files changed, 48 insertions(+), 100 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt index 3d11de7a7..7cd975b0e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -131,10 +131,10 @@ class PlaybackFragment : Fragment() { binding.playbackSeekBar.setProgress(pos) } - playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { + playbackModel.nextUp.observe(viewLifecycleOwner) { // The queue icon uses a selector that will automatically tint the icon as active or // inactive. We just need to set the flag. - queueItem.isEnabled = playbackModel.nextItemsInQueue.value!!.isNotEmpty() + queueItem.isEnabled = playbackModel.nextUp.value!!.isNotEmpty() } playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying -> 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 9fd4bf613..b09592c49 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -22,7 +22,6 @@ import android.content.Context import android.net.Uri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch @@ -50,17 +49,16 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { // Playback private val mSong = MutableLiveData() private val mParent = MutableLiveData() - private val mPosition = MutableLiveData(0L) - - // Queue - private val mQueue = MutableLiveData(listOf()) - private val mIndex = MutableLiveData(0) - private val mMode = MutableLiveData(PlaybackMode.ALL_SONGS) // States private val mIsPlaying = MutableLiveData(false) private val mIsShuffling = MutableLiveData(false) private val mLoopMode = MutableLiveData(LoopMode.NONE) + private val mPosition = MutableLiveData(0L) + + // Queue + private val mNextUp = MutableLiveData(listOf()) + private val mMode = MutableLiveData(PlaybackMode.ALL_SONGS) // Other private var mIntentUri: Uri? = null @@ -69,23 +67,18 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { val song: LiveData get() = mSong /** The current model that is being played from, such as an [Album] or [Artist] */ val parent: LiveData get() = mParent - /** The current playback position, in seconds */ - val position: LiveData get() = mPosition - - /** The current queue determined by [playbackMode] and [parent] */ - val queue: LiveData> get() = mQueue - /** The current [PlaybackMode] that also determines the queue */ - val playbackMode: LiveData get() = mMode val isPlaying: LiveData get() = mIsPlaying val isShuffling: LiveData get() = mIsShuffling /** The current repeat mode, see [LoopMode] for more information */ val loopMode: LiveData get() = mLoopMode + /** The current playback position, in seconds */ + val position: LiveData get() = mPosition /** The queue, without the previous items. */ - val nextItemsInQueue = Transformations.map(queue) { queue -> - queue.slice((mIndex.value!! + 1) until queue.size) - } + val nextUp: LiveData> get() = mNextUp + /** The current [PlaybackMode] that also determines the queue */ + val playbackMode: LiveData get() = mMode private val playbackManager = PlaybackStateManager.maybeGetInstance() private val settingsManager = SettingsManager.getInstance() @@ -220,9 +213,9 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { * [apply] is called just before the change is committed so that the adapter can be updated. */ fun removeQueueDataItem(adapterIndex: Int, apply: () -> Unit) { - val adjusted = adapterIndex + (mQueue.value!!.size - nextItemsInQueue.value!!.size) + val adjusted = adapterIndex + (playbackManager.queue.size - mNextUp.value!!.size) - if (adjusted in mQueue.value!!.indices) { + if (adjusted in mNextUp.value!!.indices) { apply() playbackManager.removeQueueItem(adjusted) } @@ -232,12 +225,12 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { * [apply] is called just before the change is committed so that the adapter can be updated. */ fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int, apply: () -> Unit): Boolean { - val delta = (mQueue.value!!.size - nextItemsInQueue.value!!.size) + val delta = (playbackManager.queue.size - mNextUp.value!!.size) val from = adapterFrom + delta val to = adapterTo + delta - if (from in mQueue.value!!.indices && to in mQueue.value!!.indices) { + if (from in mNextUp.value!!.indices && to in mNextUp.value!!.indices) { apply() playbackManager.moveQueueItems(from, to) return true @@ -287,7 +280,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { * Flip the shuffle status, e.g from on to off. Will keep song by default. */ fun invertShuffleStatus() { - playbackManager.setShuffling(!playbackManager.isShuffling, keepSong = true) + playbackManager.setShuffling(!playbackManager.isShuffling, true) } /** @@ -343,9 +336,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { mSong.value = playbackManager.song mPosition.value = playbackManager.position / 1000 mParent.value = playbackManager.parent - mQueue.value = playbackManager.queue + mNextUp.value = playbackManager.queue mMode.value = playbackManager.playbackMode - mIndex.value = playbackManager.index mIsPlaying.value = playbackManager.isPlaying mIsShuffling.value = playbackManager.isShuffling mLoopMode.value = playbackManager.loopMode @@ -369,12 +361,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { mPosition.value = position / 1000 } - override fun onQueueUpdate(queue: List) { - mQueue.value = queue - } - - override fun onIndexUpdate(index: Int) { - mIndex.value = index + override fun onQueueUpdate(queue: List, index: Int) { + mNextUp.value = queue.slice(index.inc() until queue.size) } override fun onModeUpdate(mode: PlaybackMode) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index 2150fbcaa..cc9d2bb27 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -67,7 +67,7 @@ class QueueFragment : Fragment() { // --- VIEWMODEL SETUP ---- - playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { queue -> + playbackModel.nextUp.observe(viewLifecycleOwner) { queue -> if (queue.isEmpty()) { findNavController().navigateUp() return@observe 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 e92aad70d..f42487a9e 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 @@ -63,15 +63,7 @@ class PlaybackStateManager private constructor() { // Queue private var mQueue = mutableListOf() - set(value) { - field = value - callbacks.forEach { it.onQueueUpdate(value) } - } private var mIndex = 0 - set(value) { - field = value - callbacks.forEach { it.onIndexUpdate(value) } - } private var mPlaybackMode = PlaybackMode.ALL_SONGS set(value) { field = value @@ -107,8 +99,6 @@ class PlaybackStateManager private constructor() { val position: Long get() = mPosition /** The current queue determined by [parent] and [playbackMode] */ val queue: List get() = mQueue - /** The current index of the queue */ - val index: Int get() = mIndex /** The current [PlaybackMode] */ val playbackMode: PlaybackMode get() = mPlaybackMode /** Whether playback is paused or not */ @@ -263,7 +253,7 @@ class PlaybackStateManager private constructor() { updatePlayback(mQueue[mIndex], shouldPlay = mLoopMode == LoopMode.ALL) } - forceQueueUpdate() + pushQueueUpdate() } /** @@ -280,7 +270,7 @@ class PlaybackStateManager private constructor() { } updatePlayback(mQueue[mIndex]) - forceQueueUpdate() + pushQueueUpdate() } } @@ -300,7 +290,7 @@ class PlaybackStateManager private constructor() { mQueue.removeAt(index) - forceQueueUpdate() + pushQueueUpdate() return true } @@ -318,7 +308,7 @@ class PlaybackStateManager private constructor() { val item = mQueue.removeAt(from) mQueue.add(to, item) - forceQueueUpdate() + pushQueueUpdate() return true } @@ -328,7 +318,7 @@ class PlaybackStateManager private constructor() { */ fun playNext(song: Song) { mQueue.add(min(mIndex + 1, max(mQueue.lastIndex, 0)), song) - forceQueueUpdate() + pushQueueUpdate() } /** @@ -336,7 +326,7 @@ class PlaybackStateManager private constructor() { */ fun playNext(songs: List) { mQueue.addAll(min(mIndex + 1, max(mQueue.lastIndex, 0)), songs) - forceQueueUpdate() + pushQueueUpdate() } /** @@ -344,7 +334,7 @@ class PlaybackStateManager private constructor() { */ fun addToQueue(song: Song) { mQueue.add(song) - forceQueueUpdate() + pushQueueUpdate() } /** @@ -352,14 +342,16 @@ class PlaybackStateManager private constructor() { */ fun addToQueue(songs: List) { mQueue.addAll(songs) - forceQueueUpdate() + pushQueueUpdate() } /** * Force any callbacks to receive a queue update. */ - private fun forceQueueUpdate() { - mQueue = mQueue + private fun pushQueueUpdate() { + callbacks.forEach { + it.onQueueUpdate(mQueue, mIndex) + } } // --- SHUFFLE FUNCTIONS --- @@ -398,7 +390,7 @@ class PlaybackStateManager private constructor() { mSong = mQueue[0] } - forceQueueUpdate() + pushQueueUpdate() } /** @@ -424,7 +416,7 @@ class PlaybackStateManager private constructor() { mIndex = mQueue.indexOf(lastSong) } - forceQueueUpdate() + pushQueueUpdate() } // --- STATE FUNCTIONS --- @@ -603,7 +595,7 @@ class PlaybackStateManager private constructor() { } } - forceQueueUpdate() + pushQueueUpdate() } /** @@ -632,9 +624,8 @@ class PlaybackStateManager private constructor() { fun onSongUpdate(song: Song?) {} fun onParentUpdate(parent: MusicParent?) {} fun onPositionUpdate(position: Long) {} - fun onQueueUpdate(queue: List) {} + fun onQueueUpdate(queue: List, index: Int) {} fun onModeUpdate(mode: PlaybackMode) {} - fun onIndexUpdate(index: Int) {} fun onPlayingUpdate(isPlaying: Boolean) {} fun onShuffleUpdate(isShuffling: Boolean) {} fun onLoopUpdate(loopMode: LoopMode) {} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 02e23b509..218a0c41f 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -29,7 +29,6 @@ import android.content.pm.ServiceInfo import android.media.AudioManager import android.os.Build import android.os.IBinder -import android.os.PowerManager import android.support.v4.media.session.MediaSessionCompat import com.google.android.exoplayer2.C import com.google.android.exoplayer2.ExoPlayer @@ -87,7 +86,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac // System backend components private lateinit var audioReactor: AudioReactor - private lateinit var wakeLock: PowerManager.WakeLock private lateinit var widgets: WidgetController private val systemReceiver = SystemEventReceiver() @@ -131,10 +129,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac // --- SYSTEM SETUP --- audioReactor = AudioReactor(this, player) - wakeLock = getSystemServiceSafe(PowerManager::class).newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, this::class.simpleName - ) - widgets = WidgetController(this) // Set up the media button callbacks @@ -195,7 +189,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac mediaSession.release() audioReactor.release() widgets.release() - releaseWakelock() playbackManager.removeCallback(this) settingsManager.removeCallback(this) @@ -216,10 +209,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac override fun onPlaybackStateChanged(state: Int) { when (state) { - Player.STATE_READY -> { - startPollingPosition() - releaseWakelock() - } + Player.STATE_READY -> startPollingPosition() Player.STATE_ENDED -> { if (playbackManager.loopMode == LoopMode.TRACK) { @@ -233,11 +223,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac } } - override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { - // We use the wakelock to ensure that the CPU is active while music is being loaded - acquireWakeLock() - } - override fun onPlayerError(error: PlaybackException) { // If there's any issue, just go to the next song. playbackManager.next() @@ -357,6 +342,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac return ExoPlayer.Builder(this, audioRenderer) .setMediaSourceFactory(DefaultMediaSourceFactory(this, extractorsFactory)) + .setWakeMode(C.WAKE_MODE_LOCAL) .build() } @@ -434,26 +420,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac isForeground = false } - /** - * Hold the wakelock for the default amount of time [25 Seconds] - */ - private fun acquireWakeLock() { - wakeLock.acquire(WAKELOCK_TIME) - - logD("Wakelock is held.") - } - - /** - * Release the wakelock if its currently being held. - */ - private fun releaseWakelock() { - if (wakeLock.isHeld) { - wakeLock.release() - - logD("Wakelock is released.") - } - } - /** * A [BroadcastReceiver] for receiving system events from notifications, widgets, or * headset plug events. diff --git a/info/ADDITIONS.md b/info/ADDITIONS.md index 5a0742964..83ab0f60d 100644 --- a/info/ADDITIONS.md +++ b/info/ADDITIONS.md @@ -21,9 +21,12 @@ This does not rule out these additions, but they are not accepted as often as ot Feel free to fork Auxio to add your own feature set however. -#### Additions that have already been rejected: -- ReplayGain [#7] -- Folder View/Grouping [#10] -- Recently added list [#18] -- Lyrics [#19] -- Tag editing [#33] +#### Additions that have already been rejected (and why): + +- Folder View/Grouping [#10] (Out of scope) +- Recently added list [#18] (Out of scope) +- Lyrics [#19] (Out of scope) +- Tag editing [#33] (Out of scope) +- Gapless Playback [#35] (Technical issues) +- Reduce leading instrument [#45] (Technical issues, Out of scope) +- Opening music through a provider [#30] (Out of scope) \ No newline at end of file