前言
因为公司项目原因,目前开始研究ExoPlayer的原理及实现。其中对DRM更是有所涉及,因此自己也好借此机会扩展自己的音视频知识,同时写出一些自己的技术总结与分享,希望对其他学习此播放器的朋友能够有所帮助。这篇文章是对官方文档的翻译,从最浅层的知识开始,一步一步深入探索吧。(文章末尾附官方文档及开源地址)
ExoPlayer是一个基于Android底层媒体API构建的开源应用级媒体播放器。本指南介绍了ExoPlayer库及其用法。该指南涉及使用ExoPlayer的优缺点。它展示了如何使用ExoPlayer玩DASH,SmoothStreaming和HLS自适应流,以及格式,如MP4,M4A,FMP4,支持WebM,MKV,MP3,OGG,WAV,MPEG-TS,MPEG-PS,FLV和ADTS( AAC)。它还讨论了ExoPlayer事件,消息,自定义和DRM支持。
优点和缺点
与Android内置的MediaPlayer相比,ExoPlayer具有许多优势:
#EXT-X-DISCONTINUITY
标签。值得注意的是,还有一些缺点:
Library概述
ExoPlayer库的核心是ExoPlayer
界面。一个 ExoPlayer
公开的传统高层次的媒体播放器功能,比如缓冲介质,播放,暂停和寻求的能力。实现旨在对正在播放的媒体类型,存储方式和位置以及如何呈现的媒体类型做出一些假设(并因此施加很少的限制)。实现不是直接ExoPlayer
实现媒体的加载和呈现,而是将此工作委托给创建播放器或准备播放时注入的组件。所有ExoPlayer
实现共有的组件 是:
MediaSource(媒体资源)
:定义要播放的媒体,加载媒体,并从中读取加载的媒体。(MediaSource
在播放开始时通过ExoPlayer.prepare注入)Renderer(渲染器)
:渲染媒体的各个组成部分。(Renderer
在创建播放器时注入)TrackSelector(轨道选择器)
:从MediaSource
中选择可用的二进制数据,并交给可用的Renderer
渲染。(TrackSelector
在创建播放器时注入)LoadControl(负荷控制)
:控制何时MediaSource
缓冲更多媒体,以及缓冲多少媒体。(LoadControl
在创建播放器时注入)
该库为常见用例提供了这些组件的默认实现,如下面更详细地描述。一个ExoPlayer
可以利用这些组件,但是如果需要非标准行为也可用使用自定义实现构建。例如一个自定义的LoadControl
可以通过注入改变播放器的缓冲策略,或者一个自定义的Renderer
可以通过注入使用Android本身不支持的视频编解码器。
通过注入实现播放器的组件的概念的的思想贯穿了整个库。上面列出的组件的默认实现将工作委托给进一步注入的组件。这允许使用自定义实现单独替换许多子组件。例如,默认MediaSource
实现需要DataSource
通过其构造函数注入一个或多个工厂。通过提供自定义工厂,可以通过非标准的数据源或不同的网络堆栈加载数据。
入门
对于简单的用例,入门ExoPlayer
包括实现以下步骤:
SimpleExoPlayer
实例。MediaSource
准备播放器。以下更详细地概述了这些步骤。对于一个完整的例子,是指 PlayerActivity
在主演示应用程序。
入门的第一步是确保将JCenter和Google存储库包含在build.gradle项目根目录中的文件中。
repositories {
jcenter()
google()
}
接下来在build.gradle
app模块的文件中添加依赖项。以下内容将为完整的ExoPlayer库添加依赖项:
implementation 'com.google.android.exoplayer:exoplayer:2.X.X'
2.X.X
是你选择的版本。另外,你可以只依赖实际需要到的库模块。例如,以下内容将添加对Core,DASH和UI库模块的依赖性,这对于播放DASH内容的应用程序可能是必需的:
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库添加依赖项等同于单独添加所有库模块的依赖项。
exoplayer-core
:核心功能(必需)。exoplayer-dash
:支持DASH内容。exoplayer-hls
:支持HLS内容。exoplayer-smoothstreaming
:支持SmoothStreaming内容。exoplayer-ui
:用于ExoPlayer的UI组件和资源。除了库模块,ExoPlayer还有多个扩展模块,这些模块依赖于外部库来提供附加功能。这些超出了本指南的范围。浏览扩展目录及其各自的README以获取详细信息。
您可以使用ExoPlayerFactory
创建ExoPlayer
实例。工厂提供了一系列方法,来创建具有不同级别的自定义ExoPlayer实例。对于绝大多数用例,ExoPlayerFactory
.newSimpleInstance
应该使用其中一种 方法。这些方法返回SimpleExoPlayer
,扩展ExoPlayer
为添加额外的高级播放器功能。下面的代码是创建一个SimpleExoPlayer的例子 。
// 1. Create a default TrackSelector
Handler mainHandler = new Handler();
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(bandwidthMeter);
DefaultTrackSelector trackSelector =
new DefaultTrackSelector(videoTrackSelectionFactory);
// 2. Create the player
SimpleExoPlayer player =
ExoPlayerFactory.newSimpleInstance(context, trackSelector);
必须从单个应用程序线程访问ExoPlayer实例。它必须是具有{@link Looper}的线程或者是该应用程序的主线程,才能来创建该播放器。
ExoPlayer库提供了一个PlayerView
,它封装了一个 PlayerControlView
和一个在视频渲染表层之上的Surface
。一个 PlayerView
可以包含在应用程序的布局xml中。将播放器绑定到视图非常简单:
// Bind the player to the view.
playerView.setPlayer(player);
如果您需要对播放器控件和呈现视频的Surface
进行细粒度控制,可以设置播放器的目标SurfaceView
, TextureView
,SurfaceHolder
或 Surface
直接使用SimpleExoPlayer
的 setVideoSurfaceView
,setVideoTextureView
,setVideoSurfaceHolder
和 setVideoSurface
分别的方法。您可以将其PlayerControlView
用作独立组件,或实现自己的播放控件,直接与播放器进行交互。setTextOutput
和setId3Output
可用于回放过程中接收字幕和ID3元数据输出。
在ExoPlayer中,每个媒体都由代表MediaSource
。要播放介质,您必须首先创建一个对应的MediaSource
,然后将此对象传递给ExoPlayer.prepare
。ExoPlayer库为DASH提供 MediaSource
实现(DashMediaSource
),SmoothStreaming(SsMediaSource
),HLS(HlsMediaSource
)和常规媒体文件(ExtractorMediaSource
)的实现。本指南后面将详细介绍这些实现。以下代码显示如何准备一个适合播放MP4文件的播放器的MediaSource。
// 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(context,
Util.getUserAgent(context, "yourApplicationName"), bandwidthMeter);
// This is the MediaSource representing the media to be played.
MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
.createMediaSource(mp4VideoUri);
// Prepare the player with the source.
player.prepare(videoSource);
一旦准备好了播放器,就可以通过调用播放器上的方法来控制播放。例如,setPlayWhenReady
开始和暂停播放,各种seekTo
方法在媒体内寻找,setRepeatMode
控制媒体是否以及如何循环,setShuffleModeEnabled
控制播放列表改组,以及setPlaybackParameters
调整播放速度和音高。
如果玩家被绑定到PlayerView
或者PlayerControlView
用户与这些组件的交互将导致调用播放器上的相应方法。
状态更改和回放错误等事件将报告给已注册的 Player.EventListener
实例。注册监听器以接收此类事件很容易:
// Add a listener to receive events from the player.
player.addListener(eventListener);
如果您只对事件的子集感兴趣,那么扩展 Player.DefaultEventListener
而非Player.EventListener
实现只允许您实现您感兴趣的方法。
使用时SimpleExoPlayer
,可以在播放器上设置其他侦听器。该 addVideoListener
方法允许您接收与视频渲染相关的事件,这些事件可能对调整UI有用(例如,Surface
正在渲染视频的纵横比 )。该addAnalyticsListener
方法允许您接收详细事件,这可能对分析有用。
在不再需要播放器时释放播放器非常重要,这样可以释放有限的资源,例如视频解码器,供其他应用程序使用。这可以通过调用来完成ExoPlayer.release
。
MediaSource
在ExoPlayer中,每个媒体都由代表MediaSource
。ExoPlayer库提供MediaSource
DASH(DashMediaSource
),SmoothStreaming(SsMediaSource
),HLS(HlsMediaSource
)和常规媒体文件(ExtractorMediaSource
)的实现。可以PlayerActivity
在主演示应用程序中找到如何实例化所有四个示例。
除了上面描述的MediaSource的实现方式中,ExoPlayer库还提供ConcatenatingMediaSource
,ClippingMediaSource
,LoopingMediaSource
和MergingMediaSource
。这些MediaSource
实现通过组合实现更复杂的回放功能。一些常见用例如下所述。请注意,尽管在视频播放的上下文中描述了以下示例,但它们同样适用于仅音频播放,并且实际上适用于任何支持的媒体类型的播放。
使用支持播放列表ConcatenatingMediaSource
,可以连续播放多个MediaSource
s。以下示例表示由两个视频组成的播放列表。
MediaSource firstSource =
new ExtractorMediaSource.Factory(...).createMediaSource(firstVideoUri);
MediaSource secondSource =
new ExtractorMediaSource.Factory(...).createMediaSource(secondVideoUri);
// Plays the first video, then the second video.
ConcatenatingMediaSource concatenatedSource =
new ConcatenatingMediaSource(firstSource, secondSource);
连接源之间的转换是无缝的。不要求它们具有相同的格式(例如,将包含480p H264的视频文件与包含720p VP9的视频文件连接起来就可以了)。它们甚至可能是不同类型的(例如,将视频与仅音频流连接起来很好)。允许MediaSource
在连接中多次使用单个s。
可以通过MediaSource
在a中添加,删除和移动s 来动态修改播放列表 ConcatenatingMediaSource
。这可以通过调用相应的ConcatenatingMediaSource
方法在回放之前和回放期间完成。播放器以正确的方式自动处理播放期间的修改。例如,如果MediaSource
移动当前正在播放,则不会中断播放,并且将在完成时播放其新的后继播放。如果当前播放MediaSource
被移除,则播放器将自动移动到播放第一个剩余的后继者,或者如果不存在这样的后继者则转换到结束状态。
ClippingMediaSource
可用于剪辑a,MediaSource
以便只播放部分内容。以下示例将视频播放剪辑为以5秒开始并以10秒结束。
MediaSource videoSource =
new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
// Clip to start at 5 seconds and end at 10 seconds.
ClippingMediaSource clippingSource =
new ClippingMediaSource(
videoSource,
/* startPositionUs= */ 5_000_000,
/* endPositionUs= */ 10_000_000);
要仅剪辑源的开头,endPositionUs
可以设置为 C.
TIME_END_OF_SOURCE
。要仅剪辑到特定的持续时间,有一个构造函数接受一个durationUs
参数。
剪切视频文件的开头时,尽可能将起始位置与关键帧对齐。如果开始位置未与关键帧对齐,则播放器将需要解码并丢弃从先前关键帧到开始位置的数据,然后才能开始播放。这将在播放开始时引入短暂延迟,包括当播放器转换为播放ClippingMediaSource
播放列表的一部分或由于循环播放时。
要无限循环,通常最好使用ExoPlayer.setRepeatMode
而不是LoopingMediaSource
。
视频可以使用a无缝循环固定次数 LoopingMediaSource
。以下示例播放视频两次。
MediaSource source =
new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
// Plays the video twice.
LoopingMediaSource loopingSource = new LoopingMediaSource(source, 2);
给定视频文件和单独的字幕文件,MergingMediaSource
可用于将它们合并为单个源以进行回放。
// Build the video MediaSource.
MediaSource videoSource =
new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
// Build the subtitle MediaSource.
Format subtitleFormat = Format.createTextSampleFormat(
id, // An identifier for the track. May be null.
MimeTypes.APPLICATION_SUBRIP, // The mime type. Must be set correctly.
selectionFlags, // Selection flags for the track.
language); // The subtitle language. May be null.
MediaSource subtitleSource =
new SingleSampleMediaSource.Factory(...)
.createMediaSource(subtitleUri, subtitleFormat, C.TIME_UNSET);
// Plays the video with the sideloaded subtitle.
MergingMediaSource mergedSource =
new MergingMediaSource(videoSource, subtitleSource);
可以进一步将复合材料组合起来MediaSource
用于更多不寻常的用例。给定两个视频A和B,以下示例显示如何LoopingMediaSource
和ConcatenatingMediaSource
可以一起使用来播放序列(A,A,B)。
MediaSource firstSource =
new ExtractorMediaSource.Factory(...).createMediaSource(firstVideoUri);
MediaSource secondSource =
new ExtractorMediaSource.Factory(...).createMediaSource(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);
以下示例是等效的,表明可以有多种方法来实现相同的结果。
MediaSource firstSource =
new ExtractorMediaSource.Builder(firstVideoUri, ...).build();
MediaSource secondSource =
new ExtractorMediaSource.Builder(secondVideoUri, ...).build();
// Plays the first video twice, then the second video.
ConcatenatingMediaSource concatenatedSource =
new ConcatenatingMediaSource(firstSource, firstSource, secondSource);
跟踪选择
曲目选择确定播放器播放哪些可用媒体曲目Renderer
。轨道选择是a的责任 TrackSelector
,必须在ExoPlayer
构建时提供其实例。
DefaultTrackSelector
TrackSelector
适用于大多数用例的灵活性。使用a时DefaultTrackSelector
,可以通过修改它来控制选择的轨道Parameters
。这可以在播放之前或播放期间完成。例如,以下代码告诉选择器将视频轨道选择限制为SD,并选择德语音轨(如果有):
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setMaxVideoSizeSd()
.setPreferredAudioLanguage("deu"));
这是基于约束的轨道选择的示例,其中在不知道实际可用的轨道的情况下指定约束。可以使用指定许多不同类型的约束Parameters
。Parameters
也可用于从可用的轨道中选择特定轨道。有关 更多详细信息DefaultTrackSelector
,请参阅Parameters
和ParametersBuilder
文档。
将消息发送到组件
可以向ExoPlayer组件发送消息。这些可以使用createMessage
然后使用发送来创建PlayerMessage.send
。默认情况下,消息尽快在回放线程上传递,但可以通过设置另一个回调线程(使用 PlayerMessage.setHandler
)或指定传送回放位置(使用PlayerMessage.setPosition
)来自定义。通过ExoPlayer
确保按顺序执行操作以及在播放器上执行任何其他操作来发送消息。
大多数ExoPlayer的开箱即用渲染器都支持允许在播放期间更改其配置的消息。例如,音频渲染器接受消息来设置音量,视频渲染器接受消息来设置曲面。这些消息应在回放线程上传递,以确保线程安全。
定制化
ExoPlayer相对于Android的主要优点之一MediaPlayer
是能够自定义和扩展播放器以更好地适应开发人员的使用案例。ExoPlayer库专门为此而设计,定义了许多接口和抽象基类,使应用程序开发人员可以轻松替换库提供的默认实现。以下是构建自定义组件的一些用例:
Renderer
- 您可能希望实现自定义Renderer
来处理库提供的默认实现不支持的媒体类型。TrackSelector
- 实现自定义TrackSelector
允许应用程序开发人员更改MediaSource
选择由每个可用Renderer
s 消耗的轨道的方式。LoadControl
- 实现自定义LoadControl
允许应用开发者更改播放器的缓冲策略。Extractor
- 如果您需要支持库当前不支持的容器格式,请考虑实现一个自定义Extractor
类,然后可以将该类用于ExtractorMediaSource
播放该类型的媒体。MediaSource
- MediaSource
如果您希望获得以自定义方式提供给渲染器的媒体示例,或者您希望实现自定义MediaSource
合成行为,则实现自定义类可能是合适的。DataSource
- ExoPlayer的上游包已经包含了许多DataSource
针对不同用例的 实现。您可能希望实现自己的DataSource
类以另一种方式加载数据,例如通过自定义协议,使用自定义HTTP堆栈或自定义持久缓存。构建自定义组件时,建议执行以下操作:
Handler
给组件的构造函数。ExoPlayerComponent
在其handleMessage
方法中实现并接收配置更改。您的应用应通过调用ExoPlayer sendMessages
和blockingSendMessages
方法来传递配置更改。
高级主题
ExoPlayer支持Android 4.4(API级别19)的受数字版权管理(DRM)保护的播放。有关更多详细信息,请参阅DRM页面。
注:后续我也会针对此板块专门写文章分析与总结。
有关使用ExoPlayer时电池消耗的信息,请参阅 电池消耗页面。
有关最小化ExoPlayer库大小的建议,请参阅 Shrinking ExoPlayer页面。
附:
ExoPlayer文档官方地址:https://google.github.io/ExoPlayer/
ExoPlayer官方开源项目地址:https://github.com/google/ExoPlayer