- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
主弹幕设置是 Pure Live 直播客户端中高频使用、且横跨直播间嵌入页与独立设置页的核心交互面。本文以 2026-09-12 的 Android 原生审计为骨架,完整讲解其暴露的四大缺陷(嵌入页缺恢复入口、模板恢复部分提交、旧模板兼容缺失、控件辅助语义不全)、schema 2 模板的解析与事务化设计、确定性回归验证流程,以及真实 Bilibili 直播间中经由 ADB 完成的可重复原生往返验证。读完本文,你将掌握如何把“保存/恢复模板”从字段级写值升级为“先整体校验、后一次性提交”的事务模型,并为滑块、计数器与开关补齐可被无障碍服务与自动化脚本读取的具名语义。
审计结论与闭环范围
精确产品提交cc3ab5b2(完整 SHA-256:cc3ab5b221eff2368a5c595fa4c690440a972838)修复了主弹幕设置的模板事务与辅助语义,并构建为 Android arm64 Debug 候选包;可重复原生脚本提交为9cc76caa85e7486b839aec5dda12918a4f973174。随后在25102RKBEC / myron / Android 17设备上,通过显式网络 ADB192.168.1.2:5555完成了同签名覆盖安装、小窗弹幕设置回归以及真实 Bilibili 直播间中的主弹幕模板「保存 → 修改 → 恢复 → 重启往返」验证。
该轮审计标记 A2-04 继续RUN,本批已关闭的子项包括:
- Android 主弹幕模板(保存/恢复/重启往返);
- 嵌入页恢复入口;
- 主设置开关、滑块、计数器的具名语义;
- 小窗设置回归。
宏观状态保持20 PASS / 34 RUN / 8 NR,42 组未闭环,Windows 双栏 GUI、颜色与其余滑块的逐项原生输入、真实系统小窗中的完整视觉对照和长时性能仍为RUN。
根因:四个相互关联的缺口
修改前存在四个问题,任何一个都足以让“保存的模板”变得不可信:
- 嵌入页能力缺失:直播间嵌入式弹幕设置隐藏了“恢复已保存模板”动作,与独立页面能力不一致,用户切入口不同却获得不同功能。
- 部分提交(partial commit):模板恢复时按 JSON 字段依次写入 Rx 响应式状态;一旦后部字段损坏,前部字段(区域、留白、速度、字号等)已经改变,界面处于“改了一半”的中间态。
- 保存内容不完整:保存内容未包含纯文字(noEmoji)模式;旧模板新增字段没有兼容回退,也没有完整数值边界校验。
- 辅助语义缺失:自定义
Row + Switch、主滑块和CountButton没有完整的设置名、当前值及增减动作语义,无障碍服务与自动化脚本无法读取“这是什么、现在是多少、怎么改”。
这四个缺口相互关联:模板是否可信取决于“保存了什么、恢复如何提交、损坏如何拒绝”,而所有这些最终都要通过控件语义暴露给原生层验证。
修订一:schema 2 的 DanmakuViewingTemplate 事务化解析
核心实现在 lib/domains/live/presentation/playback/widgets/danmaku/danmaku_viewing_preset.dart。新增的DanmakuViewingTemplate以schemaVersion = 2标记,核心设计是:在任何设置写入前完整解析类型、有限值、整数与范围,解码失败则不触碰任何 Rx 值。
static const int schemaVersion = 2; String encode() => jsonEncode({ 'version': schemaVersion, 'noEmojiMode': noEmojiMode, 'area': area, 'top': top, 'bottom': bottom, 'speed': speed, 'fontSize': fontSize, 'fontWeight': fontWeight, 'fontBorder': fontBorder, 'opacity': opacity, 'stroke': stroke, 'fps': fps, 'autoFps': autoFps, });tryDecode是事务的入口:它先jsonDecode,再对每个字段做类型与边界校验,任何FormatException/TypeError都返回null,调用方直接拒绝整份模板,不产生任何副作用。校验规则一览:
| 字段 | 校验规则 | 取值范围 |
|---|---|---|
area | 必填 double,有限值 | 0 ~ 1 |
top/bottom | 必填 double,有限值 | 0 ~ 300(px) |
speed | 必填 double,有限值 | 20 ~ 400(px/s) |
fontSize | 必填 double,有限值 | 10 ~ 30(px) |
fontWeight | 必填整数,可被 100 整除 | 100 ~ 900 |
fontBorder | 必填 double,有限值 | 0 ~ 4(px) |
opacity | 必填 double,有限值 | 0 ~ 1 |
stroke | 可选 bool,缺失回退当前值 | true / false |
fps | 可选整数,缺失回退当前值 | 30 ~ 240 |
autoFps | 可选 bool,缺失回退当前值 | true / false |
noEmojiMode | 可选 bool,缺失回退当前值 | true / false |
关键细节:
- 旧模板兼容:
noEmojiMode、stroke、fps、autoFps这些 schema 2 新增字段在旧模板中缺失时,不报错,而是保留调用方传入的当前值(fallbackNoEmojiMode、fallbackStroke、fallbackFps、fallbackAutoFps); - 非有限值拒绝:
_requiredDouble显式检查result.isFinite,NaN/Infinity一律抛错; - 整数严格性:
_optionalInt校验raw.toDouble() != result.toDouble(),非整数(如fps: 59.5)直接拒绝; - 字重归一化:
_normalizeFontWeight将任意 100~900 的值吸附到百位步进。
保存与恢复的调用链
保存与恢复都在 lib/domains/live/presentation/playback/pages/danmaku_settings_page.dart 中实现:
_saveTemplate():从当前 Rx 状态构造完整DanmakuViewingTemplate(含纯文字、FPS、自动 FPS),encode()后写入savedDanmakuTemplate(Hive 持久化键,见 lib/core/config/danmaku_settings_controller.dart);_restoreTemplate():先tryDecode整体解析;返回null时只提示模板无效,不写任何字段;解析成功后一次性提交全部 13 项到各 Rx 值,形成“要么全部生效、要么全部拒绝”的事务语义。
这正是修复“部分提交”的核心:与旧实现“逐字段写入、后部出错前部已变”不同,新实现把“解析验证”与“状态提交”拆成两个阶段,验证阶段完全无副作用。
修订二:嵌入页与独立页统一,主开关整行可点
弹幕设置界面由 lib/domains/live/presentation/playback/pages/danmaku_settings_page.dart 的DanmakuSettingsContent提供,竖屏嵌入直播间标签页、全屏/横屏嵌入自适应侧栏,共用同一个DanmakuSettingsSource契约(lib/domains/live/presentation/playback/widgets/danmaku/danmaku_settings_source.dart),从源头避免两套范围、默认值与持久化行为漂移。
本次修订后:
- 嵌入页与独立页始终提供“保存当前模板”和“恢复已保存模板”两个动作(
OutlinedButton.icon,分别带save_outlined/restore_rounded图标); - 自定义
Row + Switch全部替换为整行可点的SwitchListTile,点击整行即可切换开关,不再要求精确点中 Switch 拇指。
修订三:七类滑块与计数器的具名辅助语义
_slider为每个滑块设置semanticFormatterCallback,原生语义输出格式为设置名, 格式化值:
semanticFormatterCallback: (dynamic semanticValue) => '$title, ${semanticValueBuilder((semanticValue as num).toDouble())}',实际公布的原生语义示例(与审计日志完全一致):
- 区域:
画面顶部占用高度, 41%; - 字号:
字体大小, 12.0。
七类主滑块全部覆盖:区域、透明度、最大显示数、速度、字号、字重、描边宽度,另有小窗缩放(x0.40格式)与 FPS(30 FPS格式)。_counter则通过CountButton公布具名当前值以及增加/减少两个动作:
- 当前值:
顶部留白(像素), 0/1; - 增加动作:
增加顶部留白(像素); - 减少动作:
减少顶部留白(像素)。
这些语义由semanticLabel、decrementSemanticLabel、incrementSemanticLabel参数生成,小窗(PiP)统一颜色与最大显示数也一并补齐。对自动化回归而言,uiautomator dump 可以直接按文本定位控件、按动作描述执行点击,无需坐标猜测。
修订四:启动与备份导入的数值归一化
模板的健壮性不仅依赖解码期校验,还依赖运行时入口的归一化。lib/core/config/danmaku_settings_controller.dart 的onInit在启动时对全部主弹幕数值做统一钳制:非有限值回落到集中默认值,越界值钳到控件实际范围。
danmakuTopArea/danmakuBottomArea:0 ~ 300;danmakuArea/danmakuOpacity:0 ~ 1;danmakuSpeed:20 ~ 400;danmakuFontSize:10 ~ 30;danmakuFontBorder:0 ~ 4;danmakuLetterSpacing:-2 ~ 8;danmakuFps:30 ~ 240。
同一控制器还提供parseConfig/extractConfig,分别服务备份导入与 WebDAV 配置提取,同样走_boundedDouble/_boundedInt/normalizeFontWeight的统一钳制路径,因此“启动、当前格式备份、旧格式提取”三条入口对主弹幕数值的归一化行为完全一致。默认常量也集中在控制器顶部(如defaultDanmakuSpeed = 120.0、defaultDanmakuFps = 60、defaultDanmakuAutoFps = true),供恢复与边界钳制共用。
确定性回归:红灯记录与 Focused 测试套件
修订前的有效红灯记录于local-artifacts/build-records/20260912T113326275Z-quality-focused.json:
- 嵌入式设置中找不到恢复入口;
- 一个后部
opacity字段损坏的模板,在报错前已改变区域、留白、速度、字号等前部设置——直接证明部分提交存在。
修订后,用当前工具提交9cc76caa运行确定性回归:
.\tool\local_ci.ps1 -Scope Focused -TestPath @( 'test/danmaku_settings_surface_test.dart', 'test/danmaku_viewing_preset_test.dart', 'test/pip_danmaku_preview_test.dart', 'test/danmaku_settings_controller_test.dart', 'test/danmaku_refresh_rate_policy_test.dart', 'test/backup_import_validation_test.dart', 'test/translation_contract_test.dart' ) -Analyze -SkipPubGet结果:
- Flutter:52 / 52 PASS;
- Analyze:
No issues found; - 仓库审计:4802 个跟踪文件、0 个未跟踪文件、0 error;
- 质量记录:
local-artifacts/build-records/20260912T121230459Z-quality-focused.json; - 仓库审计:
local-artifacts/repository-audits/20260912T121102267Z-focused.json。
新增覆盖包括:模板全部字段往返、旧模板回退、损坏/越界/非整数模板整体拒绝、非有限备份值归一化、嵌入页恢复入口、整行开关、滑块与计数器语义、小窗颜色与计数器语义——每一条都对应根因清单中的一个缺口。
候选构建与同签名覆盖安装
Android 候选严格绑定产品提交cc3ab5b2,参数如下:
| 项目 | 值 |
|---|---|
| 文件 | local-artifacts/3.1.8-4121/PureLive-3.1.8-4121-android-arm64-v8a-debug.apk |
| 版本 | 3.1.8+4121 |
| Manifest versionCode | 6121 |
| ABI | arm64-v8a |
| 大小 | 288808125B |
| SHA-256 | 168FBAE87863684A76D8A3997D63CE639B4BA3FA9F2471303BBCD84288C4658D |
| ELF 最小 LOAD 对齐 | 0x4000 |
| 构建记录 | local-artifacts/build-records/20260912T115639759Z-build-androidarm64-debug.json |
tool/android_pip_danmaku_settings_smoke.ps1使用adb install -r -t完成同签名覆盖安装:firstInstallTime保持2026-07-21 18:07:53不变(证明是覆盖而非全新安装),安装前规范 Hive 与安装后首启前 SHA-256 均为701C666A...8EA60C,设备base.apk哈希与候选完全一致——安装操作没有污染任何数据。
小窗弹幕设置原生回归
证据:local-artifacts/diagnostics/android-pip-danmaku-settings-20260912T195738455/summary.json。
- 主开关状态序列:开 → 关 → 进程重启仍关 → 开 → 进程重启仍开;Switch 数为 4 → 1 → 1 → 4 → 4,证明开关状态跨进程持久化;
- 四个可见 Switch 均具名;字号滑块原生语义为
字体大小, 12.0; - 恢复确认取消保持自定义关闭状态;确认后恢复 14 项小窗默认值并跨进程保持;
- 候选覆盖安装保留数据,设备 APK 与候选哈希一致;
- 结束时规范 Hive 精确恢复,Pure Live 进程退出。
主弹幕模板原生往返:真实 Bilibili 直播间验证
可重复脚本为tool/android_danmaku_template_settings_smoke.ps1,通过记录:local-artifacts/diagnostics/android-danmaku-template-settings-20260912T200727935/summary.json。脚本在真实 Bilibili 直播间进入“弹幕设置”标签并逐阶段断言:
| 阶段 | 纯文字 | 顶部留白 | 区域滑块原生语义 | 模板动作 |
|---|---|---|---|---|
| 基线 | 关 | 0 | 画面顶部占用高度, 41% | 保存、恢复均可见 |
| 自定义 | 开 | 1 | 画面顶部占用高度, 41% | 使用具名增加动作 |
| 立即恢复 | 关 | 0 | 画面顶部占用高度, 41% | 两项一起回到保存值 |
| 进程重启 | 关 | 0 | 画面顶部占用高度, 41% | 恢复结果持续存在 |
计数器同时暴露顶部留白(像素), 0/1、增加顶部留白(像素)与减少顶部留白(像素)。纯文字整行点击立即改变 Switch 状态;恢复动作一次将该开关与计数器共同还原,随后强制结束并重新进入直播间仍保持——事务一次性提交与跨进程持久化同时得到验证。
前两轮脚本迭代分别记录于...T200328381与...T200559315:第一轮只因页面标题断言使用了较短别名,第二轮只因计数器解析先匹配到标题节点;两轮settingsFileRestoredExactly=true、appStopped=true,说明产品行为本身无回归,仅是证据解析口径问题。修正证据解析后第三轮完整通过。
脚本本身的工程约束也值得借鉴(见 tool/android_danmaku_template_settings_smoke.ps1):
- 强制绑定
-s $Serial与Get-Identity校验设备型号/设备名/root 身份,杜绝选错设备; Save-UiState用uiautomator dump --compressed落 XML、screencap落 PNG,逐阶段留存证据;Find-LabeledNode按文本/content-desc候选、控件 class、clickable/checkable 多条件定位节点,直接消费本次修复的具名语义;- 每轮先停止 Pure Live 再复制规范 Hive,结束时按原 UID/GID/mode 写回并
restorecon,保证设备环境可复原。
设备与清理边界
- 每轮实际命令均绑定
-s 192.168.1.2:5555,身份再次确认为25102RKBEC / myron,su -c id为uid=0(root); - 设备同时出现固定 IP 和 mDNS 两条 transport,测试始终使用明确固定 IP,不按列表顺序选择;
- 全部原生操作通过
tool/run_android_device_test_turn.ps1 -NoRotation取得设备轮次;未重启手机/adbd、未更改 Wi-Fi/ADB 端口/调试授权、未执行adb kill-server,也未更新 Root/LSP/模块; - 每轮先停止 Pure Live 再复制规范 Hive;结束时按原 UID/GID/mode 写回并
restorecon,原始/恢复 SHA-256 均为701C666A664A784F5E466D5274F5A13C015DEAAF893BFBA51801ED989D8EA60C; - 最终
stay_on_while_plugged_in=0、Pure Live 无进程、前台为com.miui.home/.launcher.Launcher。
提交与同步
- 产品修复:
cc3ab5b2已推送origin/master; - 原生回归脚本:
9cc76caa已推送origin/master; - 两次均在推送后用
git ls-remote origin refs/heads/master核对远端完整 SHA,杜绝“本地以为推送成功、远端实际不同”的假闭环。
给工程实践者的要点提炼
- 模板类数据一律“先验证、后提交”:
DanmakuViewingTemplate.tryDecode的失败返回null且零副作用,是消除部分提交的最简单可靠模型; - 新增字段用“缺失回退当前值”而非“强制默认值”:保证旧模板在 schema 升级后不丢用户既有偏好;
- 数值边界必须在解码期与启动期双保险:解码期拒绝越界,启动/导入期钳制残留脏数据,两条路径缺一不可;
- 辅助语义是自动化测试的燃料:
设置名, 格式化值与具名增减动作让 uiautomator 脚本可以按语义定位与操作,而不是靠像素坐标; - 同签名覆盖安装 + Hive 哈希核对是“不破坏用户数据”的验收底线,而“固定 IP 而非按列表选设备”是多人多设备环境的纪律。
更多关联材料可继续阅读 DANMAKU_RENDERING_CONSISTENCY_AUDIT_2026_09_12.md(渲染一致性审计)与 SHIELD_MANAGEMENT_AUDIT_2026_09_09.md(屏蔽管理审计),以获取主弹幕渲染与过滤链路的完整视图。
- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
相关推荐
CANN ops-nn Sigmoid 算子实战指南:aclnn 两段式接口调用、图模式构图与内核实现剖析
CANN ops nn Sigmoid 算子实战指南:aclnn 两段式接口调用、图模式构图与内核实现剖析 本文是 CANN 神经网络算子库 ops nn 中
音视频直播移动开发Pure Live 小窗弹幕设置页原生审计:基于 ADB 语义化路由与重启持久化的自动化验收实践
Pure Live 小窗弹幕设置页原生审计:基于 ADB 语义化路由与重启持久化的自动化验收实践 导读 本文以 Pure Live( com.mystyle.p
音视频直播移动开发pi-web 工作区终端深度解析:PTY 生命周期、SSE 传输与 node-pty 原生模块管理
pi web 工作区终端深度解析:PTY 生命周期、SSE 传输与 node pty 原生模块管理 工作区终端(Workspace Terminals)是 pi
音视频直播移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考