點擊歌曲播放後,在 status bar 上就會顯示 notification,上面有歌曲的相關資訊,並且可以控制。除了這些功能外,還有一個功能是讓 App 就算在背景播放時,還是能維持在前景,為什麼要有這個功能呢?因為如果 App 在背景,當系統資源不足時,可能就會將這個 App 給砍掉,這時候如果在播歌,這樣歌就會停下來了,會有不好的使用者體驗,因此在播歌時,會啟動 ForegroundService,並且透過 notification 來告知使用者。
ContextCompat.startForegroundService(applicationContext,
Intent(applicationContext, this@MusicService.javaClass))
startForeground(notificationId, notification)
在使用 startForegroundService 後,必須馬上呼叫 startForeground,如果沒有馬上呼叫( 5秒內),就會有 exception,StackOverflow 上還蠻多開發者在討論這個問題的,在 StackOverflow 內比較多在討論的寫法是在 Service 的 onCreate 內才會呼叫 startForeground。但可以看到這邊是接續寫的,因為這邊的順序點擊播歌後,Player 播放,啟動 notificaion,notification 的 callback 收到後,再來啟動 Service。
今天就是要來實作這個部分啦。先來看官方的介紹:Using MediaStyle notifications with a foreground service,設定了 notification 要顯示的內容(歌曲名稱、專輯圖、播放行為)。 ExoPlayer 提供了一些方便的設定方式,提供了一些預設的行為。
首先在 AndroidManifest 先加入 FOREGROUND_SERVICE permission,這個權限宣告是在 Android 9 加入的,因為會使用到 ForegroundService,因此要先宣告。
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
因為會使用到 PlayerNotificationManager 為 exoplayer-ui 的元件,import 相關元件。
implementation 'com.google.android.exoplayer:exoplayer-ui:2.11.8'
在 MusicNotificationManager 內透過 ExoPlayer 提供的 createWithNotificationChannel,來建立 NotificationManager,設定一些基本資訊 和 sessionToken。
val mediaController = MediaControllerCompat(context, sessionToken)
notificationManager = PlayerNotificationManager.createWithNotificationChannel(
context,
NOW_PLAYING_CHANNEL_ID,
R.string.notification_channel,
R.string.notification_channel_description,
NOW_PLAYING_NOTIFICATION_ID,
DescriptionAdapter(mediaController),
notificationListener
).apply {
setMediaSessionToken(sessionToken)
setSmallIcon(R.drawable.ic_default_cover_icon)
// Don't display the rewind or fast-forward buttons.
setRewindIncrementMs(0)
setFastForwardIncrementMs(0)
}
在 DescriptionAdapter 是傳入 MediaControllerCompat,取得播放歌曲的相關資訊(歌曲名稱、歌手名稱、專輯封面圖、點擊後會開啟的 activity),設定給 notification。
private inner class DescriptionAdapter(private val controller: MediaControllerCompat) :
PlayerNotificationManager.MediaDescriptionAdapter {
var currentIconUri: Uri? = null
var currentBitmap: Bitmap? = null
override fun createCurrentContentIntent(player: Player): PendingIntent? =
controller.sessionActivity
override fun getCurrentContentText(player: Player) =
controller.metadata.description.subtitle.toString()
override fun getCurrentContentTitle(player: Player): CharSequence =
controller.metadata.description.title.toString()
override fun getCurrentLargeIcon(
player: Player,
callback: PlayerNotificationManager.BitmapCallback
): Bitmap? {
......
}
設定完了 notificaion,再來就是呼叫 notification 的時間點啦,透過播放器的 callback,在 Player.STATE_READY 去啟動 notification。等同於官方文件介紹的在 MediaSessionCompat.Callback.onPlay() 裡去啟動 notification。
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
when (playbackState) {
Player.STATE_BUFFERING,
Player.STATE_READY -> {
notificationManager.showNotificationForPlayer(currentPlayer)
}
else -> {
notificationManager.hideNotification()
}
}
}
程式碼在這,分支名稱(day13_notification): Fancy/day13_notification