摘要
主要根据谷歌官方提供的demo,来了解ExoPlayer的基本使用。
[TOC]
基本概念
在介绍ExoPlayer之前,先介绍几个基本概念:
- 流媒体通信协议
- ExoPlayer中自交重要的概念- Timeline,用来指代媒体数据的灵活数据结构,本质上是一种数据的描述,由媒体片段(Period)和Window(由一个或多个Period组成)组成;
- MediaPeriod,用来加载与Timeline.Period对应的Media,并是这段Media可读;
 
基本使用流程
- 添加ExoPlayer依赖,或者clone ExoPlayer到本地然后倒入module作为依赖;
- 在项目根目录下的 - build.gradle中添加如下repositories:- 1 
 2
 3
 4- repositories { 
 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
 3- compileOptions { 
 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
 48- public 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
 9- private 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
 17- MediaSource #接口定义 
 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
 ClippingMediaSource- TrackSelector- 为播放器的渲染器(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
 14- Renderer #接口定义 
 BaseRenderer #提供了Renderer和RendererCapabilities两个接口的基本实现,同时提供了一系列的on开头的毁掉方法
 SimpleDecoderAudioRenderer # 采用SimpleDecoder作为解码器解码渲染音频
 FfmpegAudioRenderer # 基于Ffmpeg解码器的实现
 LibflacAudioRenderer # 基于Flac解码器的实现
 LibopusAudioRenderer # 基于Opus解码器的实现
 MediaCodecRenderer # 采用MediaCodec进行解码渲染
 MediaCodecAudioRenderer # 采用MediaCodec进行音频的解码渲染
 MediaCodecVideoRenderer # 采用MediaCodec进行视频的解码渲染
 LibvpxVideoRenderer # 基于Vp9实现的视频渲染器
 CameraMotionRenderer # 解析运动追踪的渲染器
 MetadataRenderer # 元数据渲染器
 TextRenderer # 字幕渲染器
 NoSampleRenderer- LoadControl- 主要职责是进行媒体资源的缓存控制,提供了基本的回调方法和缓存控制方法。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详解中详细介绍