iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 12
0
Mobile Development

Android 音樂播放器自己來系列 第 12

播放器架構實作 (5) - ExoPlayer 播歌實作

  • 分享至 

  • xImage
  •  

在 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


上一篇
播放器架構實作 (4) - ExoPlayer 設定實作
下一篇
播放器架構實作 (6) - Notification 實作
系列文
Android 音樂播放器自己來30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言