diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt index ad5102477..a4be02ad2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt @@ -21,6 +21,7 @@ package org.oxycblt.auxio.playback.service import android.app.Notification import android.content.Context import android.os.Bundle +import androidx.core.content.ContextCompat import androidx.media3.common.MediaItem import androidx.media3.session.CommandButton import androidx.media3.session.DefaultActionFactory @@ -49,10 +50,12 @@ import org.oxycblt.auxio.ForegroundListener import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R import org.oxycblt.auxio.music.service.MediaItemBrowser +import org.oxycblt.auxio.playback.PlaybackSettings import org.oxycblt.auxio.playback.state.DeferredPlayback import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.newMainPendingIntent +import org.oxycblt.auxio.widgets.WidgetComponent class MediaSessionServiceFragment @Inject @@ -60,6 +63,8 @@ constructor( @ApplicationContext private val context: Context, private val playbackManager: PlaybackStateManager, private val actionHandler: PlaybackActionHandler, + private val playbackSettings: PlaybackSettings, + private val widgetComponent: WidgetComponent, private val mediaItemBrowser: MediaItemBrowser, exoHolderFactory: ExoPlaybackStateHolder.Factory ) : @@ -86,6 +91,7 @@ constructor( .also { it.setSmallIcon(R.drawable.ic_auxio_24) } private var foregroundListener: ForegroundListener? = null + lateinit var systemReceiver: SystemPlaybackReceiver lateinit var mediaSession: MediaLibrarySession private set @@ -99,6 +105,10 @@ constructor( playbackManager.addListener(this) exoHolder.attach() actionHandler.attach(this) + systemReceiver = SystemPlaybackReceiver(playbackManager, playbackSettings, widgetComponent) + ContextCompat.registerReceiver( + context, systemReceiver, systemReceiver.intentFilter, ContextCompat.RECEIVER_EXPORTED) + widgetComponent.attach() mediaItemBrowser.attach(this) return mediaSession } @@ -142,6 +152,8 @@ constructor( fun release() { waitJob.cancel() mediaItemBrowser.release() + context.unregisterReceiver(systemReceiver) + widgetComponent.release() actionHandler.release() exoHolder.release() playbackManager.removeListener(this) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/PlaybackActionHandler.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/PlaybackActionHandler.kt index 6d0c1fbeb..3dd0acf68 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/service/PlaybackActionHandler.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/service/PlaybackActionHandler.kt @@ -18,13 +18,8 @@ package org.oxycblt.auxio.playback.service -import android.content.BroadcastReceiver import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.media.AudioManager import android.os.Bundle -import androidx.core.content.ContextCompat import androidx.media3.common.Player import androidx.media3.session.CommandButton import androidx.media3.session.SessionCommand @@ -39,41 +34,31 @@ import org.oxycblt.auxio.playback.PlaybackSettings import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.Progression import org.oxycblt.auxio.playback.state.RepeatMode -import org.oxycblt.auxio.util.logD -import org.oxycblt.auxio.widgets.WidgetComponent -import org.oxycblt.auxio.widgets.WidgetProvider class PlaybackActionHandler @Inject constructor( @ApplicationContext private val context: Context, private val playbackManager: PlaybackStateManager, - private val playbackSettings: PlaybackSettings, - private val widgetComponent: WidgetComponent + private val playbackSettings: PlaybackSettings ) : PlaybackStateManager.Listener, PlaybackSettings.Listener { interface Callback { fun onCustomLayoutChanged(layout: List) } - private val systemReceiver = - SystemPlaybackReceiver(playbackManager, playbackSettings, widgetComponent) private var callback: Callback? = null fun attach(callback: Callback) { this.callback = callback playbackManager.addListener(this) playbackSettings.registerListener(this) - ContextCompat.registerReceiver( - context, systemReceiver, systemReceiver.intentFilter, ContextCompat.RECEIVER_EXPORTED) } fun release() { callback = null playbackManager.removeListener(this) playbackSettings.unregisterListener(this) - context.unregisterReceiver(systemReceiver) - widgetComponent.release() } fun withCommands(commands: SessionCommands) = @@ -178,103 +163,3 @@ object PlaybackActions { const val ACTION_SKIP_NEXT = BuildConfig.APPLICATION_ID + ".action.NEXT" const val ACTION_EXIT = BuildConfig.APPLICATION_ID + ".action.EXIT" } - -/** - * A [BroadcastReceiver] for receiving playback-specific [Intent]s from the system that require an - * active [IntentFilter] to be registered. - */ -class SystemPlaybackReceiver( - private val playbackManager: PlaybackStateManager, - private val playbackSettings: PlaybackSettings, - private val widgetComponent: WidgetComponent -) : BroadcastReceiver() { - private var initialHeadsetPlugEventHandled = false - - val intentFilter = - IntentFilter().apply { - addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY) - addAction(AudioManager.ACTION_HEADSET_PLUG) - addAction(PlaybackActions.ACTION_INC_REPEAT_MODE) - addAction(PlaybackActions.ACTION_INVERT_SHUFFLE) - addAction(PlaybackActions.ACTION_SKIP_PREV) - addAction(PlaybackActions.ACTION_PLAY_PAUSE) - addAction(PlaybackActions.ACTION_SKIP_NEXT) - addAction(WidgetProvider.ACTION_WIDGET_UPDATE) - } - - override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - // --- SYSTEM EVENTS --- - - // Android has three different ways of handling audio plug events for some reason: - // 1. ACTION_HEADSET_PLUG, which only works with wired headsets - // 2. ACTION_ACL_CONNECTED, which allows headset autoplay but also requires - // granting the BLUETOOTH/BLUETOOTH_CONNECT permissions, which is more or less - // a non-starter since both require me to display a permission prompt - // 3. Some internal framework thing that also handles bluetooth headsets - // Just use ACTION_HEADSET_PLUG. - AudioManager.ACTION_HEADSET_PLUG -> { - logD("Received headset plug event") - when (intent.getIntExtra("state", -1)) { - 0 -> pauseFromHeadsetPlug() - 1 -> playFromHeadsetPlug() - } - - initialHeadsetPlugEventHandled = true - } - AudioManager.ACTION_AUDIO_BECOMING_NOISY -> { - logD("Received Headset noise event") - pauseFromHeadsetPlug() - } - - // --- AUXIO EVENTS --- - PlaybackActions.ACTION_PLAY_PAUSE -> { - logD("Received play event") - playbackManager.playing(!playbackManager.progression.isPlaying) - } - PlaybackActions.ACTION_INC_REPEAT_MODE -> { - logD("Received repeat mode event") - playbackManager.repeatMode(playbackManager.repeatMode.increment()) - } - PlaybackActions.ACTION_INVERT_SHUFFLE -> { - logD("Received shuffle event") - playbackManager.shuffled(!playbackManager.isShuffled) - } - PlaybackActions.ACTION_SKIP_PREV -> { - logD("Received skip previous event") - playbackManager.prev() - } - PlaybackActions.ACTION_SKIP_NEXT -> { - logD("Received skip next event") - playbackManager.next() - } - PlaybackActions.ACTION_EXIT -> { - logD("Received exit event") - playbackManager.endSession() - } - WidgetProvider.ACTION_WIDGET_UPDATE -> { - logD("Received widget update event") - widgetComponent.update() - } - } - } - - private fun playFromHeadsetPlug() { - // ACTION_HEADSET_PLUG will fire when this BroadcastReceiver is initially attached, - // which would result in unexpected playback. Work around it by dropping the first - // call to this function, which should come from that Intent. - if (playbackSettings.headsetAutoplay && - playbackManager.currentSong != null && - initialHeadsetPlugEventHandled) { - logD("Device connected, resuming") - playbackManager.playing(true) - } - } - - private fun pauseFromHeadsetPlug() { - if (playbackManager.currentSong != null) { - logD("Device disconnected, pausing") - playbackManager.playing(false) - } - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/SystemPlaybackReceiver.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/SystemPlaybackReceiver.kt new file mode 100644 index 000000000..31c931fdb --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/service/SystemPlaybackReceiver.kt @@ -0,0 +1,112 @@ +package org.oxycblt.auxio.playback.service + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.media.AudioManager +import org.oxycblt.auxio.playback.PlaybackSettings +import org.oxycblt.auxio.playback.state.PlaybackStateManager +import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.widgets.WidgetComponent +import org.oxycblt.auxio.widgets.WidgetProvider + +/** + * A [BroadcastReceiver] for receiving playback-specific [Intent]s from the system that require an + * active [IntentFilter] to be registered. + */ +class SystemPlaybackReceiver( + private val playbackManager: PlaybackStateManager, + private val playbackSettings: PlaybackSettings, + private val widgetComponent: WidgetComponent +) : BroadcastReceiver() { + private var initialHeadsetPlugEventHandled = false + + val intentFilter = + IntentFilter().apply { + addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY) + addAction(AudioManager.ACTION_HEADSET_PLUG) + addAction(PlaybackActions.ACTION_INC_REPEAT_MODE) + addAction(PlaybackActions.ACTION_INVERT_SHUFFLE) + addAction(PlaybackActions.ACTION_SKIP_PREV) + addAction(PlaybackActions.ACTION_PLAY_PAUSE) + addAction(PlaybackActions.ACTION_SKIP_NEXT) + addAction(WidgetProvider.ACTION_WIDGET_UPDATE) + } + + override fun onReceive(context: Context, intent: Intent) { + when (intent.action) { + // --- SYSTEM EVENTS --- + + // Android has three different ways of handling audio plug events for some reason: + // 1. ACTION_HEADSET_PLUG, which only works with wired headsets + // 2. ACTION_ACL_CONNECTED, which allows headset autoplay but also requires + // granting the BLUETOOTH/BLUETOOTH_CONNECT permissions, which is more or less + // a non-starter since both require me to display a permission prompt + // 3. Some internal framework thing that also handles bluetooth headsets + // Just use ACTION_HEADSET_PLUG. + AudioManager.ACTION_HEADSET_PLUG -> { + logD("Received headset plug event") + when (intent.getIntExtra("state", -1)) { + 0 -> pauseFromHeadsetPlug() + 1 -> playFromHeadsetPlug() + } + + initialHeadsetPlugEventHandled = true + } + AudioManager.ACTION_AUDIO_BECOMING_NOISY -> { + logD("Received Headset noise event") + pauseFromHeadsetPlug() + } + + // --- AUXIO EVENTS --- + PlaybackActions.ACTION_PLAY_PAUSE -> { + logD("Received play event") + playbackManager.playing(!playbackManager.progression.isPlaying) + } + PlaybackActions.ACTION_INC_REPEAT_MODE -> { + logD("Received repeat mode event") + playbackManager.repeatMode(playbackManager.repeatMode.increment()) + } + PlaybackActions.ACTION_INVERT_SHUFFLE -> { + logD("Received shuffle event") + playbackManager.shuffled(!playbackManager.isShuffled) + } + PlaybackActions.ACTION_SKIP_PREV -> { + logD("Received skip previous event") + playbackManager.prev() + } + PlaybackActions.ACTION_SKIP_NEXT -> { + logD("Received skip next event") + playbackManager.next() + } + PlaybackActions.ACTION_EXIT -> { + logD("Received exit event") + playbackManager.endSession() + } + WidgetProvider.ACTION_WIDGET_UPDATE -> { + logD("Received widget update event") + widgetComponent.update() + } + } + } + + private fun playFromHeadsetPlug() { + // ACTION_HEADSET_PLUG will fire when this BroadcastReceiver is initially attached, + // which would result in unexpected playback. Work around it by dropping the first + // call to this function, which should come from that Intent. + if (playbackSettings.headsetAutoplay && + playbackManager.currentSong != null && + initialHeadsetPlugEventHandled) { + logD("Device connected, resuming") + playbackManager.playing(true) + } + } + + private fun pauseFromHeadsetPlug() { + if (playbackManager.currentSong != null) { + logD("Device disconnected, pausing") + playbackManager.playing(false) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt index 1e95eb6f4..bb8caf693 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -57,7 +57,7 @@ constructor( ) : PlaybackStateManager.Listener, UISettings.Listener, ImageSettings.Listener { private val widgetProvider = WidgetProvider() - init { + fun attach() { playbackManager.addListener(this) uiSettings.registerListener(this) imageSettings.registerListener(this) @@ -90,7 +90,7 @@ constructor( } else if (uiSettings.roundMode) { // < Android 12, but the user still enabled round mode. logD("Using default corner radius") - context.getDimenPixels(R.dimen.size_corners_medium) + context.getDimenPixels(R.dimen.spacing_medium) } else { // User did not enable round mode. logD("Using no corner radius")