import { useState, useEffect, useRef } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { Select, Button, Typography, Space, Tag, Spin, Skeleton, Empty, message } from 'antd' import { ArrowLeftOutlined, WifiOutlined, SignalFilled } from '@ant-design/icons' import Hls from 'hls.js' import { getWatchUrls, type WatchInfo, type LineInfo } from '../../api/streams' import { useBrand } from '../../brand/BrandContext' const { Title, Text } = Typography export default function WatchPage() { const { brand } = useBrand() const navigate = useNavigate() const { id } = useParams<{ id: string }>() const videoRef = useRef(null) const [info, setInfo] = useState(null) const [loading, setLoading] = useState(true) const [activeLine, setActiveLine] = useState('') const [activeQuality, setActiveQuality] = useState('') const [hls, setHls] = useState(null) useEffect(() => { if (!id) return loadInfo() const timer = setInterval(loadInfo, 15000) return () => { clearInterval(timer); if (hls) hls.destroy() } }, [id]) async function loadInfo() { if (!id) return try { const data = await getWatchUrls(parseInt(id)) setInfo(data) if (!activeLine && data.lines.length > 0) { const best = autoSelectLine(data.lines) setActiveLine(best.node_id) if (data.qualities.length > 0) setActiveQuality(data.qualities[0].label) } } catch { } setLoading(false) } function autoSelectLine(lines: LineInfo[]): LineInfo { const enabled = lines.filter(l => l.weight > 0) if (enabled.length === 0) return lines[0] const total = enabled.reduce((s, l) => s + l.weight, 0) let r = Math.random() * total for (const line of enabled) { r -= line.weight; if (r <= 0) return line } return enabled[enabled.length - 1] } useEffect(() => { if (!info || !activeLine || !videoRef.current) return const line = info.lines.find(l => l.node_id === activeLine) if (!line) return const quality = activeQuality ? line.qualities.find(q => q.label === activeQuality) : line.qualities[0] if (!quality) return if (hls) hls.destroy() const url = quality.url if (Hls.isSupported()) { const hi = new Hls({ enableWorker: false, lowLatencyMode: false }) hi.loadSource(url); hi.attachMedia(videoRef.current!) hi.on(Hls.Events.MANIFEST_PARSED, () => { videoRef.current?.play().catch(() => {}) }) hi.on(Hls.Events.ERROR, (_e, d) => { if (d.fatal) message.warning('播放异常,尝试切换线路...') }) setHls(hi) } else if (videoRef.current!.canPlayType('application/vnd.apple.mpegurl')) { videoRef.current!.src = url } }, [activeLine, activeQuality, info]) if (loading) return
if (!info) return
const statusLabel = info.status === 'live' ? '直播中' : info.status === 'pending' ? '待开播' : info.status === 'ended' ? '已结束' : info.status return (
{info.title} {statusLabel}
{info.multicast_available ? '组播' : '单播'}
({ value: l.node_id, label: `${l.node_name} (${l.weight})` }))} prefix={} placeholder="线路" />
) }