系列文章目录
ExoPlayer架构详解与源码分析(1)——前言
ExoPlayer架构详解与源码分析(2)——Player
ExoPlayer架构详解与源码分析(3)——Timeline
ExoPlayer架构详解与源码分析(4)——整体架构
ExoPlayer架构详解与源码分析(5)——MediaSource
ExoPlayer架构详解与源码分析(6)——MediaPeriod
ExoPlayer架构详解与源码分析(7)——SampleQueue
ExoPlayer架构详解与源码分析(8)——Loader
ExoPlayer架构详解与源码分析(9)——TsExtractor
ExoPlayer架构详解与源码分析(10)——H264Reader
ExoPlayer架构详解与源码分析(11)——DataSource
ExoPlayer架构详解与源码分析(12)——Cache
ExoPlayer架构详解与源码分析(13)——TeeDataSource和CacheDataSource
文章目录
前言
如果播放器就是一只火箭,那么火箭发射就必须要有一个基于时序的发射计划,火箭在运行过程中通过获取当前时间点的发射计划就会知道当前的时序状态,以及决定下一步该干什么,如需要在什么时候点火、发动机什么时候停机、各个阶段的姿态调整等等。
所以设计了播放器还是不够的,还需要描述出媒体的时序结构,但是播放器播放的媒体种类有很多,可以是一个播放列表、一个mp4文件、一个网络的url,一段视频的流,反正千奇百怪。如何设计一个数据结构可以灵活的表示出上面各种的媒体在不同时间点的时序结构呢。ExoPlayer给出的答案是 Timeline(时间线)。Timeline贯穿在整个的Exoplayer源码中,后续系列文章提到的Player、MediaSource、LoadControl、TrackSelector等等都会使用到Timeline,所以有必要将Timeline提前了解下。
Timeline
Timeline 是媒体时序结构的灵活表示。因为只是用来获取状态,所以Timeline是一个不可变的对象,所有的属性都是不可变的(final),这样设计也保证了多线程下的数据安全。对于动态媒体(例如直播流),Timeline 表示是当前状态的快照。
Timeline 由 一个或多个Window(窗口) 和 Period(时段) 组成。
-
Window :通常对应一个播放列表的子项。 它可能会跨多个 Period 并且定义了这些 Period 中可播放的区域。Window 还包含一些其他信息,如当前Window 是否可以Seek,可以开始播放的默认位置等。
上图中window1横跨了2个period,Window包含以下属性:- uid Window的唯一标识符。单Window必须使用 Window.SINGLE_WINDOW_UID。
- firstPeriodIndex 记录横跨的第一个Period的索引。
- lastPeriodIndex 记录横跨的最后一个Period的索引。
- durationUs 表示Window的时长。
- defaultPositionUs 默认开始播放位置(图中黑点)相对于Window开始时间的差值。
- positionInFirstPeriodUs Window开始时间相对于第一个Period开始时间的位置的差值,因为Window开始时间肯定大于等于第一个Period的开始时间,这个值一定是大于0的。
- mediaItem 与窗口关联的mediaItem,mediaItem 是在Player setMediaItem时,创建Timeline并设置到其中的Window里。同样Player在获取当前mediaItem时首先获取当前的Timeline,然后在Timeline里获取当前的Window,最后在Window里获取mediaItem。
- manifest Window的清单,播放单个文件的时候为null,当在播放HLS流时,会将HLS流的索引文件相关信息设置进去。
- windowStartTimeMs Window的开始时间。
- isSeekable 是否可以Seek。
- isDynamic 是否是动态的Window,当Timeline 随着时间变化时,Window是否也会随着变化。
- isLive 是否为直播流。
- liveConfiguration 直播流的相关配置。
- elapsedRealtimeEpochOffsetMs 本地时间和服务器时间偏差,用于Timeline.getCurrentUnixTimeMs获取当前实际时间。
- isPlaceholder用于标记当前Window是否为占位的Window,因为当前正式数据还未加载,因此只包含初始值的占位信息,其中mediaItem在播放前已知,PlaceholderTimeline会将此值设置为true。
-
Period:定义了媒体的单个逻辑块,如一个视频文件。它还可以定义插入到视频里的广告组,还记录这些广告是否已经加载和播放。
上图包含了2个Period,指向同一个Window,Period包含以下属性:- uid Period的唯一标识符。
- windowIndex Period 所属Window的索引。
- durationUs 当前Period的时长,Period结束时间相对于Period开始时间的差值,对于直播流(图中period2)就没有值。
- positionInWindowUs 该Period的开始时间相对于其所属Window开始时间的差值。如果该Period的开始时间在Window左侧,则为负值,如上图的period1,对比Window的图可以看到他们都是指示的同一线段只是方向不一致,也就是这个值的绝对值是和Window的positionInFirstPeriodUs是相等的。
- adPlaybackState 插入到Period中的广告相关信息。
Timeline是不可变的,是当前播放的一个静态快照,从这个角度对比火箭发射(播放器播放),发射火箭的时段可能是连续的几天(时段),但是可以发射(可以播放)的窗口期可能就在这一天中的某1个小时,具体在这个小时的哪个时间点发射(播放)就对应Window的defaultPositionUs(小黑点),而这个窗口期可能正好在23:30-1:00,跨越2天(时段)。
下面列举出各种媒体 Timeline 的表示
单文件或者点播流媒体
这类媒体包含一个Period和一个Window。 Window和 Period 一样长,Window的默认播放位置就在Period 起点。这个很好理解,当你播放本地的一个视频文件时,由于是单个文件可以理解为只有一个文件的播放列表,这个文件可以从头播放播放到结束,由于文件只有一个所以 Period 只有一段。像单个视频文件或者点播类的HLS就是用的这种方式抽象的 Timeline,一个文件或者点播流就对应一个 Period。
文件播放列表或者点播流列表
这类媒体包含多个Window和多个Period,每个Period 都有一个自己的Window与之对应,Window默认播放位置就在每个Period的开始,这类媒体可以想象成将上面的单个文件添加到一个播放列表。这类媒体只有在列表里播放到相应的项才能获取到Window和Period。ExoPlayer 针对这种结构,其实是通过将上面的单个Timeline组合起来,抽象出一个新的ListTimeline来实现的,也就是上图相当于3个Timeline。
有限可播的直播流
因为是直播内容是实时产生的,随着时间不断增多,所以Period总时长是未知的。因为是有限的,仍然可播放内容时间只占 Period 的一段,所以Window就定义了这段可播放范围,开始播放播放位置也不一定在Window的开头。此时Window的 isLive=true,当Window改变时isDynamic将被设置为true。这类媒体的默认播放位置一般在Window的边缘,接近于当前时间,如上图的黑点。像直播类的DASH或者HLS都属于这类。举个例子,当你看一个直播时,你可以回看2分钟之前到现在的视频,这个2分钟到现在就是一个Window,随着时间的推移Window也在向右平移,那么这个Window就是动态的,isDynamic=true,而打开这个直播默认的播放位置往往是最接近当前时间的点,同时也在Window的右侧边缘。
无限可播的直播流
和上面有限可播的直播流类似,唯一不同的是Window的起点固定在Period的开头,也就是可以播放之前已播的所有直播内容。
有多个Period的直播流
这类将直播流分成了多个Period,和有限可播的直播流类似,只是Window可能跨一个或多个Period。
先点播后直播流
这类将点播流和直播流结合,当点播流播放结束的时候直播流将在Window靠近当前时间的一侧开始播放。这种可以当作将点播文件和直播文件放到一个播放列表里。
带有插播广告的点播流
这类在单个点播流中插入了广告(上图灰色)。通过查询当前的Period可以获取广告组或者广告的信息。
对于一些动态的媒体,比如说播放一个直播流,随着时间的推移不同时间点的Timeline(播放快照)对应Period的时长或者数量是不断增加的,不同时间点的Timeline对应的Window是不停改变的,其中包括Window的 开始时间、结束时间、时长等等都在不停的变化,而非直播流这些又是相对固定的。
小结下特点
- Timeline 里可能包含多个Period或者Window
- 多个Period都是连续的,而多个Window可能是不连续的
- Window时长小于等于所有Period的时长和
- 一个Window可能跨域多个Peroid
- Window开始时间大于等于第一个Period的开始时间
- Window的默认播放位置不是固定的
- Period可以随着时间没有右边界,但Window是一定有右边界的,也就是有确定的durationUs
- 对于静态媒体。在不同时间获取的Timelin对应的Period和Window是相对固定的。
- 对于动态媒体。在不同时间获取的Timelin对应的Period和Window是相对变化的。
Timeline的实现
说完结构设计,看下代码具体是怎么实现上述设计的,先看下整体架构
ExoPlayer 播放各种媒体时,主要通过这几个实现类来描述Timeline
来看下各自的作用
Timeline
这里没有定义任何属性,主要定义实现了以下几个功能
- 查询Window和Period
//使用指定Window索引的数据填充Window
public final Window getWindow(int windowIndex, Window window)
public abstract int getWindowCount();
//获取下个Window,媒体列表的循环模式最终就是在这里实现,这里其实也是填充Window容器
public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled));
public