- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
本文基于 pure_live 仓库中的搜索能力登记审计文档(
docs/SEARCH_REGISTRY_COVERAGE_AUDIT_2026_09_23.md),结合lib/domains/live/presentation/search/下的能力合同实现、Sites平台注册表与 HLS 解析链源码,完整还原本次「46 平台 vs 32 平台」能力登记缺口的发现过程、14 个站点的能力补齐决策、批量回归中暴露的三个相邻缺陷及其修复方式,并给出可验证的源码依据。读者可借此掌握 pure_live 搜索页「能力登记 → 控制器分派 → UI 文案与按钮」这条完整链路的运作机制,以及如何用能力合同表驱动平台搜索的覆盖声明。
1. 背景:能力登记与注册表之间的 14 个缺口
pure_live 的搜索页并非每个接入平台都默认具备原生搜索。它依赖两张静态表协同工作:
- 平台注册表:
Sites.supportedSiteIds(lib/domains/live/data/platforms/sites.dart)声明当前受支持并可构建适配器的站点 ID; - 能力合同表:
LiveSearchCapabilities._byPlatform(lib/domains/live/presentation/search/search_capability.dart)逐平台声明「原生搜索覆盖范围 + 是否支持分页 + 是否支持网页搜索」。
审计以这两张表对照,发现:注册适配器共46个,而能力表当时只登记了32个;缺失的14个站点其实都已在各自适配器中实现了searchRoomsCancellable(可取消原生搜索),却因未登记而被搜索页按「未知平台默认值」处理——即被判成webOnly网页搜索,统一走通用百度链接。这导致真实能力与 UI 表现脱节。
更直接的回归红灯来自 Shopee Live:能力预期应为liveAndOffline(原生搜索可返回直播中与未开播内容),实际却显示为webOnly。这正是能力表缺登记造成的「明明能搜、却被迫跳网页」的典型表现。
从当前仓库源码看,本次补齐的成果仍保留在能力表中:fc2live、steambroadcast、jdlive、kugoulive、baidulive、sixroom、looklive等站点现已在_byPlatform中完成登记(search_capability.dart);而 Shopee Live、NimoTV、Rumble、GoodGame、VK Video Live、Dailymotion、淘宝直播等站点,从 sites.dart 的retiredSiteIds可以看到后续已按 3.2.8 / 3.2.11 的维护策略退役,但被保存的收藏、历史与分享链接仍会以「retired」只读入口呈现,而非当作未知链接忽略。
2. 能力合同体系:NativeSearchCoverage与LiveSearchCapability
理解本次补齐的前提,是先看懂能力合同的数据结构。核心定义位于 lib/domains/live/presentation/search/search_capability.dart:
enum NativeSearchCoverage { liveOnly, // 原生接口只返回正在直播的房间 liveAndOffline, // 返回直播中及部分未开播房间 channelLookup, // 支持精确频道 ID / 频道直播链接(可含离线频道) roomLookup, // 支持精确场次/房间 ID / 官方直播链接(可含已结束场次) showcaseSnapshot, // 精确 UID/官方房间链接 + 公开推荐页内昵称过滤 localChannels, // 本机已导入频道(IPTV),不参与网络主播搜索 webOnly, // 只有网页搜索入口 unavailable, // 未接入搜索 } class LiveSearchCapability { const LiveSearchCapability({required this.coverage, required this.supportsPagination, this.supportsWebSearch = true}); final NativeSearchCoverage coverage; final bool supportsPagination; final bool supportsWebSearch; bool get supportsNativeSearch => coverage != NativeSearchCoverage.webOnly && coverage != NativeSearchCoverage.unavailable; bool get mayIncludeOffline => coverage == NativeSearchCoverage.liveAndOffline || coverage == NativeSearchCoverage.channelLookup || coverage == NativeSearchCoverage.roomLookup; }几个关键语义:
supportsNativeSearch是控制器筛选「可走原生搜索站点」的唯一边界:只要不是webOnly或unavailable,就进入原生搜索分派;supportsPagination决定是否允许下拉加载后续页,由_canLoadAnotherPage结合适配器是否实现LiveSearchPaginationPolicy与「停滞页上限」共同裁决;supportsWebSearch决定搜索页是否展示「继续网页搜索」按钮;mayIncludeOffline只是对语义的便捷归纳,供审计与文案引用。
查找逻辑是兜底式的(search_capability.dart):
static const LiveSearchCapability _unknown = LiveSearchCapability( coverage: NativeSearchCoverage.webOnly, supportsPagination: false, ); static LiveSearchCapability forPlatform(String id) => _byPlatform[id.toLowerCase()] ?? _unknown;_unknown正是审计发现的「缺口放大器」:任何未登记平台都会静默落入webOnly且禁分页,随后被buildSearchUrl的default分支导向通用百度链接(search_controller.dart)。因此「登记」不只是文档问题,而是直接影响用户搜索路径的真实分派开关。
3. 本批补齐的 14 个站点:三张能力合同
审计按能力合同把 14 个站点分成三批登记,表格如下(忠实继承原文档):
| 能力合同 | 本批补齐的平台 |
|---|---|
| 当前快照/目录匹配及精确查询;可能返回未开播、受限或未知状态 | Shopee Live、NimoTV、Rumble、GoodGame、FC2 Live、Steam Broadcasts、京东直播、酷狗直播、六间房直播 |
| 原生搜索及频道/场次定位,保留平台页码或游标 | VK Video Live、Dailymotion |
| 精确场次/房间与有限推荐页匹配 | 淘宝直播、百度直播、LOOK 直播 |
每个平台的具体取值,以当前仓库仍保留的登记为例:
fc2live:liveAndOffline + supportsPagination: true + 关闭网页搜索(search_capability.dart)。其适配器在 fc2_site.dart 实现了可取消搜索:先尝试把关键词解析为精确频道 ID(Fc2Link.parseChannelId),命中则单房间返回;否则在_directory()目录快照内按频道 ID、用户名、标题、分类名做本地匹配并本地分页;steambroadcast:liveAndOffline + supportsPagination: true(search_capability.dart);jdlive、kugoulive:liveAndOffline + supportsPagination: true(search_capability.dart);baidulive:roomLookup + supportsPagination: false(search_capability.dart),即只支持精确房间定位,不提供搜索后续页;sixroom:liveAndOffline + supportsPagination: false(search_capability.dart);looklive:roomLookup + supportsPagination: false(search_capability.dart)。
分页布尔值逐项与实现核对的结果同样在文档中固化下来,可当作验收基准:
- 支持后续页或有限快照本地页:VK、Nimo、Dailymotion、Rumble、GoodGame、FC2、Steam、京东、酷狗;
- 不提供搜索后续页:Shopee、淘宝、百度、六间房、LOOK。
此外,14 项的supportsWebSearch全部关闭,避免再出现「官方站点搜索按钮实际把用户丢给百度」的误导。Dailymotion 有一个值得注意的语义细节:关键词搜索返回的是当前直播,但精确live-videoID 可以返回已结束的直播,因此整体覆盖被标为「可含离线」(liveAndOffline),而不是简单的liveOnly。
4. 从登记到 UI:搜索控制器的能力分派链
能力表登记完成后,真正消费它的是 lib/domains/live/presentation/search/search_controller.dart 中的SearchController。整条链路如下。
4.1 可搜索站点筛选
_searchPage在每次发起搜索前,用forPlatform过滤出支持原生搜索的站点(search_controller.dart):
final searchableSites = selectedSites.where((site) { final capability = LiveSearchCapabilities.forPlatform(site.id); return capability.supportsNativeSearch && (!append || (_hasMoreByPlatform[site.id] ?? true)); }).toList();如果某单个平台不可原生搜索,则根据其能力给出明确文案:supportsWebSearch为真时提示「当前使用网页搜索入口」,否则提示「暂未接入搜索」(search_controller.dart)。
4.2 并发与超时边界
搜索控制器的几个硬边界常量(search_controller.dart):
liveSearchRequestTimeout = Duration(seconds: 12):单个平台的搜索请求超时;maxConsecutiveStagnantSearchPages = 2:搜索接口常有一页重复的边界重叠,允许连续两页无新增后停止继续翻页,防止「同一载荷刷到底」;maxConcurrentNativeSearchSites = 12:全部站点搜索时并发上限,_searchSitesBounded用固定槽位 +Future.any实现「完成即补位、先完成先渲染」,不让最慢平台拖住整个结果网格。
4.3 取消与旧接口兼容
适配器通过 lib/shared/platforms/live_search.dart 的两个可选接口表达能力:
LiveCancellableSearch:实现searchRoomsCancellable(keyword, {page, pageSize, cancel}),把 DioCancelToken透传给底层 HTTP;LiveSearchPaginationPolicy:实现supportsSearchPaginationFor(keyword),应对「精确查询单结果 + 关键词分页源」混合型适配器。
扩展方法searchRoomsWithCancellation(live_search.dart)对旧式searchRooms做兼容:实现可取消接口的直接等待;未实现的用Future.any同时监听请求与取消信号,超时到达时抛TimeoutException('Native search deadline', requestTimeout)。
4.4 能力文案与网页搜索按钮
capabilityText(search_controller.dart)按当前 tab 生成用户可见的覆盖说明,对应本地化文案集中在 assets/translations/zh.json:
search_coverage_all_native:「App 内原生搜索覆盖全部 {total} 个平台;未开播结果仅在平台接口返回时显示。」;search_coverage_all:原生覆盖 {native}/{total},并点名仍走网页搜索的站点;search_coverage_live_and_offline/search_coverage_live_only:分别描述「含部分未开播」与「仅直播中」。
canOpenWebSearch(search_controller.dart)与openWebSearch(search_controller.dart)决定「继续网页搜索」按钮的可见性与目标 URL:Linux 走外部浏览器,Windows 依赖 WebView2(缺失时弹下载引导),其余平台进入应用内WebSearchController(lib/domains/live/presentation/search/web_search_controller.dart),它会在浏览过程中用WebSearchRoomParser识别受支持平台的房间链接,弹确认后直接接管播放。
5. 批量回归发现的相邻缺陷与修复
补齐登记后,批量回归不只是验证搜索,还顺带暴露了三个「相邻」缺陷。这正是审计文档的实战价值所在。
5.1 Rumble:HLS master 多行文本被错误折叠
症状:Rumble 浏览器返回的 HLS master 是多行文本;此前_parseMedia调用通用字符串归一化_string,把全部空白(含换行)折叠为单个空格,导致parseMaster读不到任何#EXT-X-STREAM-INF行,最终报mediaUnavailable。
修复原则:对playlist保留原文,仅校验其类型;真正的逐行解析交给下游解析器,由它们继续执行 512 KiB 体积上限、媒体主机与路径验证。
当前仓库印证(Rumble 已退役后,同类约定仍可在这条解析链上看到):
- lib/domains/live/data/stream/playback_manifest_probe.dart:
PlaybackManifestProbe以maximumBytes = 512 * 1024限制体积,_stripBom去除 BOM 后只做classifyHlsManifest(body)分类,body 原文原样保留,不再做空白折叠; - lib/shared/platforms/steambroadcast/steam_broadcast_api.dart:
validateMaster对 master 逐行处理,先确认#EXTM3U前缀与 1 MiB 上限,再逐行扫描#EXT-X-STREAM-INF/#EXT-X-MEDIA,对其后紧跟的 URI 校验 https、媒体主机与 Steam 广播路径结构; - lib/core/stream/hls_master_selection.dart:
HlsMasterPlaylist.parse用LineSplitter逐行解析,对变体数、版本号、分辨率、帧率、码率、音频组、循环/重叠 URI 都做失败即关闭(fail-closed)的严格校验。
5.2 Dailymotion:未知观看数从null改为空字符串
Dailymotion 在「没有当前观看数」时使用空字符串表示未知,而旧断言要求null,导致测试与实现语义不一致。修订断言并新增「精确已结束 live-video 返回离线结果」的回归用例,把第 3 节提到的liveAndOffline覆盖语义固化进测试。
5.3 FC2:门票房间不被误过滤
FC2 的门票房间按适配器合同仍显示为「受限卡片」(Fc2State.restricted会触发fc2live_access_restricted提示,见 fc2_site.dart);只有 open-chat 的type=0才被排除。旧测试把门票卡片误计为「已过滤」,现对齐为保留 3 张卡片并维持受限断言,使测试与适配器合同一致。
5.4 快手:原生搜索仍为空,保持网页入口
快手适配器虽然实现了searchRooms(kuaishou_site.dart),但上游网页搜索页本身不列出房间(源码注释引用 upstream #881),其live_api/search/author返回的是主播列表而非房间流,因此本次审计结论是:快手的原生搜索结果仍为空,继续保持既有网页入口。当前仓库中快手在能力表中登记为liveAndOffline + supportsPagination: true(search_capability.dart),同时buildSearchUrl仍保留https://live.kuaishou.com/search?keyword=$q网页地址(search_controller.dart),两种入口并存。
6. 门禁与边界:回归数字与验收范围
审计文档给出了完整的门禁口径,供后续任何搜索相关改动参照:
- 搜索能力/Widget 首批:83/83 全部通过;
- 14 站适配器首批:72 PASS / 4 FAIL(4 个失败即上文相邻缺陷所引发);
- 修订及新增 Dailymotion 离线夹具后:19 文件集中回归187/187全部通过;
- 静态诊断:最终定向
Analyze无诊断输出。
测试覆盖证明的对象是:源码分派(能力表 → 控制器筛选)、输入合同(searchRoomsCancellable参数语义)、关键 UI 动作(网页搜索按钮、覆盖文案)与确定性平台夹具。审计同时明确声明边界:各平台「当前生产可达性、媒体解码、Android/Windows 原生交互」仍按验收矩阵单独闭环,本批未操作设备,即不把本批测试结果当作设备级回归结论。
7. 结论与可验证路径
这次审计把「能力存在」与「能力被 UI 承认」两件事对齐了:
- 任何新接入平台都必须先登记能力合同,否则会被
_unknown兜底成webOnly,用户看到的只会是百度网页搜索; - 能力登记必须逐字段与适配器实现核对(覆盖类型、分页、网页搜索开关),Shopee 的
liveAndOffline预期 vswebOnly实际就是缺核对的反例; - 批量回归的价值不止于搜索本身——Rumble 的
mediaUnavailable、Dailymotion 的空字符串语义、FC2 的门票卡片计数,都是在补齐过程中被一同揪出的真实缺陷。
仓库内可继续深入的关键路径:
- 能力合同定义与登记表:lib/domains/live/presentation/search/search_capability.dart
- 能力消费与 UI 分派:lib/domains/live/presentation/search/search_controller.dart、lib/domains/live/presentation/search/search_page.dart
- 平台注册表与退役表:lib/domains/live/data/platforms/sites.dart
- 可取消搜索与分页策略接口:lib/shared/platforms/live_search.dart
- HLS 校验链:lib/domains/live/data/stream/playback_manifest_probe.dart、lib/core/stream/hls_master_selection.dart、lib/shared/platforms/steambroadcast/steam_broadcast_api.dart
- 能力文案本地化:assets/translations/zh.json
验证方式上,可在仓库根目录对上述lib路径执行flutter analyze确认静态诊断干净,并按能力表逐平台构造关键词夹具执行原生搜索回归;若后续在受控环境接入设备,再按验收矩阵补充生产可达、媒体解码与 Android/Windows 原生交互的设备级闭环,即可完整复现本审计的门禁口径。
- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
相关推荐
pure_live 搜索页可达布局与能力动作审计:62 项 Widget 测试驱动的溢出修复与平台能力对齐
pure_live 搜索页可达布局与能力动作审计:62 项 Widget 测试驱动的溢出修复与平台能力对齐 本文基于仓库审计文档 SEARCH_PAGE_LAY
音视频直播移动开发Cypress搜索功能测试:智能搜索场景覆盖
Cypress搜索功能测试:智能搜索场景覆盖 引言:为什么搜索功能测试如此重要? 在现代Web应用中,搜索功能已成为用户交互的核心环节。无论是电商平台、内容管理
测试质量保障前端接口测试SGLang Lightning Attention 能力矩阵与 seg_la 线性注意力测试覆盖深度解析
SGLang Lightning Attention 能力矩阵与 seg_la 线性注意力测试覆盖深度解析 本篇指南以 SGLang 仓库中 test/regi
模型推理服务推理引擎人工智能大模型本地部署多模态
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考