@masa-dev/react-signage
Version:
This is a react library for signage.
132 lines (131 loc) • 5.23 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
import { Toaster } from 'react-hot-toast';
import { interactionDummyVideo } from './assets/interactionDummyVideo';
import { DEFAULT_SIZE, FADE_DURATION } from './consts';
import { useDebug } from './features/debug/useDebug';
import { FadeoutOverlay, useFadeoutOverlay } from './features/fadeOverlay/fadeOverlay';
import { FullscreenableContainer } from "./features/fullscreen/FullscreenableContainer";
import { Img } from './features/media/items/Img';
import { Video } from './features/media/items/Video';
export const Signage = forwardRef(function Signage(props, ref) {
const { play, fullScreen, mute, items, onFullScreenChange, size = DEFAULT_SIZE, useDbCache } = props;
const [indexData, setIndexData] = useState({ index: 0, changedAt: 0 });
const item = items[indexData.index];
const imgRef = useRef(null);
const videoRef = useRef(null);
const { ref: overlayRef } = useFadeoutOverlay();
const timerRef = useRef(undefined);
const { debug, debugMessage } = useDebug();
useImperativeHandle(ref, () => ({
advanceNext,
}));
const itemsJson = useMemo(() => JSON.stringify(items), [items]);
useEffect(() => {
debugMessage({ message: `items changed`, severity: 'info' });
setIndexData(prev => {
const index = prev.index < items.length ? prev.index : 0;
return { index, changedAt: Date.now() };
});
}, [itemsJson]);
useEffect(() => {
if (!play)
return;
startItem({ isFirst: false });
}, [indexData]);
useEffect(() => {
if (play) {
startItem({ isFirst: true });
}
else {
stopItem();
}
return () => stopItem();
}, [play]);
async function startItem(params) {
if (!videoRef.current)
return;
const process = async () => {
overlayRef.current?.startFadeout({ mediaRef: [imgRef.current?.elementRef, videoRef.current?.elementRef].filter(isVisible)[0], duration: FADE_DURATION });
await setElements();
resetEvents();
};
// 古い端末用に、一旦ダミー動画を再生させる
if (params.isFirst) {
await videoRef.current.setSrc(interactionDummyVideo, { noDbCache: true });
await videoRef.current.play();
}
await process();
}
function stopItem() {
videoRef.current?.pause();
clearTimeout(timerRef.current);
changeShow('none');
}
function advanceNext() {
setIndexData(prev => {
const newIndex = prev.index + 1;
const result = newIndex >= items.length ? 0 : newIndex;
debugMessage({ message: `advanceNext: ${result}`, severity: 'info' });
return { index: result, changedAt: Date.now() };
});
}
async function setElements() {
if (!item) {
videoRef.current?.pause();
return changeShow('none');
}
;
switch (item.type) {
case 'image':
imgRef.current?.fadeIn();
await imgRef.current?.setSrc(item.src);
videoRef.current?.pause();
changeShow('image');
break;
case 'video':
if (!videoRef.current)
break;
videoRef.current.fadeIn();
debugMessage({ message: 'start video', severity: 'info' });
await videoRef.current.setSrc(item.src);
await videoRef.current.play();
changeShow('video');
break;
}
}
function changeShow(type) {
if (!imgRef.current || !videoRef.current) {
return;
}
if (type == 'image') {
imgRef.current.changeShow(true);
videoRef.current.changeShow(false);
}
else if (type == 'video') {
imgRef.current.changeShow(false);
videoRef.current.changeShow(true);
}
else if (type == 'none') {
imgRef.current.changeShow(false);
videoRef.current.changeShow(false);
}
}
function resetEvents() {
if (item?.type != 'image')
return;
clearTimeout(timerRef.current);
const second = item.second ?? 0;
if (second > 0) {
timerRef.current = setTimeout(() => {
advanceNext();
}, second * 1000);
}
}
return _jsx(_Fragment, { children: _jsxs(FullscreenableContainer, { play: play, fullScreen: fullScreen, style: { position: "relative", ...size }, onFullscreenChange: onFullScreenChange, children: [_jsx(Img, { ref: imgRef }), _jsx(Video, { ref: videoRef, onEnded: advanceNext, muted: mute, useDbCache: useDbCache }), _jsx(FadeoutOverlay, { ref: overlayRef, ...size }), debug && _jsx(Toaster, { position: "bottom-right" })] }) });
});
function isVisible(ref) {
if (!ref?.current)
return false;
return ref.current.style.display != 'none';
}