ExoPlayer播放器 开发者指南(官方权威指南译文)

 

前言

  因为公司项目原因,目前开始研究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具有许多优势

  • 支持基于HTTP的动态自适应流传输(DASH)和SmoothStreaming,而MediaPlayer却不支持这两种流。另外还支持许多其他格式。
  • 支持高级HLS功能,例如正确处理 #EXT-X-DISCONTINUITY标签。
  • 能够无缝合并,连接和循环媒体。
  • 能够随应用程序更新播放器。由于ExoPlayer是您在应用程序apk中包含的库,因此您可以控制使用的版本,并且可以轻松地将更新版本更新为常规应用程序更新的一部分。
  • 不同的设备特定问题和不同设备和Android版本的行为变化较小。
  • 支持Android 4.4(API级别19)及更高版本的Widevine通用加密。
  • 能够根据您的使用情况自定义和扩展播放器。ExoPlayer专门针对这一点而设计,允许使用自定义实现替换许多组件。
  • 能够使用官方扩展快速集成许多其他库。例如,IMA扩展程序可让您使用互动式媒体广告SDK轻松通过内容获利。

值得注意的是,还有一些缺点

  • 对于某些设备上的仅音频播放,ExoPlayer可能比MediaPlayer消耗更多的电池。有关详细信息,请参阅电池消耗页
  • 因为库是基于Android4.1的MediaCodec 和 Android 4.4的Widevine组件,所以要想完全使用ExoPlayer的API需要API级别至少为19(Android4.4)。当然如果不用高级别的API(比如DRM的加密),那么最低Android 4.1也是能够使用的。

      

       

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包括实现以下步骤:

  1. 将ExoPlayer添加为项目的依赖项。
  2. 创建一个SimpleExoPlayer实例。
  3. 将播放器连接到视图(用于视频输出和用户输入)。
  4. 使用MediaSource准备播放器
  5. 完成后释放播放器。

以下更详细地概述了这些步骤。对于一个完整的例子,是指 PlayerActivity在主演示应用程序。

 

1.添加ExoPlayer作为依赖项

入门的第一步是确保将JCenter和Google存储库包含在build.gradle项目根目录中的文件中。

repositories {
    jcenter()
    google()
}

    接下来在build.gradleapp模块的文件中添加依赖项。以下内容将为完整的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实例。工厂提供了一系列方法,来创建具有不同级别的自定义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}的线程或者是该应用程序的主线程,才能来创建该播放器。

 

3.将播放器附加到视图

     ExoPlayer库提供了一个PlayerView,它封装了一个 PlayerControlView和一个在视频渲染表层之上的Surface。一个 PlayerView 可以包含在应用程序的布局xml中。将播放器绑定到视图非常简单:

// Bind the player to the view.
playerView.setPlayer(player);

     如果您需要对播放器控件和呈现视频的Surface进行细粒度控制,可以设置播放器的目标SurfaceView, TextureViewSurfaceHolder 或  Surface直接使用SimpleExoPlayer的 setVideoSurfaceViewsetVideoTextureViewsetVideoSurfaceHolder和 setVideoSurface分别的方法。您可以将其PlayerControlView用作独立组件,或实现自己的播放控件,直接与播放器进行交互。setTextOutputsetId3Output可用于回放过程中接收字幕和ID3元数据输出。

 

4.准备播放器

    在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);

 

5.控制播放器

    一旦准备好了播放器,就可以通过调用播放器上的方法来控制播放。例如,setPlayWhenReady开始和暂停播放,各种seekTo方法在媒体内寻找,setRepeatMode控制媒体是否以及如何循环,setShuffleModeEnabled控制播放列表改组,以及setPlaybackParameters调整播放速度和音高。

如果玩家被绑定到PlayerView或者PlayerControlView用户与这些组件的交互将导致调用播放器上的相应方法。

6.监听播放器事件

    状态更改和回放错误等事件将报告给已注册的 Player.EventListener实例。注册监听器以接收此类事件很容易:

// Add a listener to receive events from the player.
player.addListener(eventListener);

    如果您只对事件的子集感兴趣,那么扩展 Player.DefaultEventListener而非Player.EventListener 实现只允许您实现您感兴趣的方法。

    使用时SimpleExoPlayer,可以在播放器上设置其他侦听器。该 addVideoListener方法允许您接收与视频渲染相关的事件,这些事件可能对调整UI有用(例如,Surface正在渲染视频的纵横比 )。该addAnalyticsListener方法允许您接收详细事件,这可能对分析有用。

 

7.释放播放器

    在不再需要播放器时释放播放器非常重要,这样可以释放有限的资源,例如视频解码器,供其他应用程序使用。这可以通过调用来完成ExoPlayer.release

   

  

MediaSource

  在ExoPlayer中,每个媒体都由代表MediaSource。ExoPlayer库提供MediaSourceDASH(DashMediaSource),SmoothStreaming(SsMediaSource),HLS(HlsMediaSource)和常规媒体文件(ExtractorMediaSource)的实现。可以PlayerActivity在主演示应用程序中找到如何实例化所有四个示例。

   除了上面描述的MediaSource的实现方式中,ExoPlayer库还提供ConcatenatingMediaSourceClippingMediaSourceLoopingMediaSourceMergingMediaSource。这些MediaSource 实现通过组合实现更复杂的回放功能。一些常见用例如下所述。请注意,尽管在视频播放的上下文中描述了以下示例,但它们同样适用于仅音频播放,并且实际上适用于任何支持的媒体类型的播放。

   

1.播放列表

    使用支持播放列表ConcatenatingMediaSource,可以连续播放多个MediaSources。以下示例表示由两个视频组成的播放列表。

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被移除,则播放器将自动移动到播放第一个剩余的后继者,或者如果不存在这样的后继者则转换到结束状态。

2.剪辑视频

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播放列表的一部分或由于循环播放时。

3.循环播放视频

要无限循环,通常最好使用ExoPlayer.setRepeatMode而不是LoopingMediaSource

视频可以使用a无缝循环固定次数 LoopingMediaSource。以下示例播放视频两次。

MediaSource source =
    new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
// Plays the video twice.
LoopingMediaSource loopingSource = new LoopingMediaSource(source, 2);

4.侧加载字幕文件

    给定视频文件和单独的字幕文件,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);

5.高级组件

    可以进一步将复合材料组合起来MediaSource用于更多不寻常的用例。给定两个视频A和B,以下示例显示如何LoopingMediaSourceConcatenatingMediaSource可以一起使用来播放序列(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 构建时提供其实例。

DefaultTrackSelectorTrackSelector适用于大多数用例的灵活性。使用a时DefaultTrackSelector,可以通过修改它来控制选择的轨道Parameters。这可以在播放之前或播放期间完成。例如,以下代码告诉选择器将视频轨道选择限制为SD,并选择德语音轨(如果有):

trackSelector.setParameters(
    trackSelector
        .buildUponParameters()
        .setMaxVideoSizeSd()
        .setPreferredAudioLanguage("deu"));

    这是基于约束的轨道选择的示例,其中在不知道实际可用的轨道的情况下指定约束。可以使用指定许多不同类型的约束ParametersParameters 也可用于从可用的轨道中选择特定轨道。有关 更多详细信息DefaultTrackSelector,请参阅ParametersParametersBuilder文档。

 

  

  

将消息发送到组件

    可以向ExoPlayer组件发送消息。这些可以使用createMessage然后使用发送来创建PlayerMessage.send。默认情况下,消息尽快在回放线程上传递,但可以通过设置另一个回调线程(使用 PlayerMessage.setHandler)或指定传送回放位置(使用PlayerMessage.setPosition)来自定义。通过ExoPlayer 确保按顺序执行操作以及在播放器上执行任何其他操作来发送消息。

    大多数ExoPlayer的开箱即用渲染器都支持允许在播放期间更改其配置的消息。例如,音频渲染器接受消息来设置音量,视频渲染器接受消息来设置曲面。这些消息应在回放线程上传递,以确保线程安全。

 

  

  

定制化

    ExoPlayer相对于Android的主要优点之一MediaPlayer是能够自定义和扩展播放器以更好地适应开发人员的使用案例。ExoPlayer库专门为此而设计,定义了许多接口和抽象基类,使应用程序开发人员可以轻松替换库提供的默认实现。以下是构建自定义组件的一些用例:

  • Renderer- 您可能希望实现自定义Renderer来处理库提供的默认实现不支持的媒体类型。
  • TrackSelector- 实现自定义TrackSelector允许应用程序开发人员更改MediaSource选择由每个可用Renderers 消耗的轨道的方式。
  • LoadControl- 实现自定义LoadControl允许应用开发者更改播放器的缓冲策略。
  • Extractor- 如果您需要支持库当前不支持的容器格式,请考虑实现一个自定义Extractor类,然后可以将该类用于ExtractorMediaSource播放该类型的媒体。
  • MediaSourceMediaSource如果您希望获得以自定义方式提供给渲染器的媒体示例,或者您希望实现自定义MediaSource合成行为,则实现自定义类可能是合适的。
  • DataSource- ExoPlayer的上游包已经包含了许多DataSource针对不同用例的 实现。您可能希望实现自己的DataSource类以另一种方式加载数据,例如通过自定义协议,使用自定义HTTP堆栈或自定义持久缓存。

构建自定义组件时,建议执行以下操作:

  • 如果自定义组件需要将事件报告回应用程序,我们建议您使用与现有ExoPlayer组件相同的模型执行此操作,其中事件侦听器与a一起传递Handler给组件的构造函数。
  • 我们建议自定义组件使用与现有ExoPlayer组件相同的模型,以允许应用程序在播放期间重新配置,如向组件发送消息中所述。为此,您应该ExoPlayerComponent在其handleMessage方法中实现并接收配置更改。您的应用应通过调用ExoPlayer sendMessagesblockingSendMessages 方法来传递配置更改。

 

   

   

高级主题

1.数字版权管理

    ExoPlayer支持Android 4.4(API级别19)的受数字版权管理(DRM)保护的播放。有关更多详细信息,请参阅DRM页面。

注:后续我也会针对此板块专门写文章分析与总结。

2.电池消耗

    有关使用ExoPlayer时电池消耗的信息,请参阅 电池消耗页面。

3.缩小ExoPlayer库

    有关最小化ExoPlayer库大小的建议,请参阅 Shrinking ExoPlayer页面。

 

附:

    ExoPlayer文档官方地址:https://google.github.io/ExoPlayer/

    ExoPlayer官方开源项目地址:https://github.com/google/ExoPlayer 

你可能感兴趣的:(Android,音视频探索)