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 开始加载的完整流程:

MediaItem 构建与媒体源加载流程 输入 Uri MediaItem.Builder setMimeType() setCustomHeaders() build() → MediaItem player.setMediaItem() DataSource 加载 可选步骤 若不设置 MimeType 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 常见错误与避坑指南

错误现象原因解决方案
播放失败,报 IllegalArgumentExceptionUri 为 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() 实现无缝切换。

4.8 章节小结