/* eslint-disable @typescript-eslint/no-explicit-any, no-console */ 'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; import { deleteSkipConfig, EpisodeSkipConfig, getSkipConfig, saveSkipConfig, SkipSegment, } from '@/lib/db.client'; interface SkipControllerProps { source: string; id: string; title: string; artPlayerRef: React.MutableRefObject; currentTime?: number; duration?: number; isSettingMode?: boolean; onSettingModeChange?: (isOpen: boolean) => void; onNextEpisode?: () => void; // 新增:跳转下一集的回调 } export default function SkipController({ source, id, title, artPlayerRef, currentTime = 0, duration = 0, isSettingMode = false, onSettingModeChange, onNextEpisode, }: SkipControllerProps) { const [skipConfig, setSkipConfig] = useState(null); const [showSkipButton, setShowSkipButton] = useState(false); const [currentSkipSegment, setCurrentSkipSegment] = useState(null); const [newSegment, setNewSegment] = useState>({}); // 新增状态:批量设置模式 - 支持分:秒格式 const [batchSettings, setBatchSettings] = useState({ openingStart: '0:00', // 片头开始时间(分:秒格式) openingEnd: '1:30', // 片头结束时间(分:秒格式,90秒=1分30秒) endingStart: '20:00', // 片尾开始时间(分:秒格式) endingEnd: '', // 片尾结束时间(可选,空表示直接跳转下一集) autoSkip: true, // 自动跳过开关 autoNextEpisode: true, // 自动下一集开关 }); const [showCountdown, setShowCountdown] = useState(false); const [countdownSeconds, setCountdownSeconds] = useState(0); const lastSkipTimeRef = useRef(0); const skipTimeoutRef = useRef(null); const autoSkipTimeoutRef = useRef(null); const countdownIntervalRef = useRef(null); // 时间格式转换函数 const timeToSeconds = useCallback((timeStr: string): number => { if (!timeStr || timeStr.trim() === '') return 0; // 支持多种格式: "2:10", "2:10.5", "130", "130.5" if (timeStr.includes(':')) { const parts = timeStr.split(':'); const minutes = parseInt(parts[0]) || 0; const seconds = parseFloat(parts[1]) || 0; return minutes * 60 + seconds; } else { return parseFloat(timeStr) || 0; } }, []); const secondsToTime = useCallback((seconds: number): string => { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); const decimal = seconds % 1; if (decimal > 0) { return `${mins}:${secs.toString().padStart(2, '0')}.${Math.floor(decimal * 10)}`; } return `${mins}:${secs.toString().padStart(2, '0')}`; }, []); // 加载跳过配置 const loadSkipConfig = useCallback(async () => { try { const config = await getSkipConfig(source, id); setSkipConfig(config); } catch (err) { console.error('加载跳过配置失败:', err); } }, [source, id]); // 自动跳过逻辑 const handleAutoSkip = useCallback((segment: SkipSegment) => { if (!artPlayerRef.current) return; const targetTime = segment.end + 1; artPlayerRef.current.currentTime = targetTime; lastSkipTimeRef.current = Date.now(); // 显示跳过提示 if (artPlayerRef.current.notice) { const segmentName = segment.type === 'opening' ? '片头' : '片尾'; artPlayerRef.current.notice.show = `自动跳过${segmentName}`; } setCurrentSkipSegment(null); }, [artPlayerRef]); // 开始片尾倒计时 const startEndingCountdown = useCallback((seconds: number) => { setShowCountdown(true); setCountdownSeconds(seconds); if (countdownIntervalRef.current) { clearInterval(countdownIntervalRef.current); } countdownIntervalRef.current = setInterval(() => { setCountdownSeconds(prev => { if (prev <= 1) { // 倒计时结束,跳转下一集 if (onNextEpisode) { onNextEpisode(); } setShowCountdown(false); if (countdownIntervalRef.current) { clearInterval(countdownIntervalRef.current); } return 0; } return prev - 1; }); }, 1000); }, [onNextEpisode]); // 检查片尾倒计时 const checkEndingCountdown = useCallback((time: number) => { if (!skipConfig?.segments?.length || !duration || !onNextEpisode) return; const endingSegments = skipConfig.segments.filter(s => s.type === 'ending' && s.autoNextEpisode !== false); if (!endingSegments.length) return; for (const segment of endingSegments) { const timeToEnd = duration - time; const timeToSegmentStart = duration - segment.start; // 当距离视频结束的时间等于设定的片尾开始时间时,开始倒计时 if (timeToEnd <= timeToSegmentStart && timeToEnd > 0 && !showCountdown) { startEndingCountdown(Math.ceil(timeToEnd)); break; } } }, [skipConfig, duration, onNextEpisode, showCountdown, startEndingCountdown]); // 检查当前播放时间是否在跳过区间内 const checkSkipSegment = useCallback( (time: number) => { if (!skipConfig?.segments?.length) return; const currentSegment = skipConfig.segments.find( (segment) => time >= segment.start && time <= segment.end ); if (currentSegment && currentSegment !== currentSkipSegment) { setCurrentSkipSegment(currentSegment); // 检查是否开启自动跳过 const hasAutoSkipSetting = skipConfig.segments.some(s => s.autoSkip !== false); if (hasAutoSkipSetting) { // 自动跳过:延迟1秒执行跳过 if (autoSkipTimeoutRef.current) { clearTimeout(autoSkipTimeoutRef.current); } autoSkipTimeoutRef.current = setTimeout(() => { handleAutoSkip(currentSegment); }, 1000); setShowSkipButton(false); // 自动跳过时不显示按钮 } else { // 手动模式:显示跳过按钮 setShowSkipButton(true); // 自动隐藏跳过按钮 if (skipTimeoutRef.current) { clearTimeout(skipTimeoutRef.current); } skipTimeoutRef.current = setTimeout(() => { setShowSkipButton(false); setCurrentSkipSegment(null); }, 8000); } } else if (!currentSegment && currentSkipSegment) { setCurrentSkipSegment(null); setShowSkipButton(false); if (skipTimeoutRef.current) { clearTimeout(skipTimeoutRef.current); } if (autoSkipTimeoutRef.current) { clearTimeout(autoSkipTimeoutRef.current); } } // 检查片尾倒计时 checkEndingCountdown(time); }, [skipConfig, currentSkipSegment, handleAutoSkip, checkEndingCountdown] ); // 执行跳过 const handleSkip = useCallback(() => { if (!currentSkipSegment || !artPlayerRef.current) return; const targetTime = currentSkipSegment.end + 1; // 跳到片段结束后1秒 artPlayerRef.current.currentTime = targetTime; lastSkipTimeRef.current = Date.now(); setShowSkipButton(false); setCurrentSkipSegment(null); if (skipTimeoutRef.current) { clearTimeout(skipTimeoutRef.current); } // 显示跳过提示 if (artPlayerRef.current.notice) { const segmentName = currentSkipSegment.type === 'opening' ? '片头' : '片尾'; artPlayerRef.current.notice.show = `已跳过${segmentName}`; } }, [currentSkipSegment, artPlayerRef]); // 保存新的跳过片段(单个片段模式) const handleSaveSegment = useCallback(async () => { if (!newSegment.start || !newSegment.end || !newSegment.type) { alert('请填写完整的跳过片段信息'); return; } if (newSegment.start >= newSegment.end) { alert('开始时间必须小于结束时间'); return; } try { const segment: SkipSegment = { start: newSegment.start, end: newSegment.end, type: newSegment.type as 'opening' | 'ending', title: newSegment.title || (newSegment.type === 'opening' ? '片头' : '片尾'), autoSkip: true, // 默认开启自动跳过 autoNextEpisode: newSegment.type === 'ending', // 片尾默认开启自动下一集 }; const updatedConfig: EpisodeSkipConfig = { source, id, title, segments: skipConfig?.segments ? [...skipConfig.segments, segment] : [segment], updated_time: Date.now(), }; await saveSkipConfig(source, id, updatedConfig); setSkipConfig(updatedConfig); onSettingModeChange?.(false); setNewSegment({}); alert('跳过片段已保存'); } catch (err) { console.error('保存跳过片段失败:', err); alert('保存失败,请重试'); } }, [newSegment, skipConfig, source, id, title, onSettingModeChange]); // 保存批量设置的跳过配置 const handleSaveBatchSettings = useCallback(async () => { const segments: SkipSegment[] = []; // 添加片头设置 if (batchSettings.openingStart && batchSettings.openingEnd) { const start = timeToSeconds(batchSettings.openingStart); const end = timeToSeconds(batchSettings.openingEnd); if (start >= end) { alert('片头开始时间必须小于结束时间'); return; } segments.push({ start, end, type: 'opening', title: '片头', autoSkip: batchSettings.autoSkip, }); } // 添加片尾设置 if (batchSettings.endingStart) { const endingStartSeconds = timeToSeconds(batchSettings.endingStart); // 如果没有设置结束时间,则直接跳转到下一集 if (!batchSettings.endingEnd || batchSettings.endingEnd.trim() === '') { // 直接从指定时间跳转下一集 segments.push({ start: endingStartSeconds, end: duration, // 设置为视频总长度 type: 'ending', title: '片尾跳转下一集', autoSkip: batchSettings.autoSkip, autoNextEpisode: batchSettings.autoNextEpisode, }); } else { const endingEndSeconds = timeToSeconds(batchSettings.endingEnd); if (endingStartSeconds >= endingEndSeconds) { alert('片尾开始时间必须小于结束时间'); return; } segments.push({ start: endingStartSeconds, end: endingEndSeconds, type: 'ending', title: '片尾', autoSkip: batchSettings.autoSkip, autoNextEpisode: batchSettings.autoNextEpisode, }); } } if (segments.length === 0) { alert('请至少设置片头或片尾时间'); return; } try { const updatedConfig: EpisodeSkipConfig = { source, id, title, segments, updated_time: Date.now(), }; await saveSkipConfig(source, id, updatedConfig); setSkipConfig(updatedConfig); onSettingModeChange?.(false); // 重置批量设置 setBatchSettings({ openingStart: '0:00', openingEnd: '1:30', endingStart: '20:00', endingEnd: '', autoSkip: true, autoNextEpisode: true, }); alert('跳过配置已保存'); } catch (err) { console.error('保存跳过配置失败:', err); alert('保存失败,请重试'); } }, [batchSettings, duration, source, id, title, onSettingModeChange, timeToSeconds]); // 删除跳过片段 const handleDeleteSegment = useCallback( async (index: number) => { if (!skipConfig?.segments) return; try { const updatedSegments = skipConfig.segments.filter((_, i) => i !== index); if (updatedSegments.length === 0) { // 如果没有片段了,删除整个配置 await deleteSkipConfig(source, id); setSkipConfig(null); } else { // 更新配置 const updatedConfig: EpisodeSkipConfig = { ...skipConfig, segments: updatedSegments, updated_time: Date.now(), }; await saveSkipConfig(source, id, updatedConfig); setSkipConfig(updatedConfig); } alert('跳过片段已删除'); } catch (err) { console.error('删除跳过片段失败:', err); alert('删除失败,请重试'); } }, [skipConfig, source, id] ); // 格式化时间显示 const formatTime = (seconds: number): string => { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; }; // 初始化加载配置 useEffect(() => { loadSkipConfig(); }, [loadSkipConfig]); // 监听播放时间变化 useEffect(() => { if (currentTime > 0) { checkSkipSegment(currentTime); } }, [currentTime, checkSkipSegment]); // 清理定时器 useEffect(() => { return () => { if (skipTimeoutRef.current) { clearTimeout(skipTimeoutRef.current); } if (autoSkipTimeoutRef.current) { clearTimeout(autoSkipTimeoutRef.current); } if (countdownIntervalRef.current) { clearInterval(countdownIntervalRef.current); } }; }, []); return (
{/* 倒计时显示 - 片尾自动跳转下一集 */} {showCountdown && (
{countdownSeconds}秒后自动播放下一集
)} {/* 跳过按钮 */} {showSkipButton && currentSkipSegment && (
{currentSkipSegment.type === 'opening' ? '检测到片头' : '检测到片尾'}
)} {/* 设置模式面板 - 增强版批量设置 */} {isSettingMode && (

智能跳过设置

{/* 全局开关 */}

开启后将自动跳过设定的片头片尾,无需手动点击

{/* 片头设置 */}

🎬 片头设置

setBatchSettings({...batchSettings, openingStart: e.target.value})} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100" placeholder="0:00" />

格式: 分:秒 (如 0:00)

setBatchSettings({...batchSettings, openingEnd: e.target.value})} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100" placeholder="1:30" />

格式: 分:秒 (如 1:30)

{/* 片尾设置 */}

🎭 片尾设置

setBatchSettings({...batchSettings, endingStart: e.target.value})} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100" placeholder="20:00" />

从此时间开始检测片尾

setBatchSettings({...batchSettings, endingEnd: e.target.value})} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100" placeholder="留空直接跳下一集" />

空白=直接跳下一集

当前播放时间: {secondsToTime(currentTime)}

{duration > 0 && (

视频总长度: {secondsToTime(duration)}

)}

💡 片头示例: 从 0:00 自动跳到 1:30

💡 片尾示例: 从 20:00 开始倒计时,自动跳下一集

💡 支持格式: 1:30 (1分30秒) 或 90 (90秒)

{/* 分割线 */}
{/* 传统单个设置模式 */}
高级设置:添加单个片段
setNewSegment({ ...newSegment, start: parseFloat(e.target.value) })} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100" />
setNewSegment({ ...newSegment, end: parseFloat(e.target.value) })} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100" />
)} {/* 管理已有片段 - 优化布局避免重叠 */} {skipConfig && skipConfig.segments && skipConfig.segments.length > 0 && !isSettingMode && (

跳过配置

{skipConfig.segments.map((segment, index) => (
{segment.type === 'opening' ? '🎬片头' : '🎭片尾'}
{formatTime(segment.start)} - {formatTime(segment.end)} {segment.autoSkip && ( 自动 )}
))}
)}
); } // 导出跳过控制器的设置按钮组件 export function SkipSettingsButton({ onClick }: { onClick: () => void }) { return ( ); }