一、简介
在安卓设备上播放视频和音乐是很受欢迎的活动。Android框架提供了MediaPlayer作为一个快速的解决方案,可以用最少的代码来播放媒体。Android还提供低级别的媒体api框架,如MediaCodec、AudioTrack和MediaDrm,可用于构建自定义媒体播放器解决方案。
ExoPlayer是一款开源的应用级媒体播放器,基于Android的低级媒体API构建。本指南描述了ExoPlayer库及其使用。它是指ExoPlayer的主要演示应用程序中的代码,以提供具体的示例。该指南介绍了使用ExoPlayer的优缺点。它展示了如何使用ExoPlayer播放DASH,SmoothStreaming和HLS自适应流,以及MP4、M4A、FMP4、WebM、MKV、MP3、Ogg、WAV、MPEG-TS、MPEG-PS、FLV和ADTS (AAC)的格式。它还讨论了ExoPlayer事件、消息、自定义和DRM支持。
ExoPlayer是Android的应用程序级媒体播放器。 它提供了Android的MediaPlayer API的替代品,用于在本地和互联网上播放音频和视频。 ExoPlayer支持Android MediaPlayer API目前不支持的功能,包括DASH和SmoothStreaming自适应回放。 与MediaPlayer API不同,ExoPlayer易于定制和扩展,并可通过Play Store应用程序更新进行更新。
二、优点和缺点
ExoPlayer与MediaPlayer内置的Android相比具有许多优势:
- 支持HTTP动态自适应流媒体(DASH)和SmoothStreaming(这两者在MediaPlayer上都不支持)。ExoPlayer还支持许多其他格式,详细信息请参阅 ExoPlayer支持的格式页面。
- 支持高级HLS功能,如正确处理
#EXT-X-DISCONTINUITY
标记。 - 无缝合并,连接和循环媒体的能力。
- 随着您的应用程序一起更新播放器的能力。因为ExoPlayer是一个包含在应用程序apk中的库,所以您可以控制使用哪个版本,并且可以轻松地将其更新为新版本,作为常规应用程序更新的一部分。
- 更少的设备特定问题以及不同设备和Android版本的行为差异更小。
- 在Android 4.4(API级别19)及更高版本上支持Widevine通用加密。
- 您可以根据使用情况来自定义和扩展播放器。ExoPlayer是专门为此设计的,并允许将许多组件替换为自定义实现。
- 能够使用官方扩展快速集成多个附加库。例如,IMA扩展 可以很容易地使用 交互式媒体广告(SDKInteractive Media Ads SDK) 将内容货币化,为你带来收益。
缺点:
- ExoPlayer的标准音频和视频组件依赖Android的MediaCodec API,该API在Android 4.1 (API级别16)中发布。因此,他们不支持早期版本的Android。Widevine通用加密可以在Android 4.4 (API级别19)和更高版本上使用。
三、该库的功能模块概述
ExoPlayer库的核心是ExoPlayer
接口。ExoPlayer
暴露了普遍使用的高级媒体播放器api功能,比如缓冲媒体、播放、暂停和拖动条的功能。实现的目的是关于对(并因此加以很少的限制)所播放的媒体类型、存储方式和存储方式、以及如何呈现的方式进行很少的假设。ExoPlayer
实现不是直接实现媒体的加载和渲染,而是将这项工作委托给创建播放器或准备播放时注入的组件。
所有
ExoPlayer
实现的常见组件是:
- 用于定义要播放的媒体的
MediaSource
,加载media,并从中读取加载的media 。MediaSource
在播放开始时通过ExoPlayer.prepare
注入。 - 渲染媒体个别组件的
Renderers(渲染器)
。当播放器创建时,Renderers
被注入。 TrackSelector
用于选择由MediaSource
提供的轨道以供每个可用的Renderers
使用。在创建播放器时注入TrackSelector
。LoadControl
用于控制MediaSource
何时缓冲更多media以及缓冲多少media。LoadControl
是在播放器创建时注入的。
该库为常见用例提供了这些组件的默认实现,更详细的描述请看以下介绍。
ExoPlayer
可以使用这些组件,但是如果您不想用ExoPlayer
默认的实现方式,也可以使用自定义实现来构建。例如,可以注入自定义LoadControl
来更改播放器的缓冲策略,可以在Android设备上将自定义Renderer
注入到Android不支持的视频编解码器。
注入组件以实现播放器功能部件的概念存在于整个库中。上面列出的组件的默认实现可以进一步注入组件,所以许多子组件可以被自定义实现单独替换。例如,默认的MediaSource
实现需要通过构造函数注入一个或多个DataSource
工厂。通过提供自定义工厂,可以从非标准的源或通过不同的网络栈加载数据。
四、开始入门
使用ExoPlayer
写一个简单的用例,主要包括以下步骤:
- 将
ExoPlayer
作为依赖添加到您的项目中。 - 创建一个
SimpleExoPlayer
实例。 - 将播放器添加到view (用于视频输出和用户输入)。
- 准备播放器与
MediaSource
播放。 - 完成后释放播放器。
这些步骤在下面更详细地概述。 有关完整示例,请参阅 主应用程序demo中的PlayerActivity
。
(1)将ExoPlayer添加为依赖项
入门的第一步是确保您的项目根目录中的build.gradle文件中包含JCenter和Google存储库。
repositories {
jcenter()
google()
}
接下来,在应用程序moule的build.gradle文件中添加一个依赖项。以下内容将为完整的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
文件。
(2)创建播放器
您可以使用ExoPlayerFactory
创建一个ExoPlayer
实例。 ExoPlayerFactory
提供了一系列用于创建具有不同级别定制的ExoPlayer
实例的方法。 对绝大多数用例而言,应该使用ExoPlayerFactory.newSimpleInstance
方法之一。 这些方法返回SimpleExoPlayer
,它扩展了ExoPlayer
,添加了额外的高级播放器功能。 下面的代码是创建SimpleExoPlayer
的示例。
//1.创建默认的 TrackSelector
Handler mainHandler = new Handler();
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector =
new DefaultTrackSelector(videoTrackSelectionFactory);
//2.创建播放器
SimpleExoPlayer player =
ExoPlayerFactory.newSimpleInstance(context, trackSelector);
(3)将播放器附加到view
ExoPlayer
库提供了一个PlayerView
,它封装了一个PlayerControlView
和一个显示视频的Surface
。可以将PlayerView
包含在应用程序的布局xml中。将播放器绑定到view很简单,代码如下:
// 将播放器附加到view
playerView.setPlayer(player);
如果您需要对播放器控件和渲染视频的Surface进行更详细的控制,则可以分别使用SimpleExoPlayer
的setVideoSurfaceView
,setVideoTextureView
,setVideoSurfaceHolder
和setVideoSurface
方法直接设置播放器的目标SurfaceView
,TextureView
,SurfaceHolder
或Surface
。 您可以将PlayerControlView
作为独立组件使用,或者实现您自己的播放控件,直接与播放器交互。 可以使用setTextOutput
和setId3Output
在播放过程中接收字幕和ID3元数据输出。
(4)准备播放器
在ExoPlayer
中,每个media都由MediaSource
表示。要播放一段media,您必须先创建一个相应的MediaSource
,然后将该对象传递给ExoPlayer.prepare
。 ExoPlayer
库为DASH(DashMediaSource
),SmoothStreaming(SsMediaSource
),HLS(HlsMediaSource
)和常规媒体文件(ExtractorMediaSource
)提供MediaSource
实现。 这些实现在本指南后面会有更详细的介绍。
以下代码显示了如何使用适用于播放MP4文件的MediaSource
准备播放器。
// 在播放期间测量带宽。 如果不需要,可以为null
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// 生成用于加载媒体数据的 DataSource 实例
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context,
Util.getUserAgent(context, "yourApplicationName"), bandwidthMeter);
// 这是要播放媒体的MediaSource
MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
.createMediaSource(mp4VideoUri);
// 准备播放器的资源
player.prepare(videoSource);
(5)控制播放器
播放器准备就绪后,可以通过播放器上的调用方法来控制播放。 例如:
setPlayWhenReady
可用于开始和暂停播放
各种seekTo
方法可用于在媒体内搜索
setRepeatMode
可用于控制媒体是否以及如何循环播放
并且setPlaybackParameters
可用于调整播放速度和音调。
如果玩家绑定到PlayerView或PlayerControlView,则用户与这些组件的交互将导致玩家调用相应的方法。
(6)释放播放器
当播放器不再需要时释放播放器非常重要,以释放视频解码器等有限资源以供其他应用程序使用。 这可以通过调用ExoPlayer.release
完成。
五、媒体资源(MediaSource)的使用
在ExoPlayer中,每个media都由 MediaSource
表示。 ExoPlayer
库为DASH(DashMediaSource
),SmoothStreaming(SsMediaSource
),HLS(HlsMediaSource
)和常规媒体文件(ExtractorMediaSource
)提供了MediaSource
实现。 在 main demo app 的PlayerActivity
中可以找到如何实例化所有四个示例。
MediaSource实例不适用于重新使用的情况。 如果您想用相同的media多次准备播放器,请每次使用新的实例。
除了上述的MediaSource实现外,ExoPlayer库还提供了MergingMediaSource,LoopingMediaSource,ConcatenatingMediaSource和DynamicConcatenatingMediaSource。这些MediaSource
实现可以通过组合来实现更复杂的播放功能。下面描述了一些常见的用例。请注意,尽管在视频播放的上下文中描述了以下示例,但它们同样适用于仅播放音频,以及任何支持的媒体类型的播放的情况。
(1)从侧面加载字幕文件
给定一个视频文件和一个单独的字幕文件,可以使用 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);
(2)循环播放视频
要无限循环,通常最好使用 ExoPlayer.setRepeatMode 而不是 LoopingMediaSource。
使用 LoopingMediaSource 可以将视频无缝地循环固定次数。 以下是播放视频两次的示例。
MediaSource source =
new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
// Plays the video twice.
LoopingMediaSource loopingSource = new LoopingMediaSource(source, 2);
(3)播放一系列视频
ConcatenatingMediaSource 可以连续播放两个或多个单独的MediaSource
。 下面是按顺序播放了两个视频的例子。 数据源之间的转换是无缝的。对连接的源具有相同的格式这一点不做强制要求,您可以把两个不同格式的数据源连接起来(例如,将包含480p H264的视频文件与包含720p VP9的视频文件连接起来就可以)。 同时这些源甚至可以是不同类型的(例如,将视频与仅音频流串接也是很友好的)。
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);
DynamicConcatenatingMediaSource 类似于 ConcatenatingMediaSource,不同之处在于它允许在播放前和播放期间动态添加,删除和移动MediaSource
。 DynamicConcatenatingMediaSource非常适合于播放列表的使用场景,即用户可以在播放期间修改播放列表。
MediaSource
实例不应该多次添加到 DynamicConcatenatingMediaSource
中,或者在之前被删除的情况下重新添加。 推荐创建新的实例去操作。
(4)高级组合
有可能进一步将复合MediaSources组合起来,用于更多不常见的用法。 给定两个视频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);
除非根据文档明确允许,否则避免在组合中多次使用相同的MediaSource
实例很重要。 上面的示例中使用firstSource两次就是这种情况,因为用于ConcatenatingMediaSource
的Javadoc明确指出允许重复条目。 然而,一般来说,由构造组成的对象的图形应该是树形结构(这个地方不好翻译,英文不好见谅)。 在组合中使用多个等效的MediaSource
实例是允许的。
六、播放器事件
在播放过程中,您的应用程序可以侦听由ExoPlayer生成的 显示播放器整体状态 的事件。 这些事件对于更新用户界面组件(如播放控件)非常有用。 许多ExoPlayer组件还会报告它们自己组件特定的低级别事件,这对性能监视非常有用。
(1)高等级事件
ExoPlayer允许通过调用 addListener
和 removeListener
方法来添加和删除 EventListener
。 已注册的监听器会收到播放状态更改以及何时发生导致播放失败的错误的通知。
实现自定义播放控制的开发人员应该注册一个监听器,并在播放器的状态发生变化时使用它来更新控件。 如果播放失败,应用程序还应该向用户显示适当的错误信息。
使用SimpleExoPlayer
时,可以在播放器上设置其他监听器。 特别要说明的是,addVideoListener
允许应用程序接收可能对调整UI有用的视频呈现相关的事件(例如,正在呈现视频的 Surface
的高宽比)。 监听器也可以设置为接收调试信息,例如调用 setVideoDebugListener
和 setAudioDebugListener
。
(2)低等级事件
除了高级监听器之外,ExoPlayer库提供的许多单独组件允许自己的事件监听器。 通常需要将 Handler
对象传递给这些组件,这决定了调用监听器方法的线程。 在大多数情况下,您应该使用 Handler
与app的主线程关联。
七、将消息发送到组件
可以将消息发送到ExoPlayer组件,可以使用createMessage
创建,然后使用PlayerMessage.send
发送。 默认情况下,消息尽快在回放线程上传递,但可以通过设置另一个回调线程(使用PlayerMessage.setHandler
)或通过指定传递播放位置(使用PlayerMessage.setPosition
)来定制消息。 通过ExoPlayer发送消息可确保操作按照播放器上正在执行的任何其他操作的顺序执行。
大多数ExoPlayer的开箱即用的渲染器支持在回放期间允许对其配置进行更改的消息。 例如,音频渲染器接受消息来设置音量,视频渲染器接受消息来设置Surface。 这些消息应该在回放线程上传递以确保线程安全
八、定制
ExoPlayer相比Android的MediaPlayer的主要优点之一是可以自定义和扩展播放器,以更好地适应开发人员的使用情况。 ExoPlayer库专为此设计的,定义了许多接口和抽象基类,使应用程序开发人员可以轻松地替换库提供的默认实现。
(1)构建自定义组件的一些示范:
- Renderer - 您可能想要实现自定义渲染器来处理媒体类型,该类型不受库提供的默认实现支持。
- TrackSelector - 实现自定义TrackSelector,允许应用程序开发人员更改由每个可用Renderer选择供MediaSource公开的轨道的方式。
- LoadControl - 实现自定义的LoadControl允许应用程序开发人员更改播放器的缓冲策略。
- Extractor - 如果您需要支持当前不支持的容器格式,请考虑实现一个自定义Extractor类,然后可以将它与ExtractorMediaSource一起用于播放该类型的媒体。
- MediaSource - 如果您希望以自定义的方式获取媒体示例以供给呈现器,或者如果您希望实现自定义MediaSource合成行为,则实现自定义MediaSource类可能是合适的。
- DataSource - ExoPlayer的上游软件包已经包含了许多用于不同用例的DataSource实现。您可能希望实现您自己的DataSource类以另一种方式加载数据,例如通过自定义协议,使用自定义HTTP堆栈或从自定义持久性高速缓存中加载数据。
(2)定制指南
- 如果自定义组件需要将事件报告回应用程序,我们建议您使用与现有ExoPlayer组件相同的模型,将事件监听器与Handler一起传递给组件的构造函数。
- 我们建议自定义组件使用与现有ExoPlayer组件相同的模型,以便在回放时允许应用程序重新配置,如 第七条中的 将消息发送到组件 所描述的那样。 为此,您应该实现一个ExoPlayerComponent并在其handleMessage方法中接收配置更改。 您的应用程序应通过调用ExoPlayer的sendMessages和blockingSendMessages方法来传递配置更改。
九、数字版权管理
在Android 4.4 (API级别19)和更高版本中,ExoPlayer支持数字版权管理(DRM)保护回放。
为了使用ExoPlayer播放DRM保护的内容,您的应用程序必须在实例化播放器时注入DrmSessionManager。ExoPlayerFactory提供了允许这种情况的工厂方法。DrmSessionManager对象负责提供DrmSession实例,该实例为解密提供了MediaCrypto对象,并确保所需的解密密钥可用于正在使用的底层DRM模块。
ExoPlayer库提供了一个DrmSessionManager的默认实现,名为DefaultDrmSessionManager,它使用MediaDrm。会话管理器支持在设备上存在模块DRM组件的任何DRM方案。所有的Android设备都需要支持Widevine模块DRM(使用L3安全性,尽管许多设备也支持L1)。某些设备可能支持其他方案,例如PlayReady。所有的Android TV 设备都支持PlayReady。
主演示应用程序 中的PlayerActivity演示了如何在实例化播放器时创建和注入DefaultDrmSessionManager。
本文翻译自:https://google.github.io/ExoPlayer/guide.html#mediasource
ExoPlayer库地址: https://github.com/google/ExoPlayer