文件已经下载到本地了,系统凭什么不让我播?

文章封面

做视频播放的时候,最开始的思路很简单:MP4 文件都下载到本地了,播放器直接读文件不就行了?

结果真做的时候发现不对:文件就在本地,播放器就是播不了。报错说需要 DRM 授权。

这时候才意识到:受保护视频不是"有文件就能播"。文件只是容器,真正的视频数据是加密的。你拿到了文件,不代表你拿到了解密的钥匙。

一、先想清楚:为什么文件在本地还播不了

先把最基础的问题想明白。

普通 MP4:视频数据是明文的,播放器直接读、直接解。

受保护 MP4:视频数据是加密的,播放器需要解密密钥才能解。密钥不在文件里,在 DRM 系统里。

类型数据能不能直接播
普通 MP4明文
受保护 MP4加密不能,需要密钥

文件在本地只是第一步。你还得通过 DRM 授权,拿到解密密钥,才能真正播放。

二、DRM 系统到底有哪些东西

DRM 系统不是一个东西,是一套体系。

组件作用
MediaKeySystemDRM 系统入口,管理证书
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。

五、完整解密链路是什么样的

完整的受保护视频播放链路是这样的:

  1. 播放器从 MP4 里解析出 PSSH/initData;
  2. 创建 MediaKeySession,把 initData 传进去;
  3. Session 根据 initData 生成 License Request;
  4. 播放器把 License Request 发给 License Server;
  5. License Server 验证用户权限,返回 License Response;
  6. Session 处理 License Response,拿到解密密钥;
  7. 解码器用密钥解密视频数据;
  8. 播放成功。

这段代码解决什么问题: 申请媒体许可证。
文件: 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 释放、异常情况处理。这些才是工程上真正容易出问题的地方。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐