UNPKG

@masa-dev/react-signage

Version:

This is a react library for signage.

132 lines (131 loc) 5.23 kB
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'; }