增加自行上传弹幕

This commit is contained in:
mtvpls
2026-01-05 00:52:17 +08:00
parent bc49b60c0b
commit fdf14dbae0
4 changed files with 167 additions and 2 deletions

View File

@@ -6,6 +6,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { getEpisodes, searchAnime } from '@/lib/danmaku/api';
import type {
DanmakuAnime,
DanmakuComment,
DanmakuEpisode,
DanmakuSelection,
} from '@/lib/danmaku/types';
@@ -15,6 +16,7 @@ interface DanmakuPanelProps {
currentEpisodeIndex: number;
onDanmakuSelect: (selection: DanmakuSelection) => void;
currentSelection: DanmakuSelection | null;
onUploadDanmaku?: (comments: DanmakuComment[]) => void;
}
export default function DanmakuPanel({
@@ -22,6 +24,7 @@ export default function DanmakuPanel({
currentEpisodeIndex,
onDanmakuSelect,
currentSelection,
onUploadDanmaku,
}: DanmakuPanelProps) {
const [searchKeyword, setSearchKeyword] = useState('');
const [searchResults, setSearchResults] = useState<DanmakuAnime[]>([]);
@@ -31,6 +34,7 @@ export default function DanmakuPanel({
const [isLoadingEpisodes, setIsLoadingEpisodes] = useState(false);
const [searchError, setSearchError] = useState<string | null>(null);
const initializedRef = useRef(false); // 标记是否已初始化过
const fileInputRef = useRef<HTMLInputElement>(null);
// 搜索弹幕
const handleSearch = useCallback(async (keyword: string) => {
@@ -118,6 +122,38 @@ export default function DanmakuPanel({
[currentSelection]
);
// 处理文件上传
const handleFileUpload = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
if (!file.name.endsWith('.xml')) {
setSearchError('请上传XML格式的弹幕文件');
return;
}
try {
const text = await file.text();
const { parseXmlDanmaku } = await import('@/lib/danmaku/xml-parser');
const comments = parseXmlDanmaku(text);
if (comments.length === 0) {
setSearchError('弹幕文件解析失败或文件为空');
return;
}
onUploadDanmaku?.(comments);
setSearchError(null);
} catch (error) {
console.error('上传弹幕失败:', error);
setSearchError('弹幕文件解析失败');
} finally {
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
}
}, [onUploadDanmaku]);
// 当视频标题首次加载时,初始化搜索关键词(仅执行一次)
useEffect(() => {
if (videoTitle && !initializedRef.current) {
@@ -369,6 +405,25 @@ export default function DanmakuPanel({
)}
</div>
</div>
{/* 上传弹幕区域 - 固定在底部 */}
{onUploadDanmaku && (
<div className='mt-3 flex-shrink-0 border-t border-gray-200 pt-3 dark:border-gray-700'>
<input
ref={fileInputRef}
type='file'
accept='.xml'
onChange={handleFileUpload}
className='hidden'
/>
<button
onClick={() => fileInputRef.current?.click()}
className='w-full text-center text-xs text-gray-500 dark:text-gray-400 hover:text-green-500 dark:hover:text-green-400 transition-colors py-2'
>
</button>
</div>
)}
</div>
);
}

View File

@@ -12,7 +12,7 @@ import { Settings } from 'lucide-react';
import DanmakuPanel from '@/components/DanmakuPanel';
import EpisodeFilterSettings from '@/components/EpisodeFilterSettings';
import type { DanmakuSelection } from '@/lib/danmaku/types';
import type { DanmakuSelection, DanmakuComment } from '@/lib/danmaku/types';
import { SearchResult, EpisodeFilterConfig } from '@/lib/types';
import { getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
@@ -51,6 +51,7 @@ interface EpisodeSelectorProps {
/** 弹幕相关 */
onDanmakuSelect?: (selection: DanmakuSelection) => void;
currentDanmakuSelection?: DanmakuSelection | null;
onUploadDanmaku?: (comments: DanmakuComment[]) => void;
/** 观影室房员状态 - 禁用选集和换源,但保留弹幕 */
isRoomMember?: boolean;
/** 集数过滤配置 */
@@ -79,6 +80,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
precomputedVideoInfo,
onDanmakuSelect,
currentDanmakuSelection,
onUploadDanmaku,
isRoomMember = false,
episodeFilterConfig = null,
onFilterConfigUpdate,
@@ -576,6 +578,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
currentEpisodeIndex={value - 1}
onDanmakuSelect={onDanmakuSelect}
currentSelection={currentDanmakuSelection || null}
onUploadDanmaku={onUploadDanmaku}
/>
</div>
)}