iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 8
0
Mobile Development

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

播放器架構實作 (1) - MediaBrowseService 實作

https://ithelp.ithome.com.tw/upload/images/20200908/20129728pFIH1d0C3u.png

今天就開始實作音樂播放器整體架構了,還記得在第一天最後介紹到的架構圖嗎,這篇會從MediaBrowserService 開始實作,先從比較底層元件開始實作,播歌時需要 Android 架構的 Service 元件,讓音樂在背景能繼續播放,首先先 import androidx media 相關的 library。

implementation 'androidx.media:media:1.1.0'

實作的 Service 繼承 MediaBrowserServiceCompat,為官方為了播歌和瀏覽歌曲實作的 Service,並搭配之後會使用到 MediaBrowser,實作 Client / Server 架構。

同時在 AndroidManifest 內要註冊 Service,就像是 Activity 一樣,其中比較特別的是要加入 intent-filter,和 Android 系統註冊, App 有 MediaBrowser 的功能。

       <service
            android:name=".media.MusicService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.media.browse.MediaBrowserService" />
            </intent-filter>
        </service>

繼承 MediaBrowserServiceCompat 後,有兩個 abstract function 是要實作的:

class MusicService: MediaBrowserServiceCompat() {

    override fun onLoadChildren(
        parentId: String,
        result: Result<MutableList<MediaBrowserCompat.MediaItem>>
    ) {
        TODO("Not yet implemented")
    }

    override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
    ): BrowserRoot? {
        TODO("Not yet implemented")
    }
}

首先是 onLoadChildren 屬於瀏覽歌曲相關,可以看到會傳入 parentId,然後再將 result 傳出,在 uamp 專案內點擊專輯後,會顯示專輯內的歌曲,收到 Id 後,去 local 的 repository 尋找相關的歌曲,然後將結果透過 result.sendResult(items) 回傳,但這個 function 目前用不到,因此就先不實作。

onGetRoot 有很重要的功能,可以決定要不要讓其他 App 或是服務連接上,連上後可以瀏覽音樂檔案並進行音樂播放相關的操作,聽起來有點抽象,就像是第一天提到的可以透過手錶或是其他 App 來控制或是取得相關的音樂資料,就是透過這個 function。

val isKnownCaller = packageValidator.isKnownCaller(clientPackageName, clientUid)
return if (isKnownCaller) {
            /**
             * By default return the browsable root. Treat the EXTRA_RECENT flag as a special case
             * and return the recent root instead.
             */
            val isRecentRequest = rootHints?.getBoolean(EXTRA_RECENT) ?: false
            val browserRootPath = if (isRecentRequest) UAMP_RECENT_ROOT else UAMP_BROWSABLE_ROOT
            BrowserRoot(browserRootPath, rootExtras)
        } else {
            /**
             * Unknown caller. There are two main ways to handle this:
             * 1) Return a root without any content, which still allows the connecting client
             * to issue commands.
             * 2) Return `null`, which will cause the system to disconnect the app.
             *
             * UAMP takes the first approach for a variety of reasons, but both are valid
             * options.
             */
            BrowserRoot(UAMP_EMPTY_ROOT, rootExtras)
        }

那我們要怎麼決定規則呢?先來看 uamp 專案的寫法,會有一個進行確認的 function,裡面仔細看還蠻複雜的,會判一些可以連接的 Package 名稱的白名單(Android Auto, WearOS, Android Auto Simulator, Google Assistant),還會判斷 Sign key 的 signature 是否符合。用 uid 可以判斷是否為自己本身得 App 想要連接或是系統端要連:

val isCallerKnown = when {
            // If it's our own app making the call, allow it.
            callingUid == Process.myUid() -> true
            // If it's one of the apps on the allow list, allow it.
            isPackageInAllowList -> true
            // If the system is making the call, allow it.
            callingUid == Process.SYSTEM_UID -> true
...

那如果判斷是非預期的呼叫端呢,從 uamp 上的註解可以得知有兩個處理方式,一個方是回傳空的 BrowserRoot,就不能瀏覽音樂檔案,但可以進行播放的操作,另外一個方式直接回傳 null,完全不能連上,也就不能控制音樂的播放行為了。

目前先採用比較簡單的做法,只判斷是否為自己本身的 App 端呼叫,如果非自己本身的 App 呼叫,在 debug 版才能連上,release 版就先連不上。之後有其他的服務(ex: Google Assistant)有需要連上,就再來這邊加入。

val isKnownCaller = allowBrowsing(clientPackageName)

return if (isKnownCaller) {
         BrowserRoot(FANCY_BROWSABLE_ROOT, null)
       } else {
         if (BuildConfig.DEBUG) {
             BrowserRoot(FANCY_EMPTY_ROOT, null)
         } else {
            null
         }
       }
private fun allowBrowsing(clientPackageName: String, clientUid: Int): Boolean {
        return clientPackageName == packageName
    }

程式碼在這邊,分支名稱(day8_media_browser_service):Fancy/day8_media_browser_service


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

尚未有邦友留言

立即登入留言