Android媒体播放框架MediaSession分析与实践

更新时间:2023-07-05 19:18:19 阅读: 评论:0

Android媒体播放框架MediaSession分析与实践
版权声明:本⽂为博主原创⽂章,未经博主允许不得转载
源码:
⼤家要是看到有错误的地⽅或者有啥好的建议,欢迎留⾔评论
前⾔
最近⼀直在忙着学习和研究⾳乐播放器,发现介绍MediaSession框架的资料⾮常少,更多的是⼀些源码和开源库,这对于初学者来说不是很友好,可能看着看着就绕晕了,遂博主决定动⼿写点这⽅⾯的博客分享给⼤家
参考资料
(有前辈翻译后的版本)
MediaSession框架简介
我们先来看看如何设计⼀款⾳乐播放App的架构,传统的做法是这样的:
* 注册⼀个Service,⽤于异步获取⾳乐库数据、⾳乐控制等,在Service中我们可能还需要⾃定义⼀些状态值和回调接⼝⽤于流程控制
* 通过⼴播(其他⽅式如接⼝、Mesnger都可以)实现Activity和Service之间的通信,使得⽤户可以通过界⾯上的组件控制⾳乐的播放、暂停、拖动进度条等操作
如果我们的⾳乐播放器还需要⽀持通知栏快捷控制⾳乐播放的功能,那么⼜得新增⼀套⼴播和相应的接⼝去响应通知栏按钮的事件
如果还需要⽀持多端(电视、⼿表、⽿机等)控制同⼀个播放器,那么整个系统架构可能会变得⾮常复杂,我们要花费⼤量的时间和精⼒去设计、优化代码的结构。那么有什么⽅法可以节省这些⼯作,提⾼我们的效率,然后还可以优雅地实现上述这些功能呢?
Google在Android 5.0中加⼊了MediaSession框架(在support-v4中同样提供了相应的兼容包,相关的类以Compat结尾,Api基本相同),专门⽤来解决媒体播放时界⾯和Service通讯的问题,意在规范上述这些功能的流程。使⽤这个框架我们可以减少⼀些流程复杂的开发⼯作,例如使⽤各种⼴播来控制播放器,⽽且其代码可读性、结构耦合度⽅⾯都控制得⾮常好,因此推荐⼤家尝试下这个框架。下⾯我们就开始介绍MediaSession框架的核⼼成员和使⽤流程
MediaSession框架的使⽤
常⽤成员类概述
MediaSession框架中有四个常⽤的成员类,它们是整个流程控制的核⼼
MediaBrowr
媒体浏览器,⽤来连接MediaBrowrService和订阅数据,通过它的回调接⼝我们可以获取和Service的连接状态以及获取在Service 中异步获取的⾳乐库数据。媒体浏览器⼀般创建于客户端(可以理解为各个终端负责控制⾳乐播放的界⾯)中
MediaBrowrService
浏览器服务,提供onGetRoot(控制客户端媒体浏览器的连接请求,通过返回值决定是否允许该客户端连接服务)和
onLoadChildren(媒体浏览器向Service发送数据订阅时调⽤,⼀般在这执⾏异步获取数据的操作,最后将数据发送⾄媒体浏览器的回调接⼝中)这两个抽象⽅法
同时MediaBrowrService还作为承载媒体播放器(如MediaPlayer、ExoPlayer等)和MediaSession的容器
MediaSession
媒体会话,即受控端,通过设置MediaSessionCompat.Callback回调来接收媒体控制器MediaController发送的指令,当收到指令时会触发Callback中各个指令对应的回调⽅法(回调⽅法中会执⾏播放器相应的操作,如播放、暂停等)。Session⼀般在
MediaController
媒体控制器,在客户端中开发者不仅可以使⽤控制器向Service中的受控端发送指令,还可以通过设置
MediaControllerCompat.Callback回调⽅法接收受控端的状态,从⽽根据相应的状态刷新界⾯UI。MediaController的创建需要受控端的配对令牌,因此需在浏览器成功连接服务的回调执⾏创建的操作
通过上述的简介中我们不难看出这四个成员之间有着⾮常明确的分⼯和作⽤范围,使得整个代码结构变得清晰易读。可以通过下⾯这张图来简单归纳它们之间的关系
除此之外,MediaSession框架中还有⼀些同样重要的类需要拿出来讲,例如封装了各种播放状态的Pl
aybackState,和Map相似通过键值对保存媒体信息的MediaMetadata,以及⽤于MediaBrowr和MediaBrowrService之间进⾏数据交互的MediaItem等等,下⾯我们通过实现⼀个简单的demo来具体分析这套框架的⼯作流程
使⽤MediaSession框架构建简单的⾳乐播放器
例如我们的demo是这样的(见下图),只提供简单的播放暂停操作,⾳乐数据源从raw资源⽂件夹中获取
按照⼯作流程,我们就从获取⾳乐库数据开始吧。⾸先界⾯上⽅添加⼀个RecyclerView来展⽰获取的⾳乐列表,我们在DemoActivity中完成⼀些RecyclerView的初始化操作
public class DemoActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<MediaBrowrCompat.MediaItem> list;
private DemoAdapter demoAdapter;
private LinearLayoutManager layoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
tContentView(R.layout.activity_demo);
list = new ArrayList<>();
layoutManager = new LinearLayoutManager(this);
layoutManager.tOrientation(LinearLayoutManager.VERTICAL);
demoAdapter = new DemoAdapter(this,list);
recyclerView = (RecyclerView) findViewById(lerView);
recyclerView.tLayoutManager(layoutManager);
recyclerView.tAdapter(demoAdapter);
}
}
注意List元素的类型为MediaBrowrCompat.MediaItem,因为MediaBrowr从服务中获取的每⼀⾸⾳乐都会封装成MediaItem对象。接下来我们创建MediaBrowr,并执⾏连接服务端和订阅数据的操作
public class DemoActivity extends AppCompatActivity {
...
private MediaBrowrCompat mBrowr;
@Override
protected void onCreate(Bundle savedInstanceState) {
姐妹城市...
mBrowr = new MediaBrowrCompat(
this,
new ComponentName(this, MusicService.class),//绑定浏览器服务
BrowrConnectionCallback,//设置连接回调
null
);
}
@Override
protected void onStart() {
//Browr发送连接请求
}
@Override
protected void onStop() {
mBrowr.disconnect();
}
/**
* 连接状态的回调接⼝,连接成功时会调⽤onConnected()⽅法
*/
private MediaBrowrCompat.ConnectionCallback BrowrConnectionCallback =
飞快近义词new MediaBrowrCompat.ConnectionCallback(){
@Override
public void onConnected() {
Log.e(TAG,"onConnected------");
//必须在确保连接成功的前提下执⾏订阅的操作
if (mBrowr.isConnected()) {
//mediaId即为GetRoot的返回值
//mediaId即为GetRoot的返回值
//若Service允许客户端连接,则返回结果不为null,其值为数据内容层次结构的根ID
//若拒绝连接,则返回null
String mediaId = Root();
//Browr通过订阅的⽅式向Service请求数据,发起订阅请求需要两个参数,其⼀为mediaId
/
/⽽如果该mediaId已经被其他Browr实例订阅,则需要在订阅之前取消mediaId的订阅者
//虽然订阅⼀个已被订阅的mediaId 时会取代原Browr的订阅回调,但却⽆法触发onChildrenLoaded回调
//ps:虽然基本的概念是这样的,但是Google在官⽅demo中有这么⼀段注释...
// This is temporary: A bug is being fixed that will make subscribe
// consistently call onChildrenLoaded initially, no matter if it is replacing an existing
// subscriber or not. Currently this only happens if the mediaID has no previous
// subscriber or if the media content changes on the rvice side, so we need to
// unsubscribe first.
//⼤概的意思就是现在这⾥还有BUG,即只要发送订阅请求就会触发onChildrenLoaded回调
//所以⽆论怎样我们发起订阅请求之前都需要先取消订阅
mBrowr.unsubscribe(mediaId);
//之前说到订阅的⽅法还需要⼀个参数,即设置订阅回调SubscriptionCallback
//当Service获取数据后会将数据发送回来,此时会触发ChildrenLoaded回调                        mBrowr.subscribe(mediaId, BrowrSubscriptionCallback);
}
}
@Override
public void onConnectionFailed() {
Log.e(TAG,"连接失败!");
}
简爱剧本
};
/**
* 向媒体浏览器服务(MediaBrowrService)发起数据订阅请求的回调接⼝
*/
private final MediaBrowrCompat.SubscriptionCallback BrowrSubscriptionCallback =
樊读音new MediaBrowrCompat.SubscriptionCallback(){
@Override恩格尔定律
public void onChildrenLoaded(@NonNull String parentId,
@NonNull List<MediaBrowrCompat.MediaItem> children) {
Log.e(TAG,"onChildrenLoaded------");
//children 即为Service发送回来的媒体数据集合
for (MediaBrowrCompat.MediaItem item:children){
Log.e(Description().getTitle().toString());
list.add(item);
}
//在onChildrenLoaded可以执⾏刷新列表UI的操作
}
};
}
通过上述的代码和注释⼤家应该清楚MediaBrowr从连接服务到向其订阅数据的流程了,简单总结⼀下就是
connect →on Connected → subscribe →on ChildrenLoaded
那么Service端那边在这段流程中⼜做了什么呢?⾸先我们得继承MediaBrowrService(这⾥使⽤了support-v4包的类)创建MusicService类。MediaBrowrService继承⾃Service,所以记得在l中完成配置
<rvice
android:name=".demo.MusicService">
<intent-filter>
<action android:name="dia.brow.MediaBrowrService" />
</intent-filter>
</rvice>
我们需要在Service初始化的时候就完成MediaSession的构建,并为它设置相应的标志、状态等,具体的代码如下
public class MusicService extends MediaBrowrServiceCompat {
private MediaSessionCompat mSession;
private PlaybackStateCompat mPlaybackState;
@Override
public void onCreate() {
mPlaybackState = new PlaybackStateCompat.Builder()
.tState(PlaybackStateCompat.STATE_NONE,0,1.0f)
.build();
mSession = new MediaSessionCompat(this,"MusicService");
mSession.tCallback(SessionCallback);//设置回调
mSession.tFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.tPlaybackState(mPlaybackState);
//设置token后会触发MediaBrowrCompat.ConnectionCallback的回调⽅法
//表⽰MediaBrowr与MediaBrowrService连接成功
SessionToken());
}
}
遮挡的反义词这⾥解释下其中的⼀些细节,⾸先是调⽤MediaSession.tFlag为Session设置标志位,以便Session接收控制器的指令。然后是播放状态的设置,需调⽤MediaSession.tPlaybackState,那么PlaybackState⼜是什么呢?之前我们简单介绍过它是封装了各种播放状态的类,我们可以通过判断当前播放状态来控制各个成员的⾏为,⽽PlaybackState类为我们定义了各种状态的规范。此外我们还需要设置SessionCallback回调,当客户端使⽤控制器发送指令时,就会触发这些回调⽅法,从⽽达到控制播放器的⽬的
public class MusicService extends MediaBrowrServiceCompat {
...
private MediaPlayer mMediaPlayer;
@Override
public void onCreate() {
...
mMediaPlayer = new MediaPlayer();
mMediaPlayer.tOnPreparedListener(PreparedListener);
mMediaPlayer.tOnCompletionListener(CompletionListener);
早泄外用药
}
/**
* 响应控制器指令的回调
你离开以后*/
private android.dia.ssion.MediaSessionCompat.Callback SessionCallback = new MediaSessionCompat.Callback(){
/**
* 响应TransportControls().play
*/
@Override
public void onPlay() {
Log.e(TAG,"onPlay");
State() == PlaybackStateCompat.STATE_PAUSED){
mMediaPlayer.start();
mPlaybackState = new PlaybackStateCompat.Builder()
.tState(PlaybackStateCompat.STATE_PLAYING,0,1.0f)
.build();
mSession.tPlaybackState(mPlaybackState);
}
}
/**
* 响应TransportControls().onPau
*/
@Override

本文发布于:2023-07-05 19:18:19,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1069189.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:连接   数据   订阅   媒体
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图