摘要
主要根据谷歌官方提供的demo,来了解ExoPlayer的基本使用。
[TOC]
基本概念
在介绍ExoPlayer之前,先介绍几个基本概念:
- 流媒体通信协议
- ExoPlayer中自交重要的概念
- Timeline,用来指代媒体数据的灵活数据结构,本质上是一种数据的描述,由媒体片段(Period)和Window(由一个或多个Period组成)组成;
- MediaPeriod,用来加载与Timeline.Period对应的Media,并是这段Media可读;
基本使用流程
- 添加ExoPlayer依赖,或者clone ExoPlayer到本地然后倒入module作为依赖;
在项目根目录下的
build.gradle
中添加如下repositories:1
2
3
4repositories {
google()
jcenter()
}在项目的主module(app)的
build.gradle
文件中添加ExoPlayer依赖:1
2
3
4
5
6# 添加全部依赖
implementation 'com.google.android.exoplayer:exoplayer:2.X.X'
# 添加部分依赖
implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X'可以添加的依赖如下:
exoplayer-core
: ExoPlayer的核心库,必须添加;exoplayer-dash
: DASH内容支持;exoplayer-hls
: HLS内容支持;exoplayer-smoothstreaming
: SmoothStreaming内容支持;exoplayer-ui
: ExoPlayer提供的UI组件;
在依赖ExoPlayer的Moudle都添加Java 8支持,在build.gradle的android闭包中添加:
1
2
3compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
}
创建Player实例
ExoPlayer库提供了一个工厂方法来获取播放器实例,即ExoPlayerFactory.newInstance(...)和ExoPlayerFactory.newSimpleInstance(...)
方法,最终都是通过ExoPlayerImpl
(ExoPlayer接口的实现类)的构造方法来创建的,该类只有一个构造函数,最终的处理是通过内部实现类ExoPlayerImplInternal
来实现的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48public ExoPlayerImpl(
Renderer[] renderers,
TrackSelector trackSelector,
LoadControl loadControl,
BandwidthMeter bandwidthMeter,
Clock clock,
Looper looper) {
Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
Assertions.checkState(renderers.length > 0);
this.renderers = Assertions.checkNotNull(renderers);
this.trackSelector = Assertions.checkNotNull(trackSelector);
this.playWhenReady = false;
this.repeatMode = Player.REPEAT_MODE_OFF;
this.shuffleModeEnabled = false;
this.listeners = new CopyOnWriteArrayList<>();
emptyTrackSelectorResult =
new TrackSelectorResult(
new RendererConfiguration[renderers.length],
new TrackSelection[renderers.length],
null);
period = new Timeline.Period();
playbackParameters = PlaybackParameters.DEFAULT;
seekParameters = SeekParameters.DEFAULT;
playbackSuppressionReason = PLAYBACK_SUPPRESSION_REASON_NONE;
eventHandler =
new Handler(looper) {
public void handleMessage(Message msg) {
ExoPlayerImpl.this.handleEvent(msg);
}
};
playbackInfo = PlaybackInfo.createDummy(/* startPositionUs= */ 0, emptyTrackSelectorResult);
pendingListenerNotifications = new ArrayDeque<>();
internalPlayer =
new ExoPlayerImplInternal(
renderers,
trackSelector,
emptyTrackSelectorResult,
loadControl,
bandwidthMeter,
playWhenReady,
repeatMode,
shuffleModeEnabled,
eventHandler,
clock);
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
}上面是最终调用的地方,在真正使用的时候很方便,使用ExoPlayer封装好的方法即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// 是否需要根据扩展名来选择合适的解码器
boolean preferExtensionDecoder = false;
/**
* 初始化Player
*/
private void initPlayer() {
if (player == null) {
TrackSelection.Factory trackSelectionFactory = new RandomTrackSelection.Factory();
trackSelector = new DefaultTrackSelector(trackSelectionFactory);
RenderersFactory renderersFactory =
buildRenderersFactory(preferExtensionDecoder);
player = ExoPlayerFactory.newSimpleInstance(
/* context= */ this, renderersFactory, trackSelector);
player.addListener(new PlayerEventListener());
player.setPlayWhenReady(startAutoPlay);
player.addAnalyticsListener(new EventLogger(trackSelector));
}
}
// 创建RendererFactory 以获取Renderer
private RenderersFactory buildRenderersFactory(boolean preferExtensionDecoders) {
.ExtensionRendererMode
int extensionRendererMode = preferExtensionDecoders
? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
: DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON;
return new DefaultRenderersFactory(/* context= */ this)
.setExtensionRendererMode(extensionRendererMode);
}与PlayerView绑定
ExoPlayer已经封装好了对应的PlayerView(com.google.android.exoplayer2.ui.PlayerView),在布局文件声明好后,可以直接使用,具体代码如下:1
2
3
4
5
6// 3. Player与PlayerView绑定
playerView = findViewById(R.id.player_view);
playerView.setControllerVisibilityListener(this);
playerView.setPlayer(player);
playerView.setPlaybackPreparer(this);// this为实现了com.google.android.exoplayer2.PlaybackPrepare接口的Activity上面的代码就完成了Player和PlayerView的绑定操作。
准备播放源(MediaSource)
接下来就是播放的设置了,如下:1
2
3// 4. 准备MediaSource
MediaSource mediaSource = getMediaSource();
player.prepare(mediaSource);getMediaSource()代码如下:
1
2
3
4
5
6
7
8
9private MediaSource getMediaSource() {
List<String> mediaList = Arrays.asList(getResources().getStringArray(R.array.music_media_source_list));
// Uri uri = Uri.parse("https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4");
Uri uri = Uri.parse("https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-137.mp4");
DataSource.Factory dataSource = new DefaultDataSourceFactory(mContext, Util.getUserAgent(mContext, mContext.getPackageName()));
MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSource).createMediaSource(uri);
return mediaSource;
}播放控制
封装的PlayerView中包含了控制播放的View实现,也可以自行封装,这里不做详述资源释放
调用ExoPlayer的release()方法进行资源的释放
基本结构
先看核心库的目录结构
1 | . |
几个比较重要的接口
Player
,定义了5个子接口如下,同时还提供了所有播放操作API;AudioComponent
,音频控件,提供音频监听设置、属性设置、Aux效果设置、声音设置等API;VideoComponent
,视频控件,提供视频监听设置、缩放模式设置、帧元数据监听设置以及Surface相关设置等API;TextComponent
,字幕控件,提供添加和移除字幕输出的设置API;MetadataComponent
,元数据控件,提供音视频基本信息输出控制API;EventListener
,事件监听,提供基本的播放状态改变监听API;
ExoPlayer
,继承自Player
,同时额外扩展出了一些方法(如prepare、retry、setSeekParameters等),对要播放的对象没有做过多额外的处理,而是通过播放组件来实现。常用四个组件介绍
MediaSource
,定义多媒体数据源,该类的功能是从Uri中读取多媒体文件二进制数据,MediaSource对象在ExoPlayer的prepare方法中传入;TrackSelector
,轨道提取器,从MediaSource提取各个轨道的二进制数据,交给Renderer渲染;Renderer
,对多媒体中的各个轨道(音轨、视频轨、字幕轨等)数据进行渲染,即将二进制文件渲染成声音、画面播放出来。其对象在创建ExoPlayer时注入;LoadControl
,字面意思就是加载控制,可以对MediaSource进行控制,如什么时候缓存,缓存多少。(如果需要修改缓存策略,可以从这里入手)其对象也是在创建ExoPlayer时注入;上面这四种组件ExoPlayer都提供了默认的实现,能满足大部分需求,当然也能很方便的扩展。比如,可以自定义LoadControl来更改播放器的缓冲策略,或自定义Renderer来渲染Android本身不支持的编解码器。
ExoPlayer线程模型
ExoPlayer的线程主要分为以下几种- Application Tread,通常指的是主线程,主要进行Player实例的获取、监听器的绑定;
- Playback Tread,播放线程,注入ExpPlayer的组件在该线程中被调用
- Background Tread,组件的一些初始化操作如数据加载(MediaSource)、轨道提取(TrackSelector)、渲染(Renderer)
- Playback Tread和Application Thread通过Handler进行通信
几个比较重要的类
BasePlayer
,实现Player接口部分方法,即提供部分操作的通用实现,后面的ExoPlayerImpl和SimpleExoPlayer都继承该类SimpleExoPlayer
,ExoPlayerImpl的一层包装,内部持有ExoPlayerImpl实例,其实就是ExoPlayerImpl的一层包装;ExoPlayerImpl
,ExoPlayer的实现类,提供Player和ExoPlayer各种API的具体实现;ExoPlayerImplInternal
,ExoPlayerImpl内部行为的实现类,ExoPlayerImpl的大多数操作最终通过该类来实现的。
MediaSource
定义并提供可供ExoPlayer播放的数据源,有两大职责:
在开始播放和播放源内容有改变时提供定义Media结构的TimeLine,主要通过调用在prepareSource传入的
SourceInfoRefreshListener
实例的onSourceInfoRefreshed
来实现;为当前时间线上的某段时间提供
MediaPeriod
实例,并提供读取媒体数据的方式
其继承关系如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17MediaSource #接口定义
BaseMediaSource #接口的基本实现,类实例的复用处理,并维护MediaSourceEventListener类表
HlsMediaSource #Hls类型的MediaSource实现
SilenceMediaSource
ProgressiveMediaSource
SingleSampleMediaSource
DummyMediaSource in ConcatenatingMediaSource
ExtractorMediaSource
DashMediaSource #Dash类型的MediaSource实现
SsMediaSource #Ss类型MediaSource实现
CompositeMediaSource
MergingMediaSource
AdsMediaSource
ConcatenatingMediaSource
DynamicConcatenatingMediaSource
LoopingMediaSource #支持循环的MediaSource
ClippingMediaSourceTrackSelector
为播放器的渲染器(Renderer)选择合适的渲染轨道,ExoPlayer提供的默认实现(DefaultTrackSelector)能满足大多数情形。播放器(ExoPlayer)和轨道选择器(TrackSelector)之间的交互如下:
播放器创建的时候,会调用TrackSelector的init方法来初始化TrackSelector的轨道失效监听器(InvalidationListener)和带宽监听器(BandwidthMeter);
播放器需要选择轨道时会调用TrackSelector的selectTracks方法(通常发生在播放开始阶段以及监听失效时);
在进行Media合并操作之前,也可能发生轨道选择的操作;
TrackSelector通过调用初始化时传递进来的InvalidationListener的回调方法来通知ExoPlayer轨道选择失效;
Renderer
将从SampleStream中读取的内容渲染出来,提供Renderer的状态控制,SampleStream的操作相关的API。其继承关系如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14Renderer #接口定义
BaseRenderer #提供了Renderer和RendererCapabilities两个接口的基本实现,同时提供了一系列的on开头的毁掉方法
SimpleDecoderAudioRenderer # 采用SimpleDecoder作为解码器解码渲染音频
FfmpegAudioRenderer # 基于Ffmpeg解码器的实现
LibflacAudioRenderer # 基于Flac解码器的实现
LibopusAudioRenderer # 基于Opus解码器的实现
MediaCodecRenderer # 采用MediaCodec进行解码渲染
MediaCodecAudioRenderer # 采用MediaCodec进行音频的解码渲染
MediaCodecVideoRenderer # 采用MediaCodec进行视频的解码渲染
LibvpxVideoRenderer # 基于Vp9实现的视频渲染器
CameraMotionRenderer # 解析运动追踪的渲染器
MetadataRenderer # 元数据渲染器
TextRenderer # 字幕渲染器
NoSampleRendererLoadControl
主要职责是进行媒体资源的缓存控制,提供了基本的回调方法和缓存控制方法。ExoPlayer提供了一个默认实现(DefaultLoadControl,该类采用建造者模式实现),可以通过Builder.build() 来获取其实力
优点和不足
- 优点
- 默认支持提供倍速播放支持,通过设置给ExoPlayer设置
PlaybackParameters
即可; - TrackSelector提供了多种配置参数,具体参见
DefaultTrackSelector#Parameters
;
- 默认支持提供倍速播放支持,通过设置给ExoPlayer设置
- 不足
- 比较耗电
总结
ExoPlayer充分利用了组件化的思想,将多媒体播放流程中一些常用常见的概念抽象成组件,提供了组件的默认实现,同时也支持个性化扩展。
相关控件
TextureView
官方解释如下:
1 | * A TextureView can be used to display a content stream. Such a content |
大致意思就是,一个用来展示流内容(比如视频或者OpenGL的场景的View,流内容既可以由应用进程提供,也可以通过远程获取。值得注意的地方:
- 只能被用在硬件加速的窗口中,否则将啥都不绘制;
- 与
SurfaceView
不同的是,TextureView并不会单独开辟一个窗口,你可以像操作一个普通的View那样操作它; - 使用非常简单,它内部依靠
SurfaceTexture
来进行内容的渲染,
SurfaceView
关于SurfaceView,将在Android SurfaceView详解中详细介绍