55th MAD RSC
THE PREPARE NOTE
Media PlayBack with service
Section titled “Media PlayBack with service”App Class
Section titled “App Class”class App : Application() { @RequiresApi(Build.VERSION_CODES.O) override fun onCreate() { super.onCreate() createChannel() updateWidget() startForegroundService(Intent(this, PlaybackService::class.java)) }
private fun updateWidget() { val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) appScope.launch { Widget().updateAll(applicationContext) } }
@RequiresApi(Build.VERSION_CODES.O) private fun createChannel() { val channel = NotificationChannel( "media_channel", "Media Channel", NotificationManager.IMPORTANCE_LOW ) NotificationManagerCompat.from(this).createNotificationChannel(channel) }}Manifest
Section titled “Manifest”<service android:name=".PlaybackService" android:exported="true" android:foregroundServiceType="mediaPlayback"> <intent-filter> <action android:name="androidx.media3.session.MediaSessionService" /> </intent-filter></service>Service
Section titled “Service”data class PlayerState( var currentPosition: Long = 0L, var duration: Long = 1000L, var isPlaying: Boolean = false, var volume: Float = 1.0f, var currentIndex: Int = 0, var repeatMode: Int = Player.REPEAT_MODE_OFF, var metadata: MediaMetadata = MediaMetadata.Builder().build(), var isStarted: Boolean = false,)
class PlaybackService : MediaSessionService() { private var isForegroundStarted = false private var mediaSession: MediaSession? = null private lateinit var player: ExoPlayer private val serviceScope = CoroutineScope(Dispatchers.Main + Job()) private var controllerJob: Job? = null
companion object { private val _playerState = MutableStateFlow(PlayerState()) val playerState: StateFlow<PlayerState> = _playerState
var playerInstance: ExoPlayer? = null
fun init(items: List<MediaItem>) { playerInstance?.apply { setMediaItems(items) prepare() play() } } }
override fun onCreate() { super.onCreate() player = ExoPlayer.Builder(this).build() playerInstance = player player.addListener(object : Player.Listener { override fun onEvents(player: Player, events: Player.Events) { _playerState.value = _playerState.value.copy( currentIndex = player.currentMediaItemIndex, isPlaying = player.isPlaying, duration = player.duration, volume = player.volume, repeatMode = player.repeatMode, metadata = player.mediaMetadata ) serviceScope.launch { Widget().updateAll(applicationContext) } super.onEvents(player, events) } })
mediaSession = MediaSession.Builder(this, player).build() startControllerConnection() }
private fun startControllerConnection() { controllerJob?.cancel() controllerJob = CoroutineScope(Dispatchers.Main).launch { val sessionToken = SessionToken( this@PlaybackService, ComponentName(this@PlaybackService, PlaybackService::class.java) ) MediaController.Builder(this@PlaybackService, sessionToken).buildAsync() } }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) if (!isForegroundStarted) { val notification = createNotification() startForeground(1, notification) isForegroundStarted = true } return START_STICKY }
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? { return mediaSession }
override fun onDestroy() { mediaSession?.run { player.release() release() } playerInstance = null super.onDestroy() }
private fun createNotification(): Notification { return NotificationCompat.Builder(this, "media_channel") .setSmallIcon(android.R.drawable.ic_media_play) .setContentTitle("title") .setContentText("text") .setOngoing(true) .build() }}for press the notification to open the app
mediaSession = MediaSession.Builder(this, player) .setSessionActivity(pendingIntent) .build()