Skip to content

fix: Android 播放回归 media_kit/libmpv 解密红果 CENC 内容 - #2

Open
fengs2021 wants to merge 2 commits into
zhoufuweigg:mainfrom
fengs2021:fix/android-cenc-playback
Open

fengs2021 wants to merge 2 commits into
zhoufuweigg:mainfrom
fengs2021:fix/android-cenc-playback

Conversation

@fengs2021

Copy link
Copy Markdown

问题

Android 端红果站源几乎全部无法播放:短视频偶可播,长视频转圈很久后报 Source error / 「暂时无法播放」,换源无效。

根因(AVD 设备级实测确认)

红果内容是 CENC(AES-128-CTR)加密的,密钥由 spade_a 解出后已经存在于 plan.decryptionKey(诊断日志显示 密钥=32 位)。但 Android 此前使用 video_player(ExoPlayer)路径,该插件不提供任何 DRM/解密接口,加密数据被当作明文喂给 MediaCodec:

  • 同一台 AVD、同一个 CENC 测试文件:ExoPlayer 挂起 15 分钟,logcat 无任何 DRM 会话;
  • 同文件走 media_kit/libmpv 并传入 decryption_key:正常播放(playable=true, duration=20s)。

libmpv 本就随 media_kit 打包在 APK 内(libmpv.so),README 记载的正式内核也是 media_kit/libmpv。0.2.64 把 Android 换成 ExoPlayer 后,密钥传递路径被切断——这就是「怎么换源都播不了」的原因。

修复

1. Android 播放内核回归 media_kit/libmpv(lib/player_screen.dart)

  • 移除 Platform.isAndroid 的 LunaExoPlayer 分支,全平台统一走 media_kit;
  • 既有的 demuxer-lavf-o ... decryption_key= 逻辑对 Android 生效,与桌面端一致。

2. Go 本地代理修复四个真实缺陷(native/core/,均有回归测试)

  • Range 兑现:部分 CDN 忽略 Range 返回 200 + 整文件,代理原样转发导致播放器 seek 后拿到错误偏移。现在代理自行跳过前缀、限制长度并补正确的 206/Content-Range 头(TestNativeUpstreamIgnoringRangeStillHonoursOffsets,修复前必现偏移错位);
  • 会话误回收:已出过数据的播放会话不再被其他会话挤掉(此前表现为 410,TestNativeIdlePlaybackSessionSurvivesOtherSessions);
  • HLS 误判:不再用 url 包含 hls 启发式,改为路径后缀判断;
  • 传输细节:Range 请求强制 Accept-Encoding: identity;响应头立即 Flush,规避 Media3 默认 8 秒连接超时。

3. 诊断能力

  • 启动日志读取版本常量并打印包名(此前硬编码 0.2.64+71 导致两轮误判版本未更新);
  • 播放日记记录密钥位数/线路/本地标志;
  • 代理环形探针日志(最近 64 条请求往返)经 playbackProbe 桥暴露,播放失败时自动落日记。

验证

  • AVD(android-34 x86_64)设备级:
    • integration_test/plan_probe_test.dart:三夹具(明文 MP4 / AES-HLS / CENC MP4)经真实 NativeRepository.resolve + media_kit 播放,All tests passed;
    • integration_test/ui_cenc_test.dart:真实 PlayerScreen 挂载 CENC 夹具,视频表面渲染且进度前进,All tests passed;
    • 对照实验:ExoPlayer 播同一 CENC 文件挂起(本报告根因依据)。
  • Go:go vet 干净,新增 2 个回归测试通过;
  • Dart:flutter analyze 0 error;
  • 真机:用户已用本分支构建的 0.2.72+79(arm64-v8a)实测红果长剧播放恢复正常。
  • 完整回归 playback_test.dart 在改动前后结果一致(+1 -4,失败为既有测试漂移,与本修复无关)。

兼容性

  • 不改包名、不改数据目录,同一签名可直接覆盖升级;
  • Windows/macOS/Linux/iOS 路径行为不变(本就用 media_kit);
  • Android PiP 状态走原生通道,与播放器类型无关,不受影响。

红果内容以 CENC(AES-128-CTR) 加密,密钥由 spade_a 解出后经 plan.decryptionKey
传给播放器。video_player 插件不提供 DRM 接口,ExoPlayer 拿到加密数据既无法解密
也不报错,表现为 Source error 或长时间卡住;AVD 实测同一 CENC 文件 ExoPlayer 挂起
而 libmpv 正常播放。libmpv 本就随 media_kit 打包在 APK 内,Android 恢复与桌面
一致的内核路径,demuxer-lavf-o 传入 decryption_key。

本地代理同步修复四个真实缺陷(均有 Go 回归测试):
- 上游 CDN 忽略 Range 返回 200 整文件时,代理自行跳过前缀、限制长度并补 206 头,
  否则 seek 后播放器拿到错误偏移(TestNativeUpstreamIgnoringRangeStillHonoursOffsets)
- 空闲播放会话不再被其他会话回收(410),已出过数据的会话受保护
- HLS 判定不再用 url 包含 hls 的启发式,改为路径后缀
- Range 请求强制 identity 编码;响应头立即 Flush,规避 Media3 8 秒连接超时

诊断:启动日志读取版本常量并打印包名;播放日记记录密钥位数/线路;代理环形探针
日志经 playbackProbe 桥暴露,失败时 LunaExoPlayer 侧输出往返记录。
版本 0.2.72+79。
@Demonwls

Demonwls commented Oct 5, 2026

Copy link
Copy Markdown

我去了,我刚准备提,就修复了

部分鸿蒙 / OEM 不声明 FEATURE_PICTURE_IN_PICTURE 或默认禁用画中画权限,
此前一律提示 当前设备不支持画中画。现在 enter 直接尝试并以结果置位验证
标志,AppOps 检查区分 权限被禁 与 设备不支持 两种提示。
版本 0.2.73+80。
fourbain-lab added a commit to fourbain-lab/guoapp that referenced this pull request Oct 6, 2026
合并 fengs2021/fix/android-cenc-playback (PR zhoufuweigg#2) 进主银 fork。

PR zhoufuweigg#2 真凶(vs V1-V10):
- 0.2.64 把 Android 从 media_kit/libmpv 切成 LunaExoPlayer(package:video_player)
- 红果内容是 CENC (AES-128-CTR) 加密,密钥 spade_a → plan.decryptionKey
- ExoPlayer 不提供任何 DRM/解密接口 → 加密数据当明文喂 MediaCodec
- 短视频蒙混过关,长视频 Source error(之前以为的 bytevc codec 问题是表象)
- 切回 libmpv + decryption_key= 参数走原生 mpv 解密链路,AVD 实测 playable=true

PR zhoufuweigg#2 顺手修 Go 代理 4 缺陷:
1. Range 兑现:CDN 忽略 Range 时代理自己跳前缀 + 限长度 + 补 206/Content-Range
   (TestNativeUpstreamIgnoringRangeStillHonoursOffsets)
2. 会话误回收:served/inflight 计数 + served(token)/usedAt(token)
   (TestNativeIdlePlaybackSessionSurvivesOtherSessions)
3. HLS 启发式误判:去掉 url.contains('hls'),改路径后缀 .m3u8/.m3u
4. Range 请求强制 Accept-Encoding: identity + 响应头立即 Flush(Media3 8s 连接超时兜底)

PR zhoufuweigg#2 顺手修诊断能力:
- lib/main.dart 启动日志改读 appVersion+duanjuBuildNumber 常量(不再硬编码 0.2.64+71)
  → 解决此前版本号误判问题
- 播放日记记密钥位数/线路/本地标志
- 64 条环形 probe 缓冲 + playbackProbe action 桥暴露

冲突解决:
- pubspec.yaml: 选 PR zhoufuweigg#2 的 0.2.73+80(更新版本号 = 真 bug 修复后版本)
- native/core/app_stream.go: 合并字段(主银 V3 加 seriesID/videoID + PR zhoufuweigg#2 加 inflight/served)
  + 保留 PR zhoufuweigg#2 的 5 个配套方法 (servedCount/inFlight/served/usedAt)
- native/core/network_media.go: git 自动合并成功(两边都是单行 Set Accept-Encoding: identity)

build trigger: workflow_dispatch 一次(push 触发那个会被 concurrency 自动 cancel)
version: 0.2.73+80
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants