UNPKG

media-stream-player

Version:

Player built on top of media-stream-library

186 lines 8.11 kB
import React, { useState, forwardRef, useEffect, useCallback, useMemo, useLayoutEffect, useRef, } from 'react'; import { Container, Layer } from './Container'; import { PlaybackArea, Format, } from './PlaybackArea'; import { Controls } from './Controls'; import { Feedback } from './Feedback'; import { Stats } from './Stats'; import { useSwitch } from './hooks/useSwitch'; import { getImageURL } from './utils'; import { Limiter } from './components/Limiter'; import { MediaStreamPlayerContainer } from './components/MediaStreamPlayerContainer'; const DEFAULT_FORMAT = Format.JPEG; export const Player = forwardRef(({ hostname, vapixParams = {}, initialFormat = DEFAULT_FORMAT, autoPlay = false, onSdp, metadataHandler, secure, className, startTime, duration, autoRetry, }, ref) => { const [play, setPlay] = useState(autoPlay); const [offset, setOffset] = useState(0); const [refresh, setRefresh] = useState(0); const [host, setHost] = useState(hostname); const [waiting, setWaiting] = useState(autoPlay); const [volume, setVolume] = useState(); const [format, setFormat] = useState(initialFormat); /** * VAPIX parameters */ const [parameters, setParameters] = useState(vapixParams); useEffect(() => { /** * Check if localStorage actually exists, since if you * server side render, localStorage won't be available. */ if ((window === null || window === void 0 ? void 0 : window.localStorage) !== undefined) { window.localStorage.setItem('vapix', JSON.stringify(parameters)); } }, [parameters]); /** * Stats overlay */ const [showStatsOverlay, toggleStatsOverlay] = useSwitch((window === null || window === void 0 ? void 0 : window.localStorage) !== undefined ? window.localStorage.getItem('stats-overlay') === 'on' : false); useEffect(() => { if ((window === null || window === void 0 ? void 0 : window.localStorage) !== undefined) { window.localStorage.setItem('stats-overlay', showStatsOverlay ? 'on' : 'off'); } }, [showStatsOverlay]); /** * Controls */ const [videoProperties, setVideoProperties] = useState(); const onPlaying = useCallback((props) => { setVideoProperties(props); setWaiting(false); setVolume(props.volume); }, [setWaiting]); const onPlayPause = useCallback(() => { if (play) { setPlay(false); } else { setWaiting(true); setHost(hostname); setPlay(true); } }, [play, hostname]); const onRefresh = useCallback(() => { setPlay(true); setRefresh((value) => value + 1); setWaiting(true); }, []); const onScreenshot = useCallback(() => { if (videoProperties === undefined) { return undefined; } const { el, width, height } = videoProperties; const imageURL = getImageURL(el, { width, height }); const link = document.createElement('a'); const event = new window.MouseEvent('click'); link.download = `snapshot_${Date.now()}.jpg`; link.href = imageURL; link.dispatchEvent(event); }, [videoProperties]); const onStop = useCallback(() => { setPlay(false); setHost(''); setWaiting(false); }, []); const onVapix = useCallback((key, value) => { setParameters((p) => { const newParams = { ...p, [key]: value }; if (value === '') { delete newParams[key]; } return newParams; }); setRefresh((refreshCount) => refreshCount + 1); }, []); /** * Refresh when changing visibility (e.g. when you leave a tab the * video will halt, so when you return we need to play again). */ useEffect(() => { const cb = () => { if (document.visibilityState === 'visible') { setPlay(true); setHost(hostname); } else if (document.visibilityState === 'hidden') { setPlay(false); setWaiting(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; const maxWidth = element.clientHeight * naturalAspectRatio; element.style.maxWidth = `${maxWidth}px`; }); observer.observe(limiterRef.current); return () => observer.disconnect(); }, [naturalAspectRatio]); /** * Volume control on the VideoElement (h264 only) */ useEffect(() => { if ((videoProperties === null || videoProperties === void 0 ? void 0 : videoProperties.volume) !== undefined && volume !== undefined) { const videoEl = videoProperties.el; videoEl.muted = volume === 0; videoEl.volume = volume; } }, [videoProperties, volume]); /** * 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. * * There is a layer for the spinner (feedback), a statistics overlay, and a * control bar with play/pause/stop/refresh and a settings menu. */ return (React.createElement(MediaStreamPlayerContainer, { className: className }, React.createElement(Limiter, { ref: limiterRef }, React.createElement(Container, { aspectRatio: naturalAspectRatio }, React.createElement(Layer, null, React.createElement(PlaybackArea, { forwardedRef: ref, refresh: refresh, play: play, offset: offset, host: host, format: format, parameters: parameters, onPlaying: onPlaying, onSdp: onSdp, metadataHandler: metadataHandler, secure: secure, autoRetry: autoRetry })), React.createElement(Layer, null, React.createElement(Feedback, { waiting: waiting })), React.createElement(Layer, null, React.createElement(Controls, { play: play, videoProperties: videoProperties, src: host, parameters: parameters, onPlay: onPlayPause, onStop: onStop, onRefresh: onRefresh, onScreenshot: onScreenshot, onFormat: setFormat, onVapix: onVapix, onSeek: setOffset, labels: { play: 'Play', pause: 'Pause', stop: 'Stop', refresh: 'Refresh', settings: 'Settings', screenshot: 'Take a snapshot', volume: 'Volume', }, showStatsOverlay: showStatsOverlay, toggleStats: toggleStatsOverlay, format: format, volume: volume, setVolume: setVolume, startTime: startTime, duration: duration })), showStatsOverlay && videoProperties !== undefined ? (React.createElement(Stats, { format: format, videoProperties: videoProperties, refresh: refresh, volume: volume })) : null)))); }); Player.displayName = 'Player'; //# sourceMappingURL=Player.js.map