4. MediaItem 构建与媒体源加载:Uri、MimeType、MediaItem.fromUri、自定义请求头
4.1 原理概述
ExoPlayer 采用 MediaItem 作为媒体源的统一抽象。一个 MediaItem 对象封装了播放所需的最小信息集:媒体资源的 Uri(资源定位符)、可选的 MimeType(媒体类型提示)、自定义请求头(DRM / 鉴权 / CDN 回源)等。Player 收到 MediaItem 后,内部通过 MediaSource.Factory 解析 Uri 并选择合适的 MediaSource(如 ProgressiveMediaSource、DashMediaSource 等),再绑定 DataSource.Factory 完成实际网络 / 本地文件加载。
正确构造 MediaItem 是播放流程的起点,直接影响媒体源的识别效率、请求头传递以及后续的缓存策略。
4.2 MediaItem 构建流程图
下面用 SVG 呈现从输入 Uri 到 ExoPlayer 开始加载的完整流程:
4.3 核心 API 详解
| API | 作用 | 使用场景 |
|---|---|---|
MediaItem.fromUri(String uri) | 快速创建只含 Uri 的 MediaItem | 简单网络流或本地文件 |
MediaItem.Builder.setUri() | 在 Builder 中设置 Uri | 需要附加额外配置时 |
MediaItem.Builder.setMimeType() | 显式指定媒体类型(如 "video/mp4") | 流式格式不易被自动嗅探时 |
MediaItem.Builder.setCustomHeaders() | 设置自定义 HTTP 头(Map<String, List<String>>) | 鉴权、Referer、User-Agent 等 |
MediaItem.Builder.build() | 构建不可变的 MediaItem 对象 | 所有配置完成后调用 |
4.4 实现步骤与代码示例
4.4.1 使用 MediaItem.fromUri(快速方式)
MediaItem mediaItem = MediaItem.fromUri("https://example.com/media/video.mp4");
player.setMediaItem(mediaItem);
player.prepare();
player.play();
此方式适用于无需额外配置的简单场景。ExoPlayer 会根据 Uri 后缀和文件头自动识别媒体类型。
4.4.2 使用 Builder 精确控制
// 构建自定义请求头
Map> customHeaders = new HashMap<>();
customHeaders.put("Authorization", Collections.singletonList("Bearer xxxxx"));
customHeaders.put("X-Referer", Collections.singletonList("https://myapp.com"));
MediaItem mediaItem = new MediaItem.Builder()
.setUri(Uri.parse("https://example.com/drm/manifest.mpd"))
.setMimeType("application/dash+xml") // 显式指定 DASH 流
.setCustomHeaders(customHeaders)
.build();
player.setMediaItem(mediaItem);
player.prepare();
4.4.3 在自定义 DataSource.Factory 中传递请求头(进阶)
当需要更细粒度控制(如拦截重定向、缓存层)时,可以创建自定义 HttpDataSource 工厂并传递请求头。
DefaultHttpDataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory()
.setAllowCrossProtocolRedirects(true)
.setDefaultRequestProperties(customHeaders); // 全局请求头
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(dataSourceFactory);
ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(mediaSourceFactory)
.build();
MediaItem mediaItem = MediaItem.fromUri("https://example.com/stream");
player.setMediaItem(mediaItem);
player.prepare();
注意:MediaItem.Builder.setCustomHeaders() 的优先级高于 DataSource.Factory 中的默认请求头。
4.5 调试与验证
- 日志过滤:开启 ExoPlayer 的日志(
ExoPlayerLibraryInfo.TAG),观察 MediaItem 的解析日志:adb logcat -s ExoPlayerImpl:V MediaSource:V - 断点调试:在
player.prepare()之前检查 MediaItem 的 uri、mimeType 和 headers 是否被正确设置。 - 网络抓包:使用 Charles / Wireshark 确认 HTTP 请求中携带了自定义请求头。
4.6 常见错误与避坑指南
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
播放失败,报 IllegalArgumentException | Uri 为 null 或格式错误(如缺少 scheme) | 确保 Uri.parse() 传入合法字符串,或使用 Uri.EMPTY 占位后重新设置 |
| 媒体类型识别错误,播放花屏/无声音 | 未设置 MimeType 且自动嗅探失败(如直播流不带扩展名) | 显式调用 setMimeType(),如 "application/x-mpegurl" 等 |
| 自定义请求头未生效 | 将请求头设到了 DataSource.Factory 但又被 MediaItem 覆盖 | 检查优先级:MediaItem 的请求头 > DataSource.Factory 默认头 |
| 带 Cookie 的视频无法播放 | 未传递 Cookie 头或使用 CookieManager | 通过 setCustomHeaders 添加 "Cookie" 键,或使用 DefaultHttpDataSource 集成 CookieManager |
4.7 性能与稳定性优化建议
- 避免重复构建 MediaItem:对于循环播放的同一资源,复用已构建的 MediaItem 对象,减少对象创建开销。
- Uri 标准化:在传入前调用
Uri.normalizeScheme()确保 scheme 统一小写,避免部分系统缓存 miss。 - 请求头体积控制:自定义请求头不宜过大(如过长的 ),可能导致 HTTP 首部过大被服务端拒绝;可考虑使用短 或缩略签名。
- MimeType 的精确提示:对于自适应流(DASH / HLS / SmoothStreaming)务必设置正确的 MimeType,否则 ExoPlayer 会走一次探测流程,增加启动延迟。
最佳实践:在应用启动时预先创建
MediaItem 池(如 LRU 缓存),对于频繁播放的 URL 复用 MediaItem,配合 player.replaceMediaItem() 实现无缝切换。