Files
douyin-archive/app/aweme/[awemeId]/hooks/useBackgroundCanvas.ts
T

115 lines
3.3 KiB
TypeScript

import type { RefObject } from "react";
import { useEffect } from "react";
interface UseBackgroundCanvasProps {
isVideo: boolean;
isPlaying: boolean;
videoRef: RefObject<HTMLVideoElement | null>;
backgroundCanvasRef: RefObject<HTMLCanvasElement | null>;
}
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<typeof setTimeout> | 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]);
}