MediaSession 設定,設定了 PendingIntent,在之後介紹的 Notification 設定會使用到,指定點擊 Notification 要開啟的 App。
val sessionActivityPendingIntent =
packageManager?.getLaunchIntentForPackage(packageName)?.let { sessionIntent ->
PendingIntent.getActivity(this, 0, sessionIntent, 0)
}
mediaSession = MediaSessionCompat(this, TAG)
.apply {
setSessionActivity(sessionActivityPendingIntent)
isActive = true
}
sessionToken = mediaSession.sessionToken
// ExoPlayer will manage the MediaSession for us.
mediaSessionConnector = MediaSessionConnector(mediaSession)
mediaSessionConnector.setPlaybackPreparer(MusicPlaybackPreparer())
mediaSessionConnector.setQueueNavigator(MusicQueueNavigator(mediaSession))
MediaSessionConnector 這個 Class 是什麼呢?是 ExoPlayer 提供的 mediasession 的 extension,因此要 import ExoPlayer extension-mediasession。
implementation 'com.google.android.exoplayer:extension-mediasession:2.11.8'
implementation 'com.google.android.exoplayer:exoplayer-core:2.11.8'
設定完後實作 PlaybackPreparer,裡面有六個 callback 需要實作,在 getSupportedPrepareActions 設定能支援播放的行為,目前就先設定能支援 mediaId 播放,至於從 Uri (ACTION_PLAY_FROM_URI) 和 Search (PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH)的方法,之後有用到再來加入。Search 是什麼行為會被觸發呢?從 uamp 專案的註解得知,透過 Google Assistant 發送的指令會從這個 callback 收到。之後的天數有機會可以來介紹這個功能 XD
This method is used by the Google Assistant to respond to requests such as:
* - Play Geisha from Wake Up on UAMP
* - Play electronic music on UAMP
* - Play music on UAMP
Action 只有填入支援 MediaId 相關的操作,因此實作 onPrepareFromMediaId 就好,其他的填入 Unit,我們可以透過這個 mediaId 來取得相關的歌曲資訊,再傳給 ExoPlayer 進行播放,這邊在明天介紹 Player 實作時一起介紹。
private inner class MusicPlaybackPreparer : MediaSessionConnector.PlaybackPreparer {
override fun onPrepareFromSearch(
query: String, playWhenReady: Boolean,
extras: Bundle?
) = Unit
override fun onCommand(
player: Player, controlDispatcher: ControlDispatcher, command: String,
extras: Bundle?, cb: ResultReceiver?
): Boolean = false
override fun getSupportedPrepareActions(): Long =
PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID or
PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
override fun onPrepareFromMediaId(
mediaId: String, playWhenReady: Boolean,
extras: Bundle?
) {
//TODO: 1.Get song from SongListRepository by using mediaId
//TODO: 2.Use exoplayer to play song.
}
override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) = Unit
override fun onPrepare(playWhenReady: Boolean) = Unit
}
設定 setQueueNavigator 設定時,需要實作 getMediaDescription ,傳入現在播放歌曲的描述,讓 Notification 顯示使用。MediaMetadataCompat 為 Android 所內建的資料結構,代表著從 SongListRepository 拿出的 Song 要轉換成 MediaMetadataCompat 結構了,明天會和 Player 一起實作。
private var currentPlaylistItems: List<MediaMetadataCompat> = emptyList()
......
private inner class MusicQueueNavigator(
mediaSession: MediaSessionCompat
) : TimelineQueueNavigator(mediaSession) {
override fun getMediaDescription(player: Player, windowIndex: Int): MediaDescriptionCompat =
currentPlaylistItems[windowIndex].description
}
如果大家有注意到前一天的架構圖的話,會看到 MediaSession.Callback 裡面其實很多 callback,比方說 onPlay, onStop ... 等,這些 Callback 在上面的程式碼都沒有實作的,這是因為在 ExoPlayer 的 MediaSessionConnector 已經處理好了
下面這個是 ExoPlayer MediaSessionConnector 的程式碼:
// MediaSessionCompat.Callback implementation.
@Override
public void onPlay() {
if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_PLAY)) {
if (player.getPlaybackState() == Player.STATE_IDLE) {
if (playbackPreparer != null) {
playbackPreparer.onPrepare(/* playWhenReady= */ true);
}
} else if (player.getPlaybackState() == Player.STATE_ENDED) {
seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET);
}
controlDispatcher.dispatchSetPlayWhenReady(
Assertions.checkNotNull(player), /* playWhenReady= */ true);
}
}
這幫開發者處理了很多的樣板 code ,當然前提 Player是使用 ExoPlayer 啦,如果是用其他 Player 就需要自己串接了,收到 onPlay 的 callback,再去呼叫 Player 的播放。
程式碼在這邊,分支名稱(day10_media_session):Fancy/day10_media_session