Android 视频播放器 (四):使用ExoPlayer播放视频

1、简介

ExoPlayer是一个Android应用层的媒体播放器,它提供了一套可替换Android MediaPlayer的API,能够播放本地或者是线上的音视频资源。ExoPlayer支持一些Android MediaPlayer不支持的特性,好比适配DASH和SmoothStreaming的播放。和MediaPlayer不一样的是,ExoPlayer很容易自定义和扩展,而且它能够经过应用商店的应用程序更新来直接更新。html

如今在Android设备上播放视频和音乐的应用是一个很热门的应用,Android框架提供的MediaPlayer可使用不多的代码量快速的实现播放音视频的功能,并且它也提供了底层的API好比MediaCodec、AudioTrack和MediaDrm,它们一样能够建立自定义媒体播放器,而ExoPlayer是创建在底层音视频API之上的开源的应用级媒体播放器。java

项目地址:https://github.com/google/ExoPlayerandroid

ExoPlayer系列文章:https://medium.com/google-exoplayergit

ExoPlayer开发文档:https://exoplayer.dev/github

优势

对于Android内置的MediaPlayer来讲,ExoPlayer有如下几个优势:缓存

  1. 支持DASH和SmoothStreaming这两种数据格式的资源,而MediaPlayer对这两种数据格式都不支持。它还支持其它格式的数据资源,好比MP4, M4A, FMP4, WebM, MKV, MP3, Ogg, WAV, MPEG-TS, MPEG-PS, FLV and ADTS (AAC)等
  2. 支持高级的HLS特性,好比能正确的处理#EXT-X-DISCONTINUITY标签
  3. 无缝链接,合并和循环播放多媒体的能力
  4. 和应用一块儿更新播放器(ExoPlayer),由于ExoPlayer是一个集成到应用APK里面的库,你能够决定你所想使用的ExoPlayer版本,而且能够随着应用的更新把ExoPlayer更新到一个最新的版本。
  5. 较少的关于设备的特殊问题,而且在不一样的Android版本和设备上不多会有不一样的表现。
  6. 在Android4.4(API level 19)以及更高的版本上支持Widevine通用加密
  7. 为了符合你的开发需求,播放器支持自定义和扩展。其实ExoPlayer为此专门作了设计,而且容许不少组件能够被自定义的实现类替换。
  8. 使用官方的扩展功能能够很快的集成一些第三方的库,好比IMA扩展功能经过使用互动媒体广告SDK能够很容易地将视频内容货币化(变现)

缺点

  1. 好比音频在Android设备上的播放,ExoPlayer会比MediaPlayer消耗更多的电量。更多细节请参考文章:Battery consumption page

 2、ExoPlayer 使用

1.把ExoPlayer做为一个依赖添加到你的项目

添加仓库 第一步就是确保你在工程根目录的build.gradle文件里添加了Google和JCenter仓库:安全

repositories {
     google()
     jcenter()
 }

添加ExoPlayer模块 在你的app module 里面的build.gradle文件夹里添加一个ExoPlayer依赖。app

下面是ExoPlayer的全量包的依赖方式:框架

implementation 'com.google.android.exoplayer:exoplayer:2.X.X'

上面的2.x.x是选择的版本。jvm

你也能够只依赖你想要的模块,来代替全量包。好比当你的app想要播放DASH格式的内容的时候,能够只依赖Core,DASH和UI模块的库。

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全量的依赖库等同于把下面全部的依赖库都分别添加进去。

  1. exoplayer-core: 核心功能(必须的).
  2. exoplayer-dash: 支持DASH内容.
  3. exoplayer-hls: 支持HLS内容.
  4. exoplayer-smoothstreaming: 支持SmoothStreaming内容.
  5. exoplayer-ui: ExoPlayer所使用的UI组件和资源.

除了这些模块库以外,ExoPlayer还有不少能够提供额外功能的依赖于第三方库的扩展模块,能够参考extensions directory来了解更多信息

2. 打开对 java 8 的支持

若是尚未设置支持java8,那么你须要在全部依赖ExoPlayer的build.gradle文件里打开对java8的支持,经过在Android域中添加如下代码便可:

compileOptions {
   targetCompatibility JavaVersion.VERSION_1_8
}

记住若是你想在你的代码里用java8的特性,你须要添加下面额外的设置:

// For Java compilers:
 compileOptions {
   sourceCompatibility JavaVersion.VERSION_1_8
 }
 // For Kotlin compilers:
 kotlinOptions {
   jvmTarget = JavaVersion.VERSION_1_8
 }

3. 建立一个播放器

你可使用ExoPlayerFactory建立一个ExoPlayer对象。为了避免同的需求,这个工厂类提供了一系列方法来建立ExoPlayer实例,可是在大多数状况下,使用ExoPlayerFactory.newSimpleInstance方法就能够了。这些方法会返回SimpleExoPlayer类型的对象,它继承自ExoPlayer,而且添加了一些额外的高级的播放器功能。下面的代码展现了怎么建立一个SimpleExoPlayer对象的:
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(context);

应用里面的某个线程必定能够访问ExoPlayer对象,在大多数状况下它通常是应用的主线程,而且只有在应用的主线程里才能使用ExoPlayer的UI组件和IMA扩展。

可以访问ExoPlayer对象的线程能够经过建立播放器实例的时候传入一个Looper被明确的指定,若是没有指定Looper,那么建立player的线程的Looper会被使用,或者这个线程也没有Looper,那么应用的主线程的Looper会被使用。在全部的状况下,可以访问播放器的线程的Looer可以经过Player.getApplicationLooper获取到。

4. 把这个播放器实例附着到一个View上

ExoPlayer库提供了一个PlayerView,它封装了一个PlayerControlView和一个可以渲染视频的Surface。一个PlayerView能够被加入到应用的布局文件中去。能够像这样把一个player绑定到一个View上。
// 绑定播放器到View上
playerView.setPlayer(player);
若是你须要更加精确的控制播放器和渲染视频的Surface,你可使用SimpleExoPlayer的setVideoSurfaceView、setVideoTextureView、setVideoSurfaceHolder和setVideoSurface方法分别的设置播放器的属性SurfaceView、TextureView、SurfaceHolder和Surface。你还能够把PlayerControlView来当成一个单独的组件使用,或者实现自定义的播放控制类来和播放器进行直接交互。在播放的时候,setTextOutput和setId3Output能够被用来接收字幕和ID3元数据输出。

5. 准备播放器资源

在ExoPlayer里每一种媒体资源都是被MediaSource来表明的。若是想播放一种媒体资源,你首先要为它建立相应的MediaSource对象,而后把这个对象传递给ExoPlayer.prepare方法。ExoPlayer库提供了多种MediaSource的实现类,好比表明DASH资源的DashMediaSource,表明SmoothStreaming资源的SsMediaSource,表明HLS资源的HlsMediaSource和表明通常的多媒体文件的ExtractorMediaSource。下面的代码展现了如何为播放MP4文件的播放器准备适合的MediaSource。

 //建立一个DataSource对象,经过它来下载多媒体数据
 DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context,
 Util.getUserAgent(context, "yourApplicationName"));
 //这是一个表明将要被播放的媒体的MediaSource
 MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
     .createMediaSource(mp4VideoUri);
 //使用资源准备播放器
 player.prepare(videoSource);

6. 控制播放器

一旦播放器准备就绪,就能够调用player的方法进行播放了,例如调用setPlayWhenReady能够开始和暂停播放。不一样的seekTo方法能够在媒体资源里进行搜索,setRepeatMode控制了多媒体如何循环播放,setShuffleModeEnabled控制了是否打乱播放列表,setPlaybackParameters用来调整播放的速度和音调。

若是player和PlayerView或者是PlayerControlView进行绑定了,那么用户和这些控件的交互将会调用player相对应的方法。

7. 监听播放器事件

改变播放状态或者是播放错误的事件将会被注册的Player.EventListener对象接收,很容易咱们就能够注册一个接收这种事件的监听。

 // 添加一个监听来接收播放器事件.
 player.addListener(eventListener);

若是你只是对一部分事件感兴趣,那么你能够继承Player.DefaultEventListener而不是实现Player.EventListener,这样会让你只实现你想要的方法。

当使用SimpleExoPlayer的时候,也能够给player设置一些额外的监听。好比addVideoListener方法容许你获取到视频渲染相关的事件,它能够帮助你调整UI布局(渲染视频的Surface的长宽比)。addAnalyticsListener方法容许你接收更加详细的事件,它有助于你分析一些东西。

8. 释放播放器

当不在须要播放的时候释放掉播放器是很是重要的,以便释放掉有限的资源好比视频解码器供其它应用使用。释放掉播放器能够经过调用ExoPlayer.release实现。

9. MediaSource(播放资源)

在ExoPlayer里每一种媒体资源都是被MediaSource来表明的。ExoPlayer库提供了多种MediaSource的实现类,好比表明DASH资源的DashMediaSource,表明SmoothStreaming资源的SsMediaSource,表明HLS资源的HlsMediaSource和表明通常的多媒体文件的ExtractorMediaSource。你能够参考main demo app的PlayerActivity类看一下怎么实例化这四种MediaSource。

除了上面所描述的MediaSource实现类以外,ExoPlayer也提供了ConcatenatingMediaSource,ClippingMediaSource,LoopingMediaSource和MergingMediaSource。经过组合这些MediaSource的实现类能够实现更加复杂的播放功能。一些经常使用的使用功能会在下面描述。须要注意的是下面描述的是以视频播放为示例的,可是它们一样适用于音频的播放,以及适用任何所支持的媒体类型的播放。

10. Playlists(播放列表)

使用ConcatenatingMediaSource支持播放列表,它能够连续的播放多种MediaSource资源。下面的例子展现了怎么实现由两个videos组成的playlists。

 MediaSource firstSource = new ExtractorMediaSource.Factory(...).createMediaSource(firstVideoUri);
 MediaSource secondSource = new ExtractorMediaSource.Factory(...).createMediaSource(secondVideoUri);
 //先播放第一个视频,再播放第二个视频
 ConcatenatingMediaSource concatenatedSource = new ConcatenatingMediaSource(firstSource, secondSource);

 

链接资源的转换是无缝的。这种链接不要求是相同格式的资源(例如能够把包含480P H264视频文件和包含720P VP9的视频文件很好地链接在一块儿)。它们甚至能够是不一样的类型(好比能够将一个视频和一个纯音频流很好地链接在一块儿)。而且在一个链接里一个类型的MediaSource能够被屡次使用。

在一个ConcatenatingMediaSource里能够经过添加,删除和移动MediaSource动态地修改播放列表。一样在播放视频以前或者是正在播放的过程当中能够经过调用相应的ConcatenatingMediaSource方法动态修改播放列表。播放器会正确地自动处理这些动态修改。例如正在播放的MediaSource被移动了,播放不会中断而且播放完成后会自动播放它后面的一个MediaSource资源。若是正在播放的MediaSource被删除了,播放器会自动移动到第一个存在的后继者去播放,若是没有后继者的话,播放器将会转到结束的状态。

11. Clipping a video(剪辑视频)

ClippingMediaSource能够被用来剪辑视频,这样能够只播放它的一部分。下面的例子展现了怎么剪辑了一个视频,从第5s开始播放,到第10s结束播放。

MediaSource videoSource = new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
 // 从第5s开始剪辑到第10s
 ClippingMediaSource clippingSource = new ClippingMediaSource(videoSource, /* startPositionUs= */ 5_000_000, /* endPositionUs= */ 10_000_000);

若是只是从资源的开始进行剪辑,那么结束的位置能够被设置为C.TIME_END_OF_SOURCE。为了只剪辑到特定的持续时间,有一个构造函数能够接收一个durationUs参数。

当从一个视频文件的开始进行剪辑的时候,若是可能的话,尽可能把开始位置和关键帧对齐。若是开始位置没有和关键帧对齐,那么在开始播放以前播放器须要解码而后丢弃掉前一个关键帧到开始位置之间的数据,这样使 在开始播放的时候,这将会产生一小段延迟,包括当播放器将ClippingMediaSource做为播放列表的一部分播放或循环播放时。

12. Looping a video(视频循环播放)

若是想无限循环播放,最好使用ExoPlayer.setRepeatMode而不是LoopingMediaSource。

使用LoopingMediaSource一个视频能够被无缝的循环必定次数。下面的例子展现了怎么播放一个视频两次:

 MediaSource source = new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
 // Plays the video twice.
 LoopingMediaSource loopingSource = new LoopingMediaSource(source, 2);

13. Side-loading a subtitle file(侧载一个字幕文件)

给一个视频文件和一个分开的字幕文件,MergingMediaSource能够用来把它们合并成一个单独的资源来播放。

 // 建立一个视频的 MediaSource.
 MediaSource videoSource = new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
 // 建立一个字幕的 MediaSource.
 Format subtitleFormat = Format.createTextSampleFormat(id, // 一个轨道的标志,能够为空
     MimeTypes.APPLICATION_SUBRIP, // The mime type. Must be set correctly.
     selectionFlags, // 轨道的选择标志
     language); // 字幕的语言,能够为空
 MediaSource subtitleSource = new SingleSampleMediaSource.Factory(...) .createMediaSource(subtitleUri, subtitleFormat, C.TIME_UNSET);
 // 播放带有字幕的视频
 MergingMediaSource mergedSource = new MergingMediaSource(videoSource, subtitleSource);

14. 高级组合

为了更多高级的功能有可能更进一步地合并组合的MediaSource。假若有两个视频A和B,下面的例子展现了怎么一块儿使用LoopingMediaSource和ConcatenatingMediaSource来播放A、A、B序列。

 MediaSource firstSource = new ExtractorMediaSource.Factory(...).createMediaSource(firstVideoUri);
 MediaSource secondSource = new ExtractorMediaSource.Factory(...).createMediaSource(secondVideoUri);
 // 播放第一个视频两次
 LoopingMediaSource firstSourceTwice = new LoopingMediaSource(firstSource, 2);
 // 播放第一个视频两次,而后再播放第二个视频
 ConcatenatingMediaSource concatenatedSource = new ConcatenatingMediaSource(firstSourceTwice, secondSource);

下面这个例子也能够实现这个效果,这说明了不止有一种方法来实现相同的效果。

MediaSource firstSource = new ExtractorMediaSource.Builder(firstVideoUri, ...).build();
 MediaSource secondSource = new ExtractorMediaSource.Builder(secondVideoUri, ...).build();
 // 播放第一个视频两次,而后再播放第二个视频
 ConcatenatingMediaSource concatenatedSource = new ConcatenatingMediaSource(firstSource, firstSource, secondSource);

15. 轨道选择

轨道选择决定了哪个可用的媒体轨道能够被播放器的渲染器播放。轨道选择由TrackSelector负责,不管何时建立一个ExoPlayer实例,都要给它提供一个TrackSelector对象。

 DefaultTrackSelector trackSelector = new DefaultTrackSelector();
 SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(context, trackSelector);

DefaultTrackSelector是一个灵活的TrackSelector,适合更多使用场景。当使用一个DefaultTrackSelector的时候,经过修改它的参数能够控制哪个tracks被它选择,这种选择能够在播放前完成。例以下面的代码告诉选择器将视频轨道限制为SD,而且若是音频轨道只有一个就选择一个德语的音频轨道。

 trackSelector.setParameters(trackSelector.buildUponParameters().setMaxVideoSizeSd().setPreferredAudioLanguage("deu"));

这是一个基于约束的轨道选择的例子,在这个例子中,在不知道实际可用轨道的状况下指定约束。可使用参数指定许多不一样类型的约束。参数还能够用来从可用的轨道中选择特定的轨道。有关详细信息,请参阅DefaultTrackSelectorParameters和ParametersBuilderParametersBuilder文档。

16. 发送消息给组件

能够向ExoPlayer组件发送消息。这些消息可使用createMessage建立,而后使用PlayerMessage.send发送。默认状况下,消息会尽快在播放线程上传递,可是这是能够自定义的经过设置另外一个回调线程(使用PlayerMessage.setHandler)或指定一个传递消息的播放位置(使用PlayerMessage.setPosition)。经过ExoPlayer发送消息能够确保操做的执行顺序与在播放器上执行的任何其余操做一致。

大多数ExoPlayer的开箱即用渲染器都支持在播放期间更改配置的消息。例如,音频渲染器接受消息来设置音量,而视频渲染器接受消息来设置Surface。这些消息应当在播放线程上传递,以确保线程安全。

17.自定义播放器

与Android的MediaPlayer相比,ExoPlayer的主要优点之一是可以自定义和扩展播放器,以更好地适应开发人员的用例。ExoPlayer库是专门为此而设计的,它定义了许多接口和抽象基类,可使应用程序开发人员可以轻松替换库提供的默认实现。下面是一些用于构建自定义组件的用例:

Renderer——您可能想要实现一个自定义Renderer来处理库默认不支持的媒体类型。

TrackSelector——实现自定义TrackSelector容许应用程序开发人员更改MediaSource暴露tracks的方式。它会被每一个可用的渲染器选择使用。

LoadControl—实现自定义LoadControl容许应用程序开发人员更改播放器的缓冲策略。

Extractor——若是您须要支持目前该库不支持的容器格式,请考虑实现一个定制的Extractor类,而后能够将其与ExtractorMediaSource一块儿用于播放该类型的媒体。

MediaSource——若是您但愿以自定义的方式获取媒体样本以提供给渲染程序,或者但愿实现自定义的MediaSource组合行为,那么实现自定义的MediaSource类多是最好的选择。

DataSource——ExoPlayer的upstream包已经包含了许多不一样用例的DataSource实现。您可能但愿实现本身的DataSource,以另外一种方式加载数据,例如经过自定义协议、使用自定义HTTP堆栈或从自定义持久缓存加载数据。

在构建自定义组件时,咱们建议以下:

若是自定义组件须要向应用程序报告事件,咱们建议使用与现有ExoPlayer组件相同的模型,其中事件监听器和Handler一块儿传递给组件的构造函数。

咱们建议自定义组件使用与现有ExoPlayer组件相同的模型,以容许应用程序在播放期间进行从新配置,如Sending messages to components所说的。为此,您应该实现一个ExoPlayerComponent并在其handleMessage方法中接收配置更改。您的应用程序应该经过调用外部播放器的sendMessages和blockingSendMessages方法来传递配置更改。