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_0924.json'; 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 [timestamp, setTimestamp] = useState(); const [clientStatus, setClientStatus] = useState({}); const refCue = useRef(null); // const refNextCue = useRef(null); const refLight= useRef(); const refDuration= 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:'192.168.51.255:8000', }); } function sendOscToSound(addr, message){ invoke('send_osc_message', { key: addr, message, host:'0.0.0.0:0', target:'192.168.51.100:58200', }); } 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 playCue({id, name, description, type, auto, audioFile, ...props}) { console.log('Playing cue:', {id, name, description, type, auto, audioFile, ...props}); setCurrentCue({id, name, description, type, auto, audioFile, ...props}); // Handle other cue types and properties here if(props.audioCue){ sendOscToSound(OSC_ADDRESS.SCS_GO_CUE, props.audioCue); } if(props.clientCue){ sendOsc(OSC_ADDRESS.PLAYCUE, props.clientCue,'all'); } if(props.lightCue){ // sendOsc(OSC_ADDRESS.CLIENT_INPUT, props.lightCue); if(props.lightCue=='fade_in_light') refLight.current.fadeIn(); // Fade in light for conversation start if(props.lightCue=='fade_out_light') refLight.current.fadeOut(); // Fade out light for conversation end } if(props.reset){ //sendOsc(OSC_ADDRESS.RESETCUE,'','all'); refLight.current.set(1); } if(props.duration){ if(refTimer.current) clearInterval(refTimer.current); let due= props.duration; console.log('Start timer:', due); const next=cuelist.find(c=>c.id==id+1); refTimer.current= setInterval(()=>{ due--; if(due<=0){ clearInterval(refTimer.current); refTimer.current=null; if(auto){ console.log('Auto play next cue:', next); playCue(next); } } refDuration.current.innerText= secondToTime(due); },1000); } } 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, ''); refLight.current.set(1); } function secondToTime(seconds) { const minutes = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${minutes}:${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'}

{timestamp}

{ 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;