ExoPlayer 深度使用:自定义渲染器、DASH/HLS/SmoothStreaming、DRM支持
说实话,ExoPlayer 这玩意儿,我用得越久越觉得它香。官方 MediaPlayer 能做的事它都能做,官方做不了的,它也能做。今天咱们就聊聊它的几个高阶玩法——自定义渲染器、多协议流媒体支持,还有 DRM 那点事。
一、自定义渲染器:让 ExoPlayer 听懂你的“方言”
ExoPlayer 默认支持的音视频格式其实挺全的,但总有例外。我记得有一次项目里需要播放一个特殊编码的音频流,ExoPlayer 直接罢工了。怎么办?自己写渲染器呗。
说白了,渲染器就是 ExoPlayer 里的“翻译官”。它负责把解码后的数据交给硬件去展示或播放。自定义渲染器,就是让你自己定义这个“翻译”过程。
核心思路:继承 BaseRenderer,实现几个关键方法。
public class MyAudioRenderer extends BaseRenderer {
public MyAudioRenderer() {
super(C.TRACK_TYPE_AUDIO);
}
@Override
public void render(long positionUs, long elapsedRealtimeUs) {
// 在这里处理解码后的音频数据
// 我习惯在这里做音效处理或数据转发
}
@Override
protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
// 流切换时的处理
}
@Override
public boolean isReady() {
return true;
}
@Override
public boolean isEnded() {
return false;
}
}
你可能会问:“我什么时候需要自定义渲染器?”嗯,场景其实不少:
- 你想在播放前对音频做实时变声处理
- 你需要把视频帧数据传给第三方美颜 SDK
- 你想把渲染后的数据同时写入本地文件做录制
我曾经在一个直播项目里,就是靠自定义渲染器把视频帧抽出来,做了个实时人脸贴纸功能。效果还不错,就是调试时有点头疼。
小技巧:自定义渲染器里别忘了处理 reset() 和 flush(),否则播放器 Seek 时容易出问题。我踩过这个坑。
二、DASH / HLS / SmoothStreaming:自适应流媒体三兄弟
这三个协议,说白了都是“把大文件切成小片,根据网速动态切换清晰度”。ExoPlayer 对它们的支持可以说是原生级别的,但配置上还是有些门道。
2.1 DASH(Dynamic Adaptive Streaming over HTTP)
DASH 是 Google 亲儿子,ExoPlayer 对它的支持最完善。配置起来也很简单:
// 创建 DASH 媒体源
DashMediaSource.Factory dashFactory = new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory()
);
DashMediaSource dashSource = dashFactory.createMediaSource(MediaItem.fromUri(dashUrl));
我个人习惯在 DASH 流里重点关注 SegmentBase 的配置。有些 CDN 返回的 MPD 文件里,segment 的 URL 模板写得不规范,会导致播放卡顿。遇到这种情况,我一般会手动解析 MPD 并修正 URL。
2.2 HLS(HTTP Live Streaming)
苹果家的协议,但在 Android 上 ExoPlayer 照样玩得转。HLS 的配置稍微复杂一点,主要是因为它有 .m3u8 索引文件:
// 创建 HLS 媒体源
HlsMediaSource.Factory hlsFactory = new HlsMediaSource.Factory(mediaDataSourceFactory);
HlsMediaSource hlsSource = hlsFactory.createMediaSource(MediaItem.fromUri(hlsUrl));
这里有个坑——HLS 的 TS 分片有时会带奇怪的 PTS 跳变。ExoPlayer 默认会尝试修复,但如果你发现播放时画面突然卡一下,可以试试关闭 allowChunklessPreparation:
hlsFactory.setAllowChunklessPreparation(false);
2.3 SmoothStreaming
微软家的协议,现在用得少了,但一些老项目还在用。ExoPlayer 支持它,不过需要额外引入依赖:
implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.19.1'
配置方式和前两个类似,我就不重复贴代码了。我想说的是,SmoothStreaming 的 Manifest 文件(.ismc)解析起来比 DASH 的 MPD 要简单,但它的分片命名规则比较死板,遇到自定义命名的 CDN 就得自己写 UriBuilder。
| 协议 | ExoPlayer 支持度 | 常见坑点 | 我的建议 |
|---|---|---|---|
| DASH | ★★★★★ | MPD 中 Segment 模板不规范 | 优先使用,兼容性最好 |
| HLS | ★★★★☆ | TS 分片 PTS 跳变 | 注意关闭 chunkless preparation |
| SmoothStreaming | ★★★☆☆ | 分片命名规则死板 | 老项目维护时再用 |
三、DRM 支持:给内容加把锁
DRM 这东西,说白了就是版权保护。ExoPlayer 支持 Widevine、PlayReady、ClearKey 等主流方案。配置起来其实不复杂,但坑不少。
3.1 Widevine 配置
Widevine 是 Android 上最常用的 DRM 方案。ExoPlayer 通过 MediaItem 的 DrmConfiguration 来配置:
MediaItem mediaItem = new MediaItem.Builder()
.setUri(videoUrl)
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri(licenseUrl)
.setLicenseRequestHeaders(licenseHeaders)
.build()
)
.build();
这里有个关键点——licenseUrl 是获取许可证的地址。我遇到过好几次,许可证服务器要求 POST 请求带特定的 Header,但 ExoPlayer 默认只带了基本的 User-Agent。解决办法是自定义 HttpDataSource.Factory:
HttpDataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory()
.setDefaultRequestProperties(licenseHeaders);
3.2 离线 DRM 播放
有些场景需要用户下载视频后离线观看。ExoPlayer 的 DownloadManager 支持离线 DRM,但需要额外处理许可证的持久化:
// 下载时保存许可证
DrmSessionManager drmSessionManager = new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(C.WIDEVINE_UUID, FrameworkMediaDrm.DEFAULT_PROVIDER)
.setMultiSession(true)
.build(drmDataSourceFactory);
// 播放时恢复许可证
// 注意:许可证的持久化需要 License 服务器支持 offline 模式
警告:不是所有 Widevine 服务器都支持离线许可证。我曾经对接过一个厂商,他们的 License 服务器只支持 streaming 模式,离线播放时直接报错。所以,做离线 DRM 前一定要先确认服务器能力。
3.3 ClearKey 方案
如果你只是做本地测试,ClearKey 是最简单的 DRM 方案。它不需要 License 服务器,直接在客户端配置密钥:
MediaItem mediaItem = new MediaItem.Builder()
.setUri(videoUrl)
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.CLEARKEY_UUID)
.setKeySetId(keySetId)
.build()
)
.build();
嗯,ClearKey 说白了就是“明文密钥”,安全性不高,但用来调试 DRM 流程非常方便。
四、知识体系总览
说了这么多,我画了张图帮你理清思路。ExoPlayer 深度使用的核心就这三块:
你看,这三块内容其实是环环相扣的。自定义渲染器解决的是“怎么播”的问题,多协议支持解决的是“播什么”的问题,DRM 解决的是“能不能播”的问题。把它们串起来,你就能应对绝大多数播放场景了。
我的经验:刚开始接触 ExoPlayer 深度定制时,别急着写代码。先把 ExoPlayer 的源码结构摸一遍,重点看 Renderer、MediaSource、DrmSessionManager 这三个类的继承关系。搞懂了它们,你就能像搭积木一样组合各种功能了。
好了,今天就聊到这儿。ExoPlayer 这东西,越用越觉得它设计得巧妙。下次遇到播放相关的需求,别急着找第三方库,先想想 ExoPlayer 能不能自己搞定——很多时候,它比你想象的更强大。