ExoPlayer 中的 Timeline、Period 和 Window
ExoPlayer 的 Timeline
系统设计用于抽象化媒体内容的组织方式,无论是点播视频、直播流还是多内容播放列表。Timeline
提供了一个统一的方式来描述内容的时间范围、结构和元数据,而 Timeline.Period
和 Timeline.Window
分别是其更细粒度的子结构。
Timeline
Timeline
是 ExoPlayer 的抽象基类,表示媒体内容的整体时间轴结构。它包含一组窗口(Window
)和周期(Period
),每个窗口可能包含一个或多个周期。Timeline
可以是静态的(如点播视频,固定时长)或动态的(如直播流,窗口随时间滑动)。
主要方法:
getWindowCount()
:返回时间轴中的窗口数量。getWindow(int windowIndex, Timeline.Window window)
:获取指定索引的窗口信息,填充到提供的Timeline.Window
对象。getPeriodCount()
:返回时间轴中的周期数量。getPeriod(int periodIndex, Timeline.Period period)
:获取指定索引的周期信息。getLastWindowIndex(boolean shuffleModeEnabled)
:返回最后一个窗口的索引(在直播中通常是当前可用窗口)。getIndexOfPeriod(Object uid)
:根据周期的唯一 ID 查找其索引。
典型场景:
- 点播:一个
Timeline
包含一个窗口(整个视频)和一个或多个周期(章节或广告)。 - 直播:一个
Timeline
包含一个动态窗口(当前可用的直播内容)和多个周期(流的分段)。 - 播放列表:一个
Timeline
包含多个窗口(每个视频)和对应的周期。
代码示例:
Timeline timeline = player.getCurrentTimeline();
if (!timeline.isEmpty()) {Timeline.Window window = new Timeline.Window();timeline.getWindow(timeline.getLastWindowIndex(false), window);Log.d(TAG, "Window duration: " + window.getDurationMs());
}
Timeline.Window
Timeline.Window
表示时间轴上的一个逻辑时间窗口,通常对应一个连续的播放内容段。每个窗口有自己的时间范围(开始时间、持续时间)和元数据(如是否动态、是否可寻址)。
关键字段:
long presentationStartTimeMs
:窗口内容的“呈现”开始时间(以毫秒为单位),通常是内容的逻辑开始时间(例如,直播流的节目开始时间)。如果未知,设为C.TIME_UNSET
。long windowStartTimeMs
:窗口在时间轴上的实际开始时间(以毫秒为单位),通常用于直播流的时间计算。如果未知,设为C.TIME_UNSET
。long durationUs
:窗口的持续时间(以微秒为单位)。如果是直播流,可能是当前窗口的可用时长。boolean isDynamic
:指示窗口是否动态(即内容可能随时间更新,如直播流的滑动窗口)。boolean isSeekable
:指示窗口是否支持寻址(seek),通常点播为true
,直播可能为false
。long defaultPositionUs
:窗口的默认播放起始位置(以微秒为单位),用于初始化播放。
典型场景:
- 直播:窗口表示当前可用的直播内容(滑动窗口),
windowStartTimeMs
和durationUs
定义其时间范围。 - 点播:窗口表示整个视频,
durationUs
是视频总时长。 - 广告:窗口可能表示主内容或广告段。
代码示例:
Timeline.Window window = new Timeline.Window();
timeline.getWindow(timeline.getLastWindowIndex(false), window);
if (window.isDynamic) {Log.d(TAG, "Live window, duration: " + C.usToMs(window.durationUs) + "ms");
}
Timeline.Period
Timeline.Period
表示窗口内的一个逻辑分段,通常对应媒体内容的子部分(如 DASH 的一个 Period 或 HLS 的一个片段组)。周期是窗口的更细粒度划分,可能包含多个媒体片段(chunk)。
关键字段:
Object id
:周期的唯一标识符,用于区分不同的周期。long durationUs
:周期的持续时间(以微秒为单位)。如果未知,设为C.TIME_UNSET
。long positionInWindowUs
:周期相对于窗口开始的偏移量(以微秒为单位)。int windowIndex
:周期所属的窗口索引。boolean isAd
:指示周期是否为广告内容。
典型场景:
- 直播:周期表示 HLS 或 DASH 流中的一个时间段(例如,10 秒的媒体片段)。
- 点播:周期可能表示视频的章节或广告插入点。
- 播放列表:每个窗口的子内容可能由多个周期组成。
代码示例:
Timeline.Period period = new Timeline.Period();
timeline.getPeriod(timeline.getCurrentPeriodIndex(), period);
Log.d(TAG, "Period duration: " + C.usToMs(period.durationUs) + "ms");
Timeline
、Timeline.Window
和 Timeline.Period
的关系可以用以下结构表示:
Timeline
├── Window 0
│ ├── Period 0
│ ├── Period 1
│ └── ...
├── Window 1
│ ├── Period 0
│ └── ...
└── ...
一个 Timeline
包含多个 Window
,每个 Window
包含一个或多个 Period
。Window
定义宏观时间范围(如直播流的当前窗口),Period
定义窗口内的细粒度分段(如流的一个片段)。
Window
的 windowStartTimeMs
和 durationUs
定义全局时间范围。Period
的 positionInWindowUs
表示其在窗口中的相对位置。播放位置(positionUs
)通常相对于某个周期,通过 Period
映射到窗口时间,再通过 Window
映射到全局时间。
在直播场景中,Timeline
是动态的(isDynamic = true
),新窗口或周期会随时间添加,旧周期可能被移除(滑动窗口)。ExoPlayer 通过 Player.EventListener
的 onTimelineChanged
事件通知 Timeline
更新。
工作流程:
ExoPlayer 从 MediaSource
(如 DashMediaSource
或 HlsMediaSource
)加载 Timeline
。Timeline
提供窗口和周期信息,ExoPlayer 使用它们确定播放位置(player.getCurrentPosition()
)和内容边界。直播流的 Timeline
定期更新,反映新的窗口或周期(例如,HLS 播放列表刷新)。播放器通过 Timeline
导航内容(如 seekTo(windowIndex, positionMs)
)。
使用示例:
获取当前播放位置:
Timeline timeline = player.getCurrentTimeline();
Timeline.Window window = new Timeline.Window();
Timeline.Period period = new Timeline.Period();
timeline.getWindow(player.getCurrentWindowIndex(), window);
timeline.getPeriod(player.getCurrentPeriodIndex(), period);
long positionMs = player.getCurrentPosition();
Log.d(TAG, "Window: " + window.windowStartTimeMs + ", Period: " + period.positionInWindowUs + ", Position: " + positionMs);
处理直播滑动窗口:
player.addListener(new Player.Listener() {@Overridepublic void onTimelineChanged(Timeline timeline, int reason) {if (timeline.isEmpty()) return;Timeline.Window window = new Timeline.Window();timeline.getWindow(timeline.getLastWindowIndex(false), window);if (window.isDynamic) {Log.d(TAG, "Live window updated, duration: " + C.usToMs(window.durationUs));}}
});
跳转到直播边缘:
Timeline timeline = player.getCurrentTimeline();
Timeline.Window window = new Timeline.Window();
timeline.getWindow(timeline.getLastWindowIndex(false), window);
player.seekTo(window.windowIndex, window.getDefaultPositionUs());
频繁调用 getWindow
或 getPeriod
可能影响性能,建议缓存 Timeline
数据或在必要时更新。