ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

pure_live 搜索能力登记全覆盖审计:46 平台搜索矩阵、14 站点能力补齐与 Rumble HLS 解析修复

pure_live 搜索能力登记全覆盖审计:46 平台搜索矩阵、14 站点能力补齐与 Rumble HLS 解析修复
  • 音视频
  • 直播
  • 移动开发

【免费下载链接】pure_live

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

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

本文基于 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 承认」两件事对齐了:

  1. 任何新接入平台都必须先登记能力合同,否则会被_unknown兜底成webOnly,用户看到的只会是百度网页搜索;
  2. 能力登记必须逐字段与适配器实现核对(覆盖类型、分页、网页搜索开关),Shopee 的liveAndOffline预期 vswebOnly实际就是缺核对的反例;
  3. 批量回归的价值不止于搜索本身——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自定义源应有尽有。

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

相关推荐

上一篇:GPU显存终极压力测试:用memtest_vulkan深度诊断你的显卡健康
下一篇:如何用免费工具掌握德州扑克的科学打法?TexasSolver为你揭秘

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进