- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
导读
本文围绕 pure_live 开源项目在 2026-09-07 完成的"工具箱直播链接解析"专项审计展开:ToolBoxController.jumpToRoom(跳转房间)与直链获取共用LiveUrlTool.parseLiveUrl,旧实现长期依赖"取分享文本第一个 URL + 整串 contains 判断平台 + 逐平台正则提取 ID",导致大写协议、相似域名、查询参数、伪装后缀等大量误判。本文完整梳理该批审计发现的问题清单、修订后的 URI/主机/路径契约、94/94 定向回归门禁与最终源码结果,并结合仓库当前实现给出可复现的解析规则与边界说明,帮助读者理解 pure_live 如何把剪贴板/手动输入的直播分享文本稳健地解析为"平台 + 房间 ID"。
一、审计背景:剪贴板识别与手动解析共享同一契约
本批审计基线为提交96c5ad55,是 工具箱剪贴板与页面入口审查(2026-09-07) 的延续。上一轮解决了剪贴板自动填充的生命周期问题,本批则进一步区分两类解析场景:
- 本地身份解析:从分享文本中直接提取"平台 + 房间 ID",不发起任何网络请求;
- 远端短链/直播接口解析:对
b23.tv、v.douyin.com等短链做重定向跟随,或调用直播信息接口换取身份。
两个场景共用同一个入口LiveUrlTool.parseLiveUrl(见 live_url_tool.dart),而ToolBoxController的"跳转房间"与"获取直链"两个动作在 toolbox_controller.dart 的_resolve中统一走parseLink?.call(text) ?? LiveUrlTool.parseLiveUrl(...)。
注意:审计中提到的
LiveUrlTool.getLivePlayUrl是当时调用链的入口表述;当前仓库实现中,解析契约统一收敛在LiveUrlTool.parseLiveUrl,工具箱通过注入的parseLink或默认实现调用它。
审计同时与冻结上游c6c9bd70aedc503c003110dae10a83ad0bb891d8做了只读比较:上游存在相同的"首链接/contains/正则"逻辑,因此本批问题被归类为upstream-existing(上游既有问题),并非远端最新版结论。
二、首个错误状态与来源:旧实现的四大结构性缺陷
审计文档明确指出,错误在"发起直播请求之前"就已经产生。旧实现(parseLiveUrl修订前版本)的核心逻辑为:
- 取分享文本中的第一个 URL作为唯一候选;
- 用整串文本做
contains判断平台(而不是解析后的主机名); - 逐平台正则提取房间 ID;
- 对快手/CC 使用包含查询参数的整串末尾锚点;
- 对抖音
www空路径直接调用pathSegments.last。
从源码结构看,这些做法在四个层面埋下错误:
| 层面 | 旧行为 | 典型后果 |
|---|---|---|
| URL 提取 | 只看第一个正则命中 | 首个无关链接会遮蔽后面的 AcFun 房间 |
| 平台判定 | 整串contains,不看主机 | 相似域名、未知主机查询中嵌入的虎牙地址、userinfo 被误判 |
| ID 提取 | 逐平台正则 + 整串末尾锚点 | 快手/CC 带查询参数时得到空 ID;虎牙连字符被截断;Twitch 片段进入 ID |
| 空路径 | 直接pathSegments.last | 抖音www空路径抛出Bad state: No element |
本分支新增的 AcFun 解析
与旧逻辑不同,本分支新增的 AcFun 解析直接复用WebSearchRoomParser(见 web_search_room_parser.dart),并保留三条隔离规则:
- 非零 UID:AcFun 房间 ID 使用
RegExp(r'^[1-9][0-9]{0,19}$'),拒绝0开头; - 严格路径:仅接受
live.acfun.cn/live/{id}这种两段路径形态; - 作者页隔离:
live.acfun.cn/{作者}这类作者主页不视为房间链接。
三、红测复现:27 个离线用例中的 18 个失败
修订前先以确定性离线用例做红测(red test),记录20260906T234546780Z-quality-focused.json:27 个离线用例中 9 通过、18 失败,耗时 165.301 秒,结束时活跃重型进程 0。日志保存在local-artifacts/live-url-parser-red-20260907.log。
审计特别强调:18 是失败用例数,不是独立 Bug 数——同一根因可能覆盖多个用例。
红测复现的失败模式可归为三组:
第一组:协议/主机形态识别失败
- 大写协议/主机未识别(如
HTTPS://、WWW.); - 虎牙链接中的连字符被截断,ID 提取不完整;
- 快手链接带查询参数、ID 含下划线/连字符时得到空 ID;
- CC 链接带查询参数得到空 ID;
- Twitch 链接的片段(fragment)部分进入房间 ID。
第二组:链接定位与误判
- 首个无关链接遮蔽后面的 AcFun 房间;
- 相似域名被误判(子域边界判断不严谨);
- 未知主机的查询参数里嵌入的虎牙地址被当成第二个链接提取;
- 含 userinfo(如
https://user:pass@host/...)的输入被误判; - 伪装虎牙后缀的地址得到空 ID 与平台的半有效结果。
第三组:空路径与非法输入
- 抖音
www空路径抛出Bad state: No element(对应旧实现pathSegments.last); - Twitch directory、虎牙 search、FTP 输入未被正确排除;
- 其他平台的空路径返回半有效结果。
四、修订方案:先提取完整 URL,再核对主机与路径
修订后的核心思想是:剪贴板识别和手动解析共用同一套 HTTP(S) URI 提取逻辑;先分离出完整 URL,再核对主机及路径,绝不从未知主机的查询参数里提取第二个嵌套链接。
4.1 统一 URI 提取:sharedHttpUrls
修订后新增静态方法sharedHttpUrls/sharedHttpUris(见 live_url_tool.dart),取代旧的"第一个正则命中":
// 提取完整 HTTP URL 后再检查 host/path,避免把 FTP URL 内嵌的 // www 地址误当成第二个 HTTP 链接。 static Iterable<Uri> sharedHttpUris(String text) => sharedHttpUrls(text).map(Uri.parse); static Iterable<String> sharedHttpUrls(String text) sync* { final urls = RegExp(r'(?:[a-z][a-z0-9+.-]*://|www\.)[^\s<>]+', caseSensitive: false); for (final match in urls.allMatches(text)) { var candidate = match.group(0)!; if (candidate.toLowerCase().startsWith('www.')) candidate = 'https://$candidate'; // ... 尾部标点清理(含 XHS/微博中文散文粘连处理)... final uri = Uri.tryParse(candidate); if (uri == null || uri.userInfo.isNotEmpty || uri.host.isEmpty || (uri.scheme != 'http' && uri.scheme != 'https')) { continue; // 排除 userinfo、非 HTTP(S)、空主机 } // Uri.tryParse 校验百分号转义语法,但解码后的访问器仍可能因 // 非法 UTF-8(如 /%FF)抛出 FormatException,逐层排除。 try { uri.pathSegments; uri.queryParametersAll; } on FormatException { continue; } yield candidate; } }这套提取器解决了红测中的多类问题:
- 支持无协议
www.开头:自动补https://前缀; - 支持分享末尾标点:对
,。!?、;:)》」』”’及 ASCII 标点统一清理尾部; - 排除 userinfo、非 HTTP(S)、空主机/空路径与畸形路径编码(
/%FF这类非法 UTF-8 会被pathSegments/queryParametersAll的FormatException挡住); - 不从未知主机的查询参数里提取第二个嵌套链接——
FTP://...www...这类场景不再产生误提取; - 对小红书(xhslink.com / xiaohongshu.com)和微博(weibo.com)分享保留特殊的中文散文粘连处理:在中文标点处截断并清理尾部标点,同时保留
/.、/..这类属于 URL 结构而非散文标点的路径组件。
4.2 主机判断与 ID 边界
修订后的主机判断不再依赖整串contains,而是按完整根域或点分隔子域判断(_hostIs(host, root)等价于host == root || host.endsWith('.$root')),且查询/片段不参与房间 ID:
static bool _hostIs(String host, String root) => host == root || host.endsWith('.$root');抖音www空路径的崩溃也得到修复:_douyinWebRoomId(见 live_url_tool.dart)只接受恰好一个非空路径段且为 1~20 位数字的形态,/video/{id}(回放)与/search/{query}(结果页)的尾部数字不再被当作直播房间 ID。
4.3 按文本顺序寻找首个可解析目标
修订后的_parseLiveUrl(见 live_url_tool.dart)按文本出现顺序遍历sharedHttpUrls提取出的每个候选,逐个尝试平台解析;只有当前候选完全无法解析时才继续下一个。这样"首个无关链接不再遮蔽后续的 AcFun 房间"。
4.4 优先复用 WebSearchRoomParser 的平台规则
手动解析优先调用WebSearchRoomParser.parse(realUrl)(见 web_search_room_parser.dart),该解析器统一覆盖了 niconico、Kilakila、小红书、微博、Missevan、Inke、Picarto、TwitCasting、Showroom、CHZZK、17LIVE、LiveMe、TikTok、YouTube、Bigo、PandaLive、FC2、SteamBroadcast、京东直播、酷狗直播、百度直播、六间房、LOOK 直播,以及虎牙、抖音、斗鱼、快手、CC、bilibili、Twitch、SOOP、YY、AcFun 的网页房间形态。
保留导航词检查是其中的关键:_reservedSegments(web_search_room_parser.dart)收录了search、category、categories、directory、directories、game、games、video、videos、user、users、index、topic、topics、downloads、settings、login、signup等词。任何"平台首页/分类/搜索/档案页"的 URL,即使路径首段长得像房间 ID,也会因命中保留词而被拒绝——这直接对应红测中"Twitch directory、虎牙 search 未被正确排除"的修复。isRoomIdentifier被提取为公共方法(见 web_search_room_parser.dart),供containsSupportedLink复用,原搜索规则保持不变。
4.5 手动工具保留的兼容别名入口
在WebSearchRoomParser之外,手动解析保留了几个不暴露给网页搜索解析器的兼容入口(live_url_tool.dart):
- 斗鱼/CC 别名:
www.douyu.com(含douyu.com子域)与cc.163.com的首段; - 快手
cn域:live.kuaishou.cn/u/{id}; - SOOP
com域:sooplive.com(网页搜索解析器只认sooplive.co.kr系); - bilibili
www域:www.bilibili.com/{数字}(区别于live.bilibili.com); - 抖音回放形态:
webcast.amemv.com/douyin/webcast/reflow/{id}的reflow数字段。
这些入口均通过isRoomIdentifier校验后才能返回,半有效结果被杜绝。
4.6 翻译资产:补上 AcFun 房间格式
中英文支持说明同步补充了 AcFun 房间格式,见 assets/translations/zh.json 与 assets/translations/en.json 的toolbox_support_content:
AcFun直播: https://live.acfun.cn/live/123456审计明确提醒:示例地址只是格式说明,不是在线房间证据。
4.7 抖音的保守边界
- 抖音
www/video与webcast reflow用例只锁定已有 ID 提取行为; - 内部
room_id与web_rid的真实转换、视频页是否具备直播身份,仍待接口证据,不据此宣称播放成功。
五、回归门禁:75/75 → 93/94 → 94/94
5.1 首次修订:75/75 通过
首次修订后定向回归75/75 通过,构成如下:
| 类别 | 数量 |
|---|---|
| URL 输入 | 27 项 |
| 既有网页搜索 | 3 项 |
链接检测(containsSupportedLink) | 31 项 |
| 剪贴板/页面回归 | 14 项 |
记录20260906T234902234Z-quality-focused.json,耗时 140.818 秒,结束时活跃重型进程 0。
5.2 扩展门禁:44 项 URL 输入 + 2 项翻译说明
随后扩展为44 项 URL 输入和 2 项翻译说明检查。URL 用例通过HttpOverrides记录并阻止所有网络尝试,覆盖:
- 未知主机嵌套短链;
- 空短链路径;
- 畸形编码(非法百分号转义/非法 UTF-8);
- 多个链接的顺序与兼容别名。
全程没有请求任何真实平台。扩展门禁20260907T000046539Z-quality-focused.json为93/94,唯一失败用例是中文弯引号结尾——提取器的尾部标点集合漏了弯引号;补齐后重新复测,不跳过用例。该次flutter analyze有 1 条大括号风格提示(360.2 秒),一并补齐。
过程记录说明:该记录耗时 620.609 秒且结束时仍有 1 个活跃重型进程,不作为最终通过证据;后续
20260907T000310949Z-quality-focused.json在格式检查阶段停止,按资源守卫格式化变更文件后重跑门禁。最终证据如下。
5.3 最终源码结果
- 94/94 定向测试通过:44 项直接 URL + 2 项翻译说明 + 3 项网页搜索 + 31 项链接检测 + 14 项剪贴板/页面交互;
flutter analyze无诊断,249.3 秒;- 记录
20260907T001156727Z-quality-focused.json,基于96c5ad55的修订工作树,耗时 461.445 秒,结束时活跃重型进程 0; - 日志:
local-artifacts/live-url-parser-final3-20260907.log。
资源门禁串行运行;遇到其他项目的活跃 Java 进程时等待而非终止。审计同时声明边界:仅完成定向源码门禁,不等同全量回归、APK 或原生构建通过。
六、关键源码解读:解析调用链与超时/取消契约
6.1 从工具箱到解析器的调用链
以"跳转房间"为例,完整调用链为:
ToolBoxPage的按钮回调调用controller.jumpToRoom(text, context: context)(toolbox_controller.dart);jumpToRoom进入_runAction的ActionScope(超时默认 12 秒,见 toolbox_controller.dart);_resolve调用parseLink ?? LiveUrlTool.parseLiveUrl(text, cancelToken: scope.cancelToken);- 解析结果
[roomId, platform]需通过Sites.isSupported校验,才组装LiveRoom并跳转。
"获取直链"(getPlayUrl)走同一_resolve,随后进入LiveDirectLinkFlow.run(见 direct_link_flow.dart),依次执行getRoomDetail→discoverPlayQualities→ 用户选择清晰度 →resolvePlayUrls→ 用户选择线路 → 复制。用户选择环节不受超时限制(timed: false),只有网络/平台操作才被计时。
6.2parseLiveUrl的短链会话契约
parseLiveUrl每次调用创建一个LiveShortLinkSession(见 live_short_link_session.dart):
- 专属客户端:
clientFactory必须返回全新 Dio 客户端,绝不使用应用 HTTP 单例; - 总期限:connect/send/receive 均设为传入的
timeout(默认 12 秒),Future.timeout到期主动取消并强制session.close(); - 请求预算:单次解析最多8 次 HTTP 请求,配合已访问 URL 集合防止循环,片段(fragment)不改变网络请求身份;
- 幂等释放:成功、失败、超时都在
finally中ownedCancel.cancel()+session.close(),迟到结果由关闭状态挡住(见 live_url_tool.dart)。
这套契约正是对旧实现"b23 递归新建 Dio 无访问集合/跳数/总期限、抖音自动跟随 maxRedirects=100、临时客户端不 close"等问题的回应。需要说明:该契约属于后续 直播短链接请求边界审查(2026-09-07) 的完成范围(138/138 通过、analyze 无诊断),本批审计仅锁定直接链接解析。
七、剩余工作与回滚边界
7.1 明确未完成项
审计明确声明"整个工具箱保持未闭环":
- 下一组明确任务:b23 重定向循环/深度、相对 Location、请求超时与临时 HTTP 客户端释放、抖音短链映射(后由 短链请求边界 完成);
- 再下一组:工具箱动作重复触发、关闭后的迟到导航、画质/线路选择、取消和复制;
- 当前远端辅助函数仍保留旧递归与跳转上限设计,本批不声称这些问题已解决;
- Android/Windows 原生体验、播放/录制、全量门禁与发布另行验收;已安装的
67612cc9APK不包含本批变更。
7.2 回滚范围
回滚范围为四个源文件/资产与新增测试,无持久化格式变化:
live_url_tool.dartweb_search_room_parser.darttoolbox_controller.dart- 两份翻译资产(
assets/translations/zh.json、assets/translations/en.json) - 新增测试
回滚会恢复上文已复现的直接链接解析问题。
八、结论与后续批次状态
本批审计把 pure_live 工具箱的直播链接解析从"首链接 + 整串 contains + 逐平台正则"重构为"HTTP(S) URI 提取 + 主机/路径核对 + 文本顺序首个可解析目标"的契约,并通过 94/94 定向测试与无诊断的flutter analyze锁定行为。仓库中的当前实现(live_url_tool.dart、web_search_room_parser.dart)即为该批修订的最终落地形态;相关回归用例可参考 test/shared/platforms/xiaohongshu_link_test.dart(小红书分享文本提取的既有契约测试)以及审计日志中记录的定向门禁。
后续状态请见 直播短链接请求边界审查(2026-09-07)(完成跳转/期限/资源释放/loopback IO,138/138 通过)与 工具箱剪贴板与页面入口审查(2026-09-07)(剪贴板生命周期,65/65 通过)。真实平台条件、工具箱操作与原生验收仍独立待验——解析通过不等于平台在线、更不等于播放成功,这是整个审计链反复强调的证据边界。
- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
相关推荐
六间房直播接入合同全解析:pure_live 的链接、目录、搜索、房间与媒体契约核查
六间房直播接入合同全解析:pure_live 的链接、目录、搜索、房间与媒体契约核查 本文基于 pure_live 仓库中的 六间房直播合同检查点文档 http
音视频直播移动开发Zettlr 链接解析机制深度解析:从 Markdown 链接到绝对 URI 的完整技术实现
Zettlr 链接解析机制深度解析:从 Markdown 链接到绝对 URI 的完整技术实现 导读 在 Zettlr 这类以 Markdown 为核心的知识管理
桌面应用前端知识管理科研Pulse v6 API 契约体系解析:从接口契约到源码实现的运维监控全链路
Pulse v6 API 契约体系解析:从接口契约到源码实现的运维监控全链路 Pulse 是一个自托管的实时监控看板,覆盖 Proxmox VE、PBS、Doc
可观测性运维后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考