add total time left

change setInterval to setTimeout
main
reng 2 months ago
parent ec8f4445e7
commit 4fad9cd452
  1. 474
      src/App.jsx
  2. 4
      src/utils/light.jsx

@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import {Howl, Howler} from 'howler'; import { Howl, Howler } from 'howler';
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { listen } from '@tauri-apps/api/event'; import { listen } from '@tauri-apps/api/event';
import "./App.css"; import "./App.css";
@ -8,19 +8,19 @@ import { Light } from "./utils/light";
const DefaultFadeDuration = 3; // 1 second const DefaultFadeDuration = 3; // 1 second
const CLIENT_COUNT=13; const CLIENT_COUNT = 13;
const CUE_FILE='cuelist_0924.json'; const CUE_FILE = 'cuelist_0924.json';
const CueType={ const CueType = {
Bg: 'bg', Bg: 'bg',
Announce: 'announce', Announce: 'announce',
Light: 'light' Light: 'light'
} }
const EmojiType={ const EmojiType = {
bg: '🎵', bg: '🎵',
announce: '📢', announce: '📢',
light: '💡' light: '💡'
} }
function App() { function App() {
@ -28,145 +28,186 @@ function App() {
const [cuelist, setCuelist] = useState([]); const [cuelist, setCuelist] = useState([]);
const [currentCue, setCurrentCue] = useState(null); const [currentCue, setCurrentCue] = useState(null);
const [fadeDuration, setFadeDuration] = useState(DefaultFadeDuration); // Default fade duration in seconds const [fadeDuration, setFadeDuration] = useState(DefaultFadeDuration); // Default fade duration in seconds
const [timestamp, setTimestamp] = useState();
const [clientStatus, setClientStatus] = useState({}); const [clientStatus, setClientStatus] = useState({});
const refCue = useRef(null); const refCue = useRef(null);
// const refNextCue = useRef(null); // const refNextCue = useRef(null);
const refLight= useRef(); const refLight = useRef();
const refDuration= useRef(); const refDuration = useRef();
const refTimer= useRef(); const refTotalTime=useRef();
const refTimer = useRef();
function sendOsc(addr, message, id){ function sendOsc(addr, message, id) {
invoke('send_osc_message', { invoke('send_osc_message', {
key: addr, key: addr,
message: `${id}#${message}`, message: `${id}#${message}`,
host:'0.0.0.0:0', host: '0.0.0.0:0',
target:'192.168.51.255:8000', target: '192.168.51.255:8000',
}); });
} }
function sendOscToSound(addr, message){ function sendOscToSound(addr, message) {
invoke('send_osc_message', { invoke('send_osc_message', {
key: addr, key: addr,
message, message,
host:'0.0.0.0:0', host: '0.0.0.0:0',
target:'192.168.51.100:58200', target: '192.168.51.100:58200',
}); });
} }
function onOsc(message){ function onOsc(message) {
const addr= message.addr; const addr = message.addr;
const [id, status, name]= message.args[0]?.split('#'); const [id, status, name] = message.args[0]?.split('#');
// console.log('receive osc:', id, status,name); // console.log('receive osc:', id, status,name);
switch(addr){ switch (addr) {
case OSC_ADDRESS.CLIENT_STATUS: case OSC_ADDRESS.CLIENT_STATUS:
setClientStatus(prev=>({ setClientStatus(prev => ({
...prev, ...prev,
[id]:{ [id]: {
status, status,
name, name,
timestamp: new Date().toLocaleTimeString(), timestamp: new Date().toLocaleTimeString(),
} }
})); }));
break; break;
case OSC_ADDRESS.CLIENT_DURATION: case OSC_ADDRESS.CLIENT_DURATION:
setClientStatus(prev=>({ setClientStatus(prev => ({
...prev, ...prev,
[id]:{ [id]: {
...prev[id], ...prev[id],
duration: status, duration: status,
timestamp: new Date().toLocaleTimeString(), timestamp: new Date().toLocaleTimeString(),
} }
})); }));
break; break;
case OSC_ADDRESS.CLIENT_INPUT: case OSC_ADDRESS.CLIENT_INPUT:
setClientStatus(prev=>({ setClientStatus(prev => ({
...prev, ...prev,
[id]:{ [id]: {
...prev[id], ...prev[id],
input: status, input: status,
timestamp: new Date().toLocaleTimeString(), timestamp: new Date().toLocaleTimeString(),
} }
})); }));
break; 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<cuelist.length;++i){
if(cuelist[i].duration) sum+=cuelist[i].duration;
}
return sum;
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 function playCue({ id, name, description, type, auto, audioFile, ...props }) {
if(props.audioCue){
sendOscToSound(OSC_ADDRESS.SCS_GO_CUE, props.audioCue);
}
if(props.clientCue){ console.log('Playing cue:', { id, name, description, type, auto, audioFile, ...props });
sendOsc(OSC_ADDRESS.PLAYCUE, props.clientCue,'all'); setCurrentCue({ id, name, description, type, auto, audioFile, ...props });
}
if(props.lightCue){ // Handle other cue types and properties here
// sendOsc(OSC_ADDRESS.CLIENT_INPUT, props.lightCue); if (props.audioCue) {
sendOscToSound(OSC_ADDRESS.SCS_GO_CUE, props.audioCue);
}
if(props.lightCue=='fade_in_light') refLight.current.fadeIn(); // Fade in light for conversation start if (props.clientCue) {
if(props.lightCue=='fade_out_light') refLight.current.fadeOut(); // Fade out light for conversation end sendOsc(OSC_ADDRESS.PLAYCUE, props.clientCue, 'all');
}
} if (props.lightCue) {
if(props.reset){ // sendOsc(OSC_ADDRESS.CLIENT_INPUT, props.lightCue);
//sendOsc(OSC_ADDRESS.RESETCUE,'','all');
refLight.current.set(1);
} 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.duration){ }
if(refTimer.current) clearInterval(refTimer.current); if (props.reset) {
let due= props.duration; //sendOsc(OSC_ADDRESS.RESETCUE,'','all');
refLight.current.set(1);
console.log('Start timer:', due);
const next=cuelist.find(c=>c.id==id+1); }
refTimer.current= setInterval(()=>{ if (refTimer.current) {
due--; clearTimeout(refTimer.current);
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);
} }
if (props.duration) {
// calculate last time
let totalLefTime=getTotalLeftTime(id);
// Clear any existing timer
// Store the end time of the timer
const totalEndTime=new Date().getTime()+totalLefTime*1000;
const endTime = new Date().getTime() + (props.duration * 1000);
// Function to update the timer
const tick = () => {
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) {
console.log('Auto play next cue:', next);
const next = cuelist.find(c => c.id === id + 1);
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() { function stop() {
console.log('Stop all'); console.log('Stop all');
sendOsc(OSC_ADDRESS.STOPCUE,'','all'); sendOsc(OSC_ADDRESS.STOPCUE, '', 'all');
sendOscToSound(OSC_ADDRESS.SCS_FADE_ALL, ''); sendOscToSound(OSC_ADDRESS.SCS_FADE_ALL, '');
// cear timer // cear timer
if(refTimer.current) clearInterval(refTimer.current); if (refTimer.current) clearInterval(refTimer.current);
} }
function reset() { function reset() {
console.log('Reset all'); console.log('Reset all');
sendOsc(OSC_ADDRESS.RESETCUE,'','all'); sendOsc(OSC_ADDRESS.RESETCUE, '', 'all');
sendOscToSound(OSC_ADDRESS.SCS_STOP_ALL, ''); sendOscToSound(OSC_ADDRESS.SCS_STOP_ALL, '');
refLight.current.set(1); refLight.current.set(1);
@ -174,18 +215,18 @@ function App() {
function secondToTime(seconds) { function secondToTime(seconds) {
const minutes = Math.floor(seconds / 60); const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60); const secs = Math.floor(seconds % 60);
return `${minutes}:${secs < 10 ? '0' : ''}${secs}`; return `${minutes.toString().padStart(2,'0')}:${secs < 10 ? '0' : ''}${secs}`;
} }
function getAudioDuration(){ function getAudioDuration() {
const type= refCue.current?.type; const type = refCue.current?.type;
// console.log('getAudioDuration', type, refCue.current); // console.log('getAudioDuration', type, refCue.current);
// switch(type) { // switch(type) {
// case CueType.Bg: // case CueType.Bg:
if(refAudioBg.current) { if (refAudioBg.current) {
return `${secondToTime(refAudioBg.current.seek())} / ${secondToTime(refAudioBg.current.duration())} ${refAudioBg.current?.volume()}`; return `${secondToTime(refAudioBg.current.seek())} / ${secondToTime(refAudioBg.current.duration())} ${refAudioBg.current?.volume()}`;
} else { } else {
return 'N/A'; return 'N/A';
} }
// case CueType.Announce: // case CueType.Announce:
// return refAudioAnnounce.current ? `${secondToTime(refAudioAnnounce.current.seek())} / ${secondToTime(refAudioAnnounce.current.duration())}` : 'N/A'; // return refAudioAnnounce.current ? `${secondToTime(refAudioAnnounce.current.seek())} / ${secondToTime(refAudioAnnounce.current.duration())}` : 'N/A';
@ -195,138 +236,137 @@ function App() {
} }
useEffect(()=>{ useEffect(() => {
if(currentCue) { if (currentCue) {
refCue.current = currentCue; refCue.current = currentCue;
// console.log('Current Cue:', currentCue); // console.log('Current Cue:', currentCue);
} }
},[currentCue]); }, [currentCue]);
useEffect(()=>{ useEffect(() => {
fetch(`/${CUE_FILE}`) fetch(`/${CUE_FILE}`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
console.log('Cuelist data:', data); console.log('Cuelist data:', data);
setCuelist(data.cuelist); setCuelist(data.cuelist);
}) })
.catch(error => { .catch(error => {
console.error('Error fetching cuelist:', error); console.error('Error fetching cuelist:', error);
});
listen('osc_message', (event) => {
// console.log(`Received OSC message: ${JSON.stringify(event.payload)}`);
onOsc(event.payload);
}); });
listen('osc_message', (event) => {
// console.log(`Received OSC message: ${JSON.stringify(event.payload)}`);
onOsc(event.payload);
});
},[]);
}, []);
return ( return (
<main className="overflow-y-auto flex flex-row gap-8 p-4 min-h-screen"> <main className="overflow-y-auto flex flex-row gap-8 p-4 min-h-screen">
<section className="flex-1 flex flex-col gap-4"> <section className="flex-1 flex flex-col gap-4">
<section className="flex flex-row justify-between items-center gap-2"> <section className="grid grid-cols-4 items-center gap-2">
<div className="flex flex-row font-bold items-stretch justify-center w-1/3 bg-pink-300 gap-4 p-2"> <div className="flex flex-row font-bold items-stretch justify-bewteen bg-pink-300 gap-4 p-2">
<div className="text-4xl">{currentCue ? `${currentCue.name}` : 'None'}</div> <div className="text-4xl">{currentCue ? `${currentCue.name}` : 'None'}</div>
<div className="text-4xl" ref={refDuration}></div> <div className="text-4xl" ref={refDuration}></div>
</div> </div>
<p> <button className="text-4xl self-stretch" onClick={stop}>stop all</button>
{timestamp} <button className="text-4xl self-stretch" onClick={reset}>reset all</button>
</p> <Light ref={refLight} />
<button className="text-4xl self-stretch" onClick={stop}>stop all</button> <span className="flex flex-col gap-2 items-stretch hidden">
<button className="text-4xl self-stretch" onClick={reset}>reset all</button> <label htmlFor="fade_duration">Fade Duration</label>
<Light ref={refLight} /> <input type="range" id="fade_duration" min="0" max="30" step="0.1" defaultValue={DefaultFadeDuration} className="slider"
<span className="flex flex-col gap-2 items-stretch hidden"> onChange={(e) => {
<label htmlFor="fade_duration">Fade Duration</label> const value = parseFloat(e.target.value);
<input type="range" id="fade_duration" min="0" max="30" step="0.1" defaultValue={DefaultFadeDuration} className="slider" setFadeDuration(value);
onChange={(e) => { }}></input>
const value = parseFloat(e.target.value); <span className="text-2xl">{fadeDuration}s</span>
setFadeDuration(value); </span>
}}></input> </section>
<span className="text-2xl">{fadeDuration}s</span> <table className="border-collapse w-full **:p-1 border-green-500 border-4">
</span> <thead className="bg-green-500">
</section> <tr className="text-left lowercase font-[900]">
<table className="border-collapse w-full **:p-1 border-green-500 border-4"> {/* <th>ID</th> */}
<thead className="bg-green-500"> <th></th>
<tr className="text-left lowercase font-[900]"> <th>Name</th>
{/* <th>ID</th> */} <th>Description</th>
<th></th> <th>Type</th>
<th>Name</th> <th>Auto</th>
<th>Description</th> <th>Due</th>
<th>Type</th> <th>Light</th>
<th>Auto</th> <th>Audio</th>
<th>Due</th> <th>Client</th>
<th>Light</th> </tr>
<th>Audio</th> </thead>
<th>Client</th> <tbody>
</tr> {cuelist?.map(({ id, name, description, type, auto, ...props }, index) => (
</thead> <tr key={id} className={currentCue?.id === id ? 'bg-green-200' : `${props.debug && 'text-red-500'}`}>
<tbody> <td className="flex flex-row gap-2">
{cuelist?.map(({id, name, description, type, auto,...props}, index) => ( <button
<tr key={id} className={currentCue?.id === id ? 'bg-green-200' : `${props.debug && 'text-red-500'}`}> onClick={() => {
<td className="flex flex-row gap-2"> playCue({ id, name, description, type, auto, ...props });
<button }}>go</button>
onClick={()=>{ </td>
playCue({id, name, description, type, auto, ...props}); <td>{name}</td>
}}>go</button> <td className="text-sm">{description}</td>
</td> <td>{EmojiType[type]}</td>
<td>{name}</td> <td>{auto ? '⤵' : ''}</td>
<td className="text-sm">{description}</td> <td>{props.duration}</td>
<td>{EmojiType[type]}</td> <td className="text-sm">{props.lightCue && `L${props.lightCue}`}</td>
<td>{auto ? '⤵' : ''}</td> <td className="text-sm">{props.audioCue && `S${props.audioCue}`}</td>
<td>{props.duration}</td> <td className={`text-sm ${props.clientCue && 'bg-green-300 rounded-full text-center font-[900]'}`}>{props.clientCue || ''}</td>
<td className="text-sm">{props.lightCue && `L${props.lightCue}`}</td>
<td className="text-sm">{props.audioCue && `S${props.audioCue}`}</td>
<td className={`text-sm ${props.clientCue&& 'bg-green-300 rounded-full text-center font-[900]'}`}>{props.clientCue || ''}</td>
</tr>
))}
</tbody>
</table>
</section>
<table className="flex-1 text-sm">
<thead className="bg-blue-500">
<tr className="text-center lowercase font-[900]">
<th>id</th>
<th>status</th>
<th>cue</th>
<th>input</th>
<th>due</th>
<th>timestamp</th>
<th></th>
</tr> </tr>
</thead> ))}
<tbody> </tbody>
{Array.from(Array(CLIENT_COUNT).keys()).map((i) => { </table>
const id=(i+1).toString();//.padStart(2,'0'); <div className="text-6xl text-center font-bold bg-blue-500 text-white p-4" ref={refTotalTime}></div>
const log = clientStatus[id.toString()];
return ( </section>
<tr key={id} className="text-left lowercase"> <table className="flex-1 text-sm">
<td className="font-[900]">{id}</td> <thead className="bg-blue-500">
{<> <tr className="text-center lowercase font-[900]">
<td>{log?.status}</td> <th>id</th>
<td> <th>status</th>
<span className="bg-green-200 rounded-full p-2">{log?.name}</span> <th>cue</th>
</td> <th>input</th>
<td className="flex-1 text-[9px] min-w-[80px]"> <th>due</th>
{log?.input?.substr(log.input.length - 20)?.split('').map((ch,i)=>i%3<1?ch:'*').join('')} <th>timestamp</th>
</td> <th></th>
<td>{log?.duration}</td> </tr>
<td className="text-[12px]">{log?.timestamp}</td> </thead>
</>} <tbody>
<td className="flex flex-row justify-end gap-1 p-1"> {Array.from(Array(CLIENT_COUNT).keys()).map((i) => {
<button onClick={()=>sendOsc(OSC_ADDRESS.STOPCUE,'',id)}>stop</button> const id = (i + 1).toString();//.padStart(2,'0');
<button onClick={()=>sendOsc(OSC_ADDRESS.RESETCUE,'',id)}>reset</button> const log = clientStatus[id.toString()];
{cuelist?.filter(c=>c.clientCue && !c.debug).map((c, index)=>( return (
<button key={`${id}${c.clientCue}${index}`} onClick={()=>{ <tr key={id} className="text-left lowercase">
sendOsc(OSC_ADDRESS.PLAYCUE, c.clientCue, id); <td className="font-[900]">{id}</td>
}}>{c.clientCue}</button> {<>
))} <td>{log?.status}</td>
<td>
<span className="bg-green-200 rounded-full p-2">{log?.name}</span>
</td>
<td className="flex-1 text-[9px] min-w-[80px]">
{log?.input?.substr(log.input.length - 20)?.split('').map((ch, i) => i % 3 < 1 ? ch : '*').join('')}
</td> </td>
</tr> <td>{log?.duration}</td>
); <td className="text-[12px]">{log?.timestamp}</td>
})} </>}
</tbody> <td className="flex flex-row justify-end gap-1 p-1">
</table> <button onClick={() => sendOsc(OSC_ADDRESS.STOPCUE, '', id)}>stop</button>
<button onClick={() => sendOsc(OSC_ADDRESS.RESETCUE, '', id)}>reset</button>
{cuelist?.filter(c => c.clientCue && !c.debug).map((c, index) => (
<button key={`${id}${c.clientCue}${index}`} onClick={() => {
sendOsc(OSC_ADDRESS.PLAYCUE, c.clientCue, id);
}}>{c.clientCue}</button>
))}
</td>
</tr>
);
})}
</tbody>
</table>
</main> </main>
); );
} }

@ -2,7 +2,7 @@ import gsap from "gsap"
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react" import { forwardRef, useEffect, useImperativeHandle, useRef } from "react"
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
const FADE_TIME=5; const FADE_TIME=8;
export const Light=forwardRef((props, ref)=>{ export const Light=forwardRef((props, ref)=>{
@ -90,7 +90,7 @@ export const Light=forwardRef((props, ref)=>{
<div ref={refContainer} className="grid grid-cols-2 justify-between p-1 border *:overflow-hidden"> <div ref={refContainer} className="grid grid-cols-2 justify-between p-1 border *:overflow-hidden">
{/* <span className="flex flex-col justify-between"> */} {/* <span className="flex flex-col justify-between"> */}
<label className="text-center">Light</label> <label className="text-center">Light</label>
<input ref={refInput} type="text" className="border w-[5vw]" defaultValue={5}/> <input ref={refInput} type="text" className="border w-[5vw]" defaultValue={FADE_TIME}/>
{/* </span> {/* </span>
<span className="flex flex-col justify-between"> */} <span className="flex flex-col justify-between"> */}
<button onClick={()=>fade(0,1)}>fadeIn</button> <button onClick={()=>fade(0,1)}>fadeIn</button>

Loading…
Cancel
Save