Fix notification actions on older Android versions and react on playback changes

This commit is contained in:
Bnyro 2023-05-22 16:51:33 +02:00
parent 33de6ca827
commit 7364986afe

View File

@ -1,11 +1,12 @@
package com.github.libretube.util package com.github.libretube.util
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -14,7 +15,6 @@ import androidx.annotation.DrawableRes
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat import androidx.core.app.PendingIntentCompat
import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.media3.common.Player import androidx.media3.common.Player
@ -24,7 +24,6 @@ import androidx.media3.session.MediaSession
import androidx.media3.session.MediaStyleNotificationHelper import androidx.media3.session.MediaStyleNotificationHelper
import androidx.media3.session.SessionCommand import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionResult import androidx.media3.session.SessionResult
import androidx.media3.ui.PlayerNotificationManager
import coil.request.ImageRequest import coil.request.ImageRequest
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.constants.BACKGROUND_CHANNEL_ID import com.github.libretube.constants.BACKGROUND_CHANNEL_ID
@ -35,10 +34,6 @@ import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.obj.PlayerNotificationData import com.github.libretube.obj.PlayerNotificationData
import com.github.libretube.ui.activities.MainActivity import com.github.libretube.ui.activities.MainActivity
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
class NowPlayingNotification( class NowPlayingNotification(
@ -67,9 +62,7 @@ class NowPlayingNotification(
private val nManager = NotificationManagerCompat.from(context) private val nManager = NotificationManagerCompat.from(context)
private fun loadCurrentLargeIcon() { private fun loadCurrentLargeIcon() {
// On Android 13 and up, the metadata is responsible for the thumbnail if (DataSaverMode.isEnabled(context)) return
if (DataSaverMode.isEnabled(context) ||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) return
if (notificationBitmap == null) enqueueThumbnailRequest { if (notificationBitmap == null) enqueueThumbnailRequest {
createOrUpdateNotification() createOrUpdateNotification()
} }
@ -94,7 +87,7 @@ class NowPlayingNotification(
// online image // online image
notificationData?.thumbnailPath?.let { path -> notificationData?.thumbnailPath?.let { path ->
ImageHelper.getDownloadedImage(context, path)?.let { ImageHelper.getDownloadedImage(context, path)?.let {
notificationBitmap = ImageHelper.getSquareBitmap(it) notificationBitmap = processBitmap(it)
callback.invoke(notificationBitmap!!) callback.invoke(notificationBitmap!!)
} }
return return
@ -103,7 +96,7 @@ class NowPlayingNotification(
val request = ImageRequest.Builder(context) val request = ImageRequest.Builder(context)
.data(notificationData?.thumbnailUrl) .data(notificationData?.thumbnailUrl)
.target { .target {
notificationBitmap = ImageHelper.getSquareBitmap(it.toBitmap()) notificationBitmap = processBitmap(it.toBitmap())
callback.invoke(notificationBitmap!!) callback.invoke(notificationBitmap!!)
} }
.build() .build()
@ -112,8 +105,15 @@ class NowPlayingNotification(
ImageHelper.imageLoader.enqueue(request) ImageHelper.imageLoader.enqueue(request)
} }
private val customActions = listOf( private fun processBitmap(bitmap: Bitmap): Bitmap {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
bitmap
} else ImageHelper.getSquareBitmap(bitmap)
}
private val legacyNotificationButtons get() = listOf(
createNotificationAction(R.drawable.ic_prev_outlined, PREV, 1), createNotificationAction(R.drawable.ic_prev_outlined, PREV, 1),
createNotificationAction(if (player.isPlaying) R.drawable.ic_pause else R.drawable.ic_play, PLAY_PAUSE, 1),
createNotificationAction(R.drawable.ic_next_outlined, NEXT, 1), createNotificationAction(R.drawable.ic_next_outlined, NEXT, 1),
createNotificationAction(R.drawable.ic_rewind_md, REWIND, 1), createNotificationAction(R.drawable.ic_rewind_md, REWIND, 1),
createNotificationAction(R.drawable.ic_forward_md, FORWARD, 1), createNotificationAction(R.drawable.ic_forward_md, FORWARD, 1),
@ -154,11 +154,16 @@ class NowPlayingNotification(
): MediaSession.ConnectionResult { ): MediaSession.ConnectionResult {
val connectionResult = super.onConnect(session, controller) val connectionResult = super.onConnect(session, controller)
val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon() val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
val availablePlayerCommands = connectionResult.availablePlayerCommands // Player.Commands.Builder().add(Player.COMMAND_PLAY_PAUSE).build() val availablePlayerCommands =
connectionResult.availablePlayerCommands // Player.Commands.Builder().add(Player.COMMAND_PLAY_PAUSE).build()
getCustomActions().forEach { button -> getCustomActions().forEach { button ->
button.sessionCommand?.let { availableSessionCommands.add(it) } button.sessionCommand?.let { availableSessionCommands.add(it) }
} }
session.setAvailableCommands(controller, availableSessionCommands.build(), availablePlayerCommands) session.setAvailableCommands(
controller,
availableSessionCommands.build(),
availablePlayerCommands
)
return MediaSession.ConnectionResult.accept( return MediaSession.ConnectionResult.accept(
availableSessionCommands.build(), availableSessionCommands.build(),
availablePlayerCommands, availablePlayerCommands,
@ -234,6 +239,10 @@ class NowPlayingNotification(
FORWARD -> { FORWARD -> {
player.seekTo(player.currentPosition + PlayerHelper.seekIncrement) player.seekTo(player.currentPosition + PlayerHelper.seekIncrement)
} }
PLAY_PAUSE -> {
if (player.isPlaying) player.pause() else player.play()
}
} }
} }
@ -254,6 +263,14 @@ class NowPlayingNotification(
if (notificationBuilder == null) { if (notificationBuilder == null) {
createMediaSession() createMediaSession()
createNotificationBuilder() createNotificationBuilder()
createActionReceiver()
// update the notification each time the player continues playing or pauses
player.addListener(object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) {
createOrUpdateNotification()
super.onIsPlayingChanged(isPlaying)
}
})
} }
createOrUpdateNotification() createOrUpdateNotification()
@ -264,27 +281,43 @@ class NowPlayingNotification(
*/ */
private fun createNotificationBuilder() { private fun createNotificationBuilder() {
notificationBuilder = NotificationCompat.Builder(context, BACKGROUND_CHANNEL_ID) notificationBuilder = NotificationCompat.Builder(context, BACKGROUND_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_lockscreen) .setSmallIcon(R.drawable.ic_launcher_lockscreen)
.setContentIntent(createCurrentContentIntent()) .setContentIntent(createCurrentContentIntent())
.setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)) .setStyle(
.setLargeIcon(notificationBitmap) MediaStyleNotificationHelper.MediaStyle(mediaSession)
.apply { .setShowActionsInCompactView(1)
customActions.forEach { )
addAction(it)
}
}
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
private fun createOrUpdateNotification() { private fun createOrUpdateNotification() {
if (notificationBuilder == null) return if (notificationBuilder == null) return
val notification = notificationBuilder!! val notification = notificationBuilder!!
.setContentText(notificationData?.title) .setContentTitle(notificationData?.title)
.setContentText(notificationData?.uploaderName) .setContentText(notificationData?.uploaderName)
.setLargeIcon(notificationBitmap)
.clearActions()
.apply {
legacyNotificationButtons.forEach {
addAction(it)
}
}
.build() .build()
nManager.notify(PLAYER_NOTIFICATION_ID, notification) nManager.notify(PLAYER_NOTIFICATION_ID, notification)
} }
private val notificationActionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
handlePlayerAction(intent.action ?: return)
}
}
private fun createActionReceiver() {
listOf(PREV, NEXT, REWIND, FORWARD, PLAY_PAUSE).forEach {
context.registerReceiver(notificationActionReceiver, IntentFilter(it))
}
}
/** /**
* Destroy the [NowPlayingNotification] * Destroy the [NowPlayingNotification]
*/ */
@ -294,7 +327,11 @@ class NowPlayingNotification(
player.stop() player.stop()
player.release() player.release()
context.getSystemService<NotificationManager>()!!.cancel(PLAYER_NOTIFICATION_ID) runCatching {
context.unregisterReceiver(notificationActionReceiver)
}
nManager.cancel(PLAYER_NOTIFICATION_ID)
} }
companion object { companion object {
@ -302,5 +339,6 @@ class NowPlayingNotification(
private const val NEXT = "next" private const val NEXT = "next"
private const val REWIND = "rewind" private const val REWIND = "rewind"
private const val FORWARD = "forward" private const val FORWARD = "forward"
private const val PLAY_PAUSE = "play_pause"
} }
} }