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.
This commit is contained in:
parent
25dd276bd8
commit
43e01e839d
6 changed files with 48 additions and 100 deletions
|
@ -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 ->
|
||||
|
|
|
@ -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<Song?>()
|
||||
private val mParent = MutableLiveData<MusicParent?>()
|
||||
private val mPosition = MutableLiveData(0L)
|
||||
|
||||
// Queue
|
||||
private val mQueue = MutableLiveData(listOf<Song>())
|
||||
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<Song>())
|
||||
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<Song?> get() = mSong
|
||||
/** The current model that is being played from, such as an [Album] or [Artist] */
|
||||
val parent: LiveData<MusicParent?> get() = mParent
|
||||
/** The current playback position, in seconds */
|
||||
val position: LiveData<Long> get() = mPosition
|
||||
|
||||
/** The current queue determined by [playbackMode] and [parent] */
|
||||
val queue: LiveData<List<Song>> get() = mQueue
|
||||
/** The current [PlaybackMode] that also determines the queue */
|
||||
val playbackMode: LiveData<PlaybackMode> get() = mMode
|
||||
|
||||
val isPlaying: LiveData<Boolean> get() = mIsPlaying
|
||||
val isShuffling: LiveData<Boolean> get() = mIsShuffling
|
||||
/** The current repeat mode, see [LoopMode] for more information */
|
||||
val loopMode: LiveData<LoopMode> get() = mLoopMode
|
||||
/** The current playback position, in seconds */
|
||||
val position: LiveData<Long> 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<List<Song>> get() = mNextUp
|
||||
/** The current [PlaybackMode] that also determines the queue */
|
||||
val playbackMode: LiveData<PlaybackMode> 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<Song>) {
|
||||
mQueue.value = queue
|
||||
}
|
||||
|
||||
override fun onIndexUpdate(index: Int) {
|
||||
mIndex.value = index
|
||||
override fun onQueueUpdate(queue: List<Song>, index: Int) {
|
||||
mNextUp.value = queue.slice(index.inc() until queue.size)
|
||||
}
|
||||
|
||||
override fun onModeUpdate(mode: PlaybackMode) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -63,15 +63,7 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
// Queue
|
||||
private var mQueue = mutableListOf<Song>()
|
||||
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<Song> 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<Song>) {
|
||||
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<Song>) {
|
||||
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<Song>) {}
|
||||
fun onQueueUpdate(queue: List<Song>, index: Int) {}
|
||||
fun onModeUpdate(mode: PlaybackMode) {}
|
||||
fun onIndexUpdate(index: Int) {}
|
||||
fun onPlayingUpdate(isPlaying: Boolean) {}
|
||||
fun onShuffleUpdate(isShuffling: Boolean) {}
|
||||
fun onLoopUpdate(loopMode: LoopMode) {}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
Loading…
Reference in a new issue