feat: add notification controls (#3723)
* feat(ios): add `showNotificationControls` prop * feat(android): add `showNotificationControls` prop * add docs * feat!: add `metadata` property to srouce This is breaking change for iOS/tvOS as we are moving some properties, but I believe that this will more readable and more user friendly * chore(ios): remove UI blocking function * code review changes for android * update example * fix readme * fix typos * update docs * fix typo * chore: improve sample metadata notification * update codegen types * rename properties * update tvOS example * reset metadata on source change * update docs --------- Co-authored-by: Olivier Bouillet <freeboub@gmail.com>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package com.brentvatne.exoplayer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.session.CommandButton
|
||||
import androidx.media3.session.MediaSession
|
||||
import androidx.media3.session.MediaSessionService
|
||||
import androidx.media3.session.MediaStyleNotificationHelper
|
||||
import androidx.media3.session.SessionCommand
|
||||
import okhttp3.internal.immutableListOf
|
||||
|
||||
class PlaybackServiceBinder(val service: VideoPlaybackService) : Binder()
|
||||
|
||||
class VideoPlaybackService : MediaSessionService() {
|
||||
private var mediaSessionsList = mutableMapOf<ExoPlayer, MediaSession>()
|
||||
private var binder = PlaybackServiceBinder(this)
|
||||
|
||||
// Controls
|
||||
private val commandSeekForward = SessionCommand(COMMAND_SEEK_FORWARD, Bundle.EMPTY)
|
||||
private val commandSeekBackward = SessionCommand(COMMAND_SEEK_BACKWARD, Bundle.EMPTY)
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
private val seekForwardBtn = CommandButton.Builder()
|
||||
.setDisplayName("forward")
|
||||
.setSessionCommand(commandSeekForward)
|
||||
.setIconResId(androidx.media3.ui.R.drawable.exo_notification_fastforward)
|
||||
.build()
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
private val seekBackwardBtn = CommandButton.Builder()
|
||||
.setDisplayName("backward")
|
||||
.setSessionCommand(commandSeekBackward)
|
||||
.setIconResId(androidx.media3.ui.R.drawable.exo_notification_rewind)
|
||||
.build()
|
||||
|
||||
// Player Registry
|
||||
|
||||
fun registerPlayer(player: ExoPlayer) {
|
||||
if (mediaSessionsList.containsKey(player)) {
|
||||
return
|
||||
}
|
||||
|
||||
val mediaSession = MediaSession.Builder(this, player)
|
||||
.setId("RNVideoPlaybackService_" + player.hashCode())
|
||||
.setCallback(VideoPlaybackCallback(SEEK_INTERVAL_MS))
|
||||
.setCustomLayout(immutableListOf(seekBackwardBtn, seekForwardBtn))
|
||||
.build()
|
||||
|
||||
mediaSessionsList[player] = mediaSession
|
||||
addSession(mediaSession)
|
||||
}
|
||||
|
||||
fun unregisterPlayer(player: ExoPlayer) {
|
||||
hidePlayerNotification(player)
|
||||
val session = mediaSessionsList.remove(player)
|
||||
session?.release()
|
||||
|
||||
if (mediaSessionsList.isEmpty()) {
|
||||
cleanup()
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
|
||||
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
super.onBind(intent)
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onUpdateNotification(session: MediaSession, startInForegroundRequired: Boolean) {
|
||||
createSessionNotification(session)
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
cleanup()
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
cleanup()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun createSessionNotification(session: MediaSession) {
|
||||
val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(
|
||||
NOTIFICATION_CHANEL_ID,
|
||||
NOTIFICATION_CHANEL_ID,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (session.player.currentMediaItem == null) {
|
||||
notificationManager.cancel(session.player.hashCode())
|
||||
return
|
||||
}
|
||||
|
||||
val notificationCompact = NotificationCompat.Builder(this, NOTIFICATION_CHANEL_ID)
|
||||
.setSmallIcon(androidx.media3.session.R.drawable.media3_icon_circular_play)
|
||||
.setStyle(MediaStyleNotificationHelper.MediaStyle(session))
|
||||
.build()
|
||||
|
||||
notificationManager.notify(session.player.hashCode(), notificationCompact)
|
||||
}
|
||||
|
||||
private fun hidePlayerNotification(player: ExoPlayer) {
|
||||
val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.cancel(player.hashCode())
|
||||
}
|
||||
|
||||
private fun hideAllNotifications() {
|
||||
val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.cancelAll()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
notificationManager.deleteNotificationChannel(NOTIFICATION_CHANEL_ID)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanup() {
|
||||
hideAllNotifications()
|
||||
mediaSessionsList.forEach { (_, session) ->
|
||||
session.release()
|
||||
}
|
||||
mediaSessionsList.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val COMMAND_SEEK_FORWARD = "SEEK_FORWARD"
|
||||
const val COMMAND_SEEK_BACKWARD = "SEEK_BACKWARD"
|
||||
const val NOTIFICATION_CHANEL_ID = "RNVIDEO_SESSION_NOTIFICATION"
|
||||
const val SEEK_INTERVAL_MS = 10000L
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user