DRM Kit 的证书、License 与 MediaKeySession:受保护视频的解密链路【鸿蒙心迹】
文件已经下载到本地了,系统凭什么不让我播?

做视频播放的时候,最开始的思路很简单:MP4 文件都下载到本地了,播放器直接读文件不就行了?
结果真做的时候发现不对:文件就在本地,播放器就是播不了。报错说需要 DRM 授权。
这时候才意识到:受保护视频不是"有文件就能播"。文件只是容器,真正的视频数据是加密的。你拿到了文件,不代表你拿到了解密的钥匙。
一、先想清楚:为什么文件在本地还播不了
先把最基础的问题想明白。
普通 MP4:视频数据是明文的,播放器直接读、直接解。
受保护 MP4:视频数据是加密的,播放器需要解密密钥才能解。密钥不在文件里,在 DRM 系统里。
| 类型 | 数据 | 能不能直接播 |
|---|---|---|
| 普通 MP4 | 明文 | 能 |
| 受保护 MP4 | 加密 | 不能,需要密钥 |
文件在本地只是第一步。你还得通过 DRM 授权,拿到解密密钥,才能真正播放。
二、DRM 系统到底有哪些东西
DRM 系统不是一个东西,是一套体系。
| 组件 | 作用 |
|---|---|
| MediaKeySystem | DRM 系统入口,管理证书 |
| MediaKeySession | 一次播放会话,管密钥状态 |
| License Server | 许可证服务器,发密钥 |
| Secure Decoder | 安全解码器,解密视频 |
每一个组件都有它存在的理由。少一个都不行。
三、证书和许可证为什么是两回事
很多人搞混了:证书和许可证,不都是 DRM 的东西吗?
不对。这是完全不同的两个东西。
| 概念 | 是什么 | 有效期 |
|---|---|---|
| DRM 证书 | 设备的身份凭证 | 很长,一般一次 Provision |
| 媒体许可证 | 某一个视频的解密密钥 | 短,跟节目走 |
证书是证明"这台设备是合法的"。许可证是证明"这个节目你有权看"。
证书一般只需要做一次 Provision,之后就一直用。许可证是每个节目都要单独申请的。
这段代码解决什么问题: 创建 DRM MediaKeySystem。
文件: drm/DrmManager.ets
用途: 初始化 DRM 环境
接入位置: 播放器创建时
import { drm } from '@kit.MediaKit';
// 创建 MediaKeySystem
let mediaKeySystem = await drm.createMediaKeySystem(
drm.MediaKeySystemUuid.KEY_SYSTEM_ID_WIDEVINE
);
// 检查设备是否支持这个 DRM
let support = await mediaKeySystem.isMediaKeySystemSupported(
drm.MediaKeySystemUuid.KEY_SYSTEM_ID_WIDEVINE
);
这里创建的是 MediaKeySystem,不是 MediaKeySession。Session 是后面每个节目一个的。

四、MediaKeySession 为什么要独立存在
很多人以为:有了 MediaKeySystem,直接拿密钥不就行了?
不对。每个播放会话都要有一个独立的 MediaKeySession。
为什么要独立?因为每个节目都不一样,密钥也不一样。一个 Session 管一个节目的密钥状态。
你播完这个节目,Session 就可以销毁了。下一个节目再开新的 Session。
五、完整解密链路是什么样的
完整的受保护视频播放链路是这样的:
- 播放器从 MP4 里解析出 PSSH/initData;
- 创建 MediaKeySession,把 initData 传进去;
- Session 根据 initData 生成 License Request;
- 播放器把 License Request 发给 License Server;
- License Server 验证用户权限,返回 License Response;
- Session 处理 License Response,拿到解密密钥;
- 解码器用密钥解密视频数据;
- 播放成功。
这段代码解决什么问题: 申请媒体许可证。
文件: drm/LicenseManager.ets
用途: 在线授权
接入位置: 开始播放受保护视频
// 创建 Session
let session = await mediaKeySystem.createMediaKeySession();
// 从 MP4 解析出 PSSH
let pssh = parsePssh(mp4File);
// 生成 License Request
let request = await session.generateMediaKeyRequest(
pssh,
drm.MediaKeyType.MEDIA_KEY_TYPE_ONLINE
);
// 发给 License Server
let response = await licenseServer.send(request.data);
// 处理 License Response
await session.processMediaKeyResponse(response.data);
这里最关键的是:密钥从来没有出现在普通内存里。密钥在 Secure Decoder 里,解密也在 Secure Decoder 里完成。

六、在线 License 和离线 License 有什么区别
在线 License:每次播放都要联网,向 License Server 申请。适合直播、点播这些需要实时验证权限的场景。
离线 License:提前把许可证下载到本地,之后不联网也能播。适合下载了离线观看的视频。
| 类型 | 要不要联网 | 场景 |
|---|---|---|
| 在线 License | 每次都要 | 直播、实时点播 |
| 离线 License | 提前下载 | 离线下载观看 |
离线 License 有个问题:有效期。离线许可证是有时间限制的,过了有效期就不能播了。
七、KeyStatus 为什么很重要
密钥不是拿到就一直有效的。密钥有状态:可用、过期、输出受限等等。
Session 会监听密钥状态变化。如果密钥过期了,播放器要做出反应:暂停播放、提示用户重新授权。
很多人只处理"拿到密钥"这一步,不处理密钥状态变化。结果就是:播到一半密钥过期了,视频卡住了,用户不知道为什么。
八、几个容易踩的坑
第一个坑:把 DRM License 当成视频文件解密密码直接保存。不对,密钥在 Secure Decoder 里,拿不出来。
第二个坑:设备证书和媒体许可证混为一谈。证书是设备身份,许可证是节目权限。
第三个坑:每播放一次都重新 Provision。证书只需要 Provision 一次,之后一直用。
第四个坑:PSSH/initData 解析错误。MP4 解析不对,License Request 就生成不对。
第五个坑:License 过期仍继续复用旧 Session。密钥状态变了,要重新申请。
第六个坑:离线 MediaKeyId 当成真正的媒体密钥保存。不对,那只是个 ID。
第七个坑:播放结束不释放 MediaKeySession。内存泄漏。
第八个坑:只实现在线授权,不考虑网络断开和离线恢复。

这次做 DRM 最大的体会是:受保护视频不是"有文件就能播"。文件只是个容器,真正的解密链路是从 PSSH 到 License Request 到 License Response 到 Secure Decoder,一整套。
每一个环节都有它存在的理由:证书证明设备身份,Session 管一次播放的密钥状态,License Server 验证用户权限,Secure Decoder 完成解密。少一个环节都不行。
真正做的时候,最容易忽略的不是流程本身,而是周边的东西:密钥状态变化、离线许可证有效期、Session 释放、异常情况处理。这些才是工程上真正容易出问题的地方。
更多推荐




所有评论(0)