import { useState, useEffect, useRef } from "react"; import { Howl, Howler } from 'howler'; import { invoke } from "@tauri-apps/api/core"; import { listen } from '@tauri-apps/api/event'; import "./App.css"; import { OSC_ADDRESS } from "./utils/constant"; import { Light } from "./utils/light"; const DefaultFadeDuration = 3; // 1 second const CLIENT_COUNT = 13; const CUE_FILE = 'cuelist_1009.json'; const OSC_BROADCAST_IP='192.168.51.255:8000'; const SCS_IP='192.168.51.100:58200'; // const OSC_BROADCAST_IP = '192.168.234.255:8000'; const UNITY_BROADCAST_IP = '192.168.234.255:9000'; // const SCS_IP = '192.168.234.41:58200'; const CueType = { Bg: 'bg', Announce: 'announce', Light: 'light' } const EmojiType = { bg: '🎵', announce: '📢', light: '💡' } function App() { const [cuelist, setCuelist] = useState([]); const [currentCue, setCurrentCue] = useState(null); const [fadeDuration, setFadeDuration] = useState(DefaultFadeDuration); // Default fade duration in seconds const [clientStatus, setClientStatus] = useState({}); const refCue = useRef(null); // const refNextCue = useRef(null); const refLight = useRef(); const refDuration = useRef(); const refTotalTime=useRef(); const refTimer = useRef(); function sendOsc(addr, message, id) { invoke('send_osc_message', { key: addr, message: `${id}#${message}`, host: '0.0.0.0:0', target: OSC_BROADCAST_IP, }); } function sendOscToSound(addr, message) { invoke('send_osc_message', { key: addr, message, host: '0.0.0.0:0', target: SCS_IP, }); } function onOsc(message) { const addr = message.addr; const [id, status, name] = message.args[0]?.split('#'); // console.log('receive osc:', id, status,name); switch (addr) { case OSC_ADDRESS.CLIENT_STATUS: setClientStatus(prev => ({ ...prev, [id]: { status, name, timestamp: new Date().toLocaleTimeString(), } })); break; case OSC_ADDRESS.CLIENT_DURATION: setClientStatus(prev => ({ ...prev, [id]: { ...prev[id], duration: status, timestamp: new Date().toLocaleTimeString(), } })); break; case OSC_ADDRESS.CLIENT_INPUT: setClientStatus(prev => ({ ...prev, [id]: { ...prev[id], input: status, timestamp: new Date().toLocaleTimeString(), } })); break; } } function getTotalLeftTime(id){ const cue=cuelist.find(el=>el.id==id); const index=cuelist.indexOf(cue); if(index==-1) return; let sum=0; for(var i=index;i { const now = new Date().getTime(); const timeLeft = endTime - now; const totalTimeLeft=totalEndTime-now; //console.log(totalTimeLeft); if (timeLeft <= 0) { // Timer has finished clearTimeout(refTimer.current); refTimer.current = null; if (auto) { // const next = cuelist.find(c => c.id === id + 1); const index = cuelist.findIndex(c => c.id === id); const next = cuelist[index + 1]; if (!next) return; console.log('Auto play next cue:', next); playCue(next); } return; } // Update the displayed duration refDuration.current.innerText = secondToTime(timeLeft / 1000); refTotalTime.current.innerText=`${secondToTime(totalTimeLeft/1000)} / ${secondToTime(totalLefTime)}`; // Call the next tick refTimer.current = setTimeout(tick, 100); }; // Start the timer console.log('Start timer:', props.duration); refTimer.current = setTimeout(tick, 100); } } function stop() { console.log('Stop all'); sendOsc(OSC_ADDRESS.STOPCUE, '', 'all'); sendOscToSound(OSC_ADDRESS.SCS_FADE_ALL, ''); // cear timer if (refTimer.current) clearInterval(refTimer.current); } function reset() { console.log('Reset all'); sendOsc(OSC_ADDRESS.RESETCUE, '', 'all'); sendOscToSound(OSC_ADDRESS.SCS_STOP_ALL, ''); resetLight(); } function resetLight(){ refLight.current.set(1); invoke('send_osc_message', { key: '/amplitude', message: 1.0.toString(), host:`0.0.0.0:0`, target: UNITY_BROADCAST_IP, }); } function secondToTime(seconds) { const minutes = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${minutes.toString().padStart(2,'0')}:${secs < 10 ? '0' : ''}${secs}`; } function getAudioDuration() { const type = refCue.current?.type; // console.log('getAudioDuration', type, refCue.current); // switch(type) { // case CueType.Bg: if (refAudioBg.current) { return `${secondToTime(refAudioBg.current.seek())} / ${secondToTime(refAudioBg.current.duration())} ${refAudioBg.current?.volume()}`; } else { return 'N/A'; } // case CueType.Announce: // return refAudioAnnounce.current ? `${secondToTime(refAudioAnnounce.current.seek())} / ${secondToTime(refAudioAnnounce.current.duration())}` : 'N/A'; // default: // return 'N/A'; // } } useEffect(() => { if (currentCue) { refCue.current = currentCue; // console.log('Current Cue:', currentCue); } }, [currentCue]); useEffect(() => { fetch(`/${CUE_FILE}`) .then(response => response.json()) .then(data => { console.log('Cuelist data:', data); setCuelist(data.cuelist); }) .catch(error => { console.error('Error fetching cuelist:', error); }); listen('osc_message', (event) => { // console.log(`Received OSC message: ${JSON.stringify(event.payload)}`); onOsc(event.payload); }); }, []); return (
{currentCue ? `${currentCue.name}` : 'None'}
{ const value = parseFloat(e.target.value); setFadeDuration(value); }}> {fadeDuration}s
{/* */} {cuelist?.map(({ id, name, description, type, auto, ...props }, index) => ( ))}
ID Name Description Type Auto Due Light Audio Client
{name} {description} {EmojiType[type]} {auto ? '⤵️' : ''} {props.duration} {props.lightCue && `L${props.lightCue}`} {props.audioCue && `S${props.audioCue}`} {props.clientCue || ''}
{Array.from(Array(CLIENT_COUNT).keys()).map((i) => { const id = (i + 1).toString();//.padStart(2,'0'); const log = clientStatus[id.toString()]; return ( {<> } ); })}
id status cue input due timestamp
{id}{log?.status} {log?.name} {log?.input?.substr(log.input.length - 20)?.split('').map((ch, i) => i % 3 < 1 ? ch : '*').join('')} {log?.duration} {log?.timestamp} {cuelist?.filter(c => c.clientCue && !c.debug).map((c, index) => ( ))}
); } export default App;