import React, { useState, forwardRef, useEffect, useCallback, useMemo, useLayoutEffect, useRef, } from 'react' import { Container, Layer } from './Container' import { PlaybackArea, VapixParameters, VideoProperties, PlayerNativeElement, } from './PlaybackArea' import { ControlArea, ControlBar } from './Controls' import { Button } from './components/Button' import { Pause, Play } from './img' import { useUserActive } from './hooks/useUserActive' import { MediaStreamPlayerContainer } from './components/MediaStreamPlayerContainer' import { Limiter } from './components/Limiter' import { Format } from './types' const DEFAULT_FORMAT = Format.JPEG interface BasicPlayerProps { readonly hostname: string readonly vapixParams?: VapixParameters readonly format?: Format readonly autoPlay?: boolean /** * Set to true if the camera requires a secure * connection, "https" and "wss" protocols. */ readonly secure?: boolean readonly className?: string /** * Activate automatic retries on RTSP errors. */ readonly autoRetry?: boolean } export const BasicPlayer = forwardRef( ( { hostname, vapixParams = {}, format = DEFAULT_FORMAT, autoPlay = false, secure, className, autoRetry, }, ref, ) => { const [play, setPlay] = useState(autoPlay) const [host, setHost] = useState(hostname) /** * Controls */ const [videoProperties, setVideoProperties] = useState() const onPlaying = useCallback( (props: VideoProperties) => { setVideoProperties(props) }, [setVideoProperties], ) const onPlayPause = useCallback(() => { if (play) { setPlay(false) } else { setHost(hostname) setPlay(true) } }, [play, hostname]) useEffect(() => { const cb = () => { if (document.visibilityState === 'visible') { setPlay(true) setHost(hostname) } else if (document.visibilityState === 'hidden') { setPlay(false) setHost('') } } document.addEventListener('visibilitychange', cb) return () => document.removeEventListener('visibilitychange', cb) }, [hostname]) /** * Aspect ratio * * This needs to be set so make the Container (and Layers) match the size of * the visible image of the video or still image. */ const naturalAspectRatio = useMemo(() => { if (videoProperties === undefined) { return undefined } const { width, height } = videoProperties return width / height }, [videoProperties]) /** * Limit video size. * * The video size should not expand outside the available container, and * should be recomputed on resize. */ const limiterRef = useRef(null) useLayoutEffect(() => { if (naturalAspectRatio === undefined || limiterRef.current === null) { return } const observer = new window.ResizeObserver(([entry]) => { const element = entry.target as HTMLElement const maxWidth = element.clientHeight * naturalAspectRatio element.style.maxWidth = `${maxWidth}px` }) observer.observe(limiterRef.current) return () => observer.disconnect() }, [naturalAspectRatio]) const controlArea = useRef(null) const userActive = useUserActive(controlArea) /** * Render * * Each layer is positioned exactly on top of the visible image, since the * aspect ratio is carried over to the container, and the layers match the * container size. */ return ( ) }, ) BasicPlayer.displayName = 'BasicPlayer'