You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

266 lines
8.7 KiB

4 months ago
import { useState, useEffect, useRef } from "react";
import {Howl, Howler} from 'howler';
4 months ago
import { invoke } from "@tauri-apps/api/core";
4 months ago
import "./App.css";
const DefaultFadeDuration = 3; // 1 second
const CueType={
Bg: 'bg',
Announce: 'announce',
Light: 'light'
}
const EmojiType={
bg: '🎵',
announce: '📢',
light: '💡'
}
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 refCue = useRef(null);
const refNextCue = useRef(null);
4 months ago
function sendOsc(addr, message){
invoke('send_osc_message', {
key: addr,
message,
host:'0.0.0.0:0',
target:'192.168.234.255:8000',
});
}
4 months ago
function onfade(e){
console.log('onfade', e);
if(refNextCue.current) {
playCue(refNextCue.current);
refNextCue.current = null;
}
4 months ago
if(refAudioBg.current.volume() === 0) {
refAudioBg.current.stop();
// refAudioBg.current = null;
}
4 months ago
}
function onAnounceFade(e){
console.log('onAnounceFade', e);
if(refAudioAnnounce.current.volume() === 0) {
refAudioAnnounce.current.stop();
}
}
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 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;
}
}
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;
default:
console.warn('Unknown cue type:', type);
}
4 months ago
if(props.clientCue){
sendOsc('/playcue', props.clientCue);
}
4 months ago
}
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);
}
4 months ago
sendOsc('/stopcue','');
4 months ago
}
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('/cuelist.json')
.then(response => response.json())
.then(data => {
console.log('Cuelist data:', data);
setCuelist(data.cuelist);
})
.catch(error => {
console.error('Error fetching cuelist:', error);
});
const intervalId = setInterval(() => {
setTimestamp(getAudioDuration());
}, 500);
return () => clearInterval(intervalId);
},[]);
return (
<main className="container">
<div className="overflow-y-auto flex flex-col gap-8 p-4">
<section className="flex flex-row gap-4 justify-between items-center">
<div className="text-6xl p-4 bg-pink-300">{currentCue ? `${currentCue.name}` : 'None'}</div>
<p>
{timestamp}
</p>
<button onClick={stop}>stop all</button>
<span className="flex flex-col gap-2 items-stretch">
<label htmlFor="fade_duration">Fade Duration</label>
<input type="range" id="fade_duration" min="0" max="30" step="0.1" defaultValue={DefaultFadeDuration} className="slider"
onChange={(e) => {
const value = parseFloat(e.target.value);
setFadeDuration(value);
}}></input>
<span className="text-2xl">{fadeDuration}s</span>
</span>
</section>
<table className="border-collapse w-full **:p-2 border-green-500 border-4">
<thead className="bg-green-500">
<tr className="text-left lowercase font-[900]">
{/* <th>ID</th> */}
<th></th>
<th>Name</th>
<th>Description</th>
<th>Type</th>
<th>Auto</th>
<th>Audio / Due</th>
4 months ago
<th>clientCue</th>
4 months ago
</tr>
</thead>
<tbody>
{cuelist?.map(({id, name, description, type, auto, audioFile,...props}, index) => (
<tr key={id} className={currentCue?.id === id ? 'bg-green-200' : ''}>
<td className="flex flex-row gap-2">
<button
onClick={()=>{
playCue({id, name, description, type, auto, audioFile, ...props});
}}>go</button>
{type==CueType.Announce && <button
onClick={()=>{
if(refAudioAnnounce.current) {
refAudioAnnounce.current.fade(1,0, fadeDuration * 1000);
}
}}>stop</button>}
</td>
<td>{name}</td>
<td>{description}</td>
<td>{EmojiType[type]}</td>
<td>{auto ? '⤵' : ''}</td>
<td>{audioFile || props.duration} {props.callback && `<${props.callback}>`}</td>
4 months ago
<td className={`${props.clientCue&& 'bg-green-300 rounded-full text-center font-[900]'}`}>{props.clientCue || ''}</td>
4 months ago
</tr>
))}
</tbody>
</table>
</div>
</main>
);
}
export default App;