media-stream-player
Version:
Player built on top of media-stream-library
186 lines • 8.11 kB
JavaScript
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