在 onPrepareFromMediaId 的參數中,會拿到 mediaId,就可以使用這個 id 去存歌曲的 Repository,找到對應的歌曲資訊:
override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) {
launch {
val itemToPlay = songListRepository.getSongs().find { item ->
item.id.toString() == mediaId
}?.toMediaMetadataCompat()
val playlist = songListRepository.getSongs().toMediaMetadataCompat()
if (itemToPlay == null) {
Log.w(TAG, "Content not found: MediaID=$mediaId")
} else {
preparePlaylist(
playlist,
itemToPlay,
playWhenReady,
0
)
}
}
}
並將原本所設定的 Song 結構,轉成系統的資料結構 MediaMetadataCompat,這個結構是 MediaSession 和 MediaController 傳遞音樂資訊時的資料結構,因此需要轉換成此結構。
display 相關欄位是設定給 notification 顯示資訊時使用的。
fun Song.toMediaMetadataCompat(): MediaMetadataCompat =
MediaMetadataCompat.Builder().also {
it.id = id.toString()
it.title = title
it.artist = artistName
it.albumArtUri = coverPath
it.mediaUri = contentUri.toString()
it.displayTitle = title
it.displaySubtitle = artistName
it.displayDescription = albumName
it.displayIconUri = coverPath
}.build()
fun List<Song>.toMediaMetadataCompat(): List<MediaMetadataCompat> =
this.map { it.toMediaMetadataCompat() }
上面的 MediaMetadataCompat.Builder 內的 id 是簡化的寫法(參考 uamp),因為有在 extension 內,設定好會對應到的 key 了。
inline var MediaMetadataCompat.Builder.id: String
@Deprecated(NO_GET, level = DeprecationLevel.ERROR)
get() = throw IllegalAccessException("Cannot get from MediaMetadataCompat.Builder")
set(value) {
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, value)
}
接著來看 preparePlaylist 做了什麼事了吧:
private fun preparePlaylist(
metadataList: List<MediaMetadataCompat>,
itemToPlay: MediaMetadataCompat?, playWhenReady: Boolean,
playbackStartPositionMs: Long) {
val initialWindowIndex = if (itemToPlay == null) 0 else metadataList.indexOf(itemToPlay)
currentPlaylistItems = metadataList
currentPlayer.playWhenReady = playWhenReady
currentPlayer.stop(/* reset= */ true)
if (currentPlayer == exoPlayer) {
val mediaSource = metadataList.toMediaSource(dataSourceFactory)
exoPlayer.prepare(mediaSource)
exoPlayer.seekTo(initialWindowIndex, playbackStartPositionMs)
}
}
傳入的第一個參數為 metadataList,為準備播放的 list,如果是點擊一張專輯內的歌,那這邊就會應該要是同張專輯內的歌曲。目前實作是拿手機內全部的歌曲,因此這邊會有傳入全部的歌曲。要播一首歌、一張專輯、一張歌單,會依據傳入歌曲而決定,這邊在之後可以實作播放一張專輯的功能。
itemToPlay 是現在要播的歌曲,從下面的 code 得知,傳給 ExoPlayer 播放時,是給整個要播放的 list,然後第一首要播放的位置,還有播放一首歌曲開始的秒數。
toMediaSource 又要來轉換資料結構了 XD,ExoPlayer 播放的來源要傳入 MediaSource 類型的,ConcatenatingMediaSource 就相當是一個播放清單的結構,裡面有很多個播放的 Uri。
currentPlayer 則是存現在用到的 Player,在目前就只有用到 exoPlayer,之後可以用 ChromeCast 播放(參考 uamp),需要用 CastPlayer 來播放,currentPlayer 就可以判斷目前是哪個 player,有對應的行為。
程式碼在這,分支名稱(day12_set_exoplayer):Fancy/day12_set_exoplayer