项目记录: Dynamic playlists with ExoPlayer
翻译一下 Dynamic playlists with ExoPlayer 2017.08.25
介绍
现在支持 Exoplayer media playlists
. 使用者可以在播放器运行的过程中,任意添加和移除 playlist item
.
从 Exoplayer 2.8.0 版本,通过更新 ConcatenatingMediaSource
与 dynamic playlist
的功能. 新的媒体资源可以通过以下简单和直接的接口:
-
addMediaSource(mediaSource)
: 添加一个媒体资源到 playlist 的末尾; -
addMediaSource(index,mediaSource)
: 在 index 位置插入一个新的媒体资源; -
addMediaSource(Collection<MediaSource>)
: 添加一整个媒体资源到 playlist 的末尾; -
addMediaSources(index, Collection<MediaSource>)
: 在 index 位置插入一整个媒体资源; -
removeMediaSource(index)
: 溢出 index 位置的 媒体资源; -
moveMediaSource(fromIndex, toIndex)
: 将 fromindex位置的媒体资源移动至 toindex 位置处;可以不需要再创建新的
MediaSource
. 而且可以不打乱播放,移动当前正在播放的item
; -
getMediaSource(index)
: 获得index位置的媒体资源 -
getSize()
: 返回当前 playlist 的长度
以上的方法,可以在播放器播放之前或者播放开始之后调用,而且无论哪个 item
正在播放.这些方法都是 thread-safe
线程安全的;
可以通过预先缓存下一个 playlist
的缓存来实现 gapless playback
无间隔播放.
而且, 在播放到一个新的元素,将会收到 eventListener.onPositionDiscontinuity
的通知.
如果你想要开始使用这个 playlist
的新功能,就去下载 新的 release
版本的ExoPlayer,开始码代码吧.
如果你对实现细节感兴趣,可以继续看下去.
实现细节
为了理解 支持 dynamiclly changing media source
其中涉及到的困难.
先一看 How ExoPlayer works with media sources internally
,Exoplayer和MediaSource在内部是如何一起工作的. 其中,涉及到5个类和5个线程:
APP
: 运行在app thread
. 这个线程调用了 如prepare media source
的命令来调用player
Player
: 创建一个实例,在PlayBack thread
播放线程中.Media Source
: 在app thread
中创建,是接触到真正的媒体资源的. 在loader threads
加载资源,而且与playback thread
的player
通信.timeline
:timelines
是一成不变的? 可以在所有线程访问.media period
: 负责buffering
(缓存数据)和playback of the media data
(播放媒体数据). 在playback thread
中被media source
创建, 但是真正加载数据的呢:是在loader thread
;
在这样的多线程环境下,一个很中药的问题是: 多个线程可能在时间上会出现 不同的 player state
(播放器状态). 因此, 当一个命令在一个线程发布而在另一个线程处理,第二个线程必须必须处在和第一个线程发布命令的同一状态,二者要保持一致这是必须的.
当实现 dynamic playlist change into the process
会带来以下两个:
- 立即反应所有线程的状态变化 和 2) 等待一个异步的过程恰当地结束 是两个竞争的期望. 会造成以下三个难题:
-
Lazily updating the master playlist on the playback thread while having a instantly updated playlist on the app thread
当
app thread
更新了一个playlist
,playback thread
更新playlist
延时了.(懒洋洋Lazily) -
Lazily waiting for new timeline information while keeping the timeline consistent with the playlist.
缓慢地等待新的 timeline信息 当 和 playlist 保持一致
-
Being able to create new periods based on the playlist while waiting for the lazy timeline information
根据 playlist 创建新的
period
当等待 timeline缓慢更新
总的来说,解决方法是非常相似的: 我们使用 mocking instances of the objects we are waiting for
(模拟的等待对象,相等于创建了临时的假的?)
这些 mocking instances
被立即创建,来尽可能地欺骗那些延迟操作. 因此呢. 整个系统的状态会得到迅速地更新,防止进入模糊不清的情况. 一旦真实的结果变得可用,这些 mocking instances
将会停止模拟,而是开始正式地调用真正的实例化对象.
Update the playlist
当 playback thread
线程的master
playlist 作为我们想要实现所有操作的 playlist
. 会有两个缺陷:
- 我们希望能够随时改变播放列表(即在
playback thread
这个线程起之前) - 我们希望可视化这播放列表的信息来立即反应所有我们做的变化.
因此,我们将保持第二个(mocked) playlist
(这个是在 app thread
线程上的),这个将会立即得到更新,而且它可以用来查询 playlist
的信息. 一旦 master playlist
是可用了,所有作应用在 这个 mocked playlist
的操作将会被使用在 playback thread
线程上的 playlist
来进行更新.
keeping the timeline in syn with playlist
无论 playlist 何时变化,我们需要有一个新的 timeline 去反应新的 media structure
. 有些时候,我们需要加载新添加的 media
的开始部分获取信息来更新 timeline. 但是呢,timeline立即反应所有 更新到playlist的所有变化是非常重要的.
- 例如, 如果 app 添加了一个新的 source 在 index为 i 的位置, 立即 seek 到这个i位置,期望的结果是这个新的source将会被播放.如果player没有感知到这些变化的话(由于它还在就的时间信息上),那么它将 seek到 另一个
item
上,而非我们刚刚添加的. 这就是为什么立即更新 timeline是非常关键的.
为了解决这个问题, 当 master playlist变化时,我们立即触发 a timeline update
, 用的是 a new mocked timeline element
. 这个 mocked timeline element
是未知的时长, 它被标志位 dynamic
去告诉播放器,它应该期待发生在它属性上的变化. 尽管这个信息不是非常有帮助,但是,至少它保证了 timeline
和 playlist 保持一致. 当 playlist element
准备好了, 这个 mocked
的 timeline element 将会返回真实的媒体属性.
Creating periods while waiting for timeline updates
第三个问题是由于第二个问题的解决方案造成的.
想想一下我们立即想要播放这个新插入的 playlist item. 播放器会叫 media source创建一个新的 media period. 但是呢,meidia source 无法完成需求,仍然在等待一个真正的 media information
相同地,我们借用了一个 mocked media period
去为 mocked timeline
创建 一个 media period
. 由于没有真实的 media, 这个 media period 将会用于保持在 缓冲模式,告诉播放器媒体还没有做不好播放.一旦呢,timeline获得更新真的media information ,这个 mocked media period
将创建真的media period, 推进调用那包装的实例.
总结:
- 这个博客描述了 新的
ConcatenatingMediaSource
特性来添加和移除 播放器上的元素.