添加热力图
This commit is contained in:
@@ -293,6 +293,32 @@ function PlayPageClient() {
|
|||||||
const danmakuPluginRef = useRef<any>(null);
|
const danmakuPluginRef = useRef<any>(null);
|
||||||
const danmakuSettingsRef = useRef(danmakuSettings);
|
const danmakuSettingsRef = useRef(danmakuSettings);
|
||||||
|
|
||||||
|
// 弹幕热力图完全禁用开关(默认不禁用,即启用热力图功能)
|
||||||
|
const [danmakuHeatmapDisabled, setDanmakuHeatmapDisabled] = useState<boolean>(() => {
|
||||||
|
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<boolean>(() => {
|
||||||
|
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<DanmakuAnime[]>([]);
|
const [danmakuMatches, setDanmakuMatches] = useState<DanmakuAnime[]>([]);
|
||||||
const [showDanmakuSourceSelector, setShowDanmakuSourceSelector] = useState(false);
|
const [showDanmakuSourceSelector, setShowDanmakuSourceSelector] = useState(false);
|
||||||
@@ -2311,6 +2337,17 @@ function PlayPageClient() {
|
|||||||
setDanmakuCount(0);
|
setDanmakuCount(0);
|
||||||
} finally {
|
} finally {
|
||||||
setDanmakuLoading(false);
|
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,
|
antiOverlap: true,
|
||||||
synchronousPlayback: danmakuSettingsRef.current.synchronousPlayback,
|
synchronousPlayback: danmakuSettingsRef.current.synchronousPlayback,
|
||||||
emitter: false,
|
emitter: false,
|
||||||
|
heatmap: !danmakuHeatmapDisabledRef.current, // 根据禁用状态决定是否创建热力图
|
||||||
// 主题
|
// 主题
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
filter: (danmu: any) => {
|
filter: (danmu: any) => {
|
||||||
@@ -3216,6 +3254,33 @@ function PlayPageClient() {
|
|||||||
return '打开设置';
|
return '打开设置';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// 只有在未禁用热力图时才显示热力图开关
|
||||||
|
...(!danmakuHeatmapDisabledRef.current ? [{
|
||||||
|
name: '弹幕热力',
|
||||||
|
html: '弹幕热力',
|
||||||
|
icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z" fill="#ffffff"/></svg>',
|
||||||
|
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 ? [
|
...(webGPUSupported ? [
|
||||||
{
|
{
|
||||||
name: 'Anime4K超分',
|
name: 'Anime4K超分',
|
||||||
@@ -3900,6 +3965,16 @@ function PlayPageClient() {
|
|||||||
danmakuPluginRef.current.hide();
|
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();
|
await autoSearchDanmaku();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const UserMenu: React.FC = () => {
|
|||||||
const [enableOptimization, setEnableOptimization] = useState(true);
|
const [enableOptimization, setEnableOptimization] = useState(true);
|
||||||
const [fluidSearch, setFluidSearch] = useState(true);
|
const [fluidSearch, setFluidSearch] = useState(true);
|
||||||
const [liveDirectConnect, setLiveDirectConnect] = useState(false);
|
const [liveDirectConnect, setLiveDirectConnect] = useState(false);
|
||||||
|
const [danmakuHeatmapDisabled, setDanmakuHeatmapDisabled] = useState(false);
|
||||||
const [doubanDataSource, setDoubanDataSource] = useState('cmliussss-cdn-tencent');
|
const [doubanDataSource, setDoubanDataSource] = useState('cmliussss-cdn-tencent');
|
||||||
const [doubanImageProxyType, setDoubanImageProxyType] = useState('cmliussss-cdn-tencent');
|
const [doubanImageProxyType, setDoubanImageProxyType] = useState('cmliussss-cdn-tencent');
|
||||||
const [doubanImageProxyUrl, setDoubanImageProxyUrl] = useState('');
|
const [doubanImageProxyUrl, setDoubanImageProxyUrl] = useState('');
|
||||||
@@ -300,6 +301,11 @@ export const UserMenu: React.FC = () => {
|
|||||||
if (savedLiveDirectConnect !== null) {
|
if (savedLiveDirectConnect !== null) {
|
||||||
setLiveDirectConnect(JSON.parse(savedLiveDirectConnect));
|
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) => {
|
const handleDoubanDataSourceChange = (value: string) => {
|
||||||
setDoubanDataSource(value);
|
setDoubanDataSource(value);
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -553,6 +566,7 @@ export const UserMenu: React.FC = () => {
|
|||||||
setEnableOptimization(true);
|
setEnableOptimization(true);
|
||||||
setFluidSearch(defaultFluidSearch);
|
setFluidSearch(defaultFluidSearch);
|
||||||
setLiveDirectConnect(false);
|
setLiveDirectConnect(false);
|
||||||
|
setDanmakuHeatmapDisabled(false);
|
||||||
setDoubanProxyUrl(defaultDoubanProxy);
|
setDoubanProxyUrl(defaultDoubanProxy);
|
||||||
setDoubanDataSource(defaultDoubanProxyType);
|
setDoubanDataSource(defaultDoubanProxyType);
|
||||||
setDoubanImageProxyType(defaultDoubanImageProxyType);
|
setDoubanImageProxyType(defaultDoubanImageProxyType);
|
||||||
@@ -563,6 +577,7 @@ export const UserMenu: React.FC = () => {
|
|||||||
localStorage.setItem('enableOptimization', JSON.stringify(true));
|
localStorage.setItem('enableOptimization', JSON.stringify(true));
|
||||||
localStorage.setItem('fluidSearch', JSON.stringify(defaultFluidSearch));
|
localStorage.setItem('fluidSearch', JSON.stringify(defaultFluidSearch));
|
||||||
localStorage.setItem('liveDirectConnect', JSON.stringify(false));
|
localStorage.setItem('liveDirectConnect', JSON.stringify(false));
|
||||||
|
localStorage.setItem('danmaku_heatmap_disabled', 'false');
|
||||||
localStorage.setItem('doubanProxyUrl', defaultDoubanProxy);
|
localStorage.setItem('doubanProxyUrl', defaultDoubanProxy);
|
||||||
localStorage.setItem('doubanDataSource', defaultDoubanProxyType);
|
localStorage.setItem('doubanDataSource', defaultDoubanProxyType);
|
||||||
localStorage.setItem('doubanImageProxyType', defaultDoubanImageProxyType);
|
localStorage.setItem('doubanImageProxyType', defaultDoubanImageProxyType);
|
||||||
@@ -1150,6 +1165,30 @@ export const UserMenu: React.FC = () => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 禁用弹幕热力 */}
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div>
|
||||||
|
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||||
|
禁用弹幕热力图
|
||||||
|
</h4>
|
||||||
|
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||||
|
完全关闭弹幕热力图功能以提升性能(需手动刷新页面生效)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<label className='flex items-center cursor-pointer'>
|
||||||
|
<div className='relative'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
className='sr-only peer'
|
||||||
|
checked={danmakuHeatmapDisabled}
|
||||||
|
onChange={(e) => handleDanmakuHeatmapDisabledToggle(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<div className='w-11 h-6 bg-gray-300 rounded-full peer-checked:bg-green-500 transition-colors dark:bg-gray-600'></div>
|
||||||
|
<div className='absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full transition-transform peer-checked:translate-x-5'></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 分割线 */}
|
{/* 分割线 */}
|
||||||
<div className='border-t border-gray-200 dark:border-gray-700'></div>
|
<div className='border-t border-gray-200 dark:border-gray-700'></div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user