欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android車載多媒體開發(fā)MediaSession框架示例詳解

 更新時間:2022年10月11日 11:28:57   作者:Coolbreeze  
這篇文章主要為大家介紹了Android車載多媒體開發(fā)MediaSession框架示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

一、多媒體應(yīng)用架構(gòu)

1.1 音視頻傳統(tǒng)應(yīng)用架構(gòu)

通常,傳統(tǒng)的播放音頻或視頻的多媒體應(yīng)用由兩部分組成:

播放器:用于吸收數(shù)字媒體并將其呈現(xiàn)為視頻和/或音頻;

界面:帶有用于運行播放器并顯示播放器狀態(tài)(可選)的傳輸控件;

在 Android 應(yīng)用開發(fā)中,從零開始構(gòu)建自己的播放器還可以考慮以下選項:

  • MediaPlayer :提供準(zhǔn)系統(tǒng)播放器的基本功能,支持最常見的音頻/視頻格式和數(shù)據(jù)源。
  • ExoPlayer :一個提供低層級 Android 音頻 API 的開放源代碼庫。ExoPlayer 支持 DASH 和 HLS 流等高性能功能,這些功能在 MediaPlayer 中未提供。 眾所周知,如果要在應(yīng)用的后臺繼續(xù)播放音頻,最常見的方式就是把 Player 放置在 Service 中,Service 提供一個 Binder 來實現(xiàn)界面播放器之間的通信。但是,如果遇到鎖屏?xí)r,如果要與 Service 之間進(jìn)行通信就不得不用到 AIDL 接口/廣播/ContentProvider 來完成與其它應(yīng)用之間的通信,而這些通信手段既增加了應(yīng)用開發(fā)者之間的溝通成本,也增加了應(yīng)用之間的耦合度。為了解決上面的問題,Android 官方從 Android5.0 開始提供了 MediaSession 框架。

1.2 MediaSession 框架

MediaSession 框架規(guī)范了音視頻應(yīng)用中界面與播放器之間的通信接口,實現(xiàn)界面與播放器之間的完全解耦。MediaSession 框架定義了媒體會話和媒體控制器兩個重要的類,它們?yōu)闃?gòu)建多媒體播放器應(yīng)用提供了一個完善的技術(shù)架構(gòu)。

媒體會話和媒體控制器通過以下方式相互通信:使用與標(biāo)準(zhǔn)播放器操作(播放、暫停、停止等)相對應(yīng)的預(yù)定義回調(diào),以及用于定義應(yīng)用獨有的特殊行為的可擴(kuò)展自定義調(diào)用。

媒體會話

媒體會話負(fù)責(zé)與播放器的所有通信。它會對應(yīng)用的其他部分隱藏播放器的 API。系統(tǒng)只能從控制播放器的媒體會話中調(diào)用播放器。

會話會維護(hù)播放器狀態(tài)(播放/暫停)的表示形式以及播放內(nèi)容的相關(guān)信息。會話可以接收來自一個或多個媒體控制器的 回調(diào) 。這樣,應(yīng)用的界面以及運行 Wear OS 和 Android Auto 的配套設(shè)備便可以控制您的播放器。響應(yīng)回調(diào)的邏輯必須保持一致。無論哪個客戶端應(yīng)用發(fā)起了回調(diào),對 MediaSession 回調(diào)的響應(yīng)都是相同的。

媒體控制器

媒體控制器的作用是隔離界面,界面的代碼只與媒體控制器(而非播放器本身)通信,媒體控制器會將傳輸控制操作轉(zhuǎn)換為對媒體會話的回調(diào)。每當(dāng)會話狀態(tài)發(fā)生變化時,它也會接收來自媒體會話的回調(diào),這為自動更新關(guān)聯(lián)界面提供了一種機(jī)制,媒體控制器一次只能連接到一個媒體會話。

當(dāng)您使用媒體控制器和媒體會話時,就可以在運行時部署不同的接口和/或播放器。這樣一來,您可以根據(jù)運行應(yīng)用的設(shè)備的功能單獨更改該應(yīng)用的外觀和/或性能。

二、MediaSession

2.1 概述

MediaSession 框架主要是用來解決音樂界面和服務(wù)之間的通信問題,屬于典型的 C/S 架構(gòu),有四個常用的成員類,分別是 MediaBrowser、MediaBrowserService、MediaController 和 MediaSession,是整個 MediaSession 框架流程控制的核心。

  • MediaBrowser:媒體瀏覽器,用來連接媒體服務(wù) MediaBrowserService 和訂閱數(shù)據(jù),在注冊的回調(diào)接口中可以獲取到 Service 的連接狀態(tài)、獲取音樂數(shù)據(jù),一般在客戶端中創(chuàng)建。
  • MediaBrowserService:媒體服務(wù),它有兩個關(guān)鍵的回調(diào)函數(shù),onGetRoot(控制客戶端媒體瀏覽器的連接請求,返回值中決定是否允許連接),onLoadChildren(媒體瀏覽器向服務(wù)器發(fā)送數(shù)據(jù)訂閱請求時會被調(diào)用,一般在這里執(zhí)行異步獲取數(shù)據(jù)的操作,然后在將數(shù)據(jù)發(fā)送回媒體瀏覽器注冊的接口中)。
  • MediaController:媒體控制器,在客戶端中工作,通過控制器向媒體服務(wù)器發(fā)送指令,然后通過 MediaControllerCompat.Callback 設(shè)置回調(diào)函數(shù)來接受服務(wù)端的狀態(tài)。MediaController 創(chuàng)建時需要受控端的配對令牌,因此需要在瀏覽器連接成功后才進(jìn)行 MediaController 的創(chuàng)建。
  • MediaSession:媒體會話,受控端,通過設(shè)置 MediaSessionCompat.Callback 回調(diào)來接收 MediaController 發(fā)送的指令,收到指令后會觸發(fā) Callback 中的回調(diào)方法,比如播放暫停等。Session 一般在 Service.onCreate 方法中創(chuàng)建,最后需調(diào)用 setSessionToken 方法設(shè)置用于和控制器配對的令牌并通知瀏覽器連接服務(wù)成功。 其中,MediaBrowser 和 MediaController 是客戶端使用的,MediaBrowserService 和 MediaSession 是服務(wù)端使用的。由于客戶端和服務(wù)端是異步通信,所以采用的大量的回調(diào),因此有大量的回調(diào)類,框架示意圖如下。

2.2 MediaBrowser

MediaBrowser 是媒體瀏覽器,用來連接 MediaBrowserService 和訂閱數(shù)據(jù),通過它的回調(diào)接口我們可以獲取與 Service的連接狀態(tài)以及獲取在 Service中的音樂庫數(shù)據(jù)。

客戶端(也就是前面提到的界面,或者說是控制端)中創(chuàng)建。媒體瀏覽器不是線程安全的,所有調(diào)用都應(yīng)在構(gòu)造 MediaBrowser 的線程上進(jìn)行。

@RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val component = ComponentName(this, MediaService::class.java)
    mMediaBrowser = MediaBrowser(this, component, connectionCallback, null);
    mMediaBrowser.connect()
}

2.2.1 MediaBrowser.ConnectionCallback

連接狀態(tài)回調(diào),當(dāng) MediaBrowser 向 service 發(fā)起連接請求后,請求結(jié)果將在這個 callback 中返回,獲取到的 meidaId 對應(yīng)服務(wù)端在 onGetRoot 函數(shù)中設(shè)置的 mediaId,如果連接成功那么就可以做創(chuàng)建媒體控制器之類的操作了。

@RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val component = ComponentName(this, MediaService::class.java)
    mMediaBrowser = MediaBrowser(this, component, connectionCallback, null);
    mMediaBrowser.connect()
}
private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
    override fun onConnected() {
        super.onConnected()
        ...  //連接成功后我們才可以創(chuàng)建媒體控制器
    }
    override fun onConnectionFailed() {
        super.onConnectionFailed()
    }
    override fun onConnectionSuspended() {
        super.onConnectionSuspended()
    }
}

2.2.2 MediaBrowser.ItemCallback

媒體控制器是負(fù)責(zé)向 service 發(fā)送例如播放暫停之類的指令的,這些指令的執(zhí)行結(jié)果將在這個回調(diào)中返回,可重寫的函數(shù)有很多,比如播放狀態(tài)的改變,音樂信息的改變等。

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
    override fun onConnected() {
        super.onConnected()
         ... //返回執(zhí)行結(jié)果
        if(mMediaBrowser.isConnected) {
            val mediaId = mMediaBrowser.root
            mMediaBrowser.getItem(mediaId, itemCallback)
        }
    }
}
@RequiresApi(Build.VERSION_CODES.M)
private val itemCallback = object : MediaBrowser.ItemCallback(){
    override fun onItemLoaded(item: MediaBrowser.MediaItem?) {
        super.onItemLoaded(item)
    }
    override fun onError(mediaId: String) {
        super.onError(mediaId)
    }
}

2.2.3 MediaBrowser.MediaItem

包含有關(guān)單個媒體項的信息,用于瀏覽/搜索媒體。MediaItem依賴于服務(wù)端提供,因此框架本身無法保證它包含的值都是正確的。

2.2.4 MediaBrowser.SubscriptionCallback

連接成功后,首先需要的是訂閱服務(wù),同樣還需要注冊訂閱回調(diào),訂閱成功的話服務(wù)端可以返回一個音樂信息的序列,可以在客戶端展示獲取的音樂列表數(shù)據(jù)。例如,下面是訂閱 MediaBrowserService 中 MediaBrowser.MediaItem 列表變化的回調(diào)。

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
    override fun onConnected() {
        super.onConnected()
        // ...
        if(mMediaBrowser.isConnected) {
            val mediaId = mMediaBrowser.root
            //需要先取消訂閱
            mMediaBrowser.unsubscribe(mediaId)
            //服務(wù)端會調(diào)用 onLoadChildren
            mMediaBrowser.subscribe(mediaId, subscribeCallback)
        }
    }
}
private val subscribeCallback = object : MediaBrowser.SubscriptionCallback(){
    override fun onChildrenLoaded(
        parentId: String,
        children: MutableList<MediaBrowser.MediaItem>
    ) {
        super.onChildrenLoaded(parentId, children)
    }
    override fun onChildrenLoaded(
        parentId: String,
        children: MutableList<MediaBrowser.MediaItem>,
        options: Bundle
    ) {
        super.onChildrenLoaded(parentId, children, options)
    }
    override fun onError(parentId: String) {
        super.onError(parentId)
    }
    override fun onError(parentId: String, options: Bundle) {
        super.onError(parentId, options)
    }
}

2.3 MediaController

媒體控制器,用來向服務(wù)端發(fā)送控制指令,例如:播放、暫停等等,在客戶端中創(chuàng)建。媒體控制器是線程安全的,MediaController 還有一個關(guān)聯(lián)的權(quán)限 android.permission.MEDIA_CONTENT_CONTROL(不是必須加的權(quán)限)必須是系統(tǒng)級應(yīng)用才可以獲取,幸運的是車載應(yīng)用一般都是系統(tǒng)級應(yīng)用。

同時,MediaController必須在 MediaBrowser 連接成功后才可以創(chuàng)建。 所以,創(chuàng)建 MediaController 的代碼如下:

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
    override fun onConnected() {
        super.onConnected()
        // ...
        if(mMediaBrowser.isConnected) {
            val sessionToken = mMediaBrowser.sessionToken
            mMediaController = MediaController(applicationContext,sessionToken)
        }
    }
}

2.3.1 MediaController.Callback

用于從 MediaSession 接收回調(diào),所以使用的時候需要將 MediaController.Callback 注冊到 MediaSession 中,如下:

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
    override fun onConnected() {
        super.onConnected()
        // ...
        if(mMediaBrowser.isConnected) {
            val sessionToken = mMediaBrowser.sessionToken
            mMediaController = MediaController(applicationContext,sessionToken)
            mMediaController.registerCallback(controllerCallback)
        }
    }
}
private val controllerCallback = object : MediaController.Callback() {
     override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
        super.onAudioInfoChanged(info)
       ... //回調(diào)方法接收受控端的狀態(tài),從而根據(jù)相應(yīng)的狀態(tài)刷新界面 UI
    }
    override fun onExtrasChanged(extras: Bundle?) {
        super.onExtrasChanged(extras)
    }
    // ...
}

2.3.2 MediaController.PlaybackInfo

獲取當(dāng)前播放的音頻信息,包含播放的進(jìn)度、時長等。

2.3.3 MediaController.TransportControls

用于控制會話中媒體播放的接口。客戶端可以通過 Session 發(fā)送媒體控制命令,使用方式如下:

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
    override fun onConnected() {
        super.onConnected()
        // ...
        if(mMediaBrowser.isConnected) {
            val sessionToken = mMediaBrowser.sessionToken
            mMediaController = MediaController(applicationContext,sessionToken)
            // 播放媒體
            mMediaController.transportControls.play()
            // 暫停媒體
            mMediaController.transportControls.pause()
        }
    }
}

2.4 MediaBrowserService

媒體瀏覽器服務(wù),繼承自 Service,MediaBrowserService 屬于服務(wù)端,也是承載播放器(如 MediaPlayer、ExoPlayer 等)和 MediaSession 的容器。 繼承 MediaBrowserService 后,我們需要復(fù)寫 onGetRoot和 onLoadChildren兩個方法。onGetRoot 通過的返回值決定是否允許客戶端的 MediaBrowser 連接到 MediaBrowserService。 當(dāng)客戶端調(diào)用 MediaBrowser.subscribe時會觸發(fā) onLoadChildren 方法。下面是使用事例:

const val FOLDERS_ID = "__FOLDERS__"
const val ARTISTS_ID = "__ARTISTS__"
const val ALBUMS_ID = "__ALBUMS__"
const val GENRES_ID = "__GENRES__"
const val ROOT_ID = "__ROOT__"
class MediaService : MediaBrowserService() {
    override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
    ): BrowserRoot? {
        // 由 MediaBrowser.connect 觸發(fā),可以通過返回 null 拒絕客戶端的連接。
        return BrowserRoot(ROOT_ID, null)
    }
    override fun onLoadChildren(
        parentId: String,
        result: Result<MutableList<MediaBrowser.MediaItem>>
    ) {
       //由 MediaBrowser.subscribe 觸發(fā)
        when (parentId) {
            ROOT_ID -> {
                // 查詢本地媒體庫
                result.detach()
                result.sendResult()
            }
            FOLDERS_ID -> {
            }
            ALBUMS_ID -> {
            }
            ARTISTS_ID -> {
            }
            GENRES_ID -> {
            }
            else -> {
            }
        }
    }
}

最后,還需要在 manifest 中注冊這個 Service。

<service
    android:name=".MediaService"
    android:label="@string/service_name">
    <intent-filter>
        <action android:name="android.media.browse.MediaBrowserService" />
    </intent-filter>
</service>

2.4.1 MediaBrowserService.BrowserRoot

返回包含瀏覽器服務(wù)首次連接時需要發(fā)送給客戶端的信息。構(gòu)造函數(shù)如下:

MediaBrowserService.BrowserRoot(String rootId, Bundle extras)

除此之外,還有兩個方法:

  • getExtras():獲取有關(guān)瀏覽器服務(wù)的附加信息
  • getRootId():獲取用于瀏覽的根 ID 2.4.2 MediaBrowserService.Result

包含瀏覽器服務(wù)返回給客戶端的結(jié)果集。通過調(diào)用 sendResult()將結(jié)果返回給調(diào)用方,但是在此之前需要調(diào)用 detach()。

  • detach():將此消息與當(dāng)前線程分離,并允許稍后進(jìn)行調(diào)用 sendResult(T)
  • sendResult():將結(jié)果發(fā)送回調(diào)用方。 2.5 MediaSession

媒體會話,即**受控端。**通過設(shè)定 MediaSession.Callback回調(diào)來接收媒體控制器 MediaController發(fā)送的指令,如控制音樂的【上一曲】、【下一曲】等。

創(chuàng)建 MediaSession后還需要調(diào)用 setSessionToken()方法設(shè)置用于和**控制器配對的令牌,使用方式如下:

const val FOLDERS_ID = "__FOLDERS__"
const val ARTISTS_ID = "__ARTISTS__"
const val ALBUMS_ID = "__ALBUMS__"
const val GENRES_ID = "__GENRES__"
const val ROOT_ID = "__ROOT__"
class MediaService : MediaBrowserService() {
    private lateinit var mediaSession: MediaSession;
    override fun onCreate() {
        super.onCreate()
        mediaSession = MediaSession(this, "TAG")
        mediaSession.setCallback(callback)
        sessionToken = mediaSession.sessionToken
    }
    // 與 MediaController.transportControls 中的大部分方法都是一一對應(yīng)的
    // 在該方法中實現(xiàn)對 播放器 的控制,
    private val callback = object : MediaSession.Callback() {
        override fun onPlay() {
            super.onPlay()
            // 處理 播放器 的播放邏輯。
            // 車載應(yīng)用的話,別忘了處理音頻焦點
        }
        override fun onPause() {
            super.onPause()
        }
    }
        override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
    ): BrowserRoot? {
        Log.e("TAG", "onGetRoot: $rootHints")
        return BrowserRoot(ROOT_ID, null)
    }
    override fun onLoadChildren(
        parentId: String,
        result: Result<MutableList<MediaBrowser.MediaItem>>
    ) {
        result.detach()
        when (parentId) {
            ROOT_ID -> {
                result.sendResult(null)
            }
            FOLDERS_ID -> {
            }
            ALBUMS_ID -> {
            }
            ARTISTS_ID -> {
            }
            GENRES_ID -> {
            }
            else -> {
            }
        }
    }
    override fun onLoadItem(itemId: String?, result: Result<MediaBrowser.MediaItem>?) {
        super.onLoadItem(itemId, result)
        Log.e("TAG", "onLoadItem: $itemId")
    }
}

2.5 MediaSession.Callback

接收來自客戶端或系統(tǒng)的媒體按鈕、傳輸控件和命令,入【上一曲】、【下一曲】。與 MediaController.transportControls 中的大部分方法都是一一對應(yīng)的。

private val callback = object : MediaSession.Callback() {
     override fun onPlay() {
        super.onPlay()       
        if (!mediaSession.isActive) {
            mediaSession.isActive = true
        }
        //更新播放狀態(tài).
        val state = PlaybackState.Builder()
            .setState(
                PlaybackState.STATE_PLAYING,1,1f
            )
            .build()
        mediaSession.setPlaybackState(state)
    }
    override fun onPause() {
        super.onPause()
    }
    override fun onStop() {
        super.onStop()
    }
}

2.5.1 MediaSession.QueueItem

播放隊列一部分的單個項目,相比 MediaMetadata,多了一個 ID 屬性。常用的方法有:

  • getDescription():返回介質(zhì)的說明,包含媒體的基礎(chǔ)信息,如標(biāo)題、封面等。
  • getQueueId():獲取此項目的隊列 ID。

 MediaSession.Token

表示正在進(jìn)行的會話,可以通過會話所有者傳遞給客戶端,以允許客戶端與服務(wù)端之間建立通信。

2.6 PlaybackState

用于承載播放狀態(tài)的類。如當(dāng)前播放位置和當(dāng)前控制功能。在 MediaSession.Callback更改狀態(tài)后需要調(diào)用 MediaSession.setPlaybackState把狀態(tài)同步給客戶端。

private val callback = object : MediaSession.Callback() {
    override fun onPlay() {
        super.onPlay()
        // 更新狀態(tài)
        val state = PlaybackState.Builder()
            .setState(
                PlaybackState.STATE_PLAYING,1,1f
            )
            .build()
        mediaSession.setPlaybackState(state)
    }
}

2.6.1 PlaybackState.Builder

PlaybackState.Builder 主要用來創(chuàng)建 PlaybackState 對象,創(chuàng)建它使用的是建造者模式,如下。

PlaybackState state = new PlaybackState.Builder()
        .setState(PlaybackState.STATE_PLAYING,
                mMediaPlayer.getCurrentPosition(), PLAYBACK_SPEED)
        .setActions(PLAYING_ACTIONS)
        .addCustomAction(mShuffle)
        .setActiveQueueItemId(mQueue.get(mCurrentQueueIdx).getQueueId())
        .build();

2.6.2 PlaybackState.CustomAction

CustomActions可用于通過將特定于應(yīng)用程序的操作發(fā)送給 MediaControllers,這樣就可以擴(kuò)展標(biāo)準(zhǔn)傳輸控件的功能。

CustomAction action = new CustomAction
        .Builder("android.car.media.localmediaplayer.shuffle",
        mContext.getString(R.string.shuffle),
        R.drawable.shuffle)
        .build();
PlaybackState state = new PlaybackState.Builder()
        .setState(PlaybackState.STATE_PLAYING,
                mMediaPlayer.getCurrentPosition(), PLAYBACK_SPEED)
        .setActions(PLAYING_ACTIONS)
        .addCustomAction(action)
        .setActiveQueueItemId(mQueue.get(mCurrentQueueIdx).getQueueId())
        .build();

常見的 API,有如下一些:

  • getAction():返回 CustomAction 的 action
  • getExtras():返回附加項,這些附加項提供有關(guān)操作的其他特定于應(yīng)用程序的信息
  • getIcon():返回 package 中圖標(biāo)的資源 ID
  • getName():返回此操作的顯示名稱 2.7 MediaMetadata

包含有關(guān)項目的基礎(chǔ)數(shù)據(jù),例如標(biāo)題、藝術(shù)家等。一般需要服務(wù)端從本地數(shù)據(jù)庫或遠(yuǎn)端查詢出原始數(shù)據(jù)在封裝成 MediaMetadata 再通過 MediaSession.setMetadata(metadata)返回到客戶端的 MediaController.Callback.onMetadataChanged中。

常見的 API 有如下:

  • containsKey():如果給定的 key 包含在元數(shù)據(jù)中,則返回 true。
  • describeContents():描述此可打包實例的封送處理表示中包含的特殊對象的種類。
  • getBitmap():返回給定的 key 的 Bitmap,如果給定 key 不存在位圖,則返回 null。
  • getBitmapDimensionLimit():獲取創(chuàng)建此元數(shù)據(jù)時位圖的寬度/高度限制
  • getDescription():獲取此元數(shù)據(jù)的簡單說明以進(jìn)行顯示。
  • keySet():返回一個 Set,其中包含在此元數(shù)據(jù)中用作 key 的字符串。 三、示例

下圖是 MediaSession 框架核心類的通信過程。

可以看到,在 MediaSession 框架中,首先客戶端通過 MediaBrowserService 連接到 MediaBrowserService,MediaBrowserService 接受到請求之后處理相關(guān)的請求,MediaSession 控制播放狀態(tài),并將狀態(tài)同步給客戶端,客戶端最后 MediaController 進(jìn)行相應(yīng)的操作。

客戶端示例代碼:

class MainActivity : AppCompatActivity() {
    private lateinit var mMediaBrowser: MediaBrowser
    private lateinit var mMediaController: MediaController
    @RequiresApi(Build.VERSION_CODES.M)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val component = ComponentName(this, MediaService::class.java)
        mMediaBrowser = MediaBrowser(this, component, connectionCallback, null);
        // 連接到 MediaBrowserService,會觸發(fā) MediaBrowserService 的 onGetRoot 方法。
        mMediaBrowser.connect()
        findViewById<Button>(R.id.btn_play).setOnClickListener {
            mMediaController.transportControls.play()
        }
    }
    private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
        override fun onConnected() {
            super.onConnected()
            if (mMediaBrowser.isConnected) {
                val sessionToken = mMediaBrowser.sessionToken
                mMediaController = MediaController(applicationContext, sessionToken)
                mMediaController.registerCallback(controllerCallback)
                // 獲取根 mediaId
                val rootMediaId = mMediaBrowser.root
                // 獲取根 mediaId 的 item 列表,會觸發(fā) MediaBrowserService.onLoadItem 方法
                mMediaBrowser.getItem(rootMediaId,itemCallback)
                mMediaBrowser.unsubscribe(rootMediaId)
                // 訂閱服務(wù)端 media item 的改變,會觸發(fā) MediaBrowserService.onLoadChildren 方法
                mMediaBrowser.subscribe(rootMediaId, subscribeCallback)
            }
        }
    }
    private val controllerCallback = object : MediaController.Callback() {
        override fun onPlaybackStateChanged(state: PlaybackState?) {
            super.onPlaybackStateChanged(state)
            Log.d("TAG", "onPlaybackStateChanged: $state")
            when(state?.state){
                PlaybackState.STATE_PLAYING ->{
                    // 處理 UI
                }
                PlaybackState.STATE_PAUSED ->{
                    // 處理 UI
                }
                // 還有其它狀態(tài)需要處理
            }
        }
        //音頻信息,音量
        override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
            super.onAudioInfoChanged(info)
            val currentVolume = info?.currentVolume
            // 顯示在 UI 上
        }
        override fun onMetadataChanged(metadata: MediaMetadata?) {
            super.onMetadataChanged(metadata)
            val artUri = metadata?.getString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI)
            // 顯示 UI 上
        }
        override fun onSessionEvent(event: String, extras: Bundle?) {
            super.onSessionEvent(event, extras)
            Log.d("TAG", "onSessionEvent: $event")
        }
        // ...
    }
    private val subscribeCallback = object : MediaBrowser.SubscriptionCallback() {
        override fun onChildrenLoaded(
            parentId: String,
            children: MutableList<MediaBrowser.MediaItem>
        ) {
            super.onChildrenLoaded(parentId, children)
        }
        override fun onChildrenLoaded(
            parentId: String,
            children: MutableList<MediaBrowser.MediaItem>,
            options: Bundle
        ) {
            super.onChildrenLoaded(parentId, children, options)
        }
        override fun onError(parentId: String) {
            super.onError(parentId)
        }
    }
    private val itemCallback = object : MediaBrowser.ItemCallback() {
        override fun onItemLoaded(item: MediaBrowser.MediaItem?) {
            super.onItemLoaded(item)
        }
        override fun onError(mediaId: String) {
            super.onError(mediaId)
        }
    }
}

下面是服務(wù)端的示例源碼,主要用于處理客戶端的請求,并將結(jié)果返回給客戶端。

const val FOLDERS_ID = "__FOLDERS__"
const val ARTISTS_ID = "__ARTISTS__"
const val ALBUMS_ID = "__ALBUMS__"
const val GENRES_ID = "__GENRES__"
const val ROOT_ID = "__ROOT__"
class MediaService : MediaBrowserService() {
    // 控制是否允許客戶端連接,并返回 root media id 給客戶端
    override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
    ): BrowserRoot? {
        Log.e("TAG", "onGetRoot: $rootHints")
        return BrowserRoot(ROOT_ID, null)
    }
    // 處理客戶端的訂閱信息
    override fun onLoadChildren(
        parentId: String,
        result: Result<MutableList<MediaBrowser.MediaItem>>
    ) {
        Log.e("TAG", "onLoadChildren: $parentId")
        result.detach()
        when (parentId) {
            ROOT_ID -> {
                result.sendResult(null)
            }
            FOLDERS_ID -> {
            }
            ALBUMS_ID -> {
            }
            ARTISTS_ID -> {
            }
            GENRES_ID -> {
            }
            else -> {
            }
        }
    }
    override fun onLoadItem(itemId: String?, result: Result<MediaBrowser.MediaItem>?) {
        super.onLoadItem(itemId, result)
        Log.e("TAG", "onLoadItem: $itemId")
        // 根據(jù) itemId,返回對用 MediaItem
        result?.detach()
        result?.sendResult(null)
    }
    private lateinit var mediaSession: MediaSession;
    override fun onCreate() {
        super.onCreate()
        mediaSession = MediaSession(this, "TAG")
        mediaSession.setCallback(callback)
        // 設(shè)置 token
        sessionToken = mediaSession.sessionToken
    }
    // 與 MediaController.transportControls 中的方法是一一對應(yīng)的。
    // 在該方法中實現(xiàn)對 播放器 的控制,
    private val callback = object : MediaSession.Callback() {
        override fun onPlay() {
            super.onPlay()
            // 處理 播放器 的播放邏輯。
            // 車載應(yīng)用的話,別忘了處理音頻焦點
            Log.e("TAG", "onPlay:")
            if (!mediaSession.isActive) {
                mediaSession.isActive = true
            }
            // 更新狀態(tài)
            val state = PlaybackState.Builder()
                .setState(
                    PlaybackState.STATE_PLAYING, 1, 1f
                )
                .build()
            mediaSession.setPlaybackState(state)
        }
        override fun onPause() {
            super.onPause()
        }
        override fun onStop() {
            super.onStop()
        }
        //省略其他方法
    }
}

上文主要介紹車載自媒體開發(fā)與MediaSession框架解析;這只是其中一小部分;更多的Android車載技術(shù)可以前往[私信]領(lǐng)取,里面內(nèi)容如下腦圖所示:(需要可以參考,當(dāng)做輔導(dǎo)資料)

文末

車載系統(tǒng)開發(fā),在這幾年崗位逐漸增多。Android轉(zhuǎn)崗車載開發(fā)是個很好的發(fā)展方向。汽車的普及同比10年增長300%以上;近幾年的新能源汽車逐漸普及,車載開發(fā)人員更是需求很大;普遍缺少人才。

感覺Android市場下滑的厲害,淘汰人員逐漸增多。一定要眼看未來,才沒有近憂。

以上就是Android車載多媒體開發(fā)MediaSession框架示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Android車載多媒體MediaSession的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論