From 0996ba45ec3ff271fe42d795c985fe2887d53a77 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Fri, 21 Aug 2026 20:30:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=AD=E8=A7=A3=E7=A0=81=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=20+=20=E5=B8=A7=E9=98=9F=E5=88=97=E6=89=A9=E5=AE=B9?= =?UTF-8?q?=201024=20+=20=E9=87=8D=E6=92=AD/seek/destroy=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E5=B8=A7=E8=AE=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/generate/hooks/useCanvasPlayer.ts | 81 +++++++++++++++---- 1 file changed, 64 insertions(+), 17 deletions(-) diff --git a/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts b/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts index 2892be01a..1c6889240 100644 --- a/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts +++ b/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts @@ -243,9 +243,11 @@ export function useCanvasPlayer( // ── 内部引用 ── const decoderRef = useRef(null) - const frameQueueRef = useRef(new FrameQueue(600)) + const frameQueueRef = useRef(new FrameQueue(1024)) /** 已解码的片段索引集合,用于按需解码(先标记防重入,失败时移除允许重试) */ const decodedSegmentsRef = useRef(new Set()) + /** 每个片段已解码的帧数,用于 decodeAroundPosition 继续未完成解码 */ + const decodedFrameCountsRef = useRef(new Map()) /** 解码代数计数器,seek 时递增以作废正在进行的异步解码 */ const decodeGenerationRef = useRef(0) const rafRef = useRef(0) @@ -477,7 +479,12 @@ export function useCanvasPlayer( // ── 初始化 VideoDecoder 并解码指定片段 ── const decodeSegment = useCallback( - async (_buffer: ArrayBuffer, meta: SegmentMeta, maxFrames?: number): Promise => { + async ( + _buffer: ArrayBuffer, + meta: SegmentMeta, + maxFrames?: number, + startFrame: number = 0, + ): Promise => { console.log(`[DIAG_v2] >>> decodeSegment ENTER assetId=${meta.assetId}`) if (isDestroyedRef.current) return @@ -573,7 +580,8 @@ export function useCanvasPlayer( let decodedCount = 0 let skippedCount = 0 let decodeErrors = 0 - for (const sample of samplesCollected) { + for (let si = startFrame; si < samplesCollected.length; si++) { + const sample = samplesCollected[si] // seek/重播时旧的 decodeSegment 立即退出,不往帧队列推废帧 if (decodeGenerationRef.current !== gen) { console.log( @@ -642,6 +650,20 @@ export function useCanvasPlayer( `[useCanvasPlayer] Segment ${meta.assetId}: decoded ${decodedCount}, skipped ${skippedCount}, errors ${decodeErrors}, decoder.state=${decoder.state}`, ) + // 记录该片段已解码帧数(用于 decodeAroundPosition 续解码) + const totalDecoded = decodedCount + startFrame + const metaArr = segmentMetaRef.current + for (let idx = 0; idx < metaArr.length; idx++) { + if (metaArr[idx].assetId === meta.assetId) { + if (totalDecoded >= samplesCollected.length) { + decodedFrameCountsRef.current.set(idx, Infinity) + } else { + decodedFrameCountsRef.current.set(idx, totalDecoded) + } + break + } + } + // 不做 flush,每个片段独立解码器,flush 在某些 Chromium 版本下会永久挂起 console.log(`[useCanvasPlayer] Segment ${meta.assetId}: decode done, skip flush`) }, @@ -658,7 +680,6 @@ export function useCanvasPlayer( const metas = segmentMetaRef.current if (!metas || metas.length === 0) return - // 记录当前代数,seek 后代数变化则中止 const gen = decodeGenerationRef.current let targetIdx = -1 @@ -673,27 +694,48 @@ export function useCanvasPlayer( } if (targetIdx === -1) targetIdx = metas.length - 1 - for ( - let i = Math.max(0, targetIdx - 1); - i <= Math.min(metas.length - 1, targetIdx + 1); - i++ - ) { - // seek 已作废当前解码任务 + // 向前看 2 个片段,提前预解码避免切换卡顿 + const endIdx = Math.min(metas.length - 1, targetIdx + 2) + for (let i = Math.max(0, targetIdx - 1); i <= endIdx; i++) { if (decodeGenerationRef.current !== gen) return - if (decodedSegmentsRef.current.has(i)) continue const meta = metas[i] const buffer = segmentDataRef.current.get(meta.assetId) if (!buffer) continue - // 先标记为解码中,防止下一帧渲染时重复发起解码 + + const frameCount = decodedFrameCountsRef.current.get(i) ?? 0 + const totalSamples = meta.samples.length + + // 完全解码的片段:检查队列深度 + if (frameCount === Infinity) { + const availableFrames = frameQueueRef.current.size + if (availableFrames > 90) continue + // 队列不足,强制重新解码 + decodedFrameCountsRef.current.delete(i) + decodedSegmentsRef.current.delete(i) + } + + // 已标记为解码中但未完成:续解码 + if (decodedSegmentsRef.current.has(i)) { + const currentCount = decodedFrameCountsRef.current.get(i) ?? 0 + if (currentCount >= totalSamples) continue + try { + await decodeSegment(buffer, meta, 300, currentCount) + } catch (e) { + console.warn(`[useCanvasPlayer] 续解码片段 ${i} 失败:`, e) + } + if (decodeGenerationRef.current !== gen) return + continue + } + + // 未开始解码的片段:开始解码 decodedSegmentsRef.current.add(i) try { - await decodeSegment(buffer, meta, 300) + const startFrame = decodedFrameCountsRef.current.get(i) ?? 0 + await decodeSegment(buffer, meta, 300, startFrame) } catch (e) { - // 解码失败则移除标记,允许后续重试 decodedSegmentsRef.current.delete(i) console.warn(`[useCanvasPlayer] 按需解码片段 ${i} 失败:`, e) } - // await 后再次检查代数,seek 期间不更新标记 if (decodeGenerationRef.current !== gen) return } }, @@ -845,8 +887,10 @@ export function useCanvasPlayer( // 播放结束后重播 或 从起点重新播放:重置到起点,清空解码缓存 if (state.currentTime >= totalDuration - 0.1 || state.currentTime <= 0.1) { decodeGenerationRef.current++ - // 不清空帧队列:帧按绝对时间戳存储,重播时 getCurrentFrame(≈0) 仍能正确找到第一帧 - // 不清空 decodedSegmentsRef:所有片段已在 init 时解码完成,重播不需要重新解码 + // 清空帧计数:让 decodeAroundPosition 认为所有片段都需要解码 + decodedFrameCountsRef.current.clear() + decodedSegmentsRef.current.clear() + // 不清空帧队列:旧帧仍有效(按绝对时间戳索引),getCurrentFrame 可继续使用 playStartOffsetRef.current = 0 setState((s) => ({ ...s, currentTime: 0 })) } @@ -880,6 +924,7 @@ export function useCanvasPlayer( // 清空帧队列(clear 内部会 close 所有帧)+ 清空已解码标记 frameQueueRef.current.clear() decodedSegmentsRef.current.clear() + decodedFrameCountsRef.current.clear() await decodeAroundPosition(clampedTime) }, [totalDuration, decodeAroundPosition], @@ -899,6 +944,7 @@ export function useCanvasPlayer( segmentDataRef.current.clear() segmentMetaRef.current = [] decodedSegmentsRef.current.clear() + decodedFrameCountsRef.current.clear() }, []) // ── 预加载下一个片段的数据 ── @@ -986,6 +1032,7 @@ export function useCanvasPlayer( // 3. 按需解码:初始只解码前 3 个片段,后续通过 decodeAroundPosition 动态加载 // 避免一次性全量解码导致 frameQueue 环形缓冲区旧帧被丢弃引发黑屏 decodedSegmentsRef.current.clear() + decodedFrameCountsRef.current.clear() const initGen = decodeGenerationRef.current const initialDecodeCount = Math.min(metas.length, 3) console.log(