diff --git a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/di/MediaPlayerModule.kt b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/di/MediaPlayerModule.kt index 247e72e..c11fc21 100644 --- a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/di/MediaPlayerModule.kt +++ b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/di/MediaPlayerModule.kt @@ -97,8 +97,7 @@ object MediaPlayerModule { @Singleton fun provideMediaSessionLayoutHandler( @ApplicationContext context: Context, - mediaLibrarySessionCallback: MediaLibrarySessionCallback, mediaLibrarySession: MediaLibrarySession ): MediaSessionLayoutHandler = - MediaSessionLayoutHandlerImpl(context, mediaLibrarySession, mediaLibrarySessionCallback) + MediaSessionLayoutHandlerImpl(context, mediaLibrarySession) } \ No newline at end of file diff --git a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaCustomActionReceiver.kt b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaCustomActionReceiver.kt new file mode 100644 index 0000000..bcddc9e --- /dev/null +++ b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaCustomActionReceiver.kt @@ -0,0 +1,69 @@ +package com.bobbyesp.mediaplayer.service.notifications + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import androidx.core.app.NotificationCompat +import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi +import androidx.media3.ui.PlayerNotificationManager.CustomActionReceiver +import com.bobbyesp.mediaplayer.R + +@UnstableApi +class MediaCustomActionReceiver : CustomActionReceiver { + companion object { + const val CUSTOM_ACTION_SHUFFLE = + "com.bobbyesp.mediaplayer.service.notifications.ACTION_SHUFFLE" + const val CUSTOM_ACTION_REPEAT = + "com.bobbyesp.mediaplayer.service.notifications.ACTION_REPEAT" + } + + override fun createCustomActions( + context: Context, + instanceId: Int + ): MutableMap { + val shuffleAction = NotificationCompat.Action.Builder( + R.drawable.shuffle, + context.getString(R.string.action_shuffle_on), + PendingIntent.getBroadcast( + context, + instanceId, + Intent(CUSTOM_ACTION_SHUFFLE).setPackage(context.packageName), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + ).build() + + val repeatAction = NotificationCompat.Action.Builder( + R.drawable.repeat, + context.getString(R.string.repeat_mode_all), + PendingIntent.getBroadcast( + context, + instanceId, + Intent(CUSTOM_ACTION_REPEAT).setPackage(context.packageName), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + ).build() + + return mutableMapOf( + CUSTOM_ACTION_SHUFFLE to shuffleAction, + CUSTOM_ACTION_REPEAT to repeatAction + ) + } + + override fun getCustomActions(player: Player): MutableList { + return mutableListOf(CUSTOM_ACTION_SHUFFLE, CUSTOM_ACTION_REPEAT) + } + + override fun onCustomAction(player: Player, action: String, intent: Intent) { + when (action) { + CUSTOM_ACTION_SHUFFLE -> player.shuffleModeEnabled = !player.shuffleModeEnabled + CUSTOM_ACTION_REPEAT -> { + when (player.repeatMode) { + Player.REPEAT_MODE_OFF -> player.repeatMode = Player.REPEAT_MODE_ALL + Player.REPEAT_MODE_ALL -> player.repeatMode = Player.REPEAT_MODE_ONE + Player.REPEAT_MODE_ONE -> player.repeatMode = Player.REPEAT_MODE_OFF + } + } + } + } +} \ No newline at end of file diff --git a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaNotificationAdapter.kt b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaNotificationAdapter.kt index 1500496..5d6a831 100644 --- a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaNotificationAdapter.kt +++ b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaNotificationAdapter.kt @@ -14,6 +14,7 @@ class MediaNotificationAdapter( private val context: Context, private val pendingIntent: PendingIntent? ) : PlayerNotificationManager.MediaDescriptionAdapter { + override fun getCurrentContentTitle(player: Player): CharSequence { return player.mediaMetadata.albumTitle ?: context.getString(androidx.media3.ui.R.string.exo_track_unknown) diff --git a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaNotificationManager.kt b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaNotificationManager.kt index 6439570..12c7b8d 100644 --- a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaNotificationManager.kt +++ b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaNotificationManager.kt @@ -6,8 +6,11 @@ import android.app.NotificationManager import android.content.Context import android.os.Build import android.util.Log +import androidx.annotation.OptIn import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat import androidx.media3.common.util.UnstableApi +import androidx.media3.common.util.Util import androidx.media3.exoplayer.ExoPlayer import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService @@ -16,16 +19,42 @@ import com.bobbyesp.mediaplayer.R import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject -class MediaNotificationManager @Inject constructor( +@UnstableApi +class MediaNotificationManager @OptIn(UnstableApi::class) +@Inject constructor( @ApplicationContext private val context: Context, private val player: ExoPlayer ) { private val notificationManager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationManagerCompat = NotificationManagerCompat.from(context) + init { - Log.i("MediaNotificationManager", "init: creating notification manager") - createNotificationChannel() + ensureNotificationChannel(notificationManagerCompat) + } + + val notificationListener = object : PlayerNotificationManager.NotificationListener { + override fun onNotificationCancelled( + notificationId: Int, + dismissedByUser: Boolean + ) { + Log.d( + "MediaNotificationManager", + "onNotificationCancelled: notification cancelled" + ) + } + + override fun onNotificationPosted( + notificationId: Int, + notification: Notification, + ongoing: Boolean + ) { + Log.d( + "MediaNotificationManager", + "onNotificationPosted: notification posted" + ) + } } @UnstableApi @@ -46,35 +75,15 @@ class MediaNotificationManager @Inject constructor( pendingIntent = mediaSession.sessionActivity ) ) + .setCustomActionReceiver(MediaCustomActionReceiver()) .setSmallIconResourceId(R.drawable.metadator_logo_player) - .setNotificationListener(object : PlayerNotificationManager.NotificationListener { - override fun onNotificationCancelled( - notificationId: Int, - dismissedByUser: Boolean - ) { - Log.d( - "MediaNotificationManager", - "onNotificationCancelled: notification cancelled" - ) - } - - override fun onNotificationPosted( - notificationId: Int, - notification: Notification, - ongoing: Boolean - ) { - Log.d( - "MediaNotificationManager", - "onNotificationPosted: notification posted" - ) - } - }) + .setNotificationListener(notificationListener) .build() - .also { playerNotificationManager -> - with(playerNotificationManager) { + .also { + with(it) { setMediaSessionToken(mediaSession.sessionCompatToken) setPriority(NotificationCompat.PRIORITY_LOW) - setPlayer(player) + setPlayer(mediaSession.player) } } } @@ -103,15 +112,21 @@ class MediaNotificationManager @Inject constructor( } } - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - NOTIFICATION_CHANNEL_ID, - NOTIFICATION_CHANNEL_NAME, - NotificationManager.IMPORTANCE_LOW - ) - notificationManager.createNotificationChannel(channel) + @OptIn(UnstableApi::class) + private fun ensureNotificationChannel(notificationManagerCompat: NotificationManagerCompat) { + if (Util.SDK_INT < 26 || notificationManagerCompat.getNotificationChannel( + NOTIFICATION_CHANNEL_ID + ) != null + ) { + return } + + val channel = NotificationChannel( + NOTIFICATION_CHANNEL_ID, + NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_DEFAULT + ) + notificationManagerCompat.createNotificationChannel(channel) } diff --git a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/customLayout/MediaSessionLayoutHandlerImpl.kt b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/customLayout/MediaSessionLayoutHandlerImpl.kt index a7a9acc..bc85fe6 100644 --- a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/customLayout/MediaSessionLayoutHandlerImpl.kt +++ b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/customLayout/MediaSessionLayoutHandlerImpl.kt @@ -8,7 +8,6 @@ import androidx.media3.common.Player.REPEAT_MODE_ONE import androidx.media3.session.CommandButton import androidx.media3.session.MediaLibraryService import com.bobbyesp.mediaplayer.R -import com.bobbyesp.mediaplayer.service.MediaLibrarySessionCallback import com.bobbyesp.mediaplayer.service.MediaSessionConstants.CommandToggleRepeatMode import com.bobbyesp.mediaplayer.service.MediaSessionConstants.CommandToggleShuffle import com.google.common.collect.ImmutableList @@ -18,41 +17,40 @@ import javax.inject.Inject class MediaSessionLayoutHandlerImpl @Inject constructor( @ApplicationContext private val context: Context, private val mediaSession: MediaLibraryService.MediaLibrarySession, - private val mediaSessionCallback: MediaLibrarySessionCallback ) : MediaSessionLayoutHandler { - val commandButtons = ImmutableList.of( - CommandButton.Builder() - .setDisplayName( - getString( - context, - if (mediaSession.player.shuffleModeEnabled) R.string.action_shuffle_on else R.string.action_shuffle_off + override fun updateNotificationLayout() { + val commandButtons = ImmutableList.of( + CommandButton.Builder() + .setDisplayName( + getString( + context, + if (mediaSession.player.shuffleModeEnabled) R.string.action_shuffle_on else R.string.action_shuffle_off + ) ) - ) - .setIconResId(if (mediaSession.player.shuffleModeEnabled) R.drawable.shuffle_on else R.drawable.shuffle) - .setSessionCommand(CommandToggleShuffle).build(), - CommandButton.Builder() - .setDisplayName( - getString( - context, + .setIconResId(if (mediaSession.player.shuffleModeEnabled) R.drawable.shuffle_on else R.drawable.shuffle) + .setSessionCommand(CommandToggleShuffle).build(), + CommandButton.Builder() + .setDisplayName( + getString( + context, + when (mediaSession.player.repeatMode) { + REPEAT_MODE_OFF -> R.string.repeat_mode_off + REPEAT_MODE_ONE -> R.string.repeat_mode_one + REPEAT_MODE_ALL -> R.string.repeat_mode_all + else -> throw IllegalStateException() + } + ) + ).setIconResId( when (mediaSession.player.repeatMode) { - REPEAT_MODE_OFF -> R.string.repeat_mode_off - REPEAT_MODE_ONE -> R.string.repeat_mode_one - REPEAT_MODE_ALL -> R.string.repeat_mode_all + REPEAT_MODE_OFF -> R.drawable.repeat + REPEAT_MODE_ONE -> R.drawable.repeat_one_on + REPEAT_MODE_ALL -> R.drawable.repeat_on else -> throw IllegalStateException() } - ) - ).setIconResId( - when (mediaSession.player.repeatMode) { - REPEAT_MODE_OFF -> R.drawable.repeat - REPEAT_MODE_ONE -> R.drawable.repeat_one_on - REPEAT_MODE_ALL -> R.drawable.repeat_on - else -> throw IllegalStateException() - } - ).setSessionCommand(CommandToggleRepeatMode).build() - ) + ).setSessionCommand(CommandToggleRepeatMode).build() + ) - override fun updateNotificationLayout() { mediaSession.setCustomLayout(commandButtons) } } \ No newline at end of file