☰
Pure Live 抖音多画面黑屏根因审计:纯音频 rendition 过滤与多画面最低档选源修复解析
2026/10/15 16:40:07 网站建设 项目流程
  • 音视频
  • 直播
  • 移动开发

【免费下载链接】pure_live

纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。

项目地址:https://gitcode.com/gh_mirrors/pur/pure_live
点击查看免费下载

导读

本文围绕 Pure Live 上游 Issue #866「抖音多画面直播黑屏」展开一次可复现的源码级审计:先锁定 3.1.3 版本中ao纯音频流被当作最后一个「清晰度」暴露给用户、而多画面小格降质路径又恰好选择列表末项的复合缺陷,再完整梳理当前维护分支的修复合同——_isAudioOnlyVariant过滤、严格房间解析、PlaybackHeaderResolver请求头继承与跨层回归。读完本文,你将理解抖音推流响应的结构(视频清晰度 + 纯音频 rendition 并存)、多画面控制器preferLowest的选择契约,以及如何用确定性夹具证明「响应含纯音频 + 最低档选择」这条缺陷链。

一、Issue 背景与审计结论

上游 Issue #866 报告的对象是 Pure Live3.1.3,症状为「多画面中的抖音直播黑屏」。审计在对应标签源码上确定了一条可复现的视频源选择缺陷链:

  • 抖音直播响应可以同时发布视频清晰度(如origin、md)与内部使用的纯音频 renditionao;
  • 3.1.3 的DouyinSite.parseStreamQualities会把ao暴露成最后一个清晰度选项;
  • 多画面小格降质路径又明确选择列表末项,最终可能把只有音轨的only_audio=1地址当作视频源打开,播放器自然只有声没有画——即「黑屏」的直接来源。

当前维护分支的处理状态:

  1. 提交56cd4d971dcfbc434171f98760c6cc35630c53ae在解析阶段过滤这类纯音频 rendition;
  2. 提交09a716e62fb4f25671d093d389d12f723dc78f7d进一步把真实抖音解析器接入多画面解析入口,补充跨层回归,证明最低档仍为视频源,同时保留严格房间解析与 User-Agent / Origin / Referer / Cookie 请求头;
  3. 该子问题归类为fixed-in-current-source / native-recheck-pending(当前源码已修复、原生复验待补)。

需要强调的是:Issue 同时提到普通播放偶发失败、平板横屏启动偶发失败与 Windows 播放卡顿三个现象,但报告没有日志、截图、房间身份、是否有声音、清晰度/线路、网络时序或性能采样,也没有评论。现有证据不把这三个现象归并到ao根因,也不据此宣称整条 Issue 已闭环——这是审计结论中严格区分「已证实缺陷」与「未证实现象」的边界。

二、3.1.3 稳定复现:为什么ao会被当作清晰度

审计选取的复现基线:

  • 标签v3.1.3,提交41b75c28d8e131fab632aeffeb458245a979b0ab;
  • DouyinSite.parseStreamQualities会保留origin / md / ao三个 key,其中ao的 URL 含only_audio=1查询参数;
  • 同一版本的MultiviewController契约是preferLowest == true时取qualities.length - 1,因此上述响应会选中ao。

复现方式是在独立的 3.1.3 worktree 中运行确定性夹具(不依赖真实网络):实际结果为['origin', 'md', 'ao'],预期视频清晰度集合为['origin', 'md'],结论为0 PASS / 1 FAIL。日志与摘要归档于local-artifacts/diagnostics/issue866-v313-regression/。

这条复现证明的是「响应含纯音频 rendition + 多画面最低档选择」的源码链,它被明确限定为源码级证据,不外推为所有房间、所有布局或 Issue 其他三个现象的结论。

三、当前源码修复合同:纯音频过滤与最低档落点

3.1 纯音频识别:_isAudioOnlyVariant

当前 douyin_site.dart 中的_isAudioOnlyVariant采用双通道识别:

static bool _isAudioOnlyVariant(String key, List<String> urls) { final token = key.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), ''); if (const {'ao', 'audio', 'audioonly'}.contains(token)) return true; if (urls.isEmpty) return false; return urls.every((url) { final uri = Uri.tryParse(url); final value = uri?.queryParameters['only_audio']?.toLowerCase(); return value == '1' || value == 'true'; }); }
  • 标识通道:将 key 规范化(去除非字母数字字符)后匹配ao / audio / audio_only集合;
  • URL 通道:当 key 是未知项但该项的所有 URL 都声明only_audio=1/true时,同样判定为纯音频。

第二通道的存在意味着即使抖音未来更换纯音频 key 的名称,只要 pull URL 携带only_audio标记,解析器仍能拦截,增强了抗漂移能力。

3.2 解析入口排除:parseStreamQualities

parseStreamQualities 把来自live_core_sdk_data.pull_data的多个数据源(options中的 SDK 选项、stream_data解码后的data、flv_pull_url、hls_pull_url_map)按 key 合并成 descriptors,再逐个解析 URL;在排序和去重之前执行过滤:

// Douyin may publish an `ao` entry beside its video renditions. It is // an audio-only pull URL (`only_audio=1`), not a selectable video // quality. Exposing it in the quality menu produced a raw "ao" button // and could leave the player without a video track after selection. if (_isAudioOnlyVariant(key, urls)) continue;

过滤后,qualities只会包含有视频轨的 rendition。排序逻辑由_douyinQualityRank(douyin_site.dart)主导:ORIGIN/ORIGION(6000000)>FULL_HD1/UHD(5000000)>HD1/HD(4000000)>SD2/SD(3000000)>SD1/LD(2000000)>MD(1000000),未知 key 回退到 SDKlevel或比特率。由于ao已被排除,最低档只能落到仍有视频轨的 rendition(典型为md),随后按 URL 去重防止同源别名(如FULL_HD1与uhd指向同一地址)重复展示。

3.3 多画面最低档选择:resolveStreamForSite的契约

多画面控制器 multiview_controller.dart 的核心流程:

final qualityIndex = preferLowest ? qualities.length - 1 : 0;
  • preferLowest == true时取列表末项(最低清晰度,用于小格降质);
  • preferLowest == false时取首项(最高清晰度,用于聚焦大格)。

preferLowest的实际来源在 multiview_controller.dart:

final preferLowest = smallCellsLowQuality.value && layout.value == MultiviewLayout.focus && cellIndex != focusedCellIndex.value;

即:开启「小格低清晰度」、处于 focus 大格布局、且当前格不是聚焦格时降质。修复前该契约配合未过滤的['origin','md','ao']恰好选中ao;修复后配合['origin','md']则稳定选中md视频档。

3.4 严格房间解析与请求头继承

resolveStreamForSite对抖音走 LiveSiteRecordRoomResolver 严格详情路径:

final detail = liveSite is LiveSiteRecordRoomResolver ? await (liveSite as LiveSiteRecordRoomResolver).getRoomDetailForRecording(liveroom) : await liveSite.getRoomDetail(liveroom);

getRoomDetailForRecording 会保留stream_url信封,再按房间 ID 长度选择getRoomDetailByWebRid(短 ID)或getRoomDetailByRoomId(长数字 ID)路径,保证每个广告的sdk_key都能被解析。

多画面源携带的请求头由 PlaybackHeaderResolver.resolve 产生,抖音分支包含四类字段:

字段值
user-agent桌面版 Chrome UA(Mozilla/5.0 … Chrome/140.0.0.0 Safari/537.36)
originhttps://live.douyin.com
refererhttps://live.douyin.com/(无房间)或https://live.douyin.com/$roomId
cookie配置的抖音 Cookie,或DouyinSite.cookie中的匿名/会话 Cookie

头部经HttpHeaderPolicy.normalize统一规范化后传给原生播放器或 FFmpeg;播放、多画面、纯音频播放与录制共享该策略,保证跨路径请求头一致。

四、验证矩阵

文档记录的验证结果如下:

层级结果
3.1.3 历史夹具0 PASS / 1 FAIL;保留ao,与最低档视频合同冲突
当前multiview_test.dart+douyin_playback_parser_test.dart59/59 PASS(含「md视频 URL + 四类请求头」的精确断言)
当前静态分析No issues found,本批一次 analyze
最终质量记录local-artifacts/build-records/20260915T103240776Z-quality-focused.json
仓库审计local-artifacts/repository-audits/20260915T103118284Z-focused.json,0 error / 2 warning

跨层回归(提交09a716e6)的价值在于:它不仅验证解析器层面过滤正确,还把真实抖音解析器接入多画面解析入口,证明从「房间详情 → 清晰度发现 → 最低档选源」的整条链路上最低档都是视频源,且请求头完整。

另外,历史 K90 cycle 46 在含56cd4d97的 Debug 候选上确认普通直播页画质菜单没有纯音频项、五个视频档和两条线路可见,短录成品同时含 H.264 与 AAC;证据为local-artifacts/diagnostics/android-recording-smoke-20260901T232058196/summary.json。这是普通播放/录制侧的证据,不替代当前源码的多画面原生复验。

本批审计没有修改产品运行时代码、构建新候选、启动 Windows GUI、连接 ADB、操作设备或发布,Astra Light 使用 0 次。

五、后续原生边界:native-recheck-pending 的验证清单

由于子问题状态为 fixed-in-current-source / native-recheck-pending,文档列出的原生复验范围包括:

多画面矩阵:在包含当前源码的 Android/Windows 累计候选上分别验证 focus 大格、小格降质开/关、quad/dual、晋升、换清晰度、换线路、刷新、退出再进,并记录首帧、音视频轨和错误提示。

Android 专项:手机/平板横竖屏冷启动、旋转期间选台、前后台返回、同房间普通页对照。

Windows 专项:对相同抖音房间采集窗口尺寸、DPR、选中源、首帧时间、buffering、CPU、GPU 3D、Video Decode、工作集及 2~10 分钟帧连续性,以事实区分「源问题」「渲染目标问题」与「机器负载问题」。

宏观账本保持:A3-06、A3-08 与 W3-03 为RUN,共20 PASS / 42 RUN / 0 NR,42 组未闭环。

六、方法论小结

这次审计值得借鉴的三点工程实践:

  1. 用确定性夹具复现源码缺陷:不依赖真实网络与房间,在独立 worktree 上以「响应样例 + 解析函数」构造断言,把「响应含纯音频 + 最低档选择」的源码链稳定锁定为 0 PASS / 1 FAIL,证据可归档、可回放;
  2. 修复合同分层可验证:纯音频识别(_isAudioOnlyVariant)、解析排除(parseStreamQualities)、多画面选源(resolveStreamForSite)与请求头(PlaybackHeaderResolver)各自独立成层,回归测试能精确定位断点;
  3. 结论分级:已证实的源码缺陷(ao黑屏链)与证据不足的现象(普通播放偶发失败、横屏启动偶发失败、Windows 卡顿)严格分开,不把未证实现象归并到单一根因,也不提前宣称整条 Issue 闭环。

读者若需进一步核对实现细节,可直接查阅 douyin_site.dart、multiview_controller.dart、playback_header_resolver.dart 与 live_site.dart 中对应的实现与接口;多画面功能的产品定义见 README.md。

  • 音视频
  • 直播
  • 移动开发

【免费下载链接】pure_live

纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。

项目地址:https://gitcode.com/gh_mirrors/pur/pure_live
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询