簡介
統一播放器提供媒體播放一致性的交互和視覺體驗,減少各個媒體應用和場景獨自開發的重復工作量,實現媒體播放鏈路的一致性,減少碎片化的Bug。本文面向應用開發者介紹如何快速接入媒體播放器。
主要功能:
- 新設計的統一播放UI組件,視頻支持的手勢操作,包括左右滑拖動進度、左上下滑調節亮度、右上下滑調節音量、雙擊暫停/播放,同時對外暴露了長按手勢接口應用可以實現類似快進快退功能;
- 支持方控、Mini播放器、PSD的播控和狀態顯示,應用只需要申請媒體中心權限的license即可,支持媒體中心的狀態保持和語音控制通道,需要APP中接入對應的接口;
- 支持視頻行車娛樂限制,非P檔播放視頻會暫停播放彈框提示;
- 使用原生的的media3 1.3.0版本,支持原生的androidx 和framework media session;
- 支持音頻焦點自管理,包括電話狀態的播控也基于音頻焦點統一處理;
整體架構圖如下
- 應用通過配置UI,然后創建MediaController播放媒體;
- controler端接入了行車娛樂限制的檢測和提示;
- service端實現了google原生的media3 session service;
- 媒體中心通過mediasession和播放器連接;
- 底層使用media3 exoplayer播放和音頻焦點管理;
接入流程
1、配置依賴
implementation 'com.max.mediaplayer:uniteplayer:1.x.x
備注:uniteplayer中包含了視頻控制UI組件,當只需要修復控制UI組件的bug時,可以更新整體uniteplayer播放器組件,也可以只增加UI組件的依賴,比如:implementation (‘com.max.media:media-video:1.2.0’)。
2、初始化
建議在application創建的時候調用初始化接口:
init(context: Context, isVideo: Boolean, enableMediaCenter: Boolean = false)
參數 | 說明 |
---|---|
context | 應用context |
isVideo | 是否視頻應用,視頻會初始化車機相關的adapterapi,音樂目前不會初始化 |
enableMediaCenter | 需要遠程方控、語音控制、mini播放器控制這些能力時設置為true,否則為false,默認為false |
3、接入 UI 組件 (視頻應用)
這里的UI組件只針對視頻應用,音頻應用的UI組件在媒體組件庫中,參考媒體視頻組件。
1)配置PlayerView
在布局中增加FlexPlayerView,視頻內容顯示和播控UI都在此view中,一般默認配置如下即可:
<com.max.uniteplayer.ui.FlexPlayerViewandroid:id="@+id/player_view"android:layout_width="match_parent"android:layout_height="match_parent"/>
其中播控UI是可單獨接入的,若需定制,接入見視頻組件接入文檔。
2)配置視頻渲染view
一般情況下不需要特別配置,支持的定制視頻顯示view參數如下:
比如在上面FlexPlayerView xml中用app:surface_type=“surface_view”
surface_type:surface類型,取值如下,默認是surface_view類型:
<attr name="surface_type" format="enum"> <enum name="none" value="0"/> <enum name="surface_view" value="1"/> <enum name="texture_view" value="2"/> <enum name="spherical_gl_surface_view" value="3"/> <enum name="video_decoder_gl_surface_view" value="4"/></attr>
resize_mode,取值如下,默認fit模式:
<attr name="resize_mode" format="enum"> <enum name="fit" value="0"/> <enum name="fixed_width" value="1"/><enum name="fixed_height" value="2"/> <enum name="fill" value="3"/> <enum name="zoom" value="4"/> </attr>
first_render_delay,首幀顯示延遲時長,用于規避首幀渲染慢閃拉伸的問題,int型,單位ms,一舨不需要設置,默認0;
3)配置視頻播控View
FlexPlayerView本身對外暴露了兩個接口:
接口 | 說明 |
---|---|
fun setTitleBarVisible(visible: Boolean) | 因此視頻播放器中頂部title和關閉按鈕,一般應用自己實現的可以通過此接口隱藏 |
fun enableUpDownGesture(enable: Boolean) | 使能上下滑動,默認播放器是支持的,可以設置false關閉 |
bindControlView(listener: FlexPlayerView.ControlViewListener) | 獲取視頻播控view控制器,提供更多配置能力,比如增加手勢監聽回調、添加自定義的播控按鈕等,具體可以參考視頻組件接入文檔。 |
4、使用播放器播放
有3種播放接口,選擇其中一個:
1)FlexSimplePlayer
封裝的最高層播放接口,內部封裝好了原生MediaItem對象,簡單的視頻播放場景推薦使用此接口:
class FlexSimplePlayer(context: Context?,isVideo: Boolean,enableParkDetect: Boolean = true,enableParkDialog: Boolean = true,controllerCallback: FlexControllerCallback? = null)
參數說明:
參數 | 說明 |
---|---|
context | 上下文; |
enableParkDetect | 只針對視頻生效,是否啟用行車娛樂限制,非駐車檔會自動暫停視頻播放(默認是強制要求的); |
enableParkDialog | 只針對視頻生效,是否彈出行車娛樂限制框,設置為disable后需要應用可以自己實現提示界面; |
開始播放:
player.startPlay(context: Context,mediaData: List<MediaData>,view: FlexPlayerView?,listener: FlexMediaListener?,mediaCenterData: MediaCenterData? = null)
參數說明:
參數 | 說明 |
---|---|
context | 上下文,注意視頻播放要使用activity的context,因為行車娛樂限制需要彈框 |
mediaData | 媒體列表,每個媒體item包括url、metadata、mimetiype,其中url是媒體地址,可以是本地、在線點播或直播地址,medadata是media3的原始類型,title和artist字段會顯示在mini播放器和PSD上,mimeTypes指定媒體類型,url是對應后綴的無需設置,有些比如優酷投屏中有一個直播url是/m3u8結尾 而不是.m3u8結尾 需要設置mimeTypes = MimeTypes.APPLICATION_M3U8; |
view | 類型為FlexPlayerView,自實現UI的此參數設置為null |
listener | 媒體播放監聽回調,具體回調接口后面詳細展開,不需要監聽設置為null |
mediaCenterData | 媒體中心中需要用的數據,比如應用包名、圖標等,具體類型后面詳細展開,這里需要注意的是sourceType要設置為6,在媒體中心中對應SourceType.SOURCE_TYPE_ONLINE。 |
controllerCallback | 媒體中心回調的方法,包括播放/暫停、上/下曲切換,在此回調中可以實現自定義處理邏輯,并屏蔽底層播放器的響應; |
退出界面停止播放,停止播放后會釋放所有播放資源:
player.stopPlay()
頁面切換的處理建議:
視頻應用界面退到后臺(onPuse)暫停: player.pause()
視頻應用從后臺回到前臺(onResume): player.play()
更新播放列表:
fun updateMediaItems(mediaData: List<MediaData>)
在已經進入播放狀態下,更換播放的視頻建議使用該接口,避免重新startPlay()會更慢。
2)FlexMediaController
音頻類應用復雜場景建議使用該接口。
FlexSimplePlayer下層的接口,使用該接口需要自己創建MediaItem,mimeType需要調用util接口設置。適合需要自己構建比較復雜的mediaitem場景,另外沒有直接提供暫停和播放接口,需要調用成員player的暫停和播放接口。
構造方法解釋同FlexSimplePlayer。
開始播放:
fun startPlay(context: Context,mediaItems: List<MediaItem>,view: FlexPlayerView?,listener: FlexMediaListener?,customData: MediaCenterData?,controllerCallback: FlexControllerCallback? = null)
參數說明:
參數 | 說明 |
---|---|
context | 上下文,注意視頻播放要使用activity的context,因為行車娛樂限制需要彈框 |
mediaItems | 媒體列表,原生類型,包括媒體url,metadata,mimetype等 |
view | 類型為FlexPlayerView,自實現UI的此參數設置為null |
listener | 媒體播放監聽回調,具體回調接口后面詳細展開,不需要監聽設置為null |
mediaCenterData | 媒體中心中需要用的數據,比如應用包名、圖標等,具體類型后面詳細展開,這里需要注意的是sourceType要設置為6,在媒體中心中對應SourceType.SOURCE_TYPE_ONLINE。 |
controllerCallback | 媒體中心回調的方法,包括播放/暫停、上/下曲切換,在此回調中可以實現自定義處理邏輯,并屏蔽底層播放器的響應; |
FlexControllerCallback接口:
interface FlexControllerCallback {companion object {const val KEY_ACTION_TYPE = "actionType"const val KEY_CALLBACK_RESULT = "callbackResult"const val TYPE_PLAY = 1const val TYPE_NEXT = 2const val TYPE_PREVIOUS = 3const val RESULT_OK = 0const val RESULT_BLOCK = 1}fun onDefaultCallback(bundle: Bundle): Bundle?{return Bundle.EMPTY}fun onPlayAction(): Int?{return RESULT_OK}fun onSeekToNext(): Int?{return RESULT_OK}fun onSeekToPrevious(): Int?{return RESULT_OK}
}
接口描述:
接口 | 說明 |
---|---|
onPlayAction(): Int? | 媒體中心調用過來的播放/暫停接口 |
onSeekToNext(): Int? | 媒體中心調用過來的下一首接口 |
onSeekToPrevious(): Int? | 媒體中心調用過來的上一首接口 |
退出界面停止播放,停止播放后會釋放所有播放資源:
player.stopPlay()
頁面切換的處理建議:
視頻應用界面退到后臺(onPuse)暫停: player.player?.pause()
視頻應用從后臺回到前臺(onResume): player.player?.play()
更新播放列表:
fun updateMediaItems(mediaItems: List<MediaItem>)
在已經進入播放狀態下,更換播放的視頻建議使用該接口,避免重新startPlay()會更慢。
3)FlexExoPlayerController
此接口一般不會用到,前面兩個接口默認會啟動sessionservice,支持媒體中心的連接,此接口直接返回exoplayer播放器,不會創建mediasession,不支持媒體中心連接,支持視頻行車娛樂限制。使用媒體UI組件和播放器需要在應用中自行對接,適用于定制化比較多,不需要遠程播放支持的視頻播放場景,目前只用在賽道模式。建議優先選用前面兩種方式播放。
構造方法解釋同FlexSimplePlayer。
fun getExoPlayer(audioFocus: Boolean = false,mediaListener: FlexMediaListener? = null): ExoPlayer?
參數和返回值:
參數 | 說明 |
---|---|
audioFocus | 是否打開音頻焦點自管理,默認是false。在賽道模式情況存在兩個視頻同時播的情況,需要設置為false否則因為音頻焦點搶占不能同時播放,其他場景建議設置為true; |
mediaListener | 狀態回調,同前面兩個接口,具體參數后面詳細描述; |
返回值 | media3 ExoPlayer對象。 |
5、狀態回調接口
自定義的播放回調接口:
interface FlexMediaListener{//player加載完成fun onLoadPlayerFinished(player: Player?) {}//返回true不會自動播放,需要調用play()后才能播放,默認falsefun pauseWhenStart(): Boolean {return false}//直接返回player的狀態接口,更多復雜場景建議拿player對象處理fun onPlaybackStateChange(state: Int){}fun onPlayWhenReadyChanged(ready: Boolean, reason: Int) {}fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {}//播放異常fun onPlayerError(errorCode: Int, errorData: Bundle?) {}//只針對視頻,播放視頻時檢測到非駐車狀態回調,會自動暫停播放fun onStartVideoWhenNoPark() {}//只針對視頻,非駐車播放視頻,彈框點擊關閉按鈕后的回調fun onStopVideoWhenNoPark() {}//關閉播放頁面fun onClose(){}//退出應用fun onExitApp() {}//Mote: This is callback from media center, not from playerfun onSeekToNext(): Boolean {return false}fun onSeekToPrevious(): Boolean {return false}
}
接口描述:
接口 | 說明 |
---|---|
onLoadPlayerFinished(player: Player?) | 開始播放是異步的調用,此回調表示已完成了到服務端的連接返回了有效的player接口 |
fun pauseWhenStart(): Boolean | 開始播放后是否自動暫停,需要用戶手動點擊播放,默認false |
fun onPlaybackStateChange(state: Int) fun onPlayWhenReadyChanged(ready: Boolean, reason: Int) fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) | 原生Player接口播放狀態變化的透傳,state取值:Player.STATE_IDLE:還未開始播放STATE_BUFFERING:緩沖中STATE_READY:準備好播放,結合PlayWhenReady狀態來判斷,true為播放中,false為暫停STATE_ENDED:播放結束onPlayWhenReadyChanged取值: true為播放中,false為暫停onMediaItemTransition:切歌可以通過這里判斷 |
fun onPlayerError(errorCode: Int, errorData: Bundle?) | 播放異常,errorCode時原生的錯誤碼,errorData暫時沒有用到 |
fun onStartVideoWhenNoPark() | 只針對視頻,檔檢測到非P檔播放視頻時會回調此接口 |
fun onStopVideoWhenNoPark() | 只針對視頻,非P播放視頻彈框后,點擊關閉按鈕或彈框自動退出時的回調 |
fun onClose() | 用戶點擊左上角關閉按鈕 |
fun onExitApp() | 退出應用,暫未用到 |
fun onSeekToNext() | 方控或PSD切換下一曲操作會回調,返回true表示應用來接管下一曲操作不會調用底層播放器的下一曲接口,返回false底層會調用播放器的下一曲接口, 目前投屏用到 |
fun onSeekToPrevious() | 方控或PSD切換上一曲操作會回調,返回true表示應用來接管上一曲操作不會調用底層播放器的上一曲接口,返回false底層會調用播放器的上一曲接口,,投屏用到 |
6、語音控制
媒體中心語音控制接口,聲明支持的語音語義,以及處理語音控制的回調
fun declareVrSemanticsCapability(channelInfo: MCVrChannelInfo?,vrSemanticCallback: VrSemanticCallback)
參數說明:
參數 | 說明 |
---|---|
channelInfo | MCVrChannelInfo類型,具體字段: mediaPackageName: 應用包名 mediaVersion: 應用版本 mediaDescription: 應用描述 channelDataType: 通道類型,一般設置為0 semantics: IntArray 支持的語義數組,MCSemanticsType.CONTROL_PLAY等 |
vrSemanticCallback | 語音的回調,具體的實現可參考媒體中心接入文檔 |
7、播放狀態保持
媒體中心的狀態保持功能可實現應用的自啟動(通過媒體中心拉起),首先在開始播放的時候需要在mediacenterdata中設置recoveryIntent;
示例:
val intent = Intent()intent.setPackage("com.max.example")intent.action = "com.max.example.action.RecoverService"intent.putExtra("type", "StateRecover")
后面在車機重啟后,通過如下接口獲取之前的播放狀態:
fun getRecoveryPlaybackInfo (infoCallback: Consumer<MCPlaybackInfo?>)
其中MCPlaybackInfo
類型和媒體中心中的MusicPlaybackInfo
對應,通過其中的包名可以判斷上一次是否自己播放然后更新媒體中心到迷你播放器,實現重啟播放恢復的功能。
8、自定義通道
1)媒體中心通道
媒體中心通道用于更新Mini播放器和PSD狀態
接口:FlexMediaController.sendCustomAction(action: Int, bundle: Bundle? = null)
- 更新歌詞:
val bd = Bundle()bd.putString(Constants.KEY_LYRIC_STRING, "hello lyric")mediaController?.sendCustomAction(Constants.ACTION_UPDATE_LYRIC, bd)
- 更新播放狀態:
用于在非播放狀態下強制更新媒體信息到媒體中心。
mediaController?.sendCustomAction(Constants.ACTION_UPDATE_MEDIACENTER, null)
2)遠程MediaSession通道
遠程Mediasession主要用于更新RSD的信息。
Androidx原生接口:MediaController.sendCustomCommand(command: SessionCommand, bundle: Bundle)
- 設置歌詞:
val bd = Bundle()bd.putString("lyrics", lyric)bd.putString("lyrics_tr", lyricTr) //英文歌詞it.sendCustomCommand(SessionCommand(Constants.COMMAND_SET_SESSION_EXTRA,Bundle.EMPTY), bd)
- 設置試看點:
val bd = Bundle()bd.putBoolean("isCanTrail", isCanTrail)//true試聽歌曲,false非試聽歌曲bd.putLong("trail_start", start)bd.putLong("trail_end", end)it.sendCustomCommand(SessionCommand(Constants.COMMAND_SET_SESSION_EXTRA,Bundle.EMPTY), bd)
9、更多功能使用原生Player/MediaController接口
有其他更多播放場景的需求,可以通過FlexSimplePlayer/FlexMediaController/player
直接拿到media3的原始player對象實現。
注意事項
- Media3要求工程的compileSDK>=34,這個改動會影響編譯過程,不影響運行時。可能會涉及少量系統接口參數適配否則編譯會報錯,targetSDK建議不動;
- Player的接口調用在主線程進行,對應的狀態listener回調也會在主線程中(需要在同一個線程中,否則會報異常);
- startPlay()和stopPlay()調用時機需要匹配,比如在onCreateView中調用startPlay() 在onDestoryView中調用stopPlay(),或者在startPlay()之前先調用stopPlay();
- 全屏的視頻播放,activity的
decorView.windowSystemUiVisibility
需要設置View.SYSTEM_UI_FLAG_FULLSCREEN
,這樣行車娛樂限制的彈框會根據全屏的狀態隱藏狀態欄和docker欄; - 當前版本還不支持mediasession service多進程;
參考Demo
uniteplayerdemo(視頻)
音頻類可參考杜比播放器和網易云音樂APP等應用,附網易云APP的播放架構:
遺留問題
1、電話中播放控制還未在框架中實現,需要應用來處理;