1. ExoPlayer 核心架构与播放组件
ExoPlayer 是 Google 官方推出的开源 Android 媒体播放器库,其架构采用高度模块化、组件化的设计理念。理解核心组件之间的关系是集成播放器的基础。本章从 Player 接口、ExoPlayer.Builder、MediaSource、Renderers 和 TrackSelector 五个关键组件入手,梳理其职责、协作流程与常见配置。
要点速览:
• Player 接口定义播放控制与状态回调的统一契约。
• ExoPlayer.Builder 是构造 ExoPlayer 实例的工厂,支持链式配置。
• MediaSource 封装媒体数据来源(本地文件、网络流、加密流)。
• Renderers 负责解码与渲染音视频帧。
• TrackSelector 决定选择哪些轨道(音轨、字幕、视频分辨率)。
1.1 Player 接口
Player 接口是 ExoPlayer 对外暴露的统一控制与监听入口,定义播放器应有的核心行为。所有 ExoPlayer 实例均实现此接口。
- 播放控制:
play()、pause()、seekTo()、stop()、setPlaybackParameters()等。 - 状态查询:
getPlaybackState()、getCurrentPosition()、getDuration()、isPlaying()。 - 监听注册:
addListener(Player.Listener)用于监听播放状态、错误、轨道变化等。 - 媒体切换:
setMediaSource()/setMediaItems()设置播放列表。
| 方法类别 | 关键 API 示例 | 说明 |
|---|---|---|
| 控制 | player.play() | 开始或恢复播放 |
| 控制 | player.pause() | 暂停播放 |
| 查询 | player.getPlaybackState() | 返回 STATE_IDLE / STATE_BUFFERING / STATE_READY / STATE_ENDED |
| 监听 | player.addListener(myListener) | 回调包括 onPlaybackStateChanged, onPlayerError 等 |
实践建议:尽量不直接强转 ExoPlayer 实现类,而是面向 Player 接口编程,便于未来切换不同实现或进行代理封装。
1.2 ExoPlayer.Builder
ExoPlayer.Builder 是构建 ExoPlayer 实例的推荐方式,支持链式调用,内部封装了默认组件(如渲染器、轨道选择器、加载控制等)的装配逻辑。
典型构建流程
ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(new DefaultMediaSourceFactory(context))
.setTrackSelector(new DefaultTrackSelector(context))
.setRenderersFactory(new DefaultRenderersFactory(context))
.setLoadControl(new DefaultLoadControl())
.build();
| Builder 方法 | 默认值 | 常用自定义场景 |
|---|---|---|
setMediaSourceFactory() | DefaultMediaSourceFactory | 支持 DASH / HLS / SmoothStreaming 时无需额外配置 |
setTrackSelector() | DefaultTrackSelector | 限制视频分辨率、默认字幕语言等 |
setRenderersFactory() | DefaultRenderersFactory | 扩展自定义编解码、添加副渲染器(如录制) |
setLoadControl() | DefaultLoadControl | 调整缓冲策略(如最小/最大缓冲毫秒) |
常见错误:未调用 build() 前调用实例方法会导致 NullPointerException。Builder 内配置的组件在 build() 时一次性注入,之后不可修改。
1.3 MediaSource
MediaSource 抽象媒体数据源,支持单文件、自适应流、带广告的播放列表等。常见实现类:
ProgressiveMediaSource:用于本地或渐进下载的 MP4 / WebM 等文件。DashMediaSource:MPEG-DASH 自适应流。HlsMediaSource:HLS 流。SsMediaSource:SmoothStreaming 流。MergingMediaSource/ClippingMediaSource:用于拼接、裁剪、音视频分离等复合场景。
创建示例(播放本地 MP4)
MediaItem mediaItem = MediaItem.fromUri(Uri.fromFile(new File("/sdcard/video.mp4")));
MediaSource mediaSource = new ProgressiveMediaSource.Factory(DefaultExtractorsFactory.newInstance())
.createMediaSource(mediaItem);
player.setMediaSource(mediaSource);
player.prepare();
性能优化:对于网络流,建议使用 DefaultMediaSourceFactory 配合 CacheDataSource 实现边播边缓存,避免重复下载。示例如下:
Cache cache = new SimpleCache(cacheDir, new LeastRecentlyUsedCacheEvictor(50 * 1024 * 1024));
CacheDataSource.Factory cacheDataSourceFactory = new CacheDataSource.Factory()
.setCache(cache)
.setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory());
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(context)
.setDataSourceFactory(cacheDataSourceFactory);
1.4 Renderers
渲染器负责解码和输出音视频/字幕/元数据。ExoPlayer 内部维护一个 Renderer[] 数组,每个渲染器处理一种轨道类型。
| 渲染器类型 | 负责轨道 | 典型实现 |
|---|---|---|
| MediaCodecAudioRenderer | 音频 | 基于 MediaCodec 解码 + AudioTrack 输出 |
| MediaCodecVideoRenderer | 视频 | 基于 MediaCodec 解码 + Surface 输出 |
| TextRenderer | 字幕 | SubtitleDecoder 输出到 TextView 或 Overlay |
| MetadataRenderer | 元数据 | 如 ID3 标签、章节信息回调 |
当使用 DefaultRenderersFactory 时,ExoPlayer 会根据设备支持的编解码器自动选择渲染器顺序。若需要强制使用软件解码或扩展渲染器,可自定义 RenderersFactory。
1.5 TrackSelector
TrackSelector 根据参数(分辨率、语言、编解码格式)为每个渲染器选取最合适的轨道。默认实现 DefaultTrackSelector 支持按参数限制。
常用配置示例(限制视频分辨率 & 选择中文字幕)
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
trackSelector.setParameters(
trackSelector.buildUponParameters()
.setMaxVideoSize(1280, 720) // 最大分辨率
.setPreferredAudioLanguage("zh") // 首选音频语言
.setPreferredTextLanguage("zh") // 首选字幕语言
.setForceLowestSupportedBitrate(true) // 低带宽时降码率
);
调试技巧:使用 TrackSelectionUtil 输出当前选择的轨道信息,或监听 Player.Listener.onTracksChanged() 实时打印轨道切换日志。
1.6 组件联动流程
- 构造:ExoPlayer.Builder 装配 Renderers、TrackSelector、LoadControl 等。
- 设源:调用
player.setMediaSource(),MediaSource 解析媒体并产生 Timelines。 - 准备:
player.prepare()触发 MediaSource 准备数据,TrackSelector 分析可用轨道。 - 渲染:根据 TrackSelector 输出的 TrackSelection,将相应数据送入对应 Renderer 解码渲染。
- 控制与监听:通过 Player 接口控制播放,Listener 回调状态变更。
1.7 常见错误与排查
| 错误现象 | 可能原因 | 解决思路 |
|---|---|---|
| prepare() 后播放无画面 | Surface 未设置或已释放 | 确保在 player.setVideoSurface() 或使用 PlayerView 时 Surface 有效 |
| ExoPlayer 频繁缓冲 | 缓冲参数过小或网络不稳定 | 调整 DefaultLoadControl 的 minBufferMs / maxBufferMs |
| TrackSelector 始终选最低码率 | 设备被判定为低端或带宽估算偏低 | 检查 setForceLowestSupportedBitrate(false) 或自定义带宽评估器 |
| MediaSource 报“Content not supported” | 缺少对应格式的 Extractor | 使用 DefaultExtractorsFactory 并确保包含所需 Extractor(如 FLV、MKV 需单独添加) |
1.8 性能与稳定性优化要点
- 复用 ExoPlayer 实例:避免重复创建和销毁,通过
setMediaSource()切换内容。 - 提前 prepare:在 UI 展示前即调用
prepare()减少用户等待。 - 合理设置 LoadControl:对于直播流可减小缓冲,点播时可适当增大缓冲以减少卡顿。
- 释放资源:在 Activity/Fragment 的
onStop()调用player.stop(),在onDestroy()调用player.release()。 - 日志分析:启用 ExoPlayer 内部日志(
ExoPlayerLogger.setLogLevel(ExoPlayerLogger.LEVEL_VERBOSE))定位渲染或缓冲问题。