import { useEffect, useState } from 'react' import toast from 'react-hot-toast' import { Cast, RefreshCw, Tv } from 'lucide-react' import { dlnaAPI, type DLNADevice } from '../api/dlna' import { mediaAPI } from '../api/library' import { streamURL } from '../api/client' import type { Media } from '../types' // DlnaPage scans the LAN for UPnP MediaRenderer devices and lets the // user push a media item to one of them via SetAVTransportURI + Play. export function DlnaPage() { const [devices, setDevices] = useState([]) const [scanning, setScanning] = useState(false) const [media, setMedia] = useState([]) const [selectedMedia, setSelectedMedia] = useState('') const scan = (force: boolean) => { setScanning(true) dlnaAPI .list(force) .then(setDevices) .catch(() => toast.error('设备发现失败,容器网络可能不支持组播')) .finally(() => setScanning(false)) } useEffect(() => { scan(false) mediaAPI.search('', 30).then((d) => { setMedia(d.items) if (d.items.length > 0) setSelectedMedia(d.items[0].id) }) }, []) const cast = async (dev: DLNADevice) => { if (!selectedMedia) { toast.error('请先选择一个媒体') return } // Build the absolute URL the renderer will pull from. const url = window.location.origin + streamURL(selectedMedia) try { await dlnaAPI.cast(dev.control_url, url) toast.success(`已投屏到 ${dev.friendly_name}`) } catch (err: unknown) { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? '投屏失败' toast.error(msg) } } return (

DLNA 投屏

扫描局域网中的 UPnP MediaRenderer 设备(电视、机顶盒等),选择媒体后一键播放。

设备 ({devices.length})

{devices.length === 0 && !scanning && (

未发现任何 DLNA 设备。请确保:服务器与设备在同一局域网,容器使用 host 网络模式, 目标设备已开启 DLNA / 屏幕镜像。

)}
{devices.map((dev) => (

{dev.friendly_name || dev.model_name}

{dev.manufacturer} · {dev.ip_address}

))}
) }