import { h, Fragment } from 'preact'; import AutoUpdatingCameraImage from '../components/AutoUpdatingCameraImage'; import ActivityIndicator from '../components/ActivityIndicator'; import JSMpegPlayer from '../components/JSMpegPlayer'; import Button from '../components/Button'; import Card from '../components/Card'; import Heading from '../components/Heading'; import Link from '../components/Link'; import SettingsIcon from '../icons/Settings'; import Switch from '../components/Switch'; import ButtonsTabbed from '../components/ButtonsTabbed'; import { usePersistence } from '../context'; import { useCallback, useMemo, useState } from 'preact/hooks'; import { useApiHost } from '../api'; import useSWR from 'swr'; import WebRtcPlayer from '../components/WebRtcPlayer'; import '../components/MsePlayer'; import CameraControlPanel from '../components/CameraControlPanel'; import { baseUrl } from '../api/baseUrl'; const emptyObject = Object.freeze({}); export default function Camera({ camera }) { const { data: config } = useSWR('config'); const apiHost = useApiHost(); const [showSettings, setShowSettings] = useState(false); const [viewMode, setViewMode] = useState('live'); const cameraConfig = config?.cameras[camera]; const restreamEnabled = cameraConfig && Object.keys(config.go2rtc.streams || {}).includes(cameraConfig.live.stream_name); const jsmpegWidth = cameraConfig ? Math.round(cameraConfig.live.height * (cameraConfig.detect.width / cameraConfig.detect.height)) : 0; const [viewSource, setViewSource, sourceIsLoaded] = usePersistence( `${camera}-source`, getDefaultLiveMode(config, cameraConfig, restreamEnabled) ); const sourceValues = restreamEnabled ? ['mse', 'webrtc', 'jsmpeg'] : ['jsmpeg']; const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject); const handleSetOption = useCallback( (id, value) => { const newOptions = { ...options, [id]: value }; setOptions(newOptions); }, [options, setOptions] ); const searchParams = useMemo( () => new URLSearchParams( Object.keys(options).reduce((memo, key) => { memo.push([key, options[key] === true ? '1' : '0']); return memo; }, []) ), [options] ); const handleToggleSettings = useCallback(() => { setShowSettings(!showSettings); }, [showSettings, setShowSettings]); if (!cameraConfig || !sourceIsLoaded) { return ; } if (!restreamEnabled) { setViewSource('jsmpeg'); } const optionContent = showSettings ? (
Mask & Zone creator
) : null; let player; if (viewMode === 'live') { if (viewSource == 'mse' && restreamEnabled) { if ('MediaSource' in window) { player = (
); } else { player = (
MSE is not supported on iOS devices. You'll need to use jsmpeg or webRTC. See the docs for more info.
); } } else if (viewSource == 'webrtc' && restreamEnabled) { player = (
); } else { player = (
); } } else if (viewMode === 'debug') { player = (
{showSettings ? : null}
); } return (
{camera.replaceAll('_', ' ')}
{player} {cameraConfig?.onvif?.host && (
Control Panel
)}
Tracked objects
{cameraConfig.objects.track.map((objectType) => ( } /> ))}
); } function getDefaultLiveMode(config, cameraConfig, restreamEnabled) { if (cameraConfig) { if (restreamEnabled) { return config.ui.live_mode; } return 'jsmpeg'; } return undefined; }