iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 10
0
Mobile Development

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

播放器架構實作 (3) - MediaSession 實作

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 已經處理好了

https://ithelp.ithome.com.tw/upload/images/20200910/20129728ALfVPB8L0k.png
Ref: Android:MediaSession框架介紹

下面這個是 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


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

尚未有邦友留言

立即登入留言