playback: rework controller into InternalPlayer
Rework the Controller interface into a standalone interface called InternalPlayer. This is mostly preparation for further changes.
This commit is contained in:
parent
8a15868ba1
commit
4d02dfb578
8 changed files with 120 additions and 101 deletions
|
@ -28,7 +28,7 @@ import androidx.core.view.updatePadding
|
||||||
import org.oxycblt.auxio.databinding.ActivityMainBinding
|
import org.oxycblt.auxio.databinding.ActivityMainBinding
|
||||||
import org.oxycblt.auxio.music.system.IndexerService
|
import org.oxycblt.auxio.music.system.IndexerService
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.InternalPlayer
|
||||||
import org.oxycblt.auxio.playback.system.PlaybackService
|
import org.oxycblt.auxio.playback.system.PlaybackService
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.util.androidViewModels
|
import org.oxycblt.auxio.util.androidViewModels
|
||||||
|
@ -70,17 +70,17 @@ class MainActivity : AppCompatActivity() {
|
||||||
startService(Intent(this, IndexerService::class.java))
|
startService(Intent(this, IndexerService::class.java))
|
||||||
startService(Intent(this, PlaybackService::class.java))
|
startService(Intent(this, PlaybackService::class.java))
|
||||||
|
|
||||||
if (!startIntentDelayedAction(intent)) {
|
if (!startIntentAction(intent)) {
|
||||||
playbackModel.startAction(PlaybackStateManager.ControllerAction.RestoreState)
|
playbackModel.startAction(InternalPlayer.Action.RestoreState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
override fun onNewIntent(intent: Intent?) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
startIntentDelayedAction(intent)
|
startIntentAction(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startIntentDelayedAction(intent: Intent?): Boolean {
|
private fun startIntentAction(intent: Intent?): Boolean {
|
||||||
if (intent == null) {
|
if (intent == null) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -97,10 +97,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
val action =
|
val action =
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
Intent.ACTION_VIEW ->
|
Intent.ACTION_VIEW -> InternalPlayer.Action.Open(intent.data ?: return false)
|
||||||
PlaybackStateManager.ControllerAction.Open(intent.data ?: return false)
|
|
||||||
AuxioApp.INTENT_KEY_SHORTCUT_SHUFFLE -> {
|
AuxioApp.INTENT_KEY_SHORTCUT_SHUFFLE -> {
|
||||||
PlaybackStateManager.ControllerAction.ShuffleAll
|
InternalPlayer.Action.ShuffleAll
|
||||||
}
|
}
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,11 +104,7 @@ class MusicStore private constructor() {
|
||||||
/** Sanitize an old item to find the corresponding item in a new library. */
|
/** Sanitize an old item to find the corresponding item in a new library. */
|
||||||
fun sanitize(genre: Genre) = findGenreById(genre.id)
|
fun sanitize(genre: Genre) = findGenreById(genre.id)
|
||||||
|
|
||||||
/**
|
/** Find a song for a [uri]. */
|
||||||
* Find a song for a [uri], this is similar to [findSong], but with some kind of content
|
|
||||||
* uri.
|
|
||||||
* @return The corresponding [Song] for this [uri], null if there isn't one.
|
|
||||||
*/
|
|
||||||
fun findSongForUri(context: Context, uri: Uri) =
|
fun findSongForUri(context: Context, uri: Uri) =
|
||||||
context.contentResolverSafe.useQuery(uri, arrayOf(OpenableColumns.DISPLAY_NAME)) {
|
context.contentResolverSafe.useQuery(uri, arrayOf(OpenableColumns.DISPLAY_NAME)) {
|
||||||
cursor ->
|
cursor ->
|
||||||
|
|
|
@ -47,7 +47,6 @@ class MusicViewModel : ViewModel(), Indexer.Callback {
|
||||||
|
|
||||||
override fun onIndexerStateChanged(state: Indexer.State?) {
|
override fun onIndexerStateChanged(state: Indexer.State?) {
|
||||||
_indexerState.value = state
|
_indexerState.value = state
|
||||||
|
|
||||||
if (state is Indexer.State.Complete && state.response is Indexer.Response.Ok) {
|
if (state is Indexer.State.Complete && state.response is Indexer.Response.Ok) {
|
||||||
_libraryExists.value = true
|
_libraryExists.value = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.MusicParent
|
import org.oxycblt.auxio.music.MusicParent
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
import org.oxycblt.auxio.playback.state.InternalPlayer
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
|
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
|
@ -63,6 +64,7 @@ class PlaybackViewModel(application: Application) :
|
||||||
/** The current playback position, in seconds */
|
/** The current playback position, in seconds */
|
||||||
val positionSecs: StateFlow<Long>
|
val positionSecs: StateFlow<Long>
|
||||||
get() = _positionSecs
|
get() = _positionSecs
|
||||||
|
|
||||||
private val _repeatMode = MutableStateFlow(RepeatMode.NONE)
|
private val _repeatMode = MutableStateFlow(RepeatMode.NONE)
|
||||||
/** The current repeat mode, see [RepeatMode] for more information */
|
/** The current repeat mode, see [RepeatMode] for more information */
|
||||||
val repeatMode: StateFlow<RepeatMode>
|
val repeatMode: StateFlow<RepeatMode>
|
||||||
|
@ -130,15 +132,15 @@ class PlaybackViewModel(application: Application) :
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform the given [PlaybackStateManager.ControllerAction].
|
* Perform the given [InternalPlayer.Action].
|
||||||
*
|
*
|
||||||
* A "controller action" is a class of playback actions that must have music present to
|
* These are a class of playback actions that must have music present to function, usually
|
||||||
* function, usually alongside a context too. Examples include:
|
* alongside a context too. Examples include:
|
||||||
* - Opening files
|
* - Opening files
|
||||||
* - Restoring the playback state
|
* - Restoring the playback state
|
||||||
* - App shortcuts
|
* - App shortcuts
|
||||||
*/
|
*/
|
||||||
fun startAction(action: PlaybackStateManager.ControllerAction) {
|
fun startAction(action: InternalPlayer.Action) {
|
||||||
playbackManager.startAction(action)
|
playbackManager.startAction(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 Auxio Project
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.oxycblt.auxio.playback.state
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import org.oxycblt.auxio.music.Song
|
||||||
|
|
||||||
|
/** Represents a class capable of managing the internal player. */
|
||||||
|
interface InternalPlayer {
|
||||||
|
/** The audio session ID of the player instance. */
|
||||||
|
val audioSessionId: Int
|
||||||
|
|
||||||
|
/** Whether the player should rewind instead of going to the previous song. */
|
||||||
|
val shouldRewindWithPrev: Boolean
|
||||||
|
|
||||||
|
/** Called when a new song should be loaded into the player. */
|
||||||
|
fun loadSong(song: Song?)
|
||||||
|
|
||||||
|
/** Seek to [positionMs] in the player. */
|
||||||
|
fun seekTo(positionMs: Long)
|
||||||
|
|
||||||
|
/** Called when the playing state is changed. */
|
||||||
|
fun onPlayingChanged(isPlaying: Boolean)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when [PlaybackStateManager] desires some [Action] to be completed. Returns true if the
|
||||||
|
* action was consumed, false otherwise.
|
||||||
|
*/
|
||||||
|
fun onAction(action: Action): Boolean
|
||||||
|
|
||||||
|
sealed class Action {
|
||||||
|
object RestoreState : Action()
|
||||||
|
object ShuffleAll : Action()
|
||||||
|
data class Open(val uri: Uri) : Action()
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.playback.state
|
package org.oxycblt.auxio.playback.state
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -46,7 +45,7 @@ import org.oxycblt.auxio.util.logW
|
||||||
* [org.oxycblt.auxio.playback.system.PlaybackService].
|
* [org.oxycblt.auxio.playback.system.PlaybackService].
|
||||||
*
|
*
|
||||||
* Internal consumers should usually use [Callback], however the component that manages the player
|
* Internal consumers should usually use [Callback], however the component that manages the player
|
||||||
* itself should instead operate as a [Controller].
|
* itself should instead operate as a [InternalPlayer].
|
||||||
*
|
*
|
||||||
* All access should be done with [PlaybackStateManager.getInstance].
|
* All access should be done with [PlaybackStateManager.getInstance].
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
|
@ -90,17 +89,17 @@ class PlaybackStateManager private constructor() {
|
||||||
var isInitialized = false
|
var isInitialized = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
/** The current audio session ID of the controller. Null if no controller present. */
|
/** The current audio session ID of the internal player. Null if no internal player present. */
|
||||||
val currentAudioSessionId: Int?
|
val currentAudioSessionId: Int?
|
||||||
get() = controller?.audioSessionId
|
get() = internalPlayer?.audioSessionId
|
||||||
|
|
||||||
/** An action that is awaiting the controller instance to consume it. */
|
/** An action that is awaiting the internal player instance to consume it. */
|
||||||
var pendingAction: ControllerAction? = null
|
var pendingAction: InternalPlayer.Action? = null
|
||||||
|
|
||||||
// --- CALLBACKS ---
|
// --- CALLBACKS ---
|
||||||
|
|
||||||
private val callbacks = mutableListOf<Callback>()
|
private val callbacks = mutableListOf<Callback>()
|
||||||
private var controller: Controller? = null
|
private var internalPlayer: InternalPlayer? = null
|
||||||
|
|
||||||
/** Add a callback to this instance. Make sure to remove it when done. */
|
/** Add a callback to this instance. Make sure to remove it when done. */
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
@ -122,33 +121,33 @@ class PlaybackStateManager private constructor() {
|
||||||
callbacks.remove(callback)
|
callbacks.remove(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Register a [Controller] with this instance. */
|
/** Register a [InternalPlayer] with this instance. */
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun registerController(controller: Controller) {
|
fun registerInternalPlayer(internalPlayer: InternalPlayer) {
|
||||||
if (BuildConfig.DEBUG && this.controller != null) {
|
if (BuildConfig.DEBUG && this.internalPlayer != null) {
|
||||||
logW("Controller is already registered")
|
logW("Internal player is already registered")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInitialized) {
|
if (isInitialized) {
|
||||||
controller.loadSong(song)
|
internalPlayer.loadSong(song)
|
||||||
controller.seekTo(positionMs)
|
internalPlayer.seekTo(positionMs)
|
||||||
controller.onPlayingChanged(isPlaying)
|
internalPlayer.onPlayingChanged(isPlaying)
|
||||||
requestAction(controller)
|
requestAction(internalPlayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.controller = controller
|
this.internalPlayer = internalPlayer
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Unregister a [Controller] with this instance. */
|
/** Unregister a [InternalPlayer] with this instance. */
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun unregisterController(controller: Controller) {
|
fun unregisterInternalPlayer(internalPlayer: InternalPlayer) {
|
||||||
if (BuildConfig.DEBUG && this.controller !== controller) {
|
if (BuildConfig.DEBUG && this.internalPlayer !== internalPlayer) {
|
||||||
logW("Given controller did not match current controller")
|
logW("Given internal player did not match current internal player")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.controller = null
|
this.internalPlayer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- PLAYING FUNCTIONS ---
|
// --- PLAYING FUNCTIONS ---
|
||||||
|
@ -215,7 +214,7 @@ class PlaybackStateManager private constructor() {
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun prev() {
|
fun prev() {
|
||||||
// If enabled, rewind before skipping back if the position is past 3 seconds [3000ms]
|
// If enabled, rewind before skipping back if the position is past 3 seconds [3000ms]
|
||||||
if (controller?.shouldPrevRewind() == true) {
|
if (internalPlayer?.shouldRewindWithPrev == true) {
|
||||||
rewind()
|
rewind()
|
||||||
isPlaying = true
|
isPlaying = true
|
||||||
} else {
|
} else {
|
||||||
|
@ -326,13 +325,13 @@ class PlaybackStateManager private constructor() {
|
||||||
isShuffled = shuffled
|
isShuffled = shuffled
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CONTROLLER FUNCTIONS ---
|
// --- INTERNAL PLAYER FUNCTIONS ---
|
||||||
|
|
||||||
/** Update the current [positionMs]. Only meant for use by [Controller] */
|
/** Update the current [positionMs]. Only meant for use by [InternalPlayer] */
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun synchronizePosition(controller: Controller, positionMs: Long) {
|
fun synchronizePosition(internalPlayer: InternalPlayer, positionMs: Long) {
|
||||||
if (BuildConfig.DEBUG && this.controller !== controller) {
|
if (BuildConfig.DEBUG && this.internalPlayer !== internalPlayer) {
|
||||||
logW("Given controller did not match current controller")
|
logW("Given internal player did not match current internal player")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,23 +344,23 @@ class PlaybackStateManager private constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun startAction(action: ControllerAction) {
|
fun startAction(action: InternalPlayer.Action) {
|
||||||
val controller = controller
|
val internalPlayer = internalPlayer
|
||||||
if (controller == null || !controller.onAction(action)) {
|
if (internalPlayer == null || !internalPlayer.onAction(action)) {
|
||||||
logD("Controller not present or did not consume action, ignoring.")
|
logD("Internal player not present or did not consume action, ignoring")
|
||||||
pendingAction = action
|
pendingAction = action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Request the stored [Controller.Action] */
|
/** Request the stored [InternalPlayer.Action] */
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun requestAction(controller: Controller) {
|
fun requestAction(internalPlayer: InternalPlayer) {
|
||||||
if (BuildConfig.DEBUG && this.controller !== controller) {
|
if (BuildConfig.DEBUG && this.internalPlayer !== internalPlayer) {
|
||||||
logW("Given controller did not match current controller")
|
logW("Given internal player did not match current internal player")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pendingAction?.let(controller::onAction) == true) {
|
if (pendingAction?.let(internalPlayer::onAction) == true) {
|
||||||
logD("Pending action consumed")
|
logD("Pending action consumed")
|
||||||
pendingAction = null
|
pendingAction = null
|
||||||
}
|
}
|
||||||
|
@ -374,7 +373,7 @@ class PlaybackStateManager private constructor() {
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun seekTo(positionMs: Long) {
|
fun seekTo(positionMs: Long) {
|
||||||
this.positionMs = positionMs
|
this.positionMs = positionMs
|
||||||
controller?.seekTo(positionMs)
|
internalPlayer?.seekTo(positionMs)
|
||||||
notifyPositionChanged()
|
notifyPositionChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,7 +466,7 @@ class PlaybackStateManager private constructor() {
|
||||||
notifyNewPlayback()
|
notifyNewPlayback()
|
||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
// Controller may have reloaded the media item, re-seek to the previous position
|
// Internal player may have reloaded the media item, re-seek to the previous position
|
||||||
seekTo(oldPosition)
|
seekTo(oldPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -484,7 +483,7 @@ class PlaybackStateManager private constructor() {
|
||||||
// --- CALLBACKS ---
|
// --- CALLBACKS ---
|
||||||
|
|
||||||
private fun notifyIndexMoved() {
|
private fun notifyIndexMoved() {
|
||||||
controller?.loadSong(song)
|
internalPlayer?.loadSong(song)
|
||||||
for (callback in callbacks) {
|
for (callback in callbacks) {
|
||||||
callback.onIndexMoved(index)
|
callback.onIndexMoved(index)
|
||||||
}
|
}
|
||||||
|
@ -503,14 +502,14 @@ class PlaybackStateManager private constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyNewPlayback() {
|
private fun notifyNewPlayback() {
|
||||||
controller?.loadSong(song)
|
internalPlayer?.loadSong(song)
|
||||||
for (callback in callbacks) {
|
for (callback in callbacks) {
|
||||||
callback.onNewPlayback(index, queue, parent)
|
callback.onNewPlayback(index, queue, parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyPlayingChanged() {
|
private fun notifyPlayingChanged() {
|
||||||
controller?.onPlayingChanged(isPlaying)
|
internalPlayer?.onPlayingChanged(isPlaying)
|
||||||
for (callback in callbacks) {
|
for (callback in callbacks) {
|
||||||
callback.onPlayingChanged(isPlaying)
|
callback.onPlayingChanged(isPlaying)
|
||||||
}
|
}
|
||||||
|
@ -534,35 +533,6 @@ class PlaybackStateManager private constructor() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Represents a class capable of managing the internal player. */
|
|
||||||
interface Controller {
|
|
||||||
val audioSessionId: Int
|
|
||||||
|
|
||||||
/** Called when a new song should be loaded into the player. */
|
|
||||||
fun loadSong(song: Song?)
|
|
||||||
|
|
||||||
/** Seek to [positionMs] in the player. */
|
|
||||||
fun seekTo(positionMs: Long)
|
|
||||||
|
|
||||||
/** Called when the class wants to determine whether it should rewind or skip back. */
|
|
||||||
fun shouldPrevRewind(): Boolean
|
|
||||||
|
|
||||||
/** Called when the playing state is changed. */
|
|
||||||
fun onPlayingChanged(isPlaying: Boolean)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when [PlaybackStateManager] desires some [ControllerAction] to be completed.
|
|
||||||
* Returns true if the action was consumed, false otherwise.
|
|
||||||
*/
|
|
||||||
fun onAction(action: ControllerAction): Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class ControllerAction {
|
|
||||||
object RestoreState : ControllerAction()
|
|
||||||
object ShuffleAll : ControllerAction()
|
|
||||||
data class Open(val uri: Uri) : ControllerAction()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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].
|
||||||
|
@ -583,7 +553,7 @@ class PlaybackStateManager private constructor() {
|
||||||
/** Called when the playing state is changed. */
|
/** Called when the playing state is changed. */
|
||||||
fun onPlayingChanged(isPlaying: Boolean) {}
|
fun onPlayingChanged(isPlaying: Boolean) {}
|
||||||
|
|
||||||
/** Called when the position is re-synchronized by the controller. */
|
/** Called when the position is re-synchronized by the internal player. */
|
||||||
fun onPositionChanged(positionMs: Long) {}
|
fun onPositionChanged(positionMs: Long) {}
|
||||||
|
|
||||||
/** Called when the repeat mode is changed. */
|
/** Called when the repeat mode is changed. */
|
||||||
|
|
|
@ -49,6 +49,7 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainAudioProcessor
|
import org.oxycblt.auxio.playback.replaygain.ReplayGainAudioProcessor
|
||||||
|
import org.oxycblt.auxio.playback.state.InternalPlayer
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
|
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||||
|
@ -76,7 +77,7 @@ import org.oxycblt.auxio.widgets.WidgetProvider
|
||||||
class PlaybackService :
|
class PlaybackService :
|
||||||
Service(),
|
Service(),
|
||||||
Player.Listener,
|
Player.Listener,
|
||||||
PlaybackStateManager.Controller,
|
InternalPlayer,
|
||||||
MediaSessionComponent.Callback,
|
MediaSessionComponent.Callback,
|
||||||
Settings.Callback,
|
Settings.Callback,
|
||||||
MusicStore.Callback {
|
MusicStore.Callback {
|
||||||
|
@ -146,7 +147,7 @@ class PlaybackService :
|
||||||
settings = Settings(this, this)
|
settings = Settings(this, this)
|
||||||
foregroundManager = ForegroundManager(this)
|
foregroundManager = ForegroundManager(this)
|
||||||
|
|
||||||
playbackManager.registerController(this)
|
playbackManager.registerInternalPlayer(this)
|
||||||
musicStore.addCallback(this)
|
musicStore.addCallback(this)
|
||||||
positionScope.launch {
|
positionScope.launch {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -198,7 +199,7 @@ class PlaybackService :
|
||||||
// Pause just in case this destruction was unexpected.
|
// Pause just in case this destruction was unexpected.
|
||||||
playbackManager.isPlaying = false
|
playbackManager.isPlaying = false
|
||||||
|
|
||||||
playbackManager.unregisterController(this)
|
playbackManager.unregisterInternalPlayer(this)
|
||||||
settings.release()
|
settings.release()
|
||||||
unregisterReceiver(systemReceiver)
|
unregisterReceiver(systemReceiver)
|
||||||
serviceJob.cancel()
|
serviceJob.cancel()
|
||||||
|
@ -279,6 +280,9 @@ class PlaybackService :
|
||||||
override val audioSessionId: Int
|
override val audioSessionId: Int
|
||||||
get() = player.audioSessionId
|
get() = player.audioSessionId
|
||||||
|
|
||||||
|
override val shouldRewindWithPrev: Boolean
|
||||||
|
get() = settings.rewindWithPrev && player.currentPosition > REWIND_THRESHOLD
|
||||||
|
|
||||||
override fun loadSong(song: Song?) {
|
override fun loadSong(song: Song?) {
|
||||||
if (song == null) {
|
if (song == null) {
|
||||||
// Stop the foreground state if there's nothing to play.
|
// Stop the foreground state if there's nothing to play.
|
||||||
|
@ -332,29 +336,26 @@ class PlaybackService :
|
||||||
player.seekTo(positionMs)
|
player.seekTo(positionMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldPrevRewind() =
|
|
||||||
settings.rewindWithPrev && player.currentPosition > REWIND_THRESHOLD
|
|
||||||
|
|
||||||
override fun onPlayingChanged(isPlaying: Boolean) {
|
override fun onPlayingChanged(isPlaying: Boolean) {
|
||||||
player.playWhenReady = isPlaying
|
player.playWhenReady = isPlaying
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAction(action: PlaybackStateManager.ControllerAction): Boolean {
|
override fun onAction(action: InternalPlayer.Action): Boolean {
|
||||||
val library = musicStore.library
|
val library = musicStore.library
|
||||||
if (library != null) {
|
if (library != null) {
|
||||||
logD("Performing action: $action")
|
logD("Performing action: $action")
|
||||||
|
|
||||||
when (action) {
|
when (action) {
|
||||||
is PlaybackStateManager.ControllerAction.RestoreState -> {
|
is InternalPlayer.Action.RestoreState -> {
|
||||||
restoreScope.launch {
|
restoreScope.launch {
|
||||||
playbackManager.restoreState(
|
playbackManager.restoreState(
|
||||||
PlaybackStateDatabase.getInstance(this@PlaybackService), false)
|
PlaybackStateDatabase.getInstance(this@PlaybackService), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is PlaybackStateManager.ControllerAction.ShuffleAll -> {
|
is InternalPlayer.Action.ShuffleAll -> {
|
||||||
playbackManager.shuffleAll(settings)
|
playbackManager.shuffleAll(settings)
|
||||||
}
|
}
|
||||||
is PlaybackStateManager.ControllerAction.Open -> {
|
is InternalPlayer.Action.Open -> {
|
||||||
library.findSongForUri(application, action.uri)?.let { song ->
|
library.findSongForUri(application, action.uri)?.let { song ->
|
||||||
playbackManager.play(song, settings.libPlaybackMode, settings)
|
playbackManager.play(song, settings.libPlaybackMode, settings)
|
||||||
}
|
}
|
||||||
|
@ -389,6 +390,7 @@ class PlaybackService :
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MUSICSTORE OVERRIDES ---
|
// --- MUSICSTORE OVERRIDES ---
|
||||||
|
|
||||||
override fun onLibraryChanged(library: MusicStore.Library?) {
|
override fun onLibraryChanged(library: MusicStore.Library?) {
|
||||||
if (library != null) {
|
if (library != null) {
|
||||||
playbackManager.requestAction(this)
|
playbackManager.requestAction(this)
|
||||||
|
@ -477,7 +479,7 @@ class PlaybackService :
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val POS_POLL_INTERVAL = 1000L
|
private const val POS_POLL_INTERVAL = 100L
|
||||||
private const val REWIND_THRESHOLD = 3000L
|
private const val REWIND_THRESHOLD = 3000L
|
||||||
|
|
||||||
const val ACTION_INC_REPEAT_MODE = BuildConfig.APPLICATION_ID + ".action.LOOP"
|
const val ACTION_INC_REPEAT_MODE = BuildConfig.APPLICATION_ID + ".action.LOOP"
|
||||||
|
|
|
@ -9,7 +9,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.4.0-alpha09'
|
classpath 'com.android.tools.build:gradle:7.4.0-alpha10'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
|
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
|
||||||
classpath "com.diffplug.spotless:spotless-plugin-gradle:6.6.1"
|
classpath "com.diffplug.spotless:spotless-plugin-gradle:6.6.1"
|
||||||
|
|
Loading…
Reference in a new issue