import type { RefObject } from "react"; import { useEffect } from "react"; interface UseBackgroundCanvasProps { isVideo: boolean; isPlaying: boolean; videoRef: RefObject; backgroundCanvasRef: RefObject; } const CANVAS_DRAW_INTERVAL_MS = 100; // 10fps 对 1/10 分辨率 + blur(40px) 已完全平滑,大幅降低 GPU 负载 export function useBackgroundCanvas({ isVideo, isPlaying, videoRef, backgroundCanvasRef, }: UseBackgroundCanvasProps): void { // 初始化尺寸与卸载清理 useEffect(() => { const canvas = backgroundCanvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx || !isVideo) { canvas.width = 0; canvas.height = 0; return; } const updateCanvasSize = (): void => { canvas.width = Math.max(1, Math.floor(window.innerWidth / 10)); canvas.height = Math.max(1, Math.floor(window.innerHeight / 10)); }; updateCanvasSize(); let resizeTimeout: ReturnType | null = null; const debouncedResize = (): void => { if (resizeTimeout !== null) clearTimeout(resizeTimeout); resizeTimeout = setTimeout(updateCanvasSize, 300); }; window.addEventListener("resize", debouncedResize); return () => { window.removeEventListener("resize", debouncedResize); if (resizeTimeout !== null) clearTimeout(resizeTimeout); ctx.clearRect(0, 0, canvas.width, canvas.height); canvas.width = 0; canvas.height = 0; }; }, [isVideo, backgroundCanvasRef]); // 仅在视频实际播放时进行低频采样绘制,暂停时不占 CPU/GPU useEffect(() => { if (!isVideo || !isPlaying) return; const canvas = backgroundCanvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; const drawMediaToCanvas = (): void => { if (document.hidden) return; const sourceElement = videoRef.current; if ( !sourceElement || sourceElement.readyState < 2 || sourceElement.paused ) { return; } const canvasWidth = canvas.width; const canvasHeight = canvas.height; const sourceWidth = sourceElement.videoWidth; const sourceHeight = sourceElement.videoHeight; if (!canvasWidth || !canvasHeight || !sourceWidth || !sourceHeight) { return; } const canvasRatio = canvasWidth / canvasHeight; const sourceRatio = sourceWidth / sourceHeight; let drawWidth: number; let drawHeight: number; let offsetX: number; let offsetY: number; if (canvasRatio > sourceRatio) { drawWidth = canvasWidth; drawHeight = canvasWidth / sourceRatio; offsetX = 0; offsetY = (canvasHeight - drawHeight) / 2; } else { drawHeight = canvasHeight; drawWidth = canvasHeight * sourceRatio; offsetX = (canvasWidth - drawWidth) / 2; offsetY = 0; } ctx.drawImage(sourceElement, offsetX, offsetY, drawWidth, drawHeight); }; drawMediaToCanvas(); const intervalId = window.setInterval( drawMediaToCanvas, CANVAS_DRAW_INTERVAL_MS, ); return () => { window.clearInterval(intervalId); }; }, [isVideo, isPlaying, videoRef, backgroundCanvasRef]); }