第2章 MediaSource 构建实战:ProgressiveMediaSource、DashMediaSource、HlsMediaSource 的选择与配置
在 ExoPlayer 中,MediaSource 是媒体数据流的抽象层,负责从底层数据源读取原始数据,并按播放器要求的格式(单轨道、多轨道、分段索引)组装为可消费的媒体序列。本章深入剖析三种核心实现——ProgressiveMediaSource(渐进式)、DashMediaSource(DASH)和 HlsMediaSource(HLS)——的架构原理、构建流程及选型策略,并通过可运行的代码片段展示如何正确配置它们。
原理说明
MediaSource 位于 ExoPlayer 的 媒体管道上层,负责将原始媒体资源转换为播放器能够理解的 MediaPeriod 序列。其核心职责可拆解为:
- 资源解析:根据输入 URI 或清单文件(MPD/M3U8),解析出轨道元数据(时长、码率、分辨率、编码类型)。
- 分段映射:将连续或分段媒体映射为
ChunkSource可管理的MediaChunk列表。 - 轨道暴露:向播放器暴露
TrackGroup集合,供TrackSelector选择渲染轨道。 - 自适应支持:DASH 和 HLS 内置码率自适应逻辑,
DashMediaSource和HlsMediaSource继承自BaseMediaSource并实现CompositeSequenceableLoader来切换分块。
三种 MediaSource 在底层数据源(DataSource)上完全统一,均通过 DataSource.Factory 接口创建读取器,从而共享缓存、重试、协议选择等能力。
选择策略
| 场景 | 推荐 MediaSource | 典型输入 | 关键配置点 |
|---|---|---|---|
| 单文件本地/网络播放(MP4/WebM) | ProgressiveMediaSource |
Uri.parse("file:///storage/video.mp4") |
需配合 ExtractorsFactory;支持 seek 精确到关键帧 |
| 自适应流媒体(多码率 ABR) | DashMediaSource |
Uri.parse("https://example.com/manifest.mpd") |
需正确传递 DashChunkSource.Factory 和 TrackSelector |
| Apple HLS 流(TS/CMAF 分段) | HlsMediaSource |
Uri.parse("https://example.com/playlist.m3u8") |
需配置 HlsMediaSource.Factory 并设置 HlsChunkSource.Factory |
| 直播低延迟(LL-DASH / LL-HLS) | DashMediaSource / HlsMediaSource |
同上 + 启低延迟模式 | 设置 LiveConfiguration 和 MediaSource.Factory 的 livePlaybackSpeedControl |
构建与配置实战
以下代码片段展示三种 MediaSource 的标准构建方式,所有示例均使用 MediaSource.Factory 子类进行统一配置。
ProgressiveMediaSource 构建
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
ProgressiveMediaSource mediaSource =
new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
.createMediaSource(MediaItem.fromUri(videoUri));
DashMediaSource 构建
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
DashMediaSource.Factory dashFactory = new DashMediaSource.Factory(dataSourceFactory);
DashMediaSource mediaSource =
dashFactory.createMediaSource(MediaItem.fromUri(manifestUri));
HlsMediaSource 构建
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
HlsMediaSource.Factory hlsFactory = new HlsMediaSource.Factory(dataSourceFactory);
HlsMediaSource mediaSource =
hlsFactory.createMediaSource(MediaItem.fromUri(playlistUri));
上述代码中,MediaItem.fromUri() 是推荐的方式,它自动根据 URI 后缀或 MIME 类型推断 MediaSource 类型(若使用 DefaultMediaSourceFactory)。但在自定义边界场景中,显式构造具体的 MediaSource 实现可以更精细地控制参数(例如 DashChunkSource 的加载行为、HLS 的播放列表类型)。
常见错误与调试技巧
错误 #1:ProgressiveMediaSource 传入分段流 URI
当服务器返回一个 MPD 或 M3U8 页面但以 .mp4 后缀伪装时,ProgressiveMediaSource 会尝试用 Extractor 解析清单文本,导致 “Content malformed” 异常。调试时查看 logcat 中的 ExoPlayerImplInternal 堆栈,若出现 UnrecognizedInputFormatException,则核实 URI 指向的真实媒体格式。
调试技巧:启用 ExoPlayer 内部日志
在构建播放器前设置 ExoPlayer.setLogTag(“ExoPlayer”); 并调高日志级别:
adb shell setprop log.tag.ExoPlayer VERBOSE
adb logcat -s ExoPlayer:* ExoPlayerImpl:V
关注 MediaSourcePeriodInfo 和 ChunkSource 的加载日志,可快速定位错误是发生在解析阶段还是下载阶段。
错误 #2:DataSource 未配置 Cookie 或 User-Agent
某些流媒体服务器要求携带特定的 HTTP 头部。若不设置 DefaultHttpDataSource 的 userAgent 或 cookieManager,可能返回 403 或 302 重定向循环。修复方式:
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory()
.setUserAgent("ExoPlayerDemo/1.0")
.setAllowCrossProtocolRedirects(true);
// 对于 DASH/HLS,同样将此 factory 传入对应的 MediaSource.Factory
另注意:DASH 和 HLS 的清单及分段共享同一个 DataSource.Factory 实例。
错误 #3:DASH 多码率流选择失败
DashMediaSource 默认使用 AdaptiveTrackSelection.Factory。若播放器始终选择最低码率,检查 TrackSelector 是否配置了 BandwidthMeter 并启用了自适应:
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
TrackSelector trackSelector = new DefaultTrackSelector(context, new AdaptiveTrackSelection.Factory(bandwidthMeter));
同时确认 MPD 清单中多个 AdaptationSet 的码率存在显著差异,否则自适应算法可能认为质量相近而选择低码率以节省带宽。