'use client'; import { ChangeEvent, FormEvent, useEffect, useRef, useState } from 'react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import HlsPlayer from './media/[id]/hls-player'; type MediaStatus = { id: string; resourceId: string | null; filename: string; status: string; hlsPath: string | null; hlsUrl: string | null; errorMessage: string | null; metadata: { hlsPath?: string; processedAt?: string } | null; createdAt: string; updatedAt: string; }; type UploadResponse = { mediaId: string; filename: string; status: string }; const terminalStates = new Set(['completed', 'failed']); export default function MediaConsole() { const [selectedFile, setSelectedFile] = useState(null); const [uploading, setUploading] = useState(false); const [error, setError] = useState(null); const [mediaStatus, setMediaStatus] = useState(null); const [lastUpload, setLastUpload] = useState(null); const inputRef = useRef(null); const mediaId = lastUpload?.mediaId; useEffect(() => { if (!mediaId || terminalStates.has(mediaStatus?.status || '')) return; let cancelled = false; async function pollStatus() { try { const response = await fetch(`/api/media/${mediaId}/status`, { cache: 'no-store' }); const payload = await response.json(); if (!response.ok) throw new Error(payload.error || '状态查询失败'); if (!cancelled) setMediaStatus(payload.media); } catch (err) { if (!cancelled) setError(err instanceof Error ? err.message : '状态查询失败'); } } pollStatus(); const timer = window.setInterval(pollStatus, 1500); return () => { cancelled = true; window.clearInterval(timer); }; }, [mediaId, mediaStatus?.status]); function handleFileChange(event: ChangeEvent) { setSelectedFile(event.target.files?.[0] || null); setError(null); } async function handleSubmit(event: FormEvent) { event.preventDefault(); if (!selectedFile) { setError('请选择一个视频文件'); return; } setUploading(true); setError(null); setMediaStatus(null); setLastUpload(null); const formData = new FormData(); formData.append('file', selectedFile); try { const response = await fetch('/api/media/upload', { method: 'POST', body: formData }); const payload = await response.json(); if (!response.ok) throw new Error(payload.error || '上传失败'); setLastUpload(payload.media); setMediaStatus({ id: payload.media.mediaId, filename: payload.media.filename, status: payload.media.status, hlsPath: null, hlsUrl: null, resourceId: null, errorMessage: null, metadata: null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); } catch (err) { setError(err instanceof Error ? err.message : '上传失败'); } finally { setUploading(false); } } const canUpload = Boolean(selectedFile) && !uploading; return (

EKB Media Pipeline

媒体处理控制台

{mediaStatus?.status || 'idle'}
{selectedFile?.name || '未选择文件'} {selectedFile ? `${Math.ceil(selectedFile.size / 1024)} KB` : '支持 MP4 等视频文件'}
{error ?
{error}
: null}
处理状态
{[ ['媒体 ID', mediaStatus?.id || '-'], ['资源 ID', mediaStatus?.resourceId || '-'], ['文件名', mediaStatus?.filename || selectedFile?.name || '-'], ['HLS Key', mediaStatus?.hlsPath || '-'], ['完成时间', mediaStatus?.metadata?.processedAt || '-'], ['失败原因', mediaStatus?.errorMessage || '-'], ].map(([label, value]) => (
{label}
{value}
))}
播放预览 {mediaStatus?.hlsUrl ? ( <> ) : (
{mediaStatus?.status === 'failed' ? '处理失败' : '等待 HLS 输出'}
)}
); }