diff --git a/public/cuelist.json b/public/cuelist.json index ae2722a..583427c 100644 --- a/public/cuelist.json +++ b/public/cuelist.json @@ -6,7 +6,8 @@ "type": "bg", "description": "preset bg", "audioFile": "assets/bg-02.mp3", - "loop": true + "loop": true, + "clientCue":"Q1" }, { "id": 2, @@ -16,6 +17,7 @@ "audioFile": "assets/bg-01 (小束袋).mp3", "loop": true, "status":"reset" + }, { "id": 3, @@ -23,7 +25,8 @@ "type": "bg", "description": "during bg", "loop": true, - "audioFile": "assets/bg-04.mp3" + "audioFile": "assets/bg-04.mp3", + "clientCue":"Q2" }, { "id": 3.1, diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 942a06b..edca882 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -509,11 +509,14 @@ dependencies = [ name = "control-panel" version = "0.1.0" dependencies = [ + "dotenv", + "rosc", "serde", "serde_json", "tauri", "tauri-build", "tauri-plugin-opener", + "tokio", ] [[package]] @@ -791,6 +794,12 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "dpi" version = "0.1.2" @@ -2004,6 +2013,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2101,6 +2116,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2999,6 +3024,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rosc" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd37602e1513794e952274082d074e8d31aa7f64d047e3acb746c91db40600a5" +dependencies = [ + "byteorder", + "nom", + "time", +] + [[package]] name = "rustc-demangle" version = "0.1.26" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b904639..8936e55 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,4 +22,6 @@ tauri = { version = "2", features = [] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" - +rosc = "0.11.4" +tokio = { version = "1.45.1", features = ["net"] } +dotenv = "0.15.0" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4a277ef..84b211f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,9 @@ // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ +use tokio::net::UdpSocket; +use std::{net::SocketAddrV4, str::FromStr}; +use rosc::{encoder, OscMessage, OscPacket, OscType}; + + #[tauri::command] fn greet(name: &str) -> String { format!("Hello, {}! You've been greeted from Rust!", name) @@ -8,7 +13,34 @@ fn greet(name: &str) -> String { pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![greet]) + .invoke_handler(tauri::generate_handler![ + send_osc_message, + ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } + + +#[tauri::command] +async fn send_osc_message( + key: &str, + message: &str, + host: &str, + target: &str, +) -> Result<(), String> { + // print + println!("Sending OSC message: {}", message); + + let sock = UdpSocket::bind(host).await.unwrap(); + let remote = SocketAddrV4::from_str(target).unwrap(); + + let msg_buf = encoder::encode(&OscPacket::Message(OscMessage { + addr: key.to_string(), + args: vec![OscType::String(message.parse().unwrap())], + })) + .unwrap(); + + sock.send_to(&msg_buf, remote).await.unwrap(); + + Ok(()) +} \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index 09998aa..666c45c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,6 @@ import { useState, useEffect, useRef } from "react"; import {Howl, Howler} from 'howler'; +import { invoke } from "@tauri-apps/api/core"; import "./App.css"; @@ -29,6 +30,17 @@ function App() { const refCue = useRef(null); const refNextCue = useRef(null); + + function sendOsc(addr, message){ + + invoke('send_osc_message', { + key: addr, + message, + host:'0.0.0.0:0', + target:'192.168.234.255:8000', + }); + + } function onfade(e){ console.log('onfade', e); @@ -37,6 +49,11 @@ function App() { refNextCue.current = null; } + if(refAudioBg.current.volume() === 0) { + refAudioBg.current.stop(); + // refAudioBg.current = null; + } + } function onAnounceFade(e){ console.log('onAnounceFade', e); @@ -116,6 +133,12 @@ function App() { console.warn('Unknown cue type:', type); } + + if(props.clientCue){ + sendOsc('/playcue', props.clientCue); + } + + } function stop() { console.log('Stop all audio'); @@ -125,6 +148,9 @@ function App() { if(refAudioAnnounce.current) { refAudioAnnounce.current.fade(1,0, fadeDuration*1000); } + + sendOsc('/stopcue',''); + } function secondToTime(seconds) { const minutes = Math.floor(seconds / 60); @@ -203,7 +229,7 @@ function App() { Type Auto Audio / Due - + clientCue @@ -226,7 +252,7 @@ function App() { {EmojiType[type]} {auto ? '⤵️' : ''} {audioFile || props.duration} {props.callback && `<${props.callback}>`} - + {props.clientCue || ''} ))}