|
|
|
|
@ -1,7 +1,9 @@ |
|
|
|
|
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"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DefaultFadeDuration = 3; // 1 second |
|
|
|
|
@ -20,12 +22,10 @@ const EmojiType={ |
|
|
|
|
function App() { |
|
|
|
|
|
|
|
|
|
const [cuelist, setCuelist] = useState([]); |
|
|
|
|
const refAudioBg= useRef(null); |
|
|
|
|
const refAudioAnnounce= useRef(null); |
|
|
|
|
const refAudio= useRef({}); |
|
|
|
|
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); |
|
|
|
|
@ -41,116 +41,75 @@ function App() { |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
function onfade(e){ |
|
|
|
|
console.log('onfade', e); |
|
|
|
|
|
|
|
|
|
if(refNextCue.current) { |
|
|
|
|
playCue(refNextCue.current); |
|
|
|
|
refNextCue.current = null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if(refAudioBg.current.volume() === 0) { |
|
|
|
|
refAudioBg.current.stop(); |
|
|
|
|
// refAudioBg.current = null; |
|
|
|
|
} |
|
|
|
|
function sendOscToSound(addr, message){ |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
function onAnounceFade(e){ |
|
|
|
|
console.log('onAnounceFade', e); |
|
|
|
|
if(refAudioAnnounce.current.volume() === 0) { |
|
|
|
|
refAudioAnnounce.current.stop(); |
|
|
|
|
} |
|
|
|
|
invoke('send_osc_message', { |
|
|
|
|
key: addr, |
|
|
|
|
message, |
|
|
|
|
host:'0.0.0.0:0', |
|
|
|
|
target:'192.168.234.255:58100', |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function loadAudio(audioFile, loop = false, type) { |
|
|
|
|
if(refAudio.current[audioFile]) { |
|
|
|
|
return refAudio.current[audioFile]; |
|
|
|
|
} |
|
|
|
|
const sound = new Howl({ |
|
|
|
|
src: [audioFile], |
|
|
|
|
volume: 1, |
|
|
|
|
loop: loop, |
|
|
|
|
onfade: type==CueType.Bg ? onfade : onAnounceFade, |
|
|
|
|
}); |
|
|
|
|
refAudio.current[audioFile] = sound; |
|
|
|
|
return sound; |
|
|
|
|
} |
|
|
|
|
function onOsc(message){ |
|
|
|
|
|
|
|
|
|
function playAudio(audioFile, loop = false, type) { |
|
|
|
|
switch(type){ |
|
|
|
|
case CueType.Bg: |
|
|
|
|
refAudioBg.current = loadAudio(audioFile, loop, type); |
|
|
|
|
refAudioBg.current.play(); |
|
|
|
|
refAudioBg.current.fade(0, 1, fadeDuration * 1000); |
|
|
|
|
break; |
|
|
|
|
case CueType.Announce: |
|
|
|
|
refAudioAnnounce.current = loadAudio(audioFile, loop, type); |
|
|
|
|
refAudioAnnounce.current.seek(0); |
|
|
|
|
refAudioAnnounce.current.volume(1); |
|
|
|
|
refAudioAnnounce.current.play(); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
const [id, status, name]= message.args[0]?.split('#'); |
|
|
|
|
if(clientStatus[id]){ |
|
|
|
|
setClientStatus(prev=>({ |
|
|
|
|
...prev, |
|
|
|
|
[id]:[ |
|
|
|
|
{ |
|
|
|
|
status, |
|
|
|
|
name, |
|
|
|
|
timestamp: new Date().toLocaleTimeString(), |
|
|
|
|
}, |
|
|
|
|
...prev[id], |
|
|
|
|
] |
|
|
|
|
})); |
|
|
|
|
}else{ |
|
|
|
|
setClientStatus(prev=>({ |
|
|
|
|
...prev, |
|
|
|
|
[id]:[{ |
|
|
|
|
status, |
|
|
|
|
name, |
|
|
|
|
timestamp: new Date().toLocaleTimeString(), |
|
|
|
|
}] |
|
|
|
|
})); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function playCue({id, name, description, type, auto, audioFile, ...props}) { |
|
|
|
|
|
|
|
|
|
console.log('Playing cue:', {id, name, description, type, auto, audioFile, ...props}); |
|
|
|
|
|
|
|
|
|
// Handle other cue types and properties here |
|
|
|
|
switch(type) { |
|
|
|
|
case CueType.Bg: |
|
|
|
|
if(refAudioBg.current) { |
|
|
|
|
if(refAudioBg.current.volume() === 0) { |
|
|
|
|
refAudioBg.current.stop(); |
|
|
|
|
playAudio(audioFile, props.loop || false, type); |
|
|
|
|
setCurrentCue({id, name, description, type, auto, audioFile, ...props}); |
|
|
|
|
}else{ |
|
|
|
|
refAudioBg.current.fade(1,0, fadeDuration * 1000); |
|
|
|
|
refNextCue.current = {id, name, description, type, auto, audioFile, ...props}; |
|
|
|
|
} |
|
|
|
|
}else{ |
|
|
|
|
playAudio(audioFile, props.loop || false, type); |
|
|
|
|
setCurrentCue({id, name, description, type, auto, audioFile, ...props}); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case CueType.Announce: |
|
|
|
|
|
|
|
|
|
playAudio(audioFile, props.loop || false, type); |
|
|
|
|
setCurrentCue({id, name, description, type, auto, audioFile, ...props}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case CueType.Light: |
|
|
|
|
// Handle light cue logic here |
|
|
|
|
console.log('Light cue:', {id, name, description, ...props}); |
|
|
|
|
setCurrentCue({id, name, description, type, auto, audioFile, ...props}); |
|
|
|
|
break; |
|
|
|
|
function playCue({id, name, description, type, auto, audioFile, ...props}) { |
|
|
|
|
|
|
|
|
|
default: |
|
|
|
|
console.warn('Unknown cue type:', type); |
|
|
|
|
console.log('Playing cue:', {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('/playcue', props.clientCue); |
|
|
|
|
sendOsc(OSC_ADDRESS.PLAYCUE, props.clientCue); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if(props.lightCue){ |
|
|
|
|
// sendOsc(OSC_ADDRESS.CLIENT_INPUT, props.lightCue); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
function stop() { |
|
|
|
|
console.log('Stop all audio'); |
|
|
|
|
if(refAudioBg.current) { |
|
|
|
|
refAudioBg.current.fade(1,0, fadeDuration*1000); |
|
|
|
|
} |
|
|
|
|
if(refAudioAnnounce.current) { |
|
|
|
|
refAudioAnnounce.current.fade(1,0, fadeDuration*1000); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
sendOsc('/stopcue',''); |
|
|
|
|
console.log('Stop all'); |
|
|
|
|
|
|
|
|
|
sendOsc(OSC_ADDRESS.STOPCUE,''); |
|
|
|
|
sendOscToSound(OSC_ADDRESS.SCS_STOP_ALL, ''); |
|
|
|
|
} |
|
|
|
|
function secondToTime(seconds) { |
|
|
|
|
const minutes = Math.floor(seconds / 60); |
|
|
|
|
@ -193,10 +152,13 @@ function App() { |
|
|
|
|
console.error('Error fetching cuelist:', error); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const intervalId = setInterval(() => { |
|
|
|
|
setTimestamp(getAudioDuration()); |
|
|
|
|
}, 500); |
|
|
|
|
return () => clearInterval(intervalId); |
|
|
|
|
|
|
|
|
|
listen('osc_message', (event) => { |
|
|
|
|
console.log(`Received OSC message: ${event.payload}`); |
|
|
|
|
onOsc(event.payload); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
},[]); |
|
|
|
|
|
|
|
|
|
@ -228,12 +190,12 @@ function App() { |
|
|
|
|
<th>Description</th> |
|
|
|
|
<th>Type</th> |
|
|
|
|
<th>Auto</th> |
|
|
|
|
<th>Audio / Due</th> |
|
|
|
|
<th>Due | Light | Audio</th> |
|
|
|
|
<th>clientCue</th> |
|
|
|
|
</tr> |
|
|
|
|
</thead> |
|
|
|
|
<tbody> |
|
|
|
|
{cuelist?.map(({id, name, description, type, auto, audioFile,...props}, index) => ( |
|
|
|
|
{cuelist?.map(({id, name, description, type, auto,...props}, index) => ( |
|
|
|
|
<tr key={id} className={currentCue?.id === id ? 'bg-green-200' : ''}> |
|
|
|
|
<td className="flex flex-row gap-2"> |
|
|
|
|
<button |
|
|
|
|
@ -242,8 +204,8 @@ function App() { |
|
|
|
|
}}>go</button> |
|
|
|
|
{type==CueType.Announce && <button |
|
|
|
|
onClick={()=>{ |
|
|
|
|
if(refAudioAnnounce.current) { |
|
|
|
|
refAudioAnnounce.current.fade(1,0, fadeDuration * 1000); |
|
|
|
|
if(props.audioCue){ |
|
|
|
|
sendOscToSound(OSC_ADDRESS.SCS_STOP_ALL, ''); |
|
|
|
|
} |
|
|
|
|
}}>stop</button>} |
|
|
|
|
</td> |
|
|
|
|
@ -251,12 +213,40 @@ function App() { |
|
|
|
|
<td>{description}</td> |
|
|
|
|
<td>{EmojiType[type]}</td> |
|
|
|
|
<td>{auto ? '⤵️' : ''}</td> |
|
|
|
|
<td>{audioFile || props.duration} {props.callback && `<${props.callback}>`}</td> |
|
|
|
|
<td>{props.duration} {props.lightCue && `L${props.lightCue}`} {props.audioCue && `S${props.audioCue}`}</td> |
|
|
|
|
<td className={`${props.clientCue&& 'bg-green-300 rounded-full text-center font-[900]'}`}>{props.clientCue || ''}</td> |
|
|
|
|
</tr> |
|
|
|
|
))} |
|
|
|
|
</tbody> |
|
|
|
|
</table> |
|
|
|
|
|
|
|
|
|
<table> |
|
|
|
|
<thead className="bg-blue-500"> |
|
|
|
|
<tr className="text-left lowercase font-[900]"> |
|
|
|
|
<th>id</th> |
|
|
|
|
<th>status</th> |
|
|
|
|
<th>cue</th> |
|
|
|
|
<th>timestamp</th> |
|
|
|
|
<th>control</th> |
|
|
|
|
</tr> |
|
|
|
|
</thead> |
|
|
|
|
<tbody> |
|
|
|
|
{Object.entries(clientStatus).map(([id, log])=>( |
|
|
|
|
<tr key={id} className="text-left lowercase font-[900]"> |
|
|
|
|
<td className="font-[900]">{id}</td> |
|
|
|
|
<td>{log[0]?.status}</td> |
|
|
|
|
<td>{log[0]?.name}</td> |
|
|
|
|
<td>{log[0]?.timestamp}</td> |
|
|
|
|
<td> |
|
|
|
|
<button>stop</button> |
|
|
|
|
<button>Q1</button> |
|
|
|
|
<button>Q2</button> |
|
|
|
|
<button>Q6</button> |
|
|
|
|
</td> |
|
|
|
|
</tr> |
|
|
|
|
))} |
|
|
|
|
</tbody> |
|
|
|
|
</table> |
|
|
|
|
</div> |
|
|
|
|
</main> |
|
|
|
|
); |
|
|
|
|
|