From 106e966931aff53415f9bbd216bbff0dfa6d1da1 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Mon, 1 Dec 2025 20:41:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=B6=85=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/play/page.tsx | 218 +++++++++++++++++++++++++++++++++--------- 1 file changed, 171 insertions(+), 47 deletions(-) diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index 669c8f5..3fcdbb5 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -100,27 +100,30 @@ function PlayPageClient() { // Anime4K超分相关状态 const [webGPUSupported, setWebGPUSupported] = useState(false); - const [anime4kEnabled, setAnime4kEnabled] = useState(() => { - if (typeof window !== 'undefined') { - const v = localStorage.getItem('enable_anime4k'); - if (v !== null) return v === 'true'; - } - return false; - }); + const [anime4kEnabled, setAnime4kEnabled] = useState(false); const [anime4kMode, setAnime4kMode] = useState(() => { if (typeof window !== 'undefined') { const v = localStorage.getItem('anime4k_mode'); if (v !== null) return v; } - return 'fast'; + return 'ModeA'; + }); + const [anime4kScale, setAnime4kScale] = useState(() => { + if (typeof window !== 'undefined') { + const v = localStorage.getItem('anime4k_scale'); + if (v !== null) return parseFloat(v); + } + return 2.0; }); const anime4kRef = useRef(null); const anime4kEnabledRef = useRef(anime4kEnabled); const anime4kModeRef = useRef(anime4kMode); + const anime4kScaleRef = useRef(anime4kScale); useEffect(() => { anime4kEnabledRef.current = anime4kEnabled; anime4kModeRef.current = anime4kMode; - }, [anime4kEnabled, anime4kMode]); + anime4kScaleRef.current = anime4kScale; + }, [anime4kEnabled, anime4kMode, anime4kScale]); // 检测WebGPU支持 useEffect(() => { @@ -552,7 +555,7 @@ function PlayPageClient() { // 初始化Anime4K超分 const initAnime4K = async () => { if (!artPlayerRef.current?.video) return; - + try { if (anime4kRef.current) { anime4kRef.current.stop?.(); @@ -560,19 +563,60 @@ function PlayPageClient() { } const video = artPlayerRef.current.video as HTMLVideoElement; + + // 等待视频元数据加载完成 + if (!video.videoWidth || !video.videoHeight) { + console.warn('视频尺寸未就绪,等待loadedmetadata事件'); + await new Promise((resolve) => { + const handler = () => { + video.removeEventListener('loadedmetadata', handler); + resolve(); + }; + video.addEventListener('loadedmetadata', handler); + // 如果已经加载过了,立即resolve + if (video.videoWidth && video.videoHeight) { + video.removeEventListener('loadedmetadata', handler); + resolve(); + } + }); + } + + // 再次检查视频尺寸 + if (!video.videoWidth || !video.videoHeight) { + throw new Error('无法获取视频尺寸'); + } + const canvas = document.createElement('canvas'); const container = artPlayerRef.current.template.$video.parentElement; - - // 设置canvas尺寸和样式 - canvas.width = video.videoWidth * 2; // 2倍超分 - canvas.height = video.videoHeight * 2; + + // 使用用户选择的超分倍数 + const scale = anime4kScaleRef.current; + canvas.width = video.videoWidth * scale; + canvas.height = video.videoHeight * scale; canvas.style.position = 'absolute'; canvas.style.top = '0'; canvas.style.left = '0'; canvas.style.width = '100%'; canvas.style.height = '100%'; canvas.style.objectFit = 'contain'; - + canvas.style.cursor = 'pointer'; + + // 在canvas上监听点击事件,触发播放器的暂停/播放切换 + const handleCanvasClick = (e: MouseEvent) => { + if (artPlayerRef.current) { + artPlayerRef.current.toggle(); + } + }; + canvas.addEventListener('click', handleCanvasClick); + + // 在canvas上监听双击事件,触发全屏切换 + const handleCanvasDblClick = (e: MouseEvent) => { + if (artPlayerRef.current) { + artPlayerRef.current.fullscreen = !artPlayerRef.current.fullscreen; + } + }; + canvas.addEventListener('dblclick', handleCanvasDblClick); + // 隐藏原始video元素 video.style.display = 'none'; @@ -581,15 +625,30 @@ function PlayPageClient() { // 动态导入对应的模式 let ModeClass: any; - if (anime4kModeRef.current === 'fast') { - const { ModeA } = await import('anime4k-webgpu'); - ModeClass = ModeA; - } else if (anime4kModeRef.current === 'balanced') { - const { ModeB } = await import('anime4k-webgpu'); - ModeClass = ModeB; - } else { - const { ModeC } = await import('anime4k-webgpu'); - ModeClass = ModeC; + const modeName = anime4kModeRef.current; + const modeModule = await import('anime4k-webgpu'); + + switch (modeName) { + case 'ModeA': + ModeClass = modeModule.ModeA; + break; + case 'ModeB': + ModeClass = modeModule.ModeB; + break; + case 'ModeC': + ModeClass = modeModule.ModeC; + break; + case 'ModeAA': + ModeClass = modeModule.ModeAA; + break; + case 'ModeBB': + ModeClass = modeModule.ModeBB; + break; + case 'ModeCA': + ModeClass = modeModule.ModeCA; + break; + default: + ModeClass = modeModule.ModeA; } // 使用anime4k-webgpu的render函数 @@ -614,11 +673,11 @@ function PlayPageClient() { }; const controller = await anime4kRender(renderConfig); - anime4kRef.current = { controller, canvas }; - - console.log('Anime4K超分已启用,模式:', anime4kModeRef.current); + anime4kRef.current = { controller, canvas, handleCanvasClick, handleCanvasDblClick }; + + console.log('Anime4K超分已启用,模式:', anime4kModeRef.current, '倍数:', scale); if (artPlayerRef.current) { - artPlayerRef.current.notice.show = `超分已启用 (${anime4kModeRef.current})`; + artPlayerRef.current.notice.show = `超分已启用 (${anime4kModeRef.current}, ${scale}x)`; } } catch (err) { console.error('初始化Anime4K失败:', err); @@ -638,19 +697,29 @@ function PlayPageClient() { try { // 停止渲染循环 anime4kRef.current.controller?.stop?.(); - + + // 移除canvas事件监听器 + if (anime4kRef.current.canvas) { + if (anime4kRef.current.handleCanvasClick) { + anime4kRef.current.canvas.removeEventListener('click', anime4kRef.current.handleCanvasClick); + } + if (anime4kRef.current.handleCanvasDblClick) { + anime4kRef.current.canvas.removeEventListener('dblclick', anime4kRef.current.handleCanvasDblClick); + } + } + // 移除canvas if (anime4kRef.current.canvas && anime4kRef.current.canvas.parentNode) { anime4kRef.current.canvas.parentNode.removeChild(anime4kRef.current.canvas); } - + anime4kRef.current = null; - + // 恢复原始video显示 if (artPlayerRef.current?.video) { artPlayerRef.current.video.style.display = 'block'; } - + console.log('Anime4K已清理'); } catch (err) { console.warn('清理Anime4K时出错:', err); @@ -678,7 +747,7 @@ function PlayPageClient() { try { setAnime4kMode(mode); localStorage.setItem('anime4k_mode', mode); - + if (anime4kEnabledRef.current) { await cleanupAnime4K(); await initAnime4K(); @@ -688,6 +757,21 @@ function PlayPageClient() { } }; + // 更改Anime4K分辨率倍数 + const changeAnime4KScale = async (scale: number) => { + try { + setAnime4kScale(scale); + localStorage.setItem('anime4k_scale', scale.toString()); + + if (anime4kEnabledRef.current) { + await cleanupAnime4K(); + await initAnime4K(); + } + } catch (err) { + console.error('更改超分倍数失败:', err); + } + }; + function filterAdsFromM3U8(type: string, m3u8Content: string): string { if (!m3u8Content) return ''; @@ -1640,25 +1724,70 @@ function PlayPageClient() { html: '超分模式', selector: [ { - html: '快速模式', - value: 'fast', - default: anime4kModeRef.current === 'fast', + html: 'ModeA (快速)', + value: 'ModeA', + default: anime4kModeRef.current === 'ModeA', }, { - html: '平衡模式', - value: 'balanced', - default: anime4kModeRef.current === 'balanced', + html: 'ModeB (平衡)', + value: 'ModeB', + default: anime4kModeRef.current === 'ModeB', }, { - html: '高质量模式', - value: 'high_quality', - default: anime4kModeRef.current === 'high_quality', + html: 'ModeC (质量)', + value: 'ModeC', + default: anime4kModeRef.current === 'ModeC', + }, + { + html: 'ModeAA (增强快速)', + value: 'ModeAA', + default: anime4kModeRef.current === 'ModeAA', + }, + { + html: 'ModeBB (增强平衡)', + value: 'ModeBB', + default: anime4kModeRef.current === 'ModeBB', + }, + { + html: 'ModeCA (最高质量)', + value: 'ModeCA', + default: anime4kModeRef.current === 'ModeCA', }, ], onSelect: async function (item: any) { await changeAnime4KMode(item.value); return item.html; }, + }, + { + name: '超分倍数', + html: '超分倍数', + selector: [ + { + html: '1.5x', + value: '1.5', + default: anime4kScaleRef.current === 1.5, + }, + { + html: '2.0x', + value: '2.0', + default: anime4kScaleRef.current === 2.0, + }, + { + html: '3.0x', + value: '3.0', + default: anime4kScaleRef.current === 3.0, + }, + { + html: '4.0x', + value: '4.0', + default: anime4kScaleRef.current === 4.0, + }, + ], + onSelect: async function (item: any) { + await changeAnime4KScale(parseFloat(item.value)); + return item.html; + }, } ] : []), { @@ -1752,11 +1881,6 @@ function PlayPageClient() { if (artPlayerRef.current && !artPlayerRef.current.paused) { requestWakeLock(); } - - // 如果启用了Anime4K,初始化超分 - if (anime4kEnabledRef.current) { - await initAnime4K(); - } }); // 监听播放状态变化,控制 Wake Lock