2024-05-07 12:30:57 +02:00
|
|
|
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
|
|
|
|
|
2024-05-11 18:57:59 +02:00
|
|
|
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = null
|
2024-05-07 12:30:57 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|