[摘]车载MediaSession框架理解

Android 91es.com站长2024年3月16日 pm5:16发布3个月前更新
0
导航号,我的单页导航
目录

前言

本文内容主要介绍Android车载多媒体开发MediaSession框架知识,而且内容是阅读参考文的笔记。

记录于此,方便自己查阅。

好记性不如烂笔头。

正文

这里摘抄我自己想了解的,而且只是记录一下,有兴趣的还是看原文,多谢理解。

Android Auto

Android Auto就是我们说的AA,车载投屏,是Android端的App,谷歌专门为车载设计的。

类似的还有百度的CarLife (CarLife可以在Android和iOS上使用),苹果的CarPlay,华为的HiCar,小米的CarWith等都是手机投屏软件,而且都用了MediaSession框架。

播放器Player

用于接收数字媒体并将其呈现为视频/音频。我们常见的MediaPlayerExoPlayer,IJKPlayer等。

  • MediaPlayer :提供准系统播放器的基本功能,支持最常见的音频/视频格式和数据源

  • ExoPlayer :一个提供低层级Android音频API的开放源代码库。ExoPlayer支持DASH和HLS流等高性能功能,这些功能在MediaPlayer中未提供

  • IJKPlayer是B站开源的一个播放器,具体看:https://github.com/bilibili/ijkplayer

MediaSession架构

[摘]车载MediaSession框架理解

MediaSession框架专门用来解决媒体播放时界面和Service通讯的问题,意在规范上述这些功能的流程。

MediaSession框架规范了音视频应用中界面播放器之间的通信接口,属于典型的 C/S 架构,实现界面与播放器之间的完全解耦。框架定义了两个重要的类媒体会话媒体控制器,它们为构建多媒体播放器应用提供了一个完善的结构。

媒体会话和媒体控制器通过以下方式相互通信:使用与标准播放器操作(播放、暂停、停止等)相对应的预定义回调,以及用于定义应用独有的特殊行为的可扩展自定义调用。

优点

总结一下优点就是,使用一套接口,减少了很多流程的繁琐和service的通信等,实现多个设备或者UI的统一调用

核心类

MediaSession框架中有四个常用的成员类,MediaBrowser、MediaBrowserService、MediaSession、MediaController,它们是MediaSession整个流程控制的核心。

MediaBrowser

媒体浏览器、就是Client,用来连接MediaBrowserService服务端Server)、调用它的onGetRoot()方法,在连接成功的结果回调后,获取token(配对令牌),并以此获得MediaController媒体控制器,在它的回调接口中可以获取和Service的连接状态以及获取在Service中异步获取的音乐库数据。然后可以有订阅并设置订阅信息回调功能来订阅数据

MediaBrowser使用

MediaBrowser一般创建于客户端Client APP)中,不是线程安全的,所有调用都应在构造MediaBrowser的线程上进行

//MusicService继承于MediaBrowserService
//参数一:上下文环境
//参数二:ComponentName,指向需要连接的服务MusicService
//参数三:MediaBrowser.ConnectionCallback连接状态回调
mMediaBrowser = new MediaBrowser(this,new ComponentName(this, MusicService.class),
    connectionCallback, null);
//开始连接
mMediaBrowser.connect();

非主线程创建MediaBrowser并connect的时候会报错。这是因为连接时底层代码会使用Handler,并且采用Handler handler = new Handler()的创建方式,如此使用必然会报错。

解决办法:

Looper.prepare()

//参数一:上下文环境
//参数二:ComponentName,指向需要连接的服务MusicService
//参数三:MediaBrowser.ConnectionCallback连接状态回调
mMediaBrowser = new MediaBrowser(this,new ComponentName(this, MusicService.class),
    connectionCallback, null);
//开始连接
mMediaBrowser.connect();

Looper.loop();
相关方法
void connect() 连接到媒体浏览器服务
void disconnect() 断开与媒体浏览器服务的连接
Bundle getExtras() 获取介质服务的任何附加信息    
boolean isConnected() 返回浏览器是否连接到服务
ComponentName getServiceComponent() 获取媒体浏览器连接到的服务组件
void getItem(String, MediaBrowser.ItemCallback) 从连接的服务中检索特定的MediaItem
String getRoot()获取根ID
void subscribe(String , MediaBrowser.SubscriptionCallback)询有关包含在指定ID 中的媒体项的信息,并订阅以在更改时接收更新
void unsubscribe(String) 取消订阅指定媒体 ID
MediaSession.Token getSessionToken()获取与媒体浏览器关联的媒体会话Token
MediaBrowser.ConnectionCallback

接收与MediaBrowserService连接状态的回调,在创建MediaBrowser时传入,当MediaBrowser向service发起连接请求后,请求结果将在这个ConnectionCallback中返回,获取到的meidaId对应服务端在onGetRoot()函数中设置的mediaId,如果连接成功那么就可以做创建媒体控制器之类的操作了

private final MediaBrowser.ConnectionCallback connectionCallback = new MediaBrowser.ConnectionCallback() {
    @Override
    public void onConnected() {
        super.onConnected();
        //与MediaBrowserService连接成功。在调用MediaBrowser.connect()后才会有回调。
    }
    @Override
    public void onConnectionFailed() {
        super.onConnectionFailed();
        //与MediaBrowserService连接失败。比如onGetRoot返回null
    }
    @Override
    public void onConnectionSuspended() {
        super.onConnectionSuspended();
        //与MediaBrowserService连接断开。进程死掉
    }
};
MediaBrowser.ItemCallback

用于接受调用MediaBrowser.getItem()后,MediaService返回的结果。媒体控制器MediaController负责向service中session发送例如播放暂停之类的指令的,这些指令的执行结果将在这个ItemCallback回调中返回,可重写的函数有很多,比如播放状态的改变,音乐信息的改变等。

private final MediaBrowser.ConnectionCallback connectionCallback = new MediaBrowser.ConnectionCallback() {
    @Override
    public void onConnected() {
        super.onConnected();
        mediaId = mMediaBrowser.getRoot();
        //获取指定mediaId的MediaItem
        mMediaBrowser.getItem(mediaId, itemCallback);
    }
};

private MediaBrowser.ItemCallback itemCallback =new MediaBrowser.ItemCallback() {
    @Override
    public void onItemLoaded(MediaBrowser.MediaItem item) {
        super.onItemLoaded(item);
        //返回Item时调用
    }

    @Override
    public void onError(@NonNull String mediaId) {
        super.onError(mediaId);
        //检索时出错,或者连接的服务不支持时回调
    }
};

这个不支持目录订阅,暂时没用过。

MediaBrowser.MediaItem

包含有关单个媒体项的信息,用于浏览/搜索媒体。MediaItem依赖于服务端提供,因此框架本身无法保证它包含的值都是正确的。

int describeContents()   描述此可打包实例的封送处理表示中包含的特殊对象的种类
MediaDescription getDescription() 获取介质的说明。包含媒体的基础信息如:标题、封面等等
int getFlags()  获取项的标志。[FLAG_BROWSABLE:表示Item具有自己的子项(是一个文件夹)。
FLAG_PLAYABLE:表示Item可播放]
String getMediaId()  返回此项的媒体 ID
boolean isBrowsable() 返回此项目是否可浏览
boolean isPlayable()返回此项是否可播放
MediaBrowser.SubscriptionCallback

连接成功后,首先客户端调用subscribe()订阅MediaBrowserService服务,同样还需要注册订阅回调,订阅成功的话服务端可以返回一个音乐信息的序列,可以在客户端展示获取的音乐列表数据MediaBrowser.MediaItem

# 连接成功后
# 一般先取消之前的订阅,然后去订阅新的。
mMediaBrowser.unsubscribe(mOldMediaId);
mMediaBrowser.subscribe(mediaId, subscriptionCallback);
private final MediaBrowser.SubscriptionCallback subscriptionCallback = new MediaBrowser.SubscriptionCallback() {
    @Override
    public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaBrowser.MediaItem> children) {
        super.onChildrenLoaded(parentId, children);
        //返回订阅的数据
    }

    @Override
    public void onError(@NonNull String parentId) {
        super.onError(parentId);
        //当Id不存在或订阅出现问题时
    }
};

MediaBrowserService

媒体浏览器服务MediaBrowserService继承自Service,MediaBrowserService属于服务端。提供onGetRoot(接受客户端媒体浏览器MediaBrowser连接请求,通过返回值决定是否允许该客户端连接服务)和onLoadChildren(媒体浏览器MediaBrowser向Service发送数据订阅时调用,一般在这执行异步获取数据的操作,最后将数据发送至媒体浏览器的回调接口onChildrenLoaded())这两个抽象方法。

客户端调用MediaBrowser.subscribe时会触发onLoadChildren方法。

AndroidManifest.xml
<service
    android:name=".service.MusicService"
    android:exported="false">
    <intent-filter>
        <action android:name="android.media.browse.MediaBrowserService" />
    </intent-filter>
</service>
MusicService.java
public class MusicService extends MediaBrowserService {
    @Nullable
    @Override
    public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
        //返回默认MediaId
        return new BrowserRoot(FOLDERS_ID, null);
    }

    @Override
    public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaBrowser.MediaItem>> result) {
        //根据parentId查询数据,然后通过result返回给客户端
    }
}
相关方法
MediaSession.Token getSessionToken() 获取会话令牌,如果尚未创建会话令牌或已销毁会话令牌,则获取 null
void notifyChildrenChanged(String ) 通知所有连接的媒体浏览器指定父 ID 的子级已经更改
void notifyChildrenChanged(String , Bundle ) 通知所有连接的媒体浏览器指定父 ID 的子级已经更改
final MediaSessionManager.RemoteUserInfo getCurrentBrowserInfo() 获取发送当前请求的浏览器信息
final Bundle getBrowserRootHints() 获取从当前连接MediaBrowser的发送的根提示
void onLoadChildren(String , Result) 处理客户端发送的请求

MediaSession

媒体会话,即受控端,通过设置MediaSession.Callback回调来接收媒体控制器MediaController发送的指令,当收到指令时会触发Callback中各个指令对应的回调方法(回调方法中会执行播放器相应的操作,如播放、暂停等)

//参数一:上下文环境
//参数二:标志,唯一的即可
mMediaSession = new MediaSession(this, TAG);
//设置Session回调,主要响应客户端等命令
mMediaSession.setCallback(mediaSessionCallback);
//设置token
setSessionToken(mMediaSession.getSessionToken());
相关方法
MediaController getController() 获取此会话的控制器
MediaSessionManager.RemoteUserInfo getCurrentControllerInfo() 获取发送当前请求的控制器信息
MediaSession.Token getSessionToken() 获取此会话令牌对象
boolean isActive() 获取此会话的当前活动状态
void release() 当服务退不用时,必须调用此项
void sendSessionEvent (String , Bundle)  将专有事件发送给监听此会话的所有MediaController。会触发MediaController.Callback.onSessionEvent
void setActive(boolean) 设置此会话当前是否处于活动状态并准备好接收命令
void setCallback (MediaSession.Callback) 设置回调以接收媒体会话的更新
void setCallback (MediaSession.Callback,Handler) 设置回调以接收媒体会话的更新
void setExtras(Bundle) 设置一些可与MediaSession关联的附加功能    
void setFlags(int flags) 为会话设置标志
void setMediaButtonBroadcastReceiver(ComponentName) 设置应接收媒体按钮的清单声明类的组件名称
void setMetadata(MediaMetadata)  更新当前MediaMetadata
void setPlaybackState(PlaybackState) 更新当前播放状态
void setPlaybackToLocal(AudioAttributes )    设置此会话音频的属性
void setPlaybackToRemote(VolumeProvider) 将此会话配置为使用远程音量处理
MediaSession.Callback

接收来自控制器MediaController系统的媒体按钮(像方向盘上面的按钮)、传输控件和命令,如【上一曲】、【下一曲】。

private final MediaSession.Callback mediaSessionCallback = new MediaSession.Callback() {
    @Override
    public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
        super.onCustomAction(action, extras);
        //处理MediaController.getTransportControls().sendCustomAction(action, bundle)命令
    }
    @Override
    public void onCommand(@NonNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb) {
        super.onCommand(command, args, cb);
        //处理MediaController.sendCommand(command, null, null)命令
    }
    @Override
    public boolean onMediaButtonEvent(@NonNull Intent intent) {
        //处理Intent.ACTION_MEDIA_BUTTON,即方控按钮
        return super.onMediaButtonEvent(intent);
    }
    @Override
    public void onPlayFromMediaId(String mediaId, Bundle extras) {
        super.onPlayFromMediaId(mediaId, extras);
        //通过mediaId播放
    }
    @Override
    public void onPlay() {
        super.onPlay();
        //响应play
    }
    @Override
    public void onSkipToNext() {
        super.onSkipToNext();
        //响应下一曲
    }
    //略
};

相关方法

void onCommand(String,Bundle,ResultReceiver)    当控制器已向此会话发送命令时调用
void onCustomAction(String , Bundle)    当要执行MediaController.PlaybackState.CustomAction时调用。对应客户端 mMediaController.getTransportControls().sendCustomAction(...)
void onFastForward()    处理快进请求
boolean onMediaButtonEvent(Intent)  当按下媒体按钮并且此会话具有最高优先级或控制器向会话发送媒体按钮事件时调用
void onPlayFromMediaId(String, Bundle)  处理播放应用提供的特定mediaId的播放请求
void onPlayFromSearch(String, Bundle)   处理从搜索查询开始播放的请求
void onPlayFromUri(Uri, Bundle) 处理播放由URI表示的特定媒体项的请求
void onPrepare()    处理准备播放的请求
void onPrepareFromMediaId(String, Bundle)   处理应用提供的特定mediaId的准备播放请求
void onPrepareFromSearch(String, Bundle)    处理准备从搜索查询播放的请求
void onPrepareFromUri(Uri, Bundle)   处理由URI表示的特定媒体项的准备请求
void onRewind() 处理倒带请求
void onSeekTo(long) 处理跳转到特定位置的请求
void onSetPlaybackSpeed(float ) 处理修改播放速度的请求
void onSetRating(Rating)    处理设定评级的请求
void onSetRating(RatingCompat, Bundle)  处理设定评级的请求。可以用extras接受如mediaId等参数
void onSkipToNext() 处理要跳到下一个媒体项的请求
void onSkipToPrevious() 处理要跳到上一个媒体项的请求
void onSkipToQueueItem(long)    处理跳转到播放队列中具有给定 ID 的项目的请求

MediaController

媒体控制器,在客户端中开发者不仅可以使用控制器向Service中的受控端发送指令(播放、暂停),还可以通过设置MediaController.Callback回调方法接收MediaSession受控端的状态,从而根据相应的状态刷新界面UI。

MediaController的创建需要受控端的配对令牌,因此需在MediaBrowser成功连接服务的回调执行创建的操作,媒体控制器是线程安全的

MediaController还有一个关联的权限android.permission.MEDIA_CONTENT_CONTROL(不是必须加的权限)必须是系统级应用才可以获取,幸运的是车载应用一般都是系统级应用。MediaController必须在MediaBrowser连接成功后才可以创建

相关方法
void adjustVolume (int, int)	调整此会话正在播放的输出的音量
boolean dispatchMediaButtonEvent (KeyEvent)	将指定的媒体按钮事件发送到会话
Bundle getExtras()	获取此会话的附加内容
long getFlags()	获取此会话的标志
MediaMetadata getMetadata()	获取此会话的当前Metadata
String getPackageName()	获取会话所有者的程序包名称
MediaController.PlaybackInfo getPlaybackInfo()	获取此会话的当前播放信息
PlaybackState getPlaybackState()	获取此会话的当前播放状态
MediaController.Callback
private final MediaController.Callback controllerCallback = new MediaController.Callback() {
    @Override
    public void onMetadataChanged(@Nullable MediaMetadata metadata) {
        super.onMetadataChanged(metadata);
		//当前Metadata发生改变。服务端运行mediaSession.setMetadata(mediaMetadata)就会到达此处
    }
    @Override
    public void onPlaybackStateChanged(@Nullable PlaybackState state) {
        super.onPlaybackStateChanged(state);
		//当前播放状态发生改变。客户端通过该回调来显示界面上音视频的播放状态
    }
};
MediaController.PlaybackInfo

保存有关当前播放以及如何处理此会话的音频的信息,也可以获取当前播放的音频信息,包含播放的进度、时长等

// 获取当前回话播放的音频信息
PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo()
AudioAttributes getAudioAttributes()	获取此会话的音频属性
int getCurrentVolume()	获取此会话的当前音量
int getMaxVolume()	获取可为此会话设置的最大音量
int getPlaybackType()	获取影响音量处理的播放类型
int getVolumeControl()	获取可以使用的音量控件的类型
String getVolumeControlId()	获取此会话的音量控制 ID
MediaController.TransportControls

用于控制MediaSession会话中媒体播放的接口。这允许客户端使用控制器MediaController,来发送如系统的媒体按钮(像方向盘上面的按钮)、命令(如【上一曲】、【下一曲】)和传输控件到MediaSession。它也与MediaSession.Callback中方法一对应。

void fastForward()	开始快进
void playFromMediaId (String , Bundle )	请求播放器开始播放特定媒体 ID
void playFromSearch (String , Bundle)	请求播放器开始播放特定的搜索查询
void playFromUri (Uri , Bundle )	请求播放器开始播放特定Uri
void prepare()	请求播放器准备播放
void prepareFromMediaId (String , Bundle )	请求播放器为特定媒体 ID 准备播放
void prepareFromSearch (String , Bundle )	请求播放器为特定搜索查询准备播放
void prepareFromUri (Uri , Bundle )	请求播放器为特定Uri
void rewind()	开始倒退
void seekTo(long )	移动到媒体流中的新位置
void sendCustomAction (PlaybackState.CustomAction , Bundle )	发送自定义操作以供MediaSession执行
void sendCustomAction (String action,Bundle )	将自定义操作中的 id 和 args 发送回去,以便MediaSession执行
void setPlaybackSpeed (float )	设置播放速度
void setRating(Rating )	对当前内容进行评级
void setRating(RatingCompat , Bundle )	对当前内容进行评级,可以用extras传递如mediaId等参数
void skipToNext()	跳到下一项
void skipToPrevious()	跳到上一项
void skipToQueueItem(long )	在播放队列中播放具有特定 ID 的项目
void stop()	请求播放器停止播放;它可以以任何适当的方式清除其状态

PlaybackState

long getActions()	获取此会话上可用的当前操作
long getActiveQueueItemId()	获取队列中当前活动项的 ID
long getBufferedPosition()	获取当前缓冲位置(以毫秒为单位)

MediaMetadata

保存媒体信息,包含有关项目的基础数据,例如标题、艺术家、专辑名、总时长等。一般需要服务端从本地数据库或远端查询出原始数据在封装成MediaMetadata再通过MediaSession.setMetadata(metadata)返回到客户端的MediaController.Callback.onMetadataChanged中

boolean containsKey(String)	如果给定的key包含在元数据中,则返回 true
int describeContents()	描述此可打包实例的封送处理表示中包含的特殊对象的种类
Bitmap getBitmap(String)	返回给定的key的Bitmap;如果给定key不存在位图,则返回 null
int getBitmapDimensionLimit()	获取创建此元数据时位图的宽度/高度限制(以像素为单位)
MediaDescription getDescription()	获取此元数据的简单说明以进行显示
long getLong(String)	返回与给定key关联的值,如果给定key不再存在,则返回 0L
Rating getRating(String)	对于给定的key返回Rating;如果给定key不存在Rating,则返回 null
String getString(String)	以String格式返回与给定key关联的文本值,如果给定key不存在所需类型的映射,或者null值显式与该key关联,则返回 null
CharSequence getText(String)	返回与给定键关联的值,如果给定键不存在所需类型的映射,或者与该键显式关联 null 值,则返回 null
Set keySet()	返回一个 Set,其中包含在此元数据中用作key的字符串
int size()	返回此元数据中的字段数
METADATA_KEY_ALBUM	媒体的唱片集标题
METADATA_KEY_ALBUM_ART	媒体原始来源的相册的插图,Bitmap格式
METADATA_KEY_ALBUM_ARTIST	媒体原始来源的专辑的艺术家
METADATA_KEY_ALBUM_ART_URI	媒体原始源的相册的图稿,Uri格式(推荐使用)
METADATA_KEY_ART	媒体封面,Bitmap格式
METADATA_KEY_ART_URI	媒体的封面,Uri格式
METADATA_KEY_ARTIST	媒体的艺术家
METADATA_KEY_AUTHOR	媒体的作者
METADATA_KEY_BT_FOLDER_TYPE	蓝牙 AVRCP 1.5 的 6.10.2.2 节中指定的媒体的蓝牙文件夹类型
METADATA_KEY_COMPILATION	媒体的编译状态
METADATA_KEY_COMPOSER	媒体的作曲家
METADATA_KEY_DATE	媒体的创建或发布日期
METADATA_KEY_DISC_NUMBER	介质原始来源的光盘编号
METADATA_KEY_DISPLAY_DESCRIPTION	适合向用户显示的说明
METADATA_KEY_DISPLAY_ICON	适合向用户显示的图标或缩略图
METADATA_KEY_DISPLAY_ICON_URI	适合向用户显示的图标或缩略图, Uri格式
METADATA_KEY_DISPLAY_SUBTITLE	适合向用户显示的副标题
METADATA_KEY_DISPLAY_TITLE	适合向用户显示的标题
METADATA_KEY_DURATION	媒体的持续时间(以毫秒为单位)
METADATA_KEY_GENRE	媒体的流派
METADATA_KEY_MEDIA_ID	用于标识内容的字符串Key
METADATA_KEY_MEDIA_URI	媒体内容,Uri格式
METADATA_KEY_NUM_TRACKS	媒体原始源中的曲目数
METADATA_KEY_RATING	媒体的总体评分
METADATA_KEY_TITLE	媒体的标题
METADATA_KEY_TRACK_NUMBER	媒体的磁道编号
METADATA_KEY_USER_RATING	用户对媒体的分级
METADATA_KEY_WRITER	媒体作家
METADATA_KEY_YEAR	媒体创建或发布为长的年份

参考文章

  1. Android车载多媒体开发MediaSession框架理解(建议收藏)

  2. 谷歌官网:媒体应用架构

  3. 谷歌官网:Build media apps for cars

版权声明 1、 本站名称 91易搜
2、 本站网址 https://www.91es.com/
3、 本站部分文章来源于网络,仅供学习与参考,如有侵权请留言
4、 本站禁止发布或转载任何违法的相关信息,如有发现请向站长举报
导航号,我的单页导航

暂无评论

评论审核已启用。您的评论可能需要一段时间后才能被显示。

暂无评论...