机制详解:停止生命周期的冻结、准入与跨轨尾部对齐实现)
音视频直播移动开发【免费下载链接】pure_live纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。项目地址https://gitcode.com/gh_mirrors/pur/pure_live点击查看免费下载本文围绕 Pure Live 开源直播录制工程中 HLS 预取prefetch停止阶段的核心机制展开详细讲解已发布分片有限排空published drain的设计契约、调度器与池的实现、20 秒停止上限的计算以及原生录制端如何通过确定性 HTTP 复现与解码验证来证明停止即补全而非裁掉音频伪造对齐。读完本文你将掌握 FFmpeg HLS 录制在 opt-in 预取模式下如何冻结清单、结算已准入依赖、提前结束下载阶段并保证音视频尾部同步。一、背景为什么停止尾部会成为问题Pure Live 的录制模块通过本地 relay 把上游 HLS 输入改写后交付给 FFmpeg 原生解复用器。在启用并行预取prefetch之前旧式按需暂存on-demand staging在停止时存在一个已知缺陷冻结清单中包含了尚未完成下载的视频分片而 relay 仍在一个 target duration 之后关闭全部上游下载导致录制停止后视频提前结束、音频拖尾。相关审计文档 HLS_PRODUCTION_PREFETCH_AUDIT_2026_09_09.md 记录了两个真实 native 场景的复现结果两场景都连续交付视频序号 0–13但停止后视频约在 28 秒结束音频约 36/34 秒tailDiscardedtrue。也就是说旧规则用按需暂存的截止时间去约束并行预取的已发布依赖两者并不匹配。本文对应的提交批次HLS_PUBLISHED_DRAIN_AUDIT_2026_09_09.md源码提交56b139f39e2c394fb555a49cb179c19a50aaa859只处理opt-in 预取停止生命周期保留默认关闭、原有非预取停止规则与用户数据不变也不归因于上游或擅自同步上游。二、停止合同冻结、结算、提前结束本批审计把停止行为收敛为四条明确契约全部可在 hls_prefetch_scheduler.dart 与 ffmpeg_hls_input_relay.dart 中找到对应实现。2.1 立即冻结不刷新、不准入、不重试停止的第一动作是冻结freeze各路最后实际发布的清单与本地 URI 立即定格停止后不再刷新清单、不准入新资源、不重试失败分片。对应实现是调度器的freeze()取消各路刷新定时器与刷新取消令牌feed.timer?.cancel()、feed.refreshCancellation?.cancel()为每条 feed 生成finishedManifest若从未发布过则生成一个含#EXT-X-ENDLIST的空清单若已发布则在最后发布清单末尾追加#EXT-X-ENDLIST但不会改写已包含 ENDLIST 的缓存从最后发布的代次feed.published.first中收集尚未交付sequence feed.delivered - 1的分片及其 MAP/KEY 依赖写入feed.wanted作为冻结后的唯一待结算集合。关键点在于立即冻结各路最后实际发布的清单与本地 URI——源码注释明确指出快照的是实际提供的 URI 映射而不是停止后后台刷新的更新元数据停止之后也不会对从未发布的 feed 触发任何回调。2.2 只结算池内已准入 ticketfreeze()之后drainPublished()只等待池内已准入 ticket对应的readyFutureFuturebool drainPublished({required Duration timeout}) { if (timeout Duration.zero || timeout const Duration(seconds: 20)) { throw ArgumentError(Invalid HLS published download drain timeout); } return _draining ?? _drainPublished(timeout); }_drainPublished遍历所有有发布记录的 feed从最后发布代次中收集sequence feed.delivered的依赖 key然后Future.wait所有对应 ticket 的ready整体以timeout封顶。两条重要规则缺少准入不新建下载_items[key]?.$2.ready ?? Future.value(false)—— 如果某个依赖因容量已满而从未获得 ticket直接视为未完成返回 false绝不临时扩大池容量去补下载幂等_draining ??保证多次调用只结算一次。2.3 完整体封口即提前结束下载阶段排空的结束条件是完整体封口ready全部为 true而不是固定睡满一个延时。一旦所有已准入依赖封口finish()立即进入下载阶段收尾_stopFetching()会调用pool.evict退休未就绪条目、停止上游客户端、取消 fetch aborter 与连接。整个 download phase 结束后已就绪的 MAP/KEY/媒体仍从就绪缓存交付不重新下载。这从源码结构看正是待结束集合与实际完成事件均明确的体现drainPublished返回的是布尔值表述缓存完整度而不是 native 消费确认返回 false 即记录尾部丢弃_inputTailDiscarded true即使 native 之后没有再请求那个缺失分片也不会在用户停止之后另发活动缺片警告。2.4 预算原响应总预算 独立 20 秒停止上限下载阶段的时间预算由两部分叠加原响应总预算每个 HLS 完整响应至多获得 4 个空闲间隔HlsResponseBudget.totalFor(idle) idle * 4见 hls_body_reader.dart原有单请求空闲/总时限继续有效独立 20 秒停止上限_prefetchDownloadGrace将总预算截断在 20 秒内clamp(1, 20000)毫秒drainPublished的参数校验同样拒绝超过 20 秒的 timeout。超时发生后finally分支调用stopFetching()只退休retire实际请求pool/close 继续持有并等待迟到响应清理绝不 abandon 一个仍存活的上游 Future。对于 native 既有清单重载与封装排空时间另行保留。默认target2秒时drainTimeout的计算为(2 * 2 2).clamp(3, 20)秒加上_prefetchDownloadGrace即总上限 26 秒而非旧式 6 秒。文档特别强调这是最大等待预算不是所有停止操作的固定耗时——完整输入实际完成后即可提前结束。// ffmpeg_hls_input_relay.dart Duration get drainTimeout Duration(seconds: (2 * _targetSeconds 2).clamp(3, 20)) (_prefetch null ? Duration.zero : _prefetchDownloadGrace); Duration get _prefetchDownloadGrace Duration(milliseconds: HlsResponseBudget.totalFor(_bodyIdleTimeout).inMilliseconds.clamp(1, 20000));relay 的finish()仅在drainOnStop模式下生效预取调度器存在时走drainPublished否则沿用旧式_finishTimer Timer(...)单 target duration 规则。三、调度器排空与池所有权的底层实现3.1 冻结后的依赖图冻结期间wanted集合由_dependencies()展开hls_prefetch_scheduler.dart每个分片依次产出其 KEY 依赖、所属 initializationMAP依赖与自身媒体资源segment.gap标记的分片不产出媒体下载。HlsPrefetchResource的三类资源media/initialization/key各自以完整 cache key 标识——key 由jsonEncode([media, feedId, sequence, uri, range.identity])等构成而非仅上游 URI保证同 URI 不同范围的资源不会互相顶替。3.2 停止时的所有权转移stopFetching()与close()的分工是本批的关键设计drainPublished超时后stopFetching()把未就绪条目evict到调度器自身_own(pool.evict(...))evict会_retire条目并等待其disposedrelay 的_close()顺序为置_closed→_stopFetching()→ 关闭本地 HTTP server → 等待全部 handler →await _prefetchDrain等排空 Future 收敛→await _prefetch?.close()池关闭→_connections.settled。close 语义是先结束本地 writer、等处理器释放租约再关闭池与连接所有者。pool 的close()会把所有 owned 条目逐一_retire再await全部disposed而_retire只标记与移除注册_maybeDispose会等_loading结束、_readers 0才真正释放 spool 与字节计数。因此pool/close 继续持有并等待迟到响应清理是一句可验证的实现事实。3.3 停止时的池状态可观测relay 暴露prefetchFeedCount、prefetchBodyCount、prefetchBytes三个 getterffmpeg_hls_input_relay.dart分别映射到调度器的feedCount、池的ownedEntries与retainedBytes。审计中的停止时池 17 条目、关闭后路/条目/字节全零正是由这些观测点取得。调度器还提供describeDownload(key)返回admitted与 ticket 的诊断快照ready/retired/disposed/failure用于_diagnosePrefetchDownloads在两个阶段stop-requested、downloads-ended留证。四、确定性复现两个慢响应 HTTP 场景旧缺陷的复现依赖真实平台、难以稳定重放因此本批新增两个确定性 HTTP 复现分别延迟响应头headers与响应体body请求先进入 relay请求已确认随后用户停止超过 target duration 后本地夹具才释放完整媒体以published-tail-red命名的红测中234 PASS / 2 FAIL两个 FAIL 均为期望 200、实际 410而不是夹具等待超时——说明在冻结清单中仍存在尚未完成的分片native 请求时被拒绝410 Gone。410 的来源可追溯到 hls_relay_prefetch.dart_servePrefetchBody中acquire返回 null 时若处于_finishing状态则置_inputTailDiscarded true并回_prefetchFailures[resource.key] ?? (_finishing ? 410 : 503)。红测证明旧实现确实在停止时把未完成的已发布分片直接判为不可得从而裁掉视频尾部。修复后的published-tail-fixed阶段两场景均通过场景TS 字节视频完整序号视频/音频包停止耗时 msbody 12 秒4,244,1000–171080 / 16888530headers 12 秒4,244,1000–171080 / 16888205对比上一批视频 0–13、约 28 秒、跨轨尾差 6–8 秒本批补齐了最后已发布的视频分片0–17约 36 秒音视频起点包时间差 21.033 ms、终点包时间差 1.633 ms视频最大包步长 33.334 ms、音频 21.334 ms。停止耗时由约 2.1 秒增至 8.2–8.5 秒但仍早于本场景 26 秒预算且两场景code0、inputDrainedtrue、forcedCancelfalsetailDiscarded/ 活动覆盖缺口 / 完整性错误均为 false。在 tool/probes/hls_scheduled_delivery_probe.dart 中可以看到与这些数字直接对应的原生探针断言if (production) { expect(prefetch[entriesAtStop] as int, lessThanOrEqualTo(32)); expect(terminal[inputTailDiscarded], false); final tracks inspection[tracks] as List; final video tracks.singleWhere((t) (t as Map)[type] video) as Map; final audio tracks.singleWhere((t) (t as Map)[type] audio) as Map; expect(((video[firstPts] as num) - (audio[firstPts] as num)).abs(), lessThan(0.05)); expect(((video[lastPts] as num) - (audio[lastPts] as num)).abs(), lessThan(0.05)); }探针还断言inputCoverageIncompletefalse、receivedVideoSequenceGapsfalse、completedUpstreamVideoRequests 10、prefetch[refreshBeforeFirstVideoComplete] true首个慢视频完成前已继续刷新清单以及关闭后entries 0、bytes 0。五、验证矩阵独立核对、全量解码与资源记录5.1 不依赖 tailDiscarded 字段的独立核对除调度器与 native 探针外本批对hls-timeline.json做了独立核对停止时两路冻结的最后一代均为 10–17每个被提供的分片 ID 均找到 GET 200、完整 body 与完成发送记录局部 HTTP 失败数为 0结果保存为frozen-generation-delivery-check.json。这避免了两个陷阱只信tailDiscarded字段而不核对实际 GET 结果把 HTTP 写完直接当作 decoder 消费——后者由实际输出包与完整解码full decode另行支撑。5.2 固定 CLI 全量解码与哈希一致性两份 TS 用固定 CLI-v error -xerror -map 0:v:0 -map 0:a:0 -f null全量解码均退出 0、错误文本为空两份 SHA-256 一致B0B497D8BAB72608335AC52AC89364C63B2FD0E5FE9CA861F98317C80A0C1BCB。仍保留 native 结束时的解复用 I/O 与封装线程诊断不把录制与解码通过写成日志零诊断——这与审计一贯的措辞纪律一致。5.3 资源记录资源记录前缀 / 阶段秒含排队峰值 CPU %峰值工作集 B结束活跃重型进程20260909T132540329Z / published-tail-red131.3889.245895565312020260909T133234637Z / published-tail-fixed296.67373.466814035968020260909T133306277Z / decode7.4890.0645175152640记录位于local-artifacts/build-records/固定夹具、FFmpegKit DLL、ffprobe 和 CLI 本地哈希均由脚本验证红测阶段 hook 的远端 ZIP SHA 校验通过、最终原生阶段 hook 未取得 SHA二者分别记录不混成统一校验结果。所有执行句柄均取得终态没有重启/终止其他任务。六、接入范围与剩余项本批实现贯穿三层调用链RecorderControllerrecorder_controller.dart每次直播 URL 尝试调用ffmpeg.start(..., hlsPrefetch: true, ...)owned 输入自带 relayFFmpegServiceffmpeg_service.darthlsPrefetch默认 false透传给startForArguments(enablePrefetch: hlsPrefetch)FFmpegManagerffmpeg_manager.dart构造 relay 时prefetchEnabled: enablePrefetch drainOnStop。预取仅对录制drainOnStop模式可用当前默认关闭不改变所有用户默认行为回滚可撤销本批源码提交不涉及数据格式或配置迁移。尚未闭环的项包括默认启用、完整 BYTERANGE/native 兼容、真实 TTing/LL-HLS 与全平台/UI/功能验收以及稳定 3.2.0 发布宏观保持20 PASS / 32 RUN / 10 NOT RUN42 项未闭环。本批没有手机操作、安装、构建或发布。七、小结HLS 已发布分片有限排空把停止从一次武断的延时等待重构为三段可验证的流水冻结最后发布的清单、URI 与依赖代次立即定格不刷新、不准入、不重试结算只等待池内已准入 ticket 封口缺准入即明确返回未完成完整体封口立即结束下载阶段而非固定睡满一个延时收尾超时主动取消实际请求pool/close 继续持有并等待迟到响应返回值表述缓存完整度而非 native 消费确认失败即记录尾部丢弃。结合确定性 HTTP 复现、独立冻结代次核对与固定 CLI 全量解码本批证明补齐最后已发布的视频可以做到与音频尾部对齐终点包时间差 1.633 ms而不是裁掉音频伪造对齐——这是 Pure Live HLS 录制走向默认启用预取前停止生命周期中一次关键的契约收敛。赞分享音视频直播移动开发【免费下载链接】pure_live纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。项目地址https://gitcode.com/gh_mirrors/pur/pure_live点击查看免费下载相关推荐npm stop 命令详解停止包运行的生命周期脚本机制npm stop 命令详解停止包运行的生命周期脚本机制 本文围绕 npm CLI 的 npm stop 命令展开说明它如何通过 package.json 中开发工具包管理器CLITelepresence compose rm 命令详解清理已停止的服务容器与 Compose 项目生命周期管理Telepresence compose rm 命令详解清理已停止的服务容器与 Compose 项目生命周期管理 导读 telepresence compos云原生开发工具微服务网络ClawX 有序退出生命周期解析并发停止 ACP、Gateway 与 Computer Use 的实现与测试验证ClawX 有序退出生命周期解析并发停止 ACP、Gateway 与 Computer Use 的实现与测试验证 本文以 ClawX 仓库中的 harness人工智能AI 应用桌面应用交互助手上一篇ConvertX多服务器部署实现高可用性架构下一篇PT 助手 Plus 文件路径处理PathHandler 自定义下载目录策略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考