From e170a6ca514522e64a035072ef839cff5e1cc238 Mon Sep 17 00:00:00 2001 From: mtvpls <247332661+mtvpls@users.noreply.github.com> Date: Sun, 2 Aug 2026 00:00:53 +0800 Subject: [PATCH] =?UTF-8?q?anime4k=E8=B6=85=E5=88=86=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E8=87=AA=E7=AE=A1=E7=90=86=E7=9A=84=20WebGPU=20=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E5=99=A8=E5=85=BC=E5=AE=B9firefox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/play/page.tsx | 205 +++-------------------------- src/lib/anime4k.ts | 299 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+), 186 deletions(-) create mode 100644 src/lib/anime4k.ts diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index 50512b2..dfd7213 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -7,6 +7,7 @@ import { useRouter, useSearchParams } from 'next/navigation'; import { Suspense, useEffect, useMemo, useRef, useState } from 'react'; import { isAnimeCategoryText } from '@/lib/anime-keyword-expr'; +import { createAnime4KRenderer } from '@/lib/anime4k'; import { getAuthInfoFromBrowserCookie } from '@/lib/auth'; import { clearDanmakuCacheByTitle, @@ -3913,7 +3914,6 @@ function PlayPageClient() { const initAnime4K = async () => { if (!artPlayerRef.current?.video) return; - let frameRequestId: number | null = null; // 在外层声明,以便错误处理中使用 let outputCanvas: HTMLCanvasElement | null = null; // 在外层声明,以便错误处理中清理 try { @@ -3948,29 +3948,18 @@ function PlayPageClient() { throw new Error('无法获取视频尺寸'); } - // 检查视频是否正在播放 - console.log('视频播放状态:', { - paused: video.paused, - ended: video.ended, - readyState: video.readyState, - currentTime: video.currentTime, - }); - - // 检测是否为Firefox - const isFirefox = navigator.userAgent.toLowerCase().includes('firefox'); - console.log('浏览器检测:', isFirefox ? 'Firefox' : 'Chrome/Edge/其他'); + // 使用用户选择的超分倍数 + const scale = anime4kScaleRef.current; // 创建输出canvas(显示给用户的) outputCanvas = document.createElement('canvas'); const container = artPlayerRef.current.template.$video.parentElement; - // 使用用户选择的超分倍数 - const scale = anime4kScaleRef.current; - outputCanvas.width = Math.floor(video.videoWidth * scale); // 确保是整数 + outputCanvas.width = Math.floor(video.videoWidth * scale); // 确保是整数 outputCanvas.height = Math.floor(video.videoHeight * scale); // 验证outputCanvas尺寸 - console.log('outputCanvas尺寸:', outputCanvas.width, 'x', outputCanvas.height); + console.log('输出Canvas尺寸:', outputCanvas.width, 'x', outputCanvas.height); if (!outputCanvas.width || !outputCanvas.height || !isFinite(outputCanvas.width) || !isFinite(outputCanvas.height)) { throw new Error(`outputCanvas尺寸无效: ${outputCanvas.width}x${outputCanvas.height}, scale: ${scale}`); @@ -3987,77 +3976,16 @@ function PlayPageClient() { // 确保canvas背景透明,避免Firefox中的渲染问题 outputCanvas.style.backgroundColor = 'transparent'; - // Firefox兼容性处理:创建中间canvas - let sourceCanvas: HTMLCanvasElement | null = null; - let sourceCtx: CanvasRenderingContext2D | null = null; - - if (isFirefox) { - // Firefox的WebGPU不支持直接使用HTMLVideoElement - // 使用标准HTMLCanvasElement(更好的兼容性) - sourceCanvas = document.createElement('canvas'); - - // 获取视频尺寸并记录 - const videoW = video.videoWidth; - const videoH = video.videoHeight; - console.log('Firefox:准备创建canvas - 视频尺寸:', videoW, 'x', videoH); - - // 设置canvas尺寸 - const canvasW = Math.floor(videoW); - const canvasH = Math.floor(videoH); - console.log('Firefox:计算后的canvas尺寸:', canvasW, 'x', canvasH); - - sourceCanvas.width = canvasW; - sourceCanvas.height = canvasH; - - // 立即验证赋值结果 - console.log('Firefox:Canvas创建后立即检查:'); - console.log(' - sourceCanvas.width:', sourceCanvas.width); - console.log(' - sourceCanvas.height:', sourceCanvas.height); - console.log(' - 赋值是否成功:', sourceCanvas.width === canvasW && sourceCanvas.height === canvasH); - - // 验证sourceCanvas尺寸 - if (!sourceCanvas.width || !sourceCanvas.height || - !isFinite(sourceCanvas.width) || !isFinite(sourceCanvas.height)) { - throw new Error(`sourceCanvas尺寸无效: ${sourceCanvas.width}x${sourceCanvas.height}`); - } - - if (sourceCanvas.width !== canvasW || sourceCanvas.height !== canvasH) { - throw new Error(`sourceCanvas尺寸赋值异常: 期望 ${canvasW}x${canvasH}, 实际 ${sourceCanvas.width}x${sourceCanvas.height}`); - } - - sourceCtx = sourceCanvas.getContext('2d', { - willReadFrequently: true, - alpha: false // 禁用alpha通道,提高性能 - }); - - if (!sourceCtx) { - throw new Error('无法创建2D上下文'); - } - - // 先绘制一帧到canvas,确保有内容 - if (video.readyState >= video.HAVE_CURRENT_DATA) { - sourceCtx.drawImage(video, 0, 0, sourceCanvas.width, sourceCanvas.height); - console.log('Firefox:已绘制初始帧到sourceCanvas'); - } - - console.log('Firefox检测:使用HTMLCanvasElement中转方案'); - } - - // 在outputCanvas上监听点击事件,触发播放器的暂停/播放切换 - const handleCanvasClick = () => { + outputCanvas.addEventListener('click', () => { if (artPlayerRef.current) { artPlayerRef.current.toggle(); } - }; - outputCanvas.addEventListener('click', handleCanvasClick); - - // 在outputCanvas上监听双击事件,触发全屏切换 - const handleCanvasDblClick = () => { + }); + outputCanvas.addEventListener('dblclick', () => { if (artPlayerRef.current) { artPlayerRef.current.fullscreen = !artPlayerRef.current.fullscreen; } - }; - outputCanvas.addEventListener('dblclick', handleCanvasDblClick); + }); // 隐藏原始video元素(使用opacity而不是display:none以保持视频解码) // Firefox在display:none时可能会停止视频解码,导致黑屏 @@ -4069,20 +3997,8 @@ function PlayPageClient() { // 插入outputCanvas到容器 container.insertBefore(outputCanvas, video); - // Firefox兼容性:创建视频帧捕获循环 - if (isFirefox && sourceCtx && sourceCanvas) { - const captureVideoFrame = () => { - if (sourceCtx && sourceCanvas && video.readyState >= video.HAVE_CURRENT_DATA) { - sourceCtx.drawImage(video, 0, 0, sourceCanvas.width, sourceCanvas.height); - } - frameRequestId = requestAnimationFrame(captureVideoFrame); - }; - captureVideoFrame(); - console.log('Firefox:视频帧捕获循环已启动'); - } - // 动态导入 anime4k-webgpu 及对应的模式 - const { render: anime4kRender, ModeA, ModeB, ModeC, ModeAA, ModeBB, ModeCA } = await import('anime4k-webgpu'); + const { ModeA, ModeB, ModeC, ModeAA, ModeBB, ModeCA } = await import('anime4k-webgpu'); let ModeClass: any; const modeName = anime4kModeRef.current; @@ -4110,66 +4026,23 @@ function PlayPageClient() { ModeClass = ModeA; } - // 使用anime4k-webgpu的render函数 - // Firefox使用sourceCanvas,其他浏览器直接使用video - const renderConfig: any = { - video: isFirefox ? sourceCanvas : video, // Firefox使用canvas中转,其他浏览器直接使用video - canvas: outputCanvas, - pipelineBuilder: (device: GPUDevice, inputTexture: GPUTexture) => { - if (!outputCanvas) { - throw new Error('outputCanvas is null in pipelineBuilder'); - } - const mode = new ModeClass({ - device, - inputTexture, - nativeDimensions: { - width: Math.floor(video.videoWidth), // 确保是整数 - height: Math.floor(video.videoHeight), - }, - targetDimensions: { - width: Math.floor(outputCanvas.width), // 确保是整数 - height: Math.floor(outputCanvas.height), - }, - }); - return [mode]; - }, - }; - + // 使用自管理的 WebGPU 渲染器。内部自动处理各浏览器的帧源差异: + // 直接从