From ece1f25f71f7a29fbbc06cc35ece95f5dfa10939 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sat, 22 Aug 2026 00:57:38 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=AC=AC12=E8=BD=AE=20=E2=80=94=20?= =?UTF-8?q?=E8=83=8C=E5=8E=8B=E8=B6=85=E6=97=B6=E4=BF=9D=E6=8A=A4+?= =?UTF-8?q?=E5=B8=A7=E8=BE=93=E5=87=BA=E7=AD=89=E5=BE=85+feedFrames?= =?UTF-8?q?=E9=98=88=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 背压等待加 idleTicks 计数,超3秒自动break防止死循环 - 解码完成后等待帧输出(最多500ms),确保队列有帧 - 加解码完成日志便于排查 - renderFrame 中 feedFrames 调用加 size<80 阈值保护 --- .../pages/generate/hooks/useCanvasPlayer.ts | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts b/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts index 2aa12f877..32043d99d 100644 --- a/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts +++ b/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts @@ -560,17 +560,26 @@ export function useCanvasPlayer( // 从 cursor 继续喂 sample(流水线批量提交,不 await 单个 decode) let decoded = 0 let si = cursor + let idleTicks = 0 // 背压等待计数器,防止死循环 while (si < samples.length && decoded < maxFrames) { if (decodeGenerationRef.current !== gen || isDestroyedRef.current) break if ((decoder.state as string) === "closed") break - // 队列快满时停止,等播放消费后再补充 if (frameQueueRef.current.size >= 180) break - // 解码器内部队列积压过多时暂停提交,做背压 + // 解码器内部队列积压时短暂等待 if (decoder.decodeQueueSize > 20) { + idleTicks++ + if (idleTicks > 300) { + // 等待超过3秒(300×10ms),解码器可能卡住,放弃这批 + console.warn( + `[useCanvasPlayer] Segment ${segIdx} decode backpressure timeout after 3s, decoded ${decoded} frames`, + ) + break + } await new Promise((r) => setTimeout(r, 10)) continue } + idleTicks = 0 const sample = samples[si] si++ if (!sample.data) continue @@ -583,7 +592,6 @@ export function useCanvasPlayer( }) try { - // 不 await 单个 decode,让硬件解码器流水线并行处理 decoder.decode(chunk) decoded++ } catch (e) { @@ -594,8 +602,18 @@ export function useCanvasPlayer( cursor = si segmentSampleCursorRef.current.set(segIdx, cursor) - // 给解码器一点时间输出帧 - await new Promise((r) => setTimeout(r, 16)) + // 等待解码器输出帧(最多500ms),但不永久阻塞 + if (decoded > 0 && (decoder.state as string) === "configured") { + let waited = 0 + while (frameQueueRef.current.size < Math.min(decoded, 10) && waited < 500) { + await new Promise((r) => setTimeout(r, 20)) + waited += 20 + if (isDestroyedRef.current) break + } + } + console.log( + `[useCanvasPlayer] Segment ${segIdx} decoded ${decoded} frames, queue size: ${frameQueueRef.current.size}`, + ) return decoded }, [], @@ -769,7 +787,9 @@ export function useCanvasPlayer( return s }) // 后台补充帧:队列不足时自动续解码 - feedFrames(currentTime) + if (frameQueueRef.current.size < 80) { + void feedFrames(currentTime) + } } if (currentTime >= totalDuration) {