Google Exoplayer 之全面认识

9,894 阅读12分钟

本文翻译自:https://google.github.io/ExoPlayer/guide.html

翻译者:SavySoda、逆流的鱼yuiop


在Android设备上播放音频和视频已经很流行了,Android框架提供了用于播放媒体用最少的代码的快速解决方案MediaPlayer。也提供了low-level 的 api 用于构建定制媒体播放器的解决方案,例如MediaCodec, AudioTrack和MediaDrm。

ExoPlayer是建立在 Android low-level api之上的 app级开源播放器。开源项目包含 ExoPlayer库和demo。


  • ExoPlayer库 - 类库源代码

  • Demo - 演示类库的使用


本文介绍了 ExoPlayer 库及其用途,使用 ExoPlayer 的利弊 ,具体的代码示例演示了如何使用,展示了如何使用ExoPlayer 播放 DASH,SmoothStreaming 和 HLS 自适应流,如 MP4,M4A,FMP4,支持 WebM,MKV,MP3,OGG,WAV,MPEG-TS,MPEG-PS,FLV 和 ADTS( AAC)。还讨论 ExoPlayer 的事件、消息、定制和 对 DRM 的支持。

1 优点和缺点

相比 Android 内置的 MediaPlayer,ExoPlayer 具有许多优于优势:

  • 支持 Dynamic Adaptive Streaming over HTTP (DASH) 和SmoothStreaming,更多支持请参阅支持的格式)详细信息页面。

  • 支持高级 HLS (HTTP Live Streaming)功能,如正确处理 #EXT-X-DISCONTINUITY的标签。

  • 能够无缝融合,串联和循环媒体资源。

  • 支持定制和扩展,ExoPlayer是考虑到这一点而设计的,并允许许多部件与定制实现替换。

  • 更新起来更方便

  • 设备通用性更强

  • 支持在Android 4.3(API级别18)和更高的Widevine通用加密。


需要注意的是,也有一些缺点,这一点很重要:

  • ExoPlayer的标准音频和视频部件依赖于Android的 MediaCodecAPI,MediaCodecAPI 在搭载Android 4.1(API级别16)发布。因此,他们不会在较早版本的 Android 的工作。Widevine 的通用加密可以在 Android 4.3(API级别18)和更高。

2 Library overview


ExoPlayer库的核心是ExoPlayer接口。ExoPlayer 接口暴露了传统的 high-level 播放器中的功能,如资源缓冲,播放,暂停和拖拽等。接口的实现类对媒体的播放类型、存储位置和渲染方式做出假设,而不是笼统的加载和渲染。Exoplayer 把播放类型、存储位置和渲染方式等任务委派给不同的部件,然后在创建播放器或后台播放的时候把这些部件注入。这些部件包括:

  • MediaSource - 负责装载 media,装载到MediaSource 的 media 可以被读取,MediaSource 在调用 ExoPlayer.prepare 方法时被注入。

  • Render S - 用于渲染 media 的部件,在创建播放器时被注入

  • TrackSelector - 从MediaSource 中选出 media 提供给可用的 Render S 来渲染,在创建播放器时被注入。

  • LoadControl - 控制 MediaSource 缓存更多的 media,有多少 media 被缓冲。在创建播放器时被注入。


类库提供这些部件在通常情况下的默认实现,下面有详细描述。一个 ExoPlayer 可以利用这些部件。如果标准实现不能满足需求,也可以使用自定义实现。例如,自定义LoadControl 可以改变播放器的缓冲策略,或自定义Renderer 可实现 Android 本身不支持的视频编解码器。

ExoPlayer提供默认的音频和视频渲染器,利用了Android框架中的MediaCodec和AudioTrack类。这两个都需要一个SampleSource对象中注入,用来实现媒体示例的播放。

组件的注入在当前ExoPlayer库中是普遍存在的。下图展示了使用一个ExoPlayer来配置和播放MP4媒体流的高级对象模型默认的音频和视频渲染器已经被注解到ExoPlayer中。一个叫ExtractorSampleSource类的实现被注解到渲染器中用于提供简单的媒体播放功能。DataSource和Extractor示例被注解到ExtractorSampleSource来支持加载媒体流和在被加载的数据中提取样板。在这个示例中DefaultUriDataSource和Mp4Extractor被用于播放从URIs中导入的MP4流。


注入的概念贯穿整个类库的实现,默认的部件实现类何以被进一步的委托注入,这让许多子部件被单独更换成自定义实现。比如,通过提供一个定制的工厂有可能从一个非标准的源或通过不同的网络协议栈加载数据。而默认的MediaSource 实现需要一个或多个 DataSource 工厂的构造函数被注入。

Adaptive media playbacks


ExoPlayer 支持自适应流,即在播放的时候,根据网络状况自动调节视频质量。DASH,SmoothStreaming和HLS展示了自适应流技术。以上三种,媒体都是通过小块的方式加载(通常2到10秒的长度)。每当一块媒体被请求,客户端将会选择一种可能的规格。例如:如果网络情况比较好,客户端将选择高质量的规格,如果网络比较差则会低质量的。在两种技术中,视频和音频都需要被分割。

DASH android SmoothStreaming


ExoPlayer库通过ChunkSampleSource支持DASH和SmoothStreaming动态播放,即通过读取独立的媒体块。每个ChunkSampleSOurce需要一个ChunkSource通过构造方法注入进来。ChunkSource主要负责加载和读取样本来提供媒体块。DashChunkSource类使用FMP4和WebM容器格式来提供DASH播放。SmoothStreamingChunkSource类使用FMP4容器格式。

两种类型的ChunkSource实例需要一个解析器FormatEvaluator和一个数据源DataSource通过构造的方式注入。FormatEvaluator在每个块被加载之前选择一种可用的格式,DataSource提供数据源。最终,ChunkSampleSource需要一个LoadControl对象去控制缓冲块。
下图演示了DASH动态播放通常的配置。通过FormatEvaluator实现视频质量的动态变化,但是音频的质量是固定的。


3 Getting started


开始使用 ExoPlayer 需要执行以下步骤:

  1. 添加依赖

  2. 创建一个 SimpleExoPlayer 实例

  3. 将 player 和 view (用于视频输出和用户输入)关联

  4. 调用 prepare 方法,注入MediaSource,准备播放

  5. 播放结束释放 player


这些步骤在下面更详细地所述。对于一个完整的示例,请参阅 PlayerActivity在ExoPlayer演示程序。

Add ExoPlayer as a dependency

确保支持 Jcenter 仓库

repositories {
   jcenter()
 }

添加依赖 - 版本

compile 'com.google.android.exoplayer:exoplayer:r2.X.X'


4 Creating the player


您可以通过 ExoPlayerFactory 工厂方法创建一个 ExoPlayer 实例。 工厂提供了一系列的方法创建不同程度定制的 ExoPlayer 实例。对于绝大多数使用情况下,默认 Renderer 库提供的实现足够用。这时应该使用 ExoPlayerFactory.newSimpleInstance 方法。方法返回 SimpleExoPlayer,SimpleExoPlayer 扩展了 ExoPlayer 并添加额外的 high-level 播放功能。下面演示了如何创建一个 SimpleExoPlayer。

 // 1. Create a default TrackSelector
Handler mainHandler = new Handler();
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
    new AdaptiveVideoTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector =
    new DefaultTrackSelector(mainHandler, 
                             videoTrackSelectionFactory);
// 2. Create a default LoadControl
LoadControl loadControl = new DefaultLoadControl();
// 3. Create the player
SimpleExoPlayer player =
    ExoPlayerFactory.newSimpleInstance(context, 
                                 trackSelector, loadControl);
                                        
5 Attaching the player to a view


ExoPlayer 库提供了 SimpleExoPlayerView ,它封装了一个 PlaybackControlView 和 Surface 用来渲染视频。一个 SimpleExoPlayerView 可以包含在布局 XML 文件中。下面演示了如何绑定 player

                                            
  1. simpleExoPlayerView.setPlayer(player);

如果您需要比播放器控制和渲染视频的 Surface 上更细粒度的控制,可以给播放器设置目标 SurfaceView, TextureView,SurfaceHolder 或 Surface 直接分别使用 SimpleExoPlayer 的 setVideoSurfaceView,setVideoTextureView,setVideoSurfaceHolder 和 setVideoSurface 方法。您可以使用PlaybackControlView 作为一个独立的部件,或实现自己的 playback 直接与 player 进行交互。setTextOutput 和setId3Output可用于 playback 时接收字幕和 ID3 元数据输出。

6 Preparing the player


在 ExoPlayer 每一个 media 都由 MediaSource 代表。要播放 media 必须先创建一个相应的 MediaSource,然后把这个对象传递给 ExoPlayer.prepare。ExoPlayer 库提供 MediaSource 的DASH实现(实DashMediaSource),SmoothStreaming 实现( SsMediaSource),HLS 实现(HlsMediaSource)和常规 media files 实现(ExtractorMediaSource)。这些实现在后面详细介绍。下面的代码演示如何使用 MediaSource prapare 一个适合播放 MP4 文件的 player。

// Measures bandwidth during playback. Can be null if not required.
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// Produces DataSource instances through which media data is loaded.
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(
this,Util.getUserAgent(this, "yourApplicationName"), bandwidthMeter);
// Produces Extractor instances for parsing the media data.
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
// This is the MediaSource representing the media to be played.
MediaSource videoSource = new ExtractorMediaSource(mp4VideoUri,
    dataSourceFactory, extractorsFactory, null, null);
 // Prepare the player with the source.
player.prepare(videoSource);

一旦 player 已经被 prepared,playback 可以通过调用播放器上的方法进行控制。例如 setPlayWhenReady 可用于启动和暂停播放,和各种 seekTo 方法可以用于改变进度。如果 player 被绑定到 SimpleExoPlayerView 或 PlaybackControlView ,那么用户与这些部件的交互将会导致 player 相应的方法被调用。

7 Releasing the player


不再使用的时候将 player 释放掉是非常重要的,以便腾出有限的资源,如为其他应用程序使用视频解码器。这可以通过调用 ExoPlayer.release 来完成。

MediaSource

在 ExoPlayer 每一个 media 都由 MediaSource 代表。要播放 media 必须先创建一个相应的 MediaSource,然后把这个对象传递给 ExoPlayer.prepare。ExoPlayer 库提供 MediaSource 的DASH实现(实DashMediaSource),SmoothStreaming 实现( SsMediaSource),HLS 实现(HlsMediaSource)和常规 media files 实现(ExtractorMediaSource)。Demo app de PlayerActivity,分别演示了如何实例化以上四种 MediaSource。

除了上述的 MediaSource 的实现方式,ExoPlayer 库还提供MergingMediaSource,LoopingMediaSource 和 ConcatenatingMediaSource。这些 MediaSource 实现能够通过组合能够支持更复杂的 playback 功能。下面对一些常见的用例进行说明。注意,尽管下面的实例使用的视频 playback,它们同样适用于仅有音频 playback,事实上适用于所有支持的 media types 的 playback。

8 8 Side-loading a subtitle file


给定的视频文件和一个单独的字幕文件,MergingMediaSource 可以将它们合并到用于 playback 的单一来源。

MediaSource videoSource = new ExtractorMediaSource(videoUri, ...);
MediaSource subtitleSource = 
   new SingleSampleMediaSource(subtitleUri, ...);
 // Plays the video with the sideloaded subtitle.
MergingMediaSource mergedSource =
    new MergingMediaSource(videoSource, subtitleSource);

Seamlessly looping a video

LoopingMediaSource 可以实现视频的无缝循环。下面演示了如何实现无缝循环。也可以创建一个指定循环计数的 LoopingMediaSource。

MediaSource source = new ExtractorMediaSource(videoUri, ...);
// Loops the video indefinitely.
LoopingMediaSource loopingSource = new LoopingMediaSource(source);

Seamlessly playing a sequence of videos

ConcatenatingMediaSource 使得 playback 支持顺序播放两个或多个单独的 MediaSource S,下面的示例顺序播放两段视频。无缝切换资源,并且对被串联资源的格式是否相同没有要求。

MediaSource firstSource = 
    new ExtractorMediaSource(firstVideoUri, ...);
 MediaSource secondSource = 
    new ExtractorMediaSource(secondVideoUri, ...);
 // Plays the first video, then the second video.
ConcatenatingMediaSource concatenatedSource =
    new ConcatenatingMediaSource(firstSource, secondSource);

Advanced composition

可以为了更不寻常的用例进一步结合复合 MediaSourceS。给定两个视频 A 和 B,下面的例子演示了如何使用 LoopingMediaSource 和ConcatenatingMediaSource 按照( A,A,B)的序列进行无线循环。

MediaSource firstSource = 
   new ExtractorMediaSource(firstVideoUri, ...);
 MediaSource secondSource = 
   new ExtractorMediaSource(secondVideoUri, ...);
 // Plays the first video twice.
LoopingMediaSource firstSourceTwice = 
   new LoopingMediaSource(firstSource, 2);
 // Plays the first video twice, then the second video.
ConcatenatingMediaSource concatenatedSource =
   new ConcatenatingMediaSource(firstSourceTwice, secondSource);
 // Loops the sequence indefinitely.
LoopingMediaSource compositeSource = 
   new LoopingMediaSource(concatenatedSource);

下面的例子是等价的,这表明实现相同的结果方式不止一个。

MediaSource firstSource = 
    new ExtractorMediaSource(firstVideoUri, ...);
 MediaSource secondSource = 
    new ExtractorMediaSource(secondVideoUri, ...);
 // Plays the first video twice, then the second video.
ConcatenatingMediaSource concatenatedSource =
    new ConcatenatingMediaSource(
      firstSource, firstSource, secondSource);
 // Loops the sequence indefinitely.
LoopingMediaSource compositeSource = 
    new LoopingMediaSource(concatenatedSource);

避免在一次 composition 中用同一个 MediaSource 实例多次,除非文档中明确允许。在上面的例子中两次使用 firstSource 的就是这样的情况,由于对 Javadoc 中 ConcatenatingMediaSource 明确规定,重复项是允许的。在一般情况下,由组合物形成的对象的曲线应该是一个树。在 composition 中使用多个等同于 MediaSource 的实例是允许的。

Player events

playback 过程中,应用程序可以监听由 ExoPlayer 生成的指示 player 全部状态的事件。可以根据这些事件来更新用户界面。许多 ExoPlayer 组件还报告它们自己特定的 low-level 的事件,可以用于性能监控。

High level events

ExoPlayer 使用 addListener 和 removeListener 添加和移除 ExoPlayer.EventListener 实例。被注册的侦听器可以发布 playback 的状态变化,以及出错时发布错误产生的原因。

开发者自定义 playback 应该注册一个监听器,并根据 player 的状态变化来更新控件。如果播放失败,应用程序还应该向用户显示相应的错误。

使用 SimpleExoPlayer 时,可以在 player 上设置额外的监听器。特别是 setVideoListener 允许应用程序接收与视频渲染有关的事件,该事件可以是用于调整用户界面。setVideoDebugListener 和setAudioDebugListener 可以接收调试信息。

Low level events

除了 high-level 监听器,许多由 ExoPlayer 库提供的组件,允许拥有自己的事件侦听器。通常需要传递一个 Handler 给部件,用来确定监听器方法被调用线程。在大多数情况下,Handler应与应用程序的主线程关联。

Sending messages to components

有些 ExoPlayer 组件允许在播放过程中更改配置。按照惯例,您通过使用 sendMessages 或 blockingSendMessages 方法传递变化消息给ExoPlayer 组件。这种方式可以确保两个线程安全并且使得配置更改和在 player 上的他操作执行有序。

Customization

相比 Android 内置的 MediaPlayer,ExoPlayer 的主要好处是支持定制和扩展的 player 以更好地满足开发者的实际用例。在考虑到这一点而设计ExoPlayer 库时,定义了大量接口和抽象基类帮助开发者轻松的替换类库提供的默认实现,下面展示了一些用于构建自定义组件的用例:

  • Render - 实现自定义Renderer处理不是由库提供的默认实现所支持的媒体类型。

  • 如果自定义组件需要向应用程序发布事件,我们建议您一定要使用现有的 ExoPlayer 组件模型,在构造组件的时候同时传递监听器和 Handler

  • 我们建议的自定义组件使用现有 ExoPlayer 组件相同的模型,允许在playback 的过程中重新配置。要做到这一点,就需要实现ExoPlayerComponent 接口,并在 handleMessage 方法中处理接收到的配置改变信息。您的应用程序应该调用 ExoPlayer 的 sendMessages和blockingSendMessages 方法来传递改变的配置信息。


ExoPlayer Demo 中 PlayerActivity 演示了 DrmSessionManager 如何在初始化 player 的时候被创建和注入。

第一时间获得博客更新提醒,以及更多 android、小程序干货,源码分析,最新开源项目推荐,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。