Android视频播放器(四):使⽤ExoPlayer播放视频
⼀、简介
ExoPlayer是⼀个Android应⽤层的媒体播放器,它提供了⼀套可替换Android MediaPlayer的API,可以播放本地或者是线上的⾳视频资源。ExoPlayer⽀持⼀些Android MediaPlayer不⽀持的特性,⽐如适配DASH和SmoothStreaming的播放。和MediaPlayer不同的是,ExoPlayer 很容易⾃定义和扩展,并且它可以通过应⽤商店的应⽤程序更新来直接更新。
现在在Android设备上播放视频和⾳乐的应⽤是⼀个很热门的应⽤,Android框架提供的MediaPlayer可以使⽤很少的代码量快速的实现播放⾳视频的功能,⽽且它也提供了底层的API⽐如MediaCodec、AudioTrack和MediaDrm,它们同样可以创建⾃定义媒体播放器,⽽ExoPlayer 是建⽴在底层⾳视频API之上的开源的应⽤级媒体播放器。
优点
对于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消耗更多的电量。更多细节请参考⽂章:
⼆、ExoPlayer 使⽤
1.把ExoPlayer作为⼀个依赖添加到你的项⽬
添加仓库第⼀步就是确保你在⼯程根⽬录的adle⽂件⾥添加了Google和JCenter仓库:
repositories {
google()
jcenter()
}
添加ExoPlayer模块在你的app module ⾥⾯的adle⽂件夹⾥添加⼀个ExoPlayer依赖。
下⾯是ExoPlayer的全量包的依赖⽅式:
implementation 'player:exoplayer:2.X.X'
上⾯的2.x.x是选择的版本。
你也可以只依赖你想要的模块,来代替全量包。⽐如当你的app想要播放DASH格式的内容的时候,可以只依赖Core,DASH和UI模块的库。
implementation 'player:exoplayer-core:2.X.X'
implementation 'player:exoplayer-dash:2.X.X'
implementation 'player: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还有很多可以提供额外功能的依赖于第三⽅库的扩展模块,可以参考来了解更多信息
2. 打开对 java 8 的⽀持
如果还没有设置⽀持java8,那么你需要在所有依赖ExoPlayer的adle⽂件⾥打开对java8的⽀持,通过在Android域中添加以下代码即可:
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
}
记住如果你想在你的代码⾥⽤java8的特性,你需要添加下⾯额外的设置:
android最新版// For Java compilers:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
}
// For Kotlin compilers:
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
3. 创建⼀个播放器
你可以使⽤ExoPlayerFactory创建⼀个ExoPlayer对象。为了不同的需求,这个⼯⼚类提供了⼀系列⽅法来创建ExoPlayer实例,但是在⼤多数情况下,使⽤wSimpleInstance⽅法就可以了。这些⽅法会返回SimpleExoPlayer类型的对象,它继承⾃ExoPlayer,并且添加了⼀些额外的⾼级的播放器功能。下⾯的代码展⽰了怎么创建⼀个SimpleExoPlayer对象的:
SimpleExoPlayer player = wSimpleInstance(context);
应⽤⾥⾯的某个线程⼀定可以访问ExoPlayer对象,在⼤多数情况下它⼀般是应⽤的主线程,并且只有在应⽤的主线程⾥才能使⽤ExoPlayer 的UI组件和IMA扩展。
能够访问ExoPlayer对象的线程可以通过创建播放器实例的时候传⼊⼀个Looper被明确的指定,如果没有指定Looper,那么创建player的线程的Looper会被使⽤,或者这个线程也没有Looper,那么应⽤的主线程的Looper会被使⽤。在所有的情况下,能够访问播放器的线程的Looer能够通过ApplicationLooper获取到。
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,
//这是⼀个代表将要被播放的媒体的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. 释放播放器
当不在需要播放的时候释放掉播放器是⾮常重要的,以便释放掉有限的资源⽐如视频解码器供其它应⽤使⽤。释放掉播放器可以通过调⽤lease实现。
9. MediaSource(播放资源)
在ExoPlayer⾥每⼀种媒体资源都是被MediaSource来代表的。ExoPlayer库提供了多种MediaSource的实现类,⽐如代表DASH资源的DashMediaSource,代表SmoothStreaming资源的SsMediaSource,代表HLS资源的HlsMediaSource和代表⼀般的多媒体⽂件的ExtractorMediaSource。你可以参考的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 = ateTextSampleFormat(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, first
Source, secondSource);
15. 轨道选择
轨道选择决定了哪⼀个可⽤的媒体轨道可以被播放器的渲染器播放。轨道选择由TrackSelector负责,⽆论什么时候创建⼀个ExoPlayer实例,都要给它提供⼀个TrackSelector对象。
DefaultTrackSelector trackSelector = new DefaultTrackSelector();
SimpleExoPlayer player = wSimpleInstance(context, trackSelector);
DefaultTrackSelector是⼀个灵活的TrackSelector,适合更多使⽤场景。当使⽤⼀个DefaultTrackSelector的时候,通过修改它的参数可以控制哪⼀个tracks被它选择,这种选择可以在播放前完成。例如下⾯的代码告诉选择器将视频轨道限制为SD,并且如果⾳频轨道只有⼀个就选择⼀个德语的⾳频轨道。
trackSelector.setParameters(trackSelector.buildUponParameters().setMaxVideoSizeSd().setPreferredAudioLanguage("deu"));
这是⼀个基于约束的轨道选择的例⼦,在这个例⼦中,在不知道实际可⽤轨道的情况下指定约束。可以使⽤参数指定许多不同类型的约束。参数还可以⽤来从可⽤的轨道中选择特定的轨道。有关详细信息,请参阅、和ParametersBuilder⽂档。
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组件相同的模型,以允许应⽤程序在播放期间进⾏重新配置,如所说的。为此,您应该实现⼀个ExoPlayerComponent并在其handleMessage⽅法中接收配置更改。您的应⽤程序应该通过调⽤外部播放器的sendMessages和blockingSendMessages⽅法来传递配置更改。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论