diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index f913a6c..3e08715 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -293,6 +293,32 @@ function PlayPageClient() { const danmakuPluginRef = useRef(null); const danmakuSettingsRef = useRef(danmakuSettings); + // 弹幕热力图完全禁用开关(默认不禁用,即启用热力图功能) + const [danmakuHeatmapDisabled, setDanmakuHeatmapDisabled] = useState(() => { + if (typeof window !== 'undefined') { + const v = localStorage.getItem('danmaku_heatmap_disabled'); + if (v !== null) return v === 'true'; + } + return false; // 默认不禁用 + }); + const danmakuHeatmapDisabledRef = useRef(danmakuHeatmapDisabled); + useEffect(() => { + danmakuHeatmapDisabledRef.current = danmakuHeatmapDisabled; + }, [danmakuHeatmapDisabled]); + + // 弹幕热力图开关(默认开启) + const [danmakuHeatmapEnabled, setDanmakuHeatmapEnabled] = useState(() => { + if (typeof window !== 'undefined') { + const v = localStorage.getItem('danmaku_heatmap_enabled'); + if (v !== null) return v === 'true'; + } + return true; // 默认开启 + }); + const danmakuHeatmapEnabledRef = useRef(danmakuHeatmapEnabled); + useEffect(() => { + danmakuHeatmapEnabledRef.current = danmakuHeatmapEnabled; + }, [danmakuHeatmapEnabled]); + // 多条弹幕匹配结果 const [danmakuMatches, setDanmakuMatches] = useState([]); const [showDanmakuSourceSelector, setShowDanmakuSourceSelector] = useState(false); @@ -2311,6 +2337,17 @@ function PlayPageClient() { setDanmakuCount(0); } finally { setDanmakuLoading(false); + + // 弹幕加载完成后,根据用户设置显示或隐藏热力图(仅在未禁用热力图时) + if (!danmakuHeatmapDisabledRef.current) { + const heatmapElement = document.querySelector('.art-control-heatmap') as HTMLElement; + if (heatmapElement) { + const isEnabled = danmakuHeatmapEnabledRef.current; + heatmapElement.style.opacity = isEnabled ? '1' : '0'; + heatmapElement.style.pointerEvents = isEnabled ? 'auto' : 'none'; + console.log('弹幕加载完成,热力图状态:', isEnabled ? '显示' : '隐藏'); + } + } } }; @@ -3136,6 +3173,7 @@ function PlayPageClient() { antiOverlap: true, synchronousPlayback: danmakuSettingsRef.current.synchronousPlayback, emitter: false, + heatmap: !danmakuHeatmapDisabledRef.current, // 根据禁用状态决定是否创建热力图 // 主题 theme: 'dark', filter: (danmu: any) => { @@ -3216,6 +3254,33 @@ function PlayPageClient() { return '打开设置'; }, }, + // 只有在未禁用热力图时才显示热力图开关 + ...(!danmakuHeatmapDisabledRef.current ? [{ + name: '弹幕热力', + html: '弹幕热力', + icon: '', + switch: danmakuHeatmapEnabledRef.current, + onSwitch: function (item: any) { + const newVal = !item.switch; + try { + localStorage.setItem('danmaku_heatmap_enabled', String(newVal)); + setDanmakuHeatmapEnabled(newVal); + + // 使用 opacity 控制热力图显示/隐藏 + const heatmapElement = document.querySelector('.art-control-heatmap') as HTMLElement; + if (heatmapElement) { + heatmapElement.style.opacity = newVal ? '1' : '0'; + heatmapElement.style.pointerEvents = newVal ? 'auto' : 'none'; + console.log('弹幕热力已', newVal ? '开启' : '关闭'); + } else { + console.warn('未找到热力图元素'); + } + } catch (err) { + console.error('切换弹幕热力失败:', err); + } + return newVal; + }, + }] : []), ...(webGPUSupported ? [ { name: 'Anime4K超分', @@ -3900,6 +3965,16 @@ function PlayPageClient() { danmakuPluginRef.current.hide(); } + // 初始隐藏热力图,等待弹幕加载完成后再显示(仅在未禁用热力图时) + if (!danmakuHeatmapDisabledRef.current) { + const heatmapElement = document.querySelector('.art-control-heatmap') as HTMLElement; + if (heatmapElement) { + heatmapElement.style.opacity = '0'; + heatmapElement.style.pointerEvents = 'none'; + console.log('热力图初始状态: 隐藏(等待弹幕加载)'); + } + } + // 自动搜索并加载弹幕 await autoSearchDanmaku(); } diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index 802fc81..a31c53b 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -86,6 +86,7 @@ export const UserMenu: React.FC = () => { const [enableOptimization, setEnableOptimization] = useState(true); const [fluidSearch, setFluidSearch] = useState(true); const [liveDirectConnect, setLiveDirectConnect] = useState(false); + const [danmakuHeatmapDisabled, setDanmakuHeatmapDisabled] = useState(false); const [doubanDataSource, setDoubanDataSource] = useState('cmliussss-cdn-tencent'); const [doubanImageProxyType, setDoubanImageProxyType] = useState('cmliussss-cdn-tencent'); const [doubanImageProxyUrl, setDoubanImageProxyUrl] = useState(''); @@ -300,6 +301,11 @@ export const UserMenu: React.FC = () => { if (savedLiveDirectConnect !== null) { setLiveDirectConnect(JSON.parse(savedLiveDirectConnect)); } + + const savedDanmakuHeatmapDisabled = localStorage.getItem('danmaku_heatmap_disabled'); + if (savedDanmakuHeatmapDisabled !== null) { + setDanmakuHeatmapDisabled(savedDanmakuHeatmapDisabled === 'true'); + } } }, []); @@ -497,6 +503,13 @@ export const UserMenu: React.FC = () => { } }; + const handleDanmakuHeatmapDisabledToggle = (value: boolean) => { + setDanmakuHeatmapDisabled(value); + if (typeof window !== 'undefined') { + localStorage.setItem('danmaku_heatmap_disabled', String(value)); + } + }; + const handleDoubanDataSourceChange = (value: string) => { setDoubanDataSource(value); if (typeof window !== 'undefined') { @@ -553,6 +566,7 @@ export const UserMenu: React.FC = () => { setEnableOptimization(true); setFluidSearch(defaultFluidSearch); setLiveDirectConnect(false); + setDanmakuHeatmapDisabled(false); setDoubanProxyUrl(defaultDoubanProxy); setDoubanDataSource(defaultDoubanProxyType); setDoubanImageProxyType(defaultDoubanImageProxyType); @@ -563,6 +577,7 @@ export const UserMenu: React.FC = () => { localStorage.setItem('enableOptimization', JSON.stringify(true)); localStorage.setItem('fluidSearch', JSON.stringify(defaultFluidSearch)); localStorage.setItem('liveDirectConnect', JSON.stringify(false)); + localStorage.setItem('danmaku_heatmap_disabled', 'false'); localStorage.setItem('doubanProxyUrl', defaultDoubanProxy); localStorage.setItem('doubanDataSource', defaultDoubanProxyType); localStorage.setItem('doubanImageProxyType', defaultDoubanImageProxyType); @@ -1150,6 +1165,30 @@ export const UserMenu: React.FC = () => { + {/* 禁用弹幕热力 */} +
+
+

+ 禁用弹幕热力图 +

+

+ 完全关闭弹幕热力图功能以提升性能(需手动刷新页面生效) +

+
+ +
+ {/* 分割线 */}