Google Exoplayer 完成视频、音频播放

exo_open_url.png

最近开发的项目涉及音频、视频播放,搜索了解到 ExoPlayer 2.x可以很好的满足需求,简单翻译了一下帮助文档。
开始的时候看到 ExoPlayer 的 demo 觉得太复杂了,为了图省事使用的他的封装版 ExoMedia ,后来发现这个库目前还不支持字幕,还是得用回 ExoPlayer。翻译文档的过程中,也逐渐明白了 ExoPlayer 2.x 的 使用。


原文链接:Developer guide

在Android设备上播放音频和视频已经很流行了,Android框架提供了用于播放媒体用最少的代码的快速解决方案MediaPlayer。也提供了low-level 的 api 用于构建定制媒体播放器的解决方案,例如MediaCodecAudioTrackMediaDrm
ExoPlayer是建立在 Android low-level api之上的 app级开源播放器。开源项目包含 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 的支持。

优点和缺点

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

  • 支持 Dynamic Adaptive Streaming over HTTP (DASH) 和SmoothStreaming,更多支持请参阅支持的格式)详细信息页面。
  • 支持高级 HLS 功能,如正确处理 #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)和更高。

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 本身不支持的视频编解码器。

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

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'

Creating the playerr

您可以通过 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);

Attaching the player to a view

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

simpleExoPlayerView.setPlayer(player);

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

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 相应的方法被调用。

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。

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处理不是由库提供的默认实现所支持的媒体类型。
  • Extractor - If you need to support a container format not currently supported by the library, consider implementing a custom Extractor class, which can then be used to together with ExtractorMediaSource to play media of that type.
  • MediaSource - Implementing a custom MediaSource class may be appropriate if you wish to obtain media samples to feed to renderers in a custom way, or if you wish to implement custom MediaSource compositing behavior.
  • TrackSelector - mplementing a custom TrackSelector allows an app developer to change the way in which tracks exposed by a MediaSource are selected for consumption by each of the available Renderers.
  • DataSource - ExoPlayer’s upstream package already contains a number of DataSource implementations for different use cases. You may want to implement you own DataSource class to load data in another way, such as over a custom protocol, using a custom HTTP stack, or through a persistent cache.

Customization guidelines

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容