From 7d7660692723c78188d518f6d122ecea0b91e980 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Mon, 23 Mar 2026 22:31:04 +0100 Subject: [PATCH] [fix](trx-frontend-http): accept remote on audio endpoint Parse the renamed `remote` query parameter on `/audio` while keeping `rig_id` as a compatibility alias, and use per-rig stream info for rig-scoped audio subscriptions. Add tests covering both query names. Co-authored-by: OpenAI Codex Signed-off-by: Stan Grams --- .../trx-frontend-http/src/audio.rs | 59 ++++++++++++++----- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/audio.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/audio.rs index cdf6f97..03dbff8 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/src/audio.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/src/audio.rs @@ -475,7 +475,8 @@ pub fn start_decode_history_collector(context: Arc) { #[derive(Deserialize)] pub struct AudioQuery { pub channel_id: Option, - pub rig_id: Option, + #[serde(alias = "rig_id")] + pub remote: Option, } #[get("/audio")] @@ -488,9 +489,6 @@ pub async fn audio_ws( let Some(tx_sender) = context.audio_tx.as_ref().cloned() else { return Ok(HttpResponse::NotFound().body("audio not enabled")); }; - let Some(mut info_rx) = context.audio_info.as_ref().cloned() else { - return Ok(HttpResponse::NotFound().body("audio not enabled")); - }; // Plain GET probe (no WebSocket upgrade) - return 204 to signal audio is available. if !req.headers().contains_key("upgrade") { @@ -501,9 +499,15 @@ pub async fn audio_ws( // The entry is created asynchronously when AUDIO_MSG_VCHAN_ALLOCATED arrives // from the server, which may lag the HTTP allocation by up to ~100 ms. // Poll for up to 2 s so a tight JS timer doesn't race and get a 404. - let rx_sub: broadcast::Receiver = if let Some(ch_id) = query.channel_id { + let (rx_sub, mut info_rx): ( + broadcast::Receiver, + tokio::sync::watch::Receiver>, + ) = if let Some(ch_id) = query.channel_id { + let Some(info_rx) = context.audio_info.as_ref().cloned() else { + return Ok(HttpResponse::NotFound().body("audio not enabled")); + }; let deadline = Instant::now() + Duration::from_secs(2); - loop { + let rx_sub = loop { match context.vchan_audio.read() { Ok(map) => { if let Some(tx) = map.get(&ch_id) { @@ -516,29 +520,37 @@ pub async fn audio_ws( return Ok(HttpResponse::NotFound().body("channel not found")); } tokio::time::sleep(Duration::from_millis(50)).await; - } - } else if let Some(ref rig_id) = query.rig_id { + }; + (rx_sub, info_rx) + } else if let Some(ref remote) = query.remote { // Per-rig audio: subscribe to the specific rig's broadcast. // Do NOT fall back to global — that would silently deliver the wrong // rig's audio. Wait briefly for the per-rig channel to appear (it is // lazily created by the audio relay sync task every 500ms). let deadline = Instant::now() + Duration::from_secs(3); - loop { - if let Some(rx) = context.rig_audio_subscribe(rig_id) { - break rx; + let (rx_sub, info_rx) = loop { + if let (Some(rx), Some(info_rx)) = ( + context.rig_audio_subscribe(remote), + context.rig_audio_info_rx(remote), + ) { + break (rx, info_rx); } if Instant::now() >= deadline { return Ok( - HttpResponse::NotFound().body(format!("audio not available for rig {rig_id}")) + HttpResponse::NotFound().body(format!("audio not available for rig {remote}")) ); } tokio::time::sleep(Duration::from_millis(100)).await; - } + }; + (rx_sub, info_rx) } else { + let Some(info_rx) = context.audio_info.as_ref().cloned() else { + return Ok(HttpResponse::NotFound().body("audio not enabled")); + }; let Some(rx) = context.audio_rx.as_ref() else { return Ok(HttpResponse::NotFound().body("audio not enabled")); }; - rx.subscribe() + (rx.subscribe(), info_rx) }; let mut rx_sub = rx_sub; @@ -621,3 +633,22 @@ pub async fn audio_ws( Ok(response) } + +#[cfg(test)] +mod tests { + use super::AudioQuery; + + #[test] + fn audio_query_accepts_remote() { + let query: AudioQuery = + serde_json::from_str(r#"{"remote":"lidzbark-vhf"}"#).expect("query parse"); + assert_eq!(query.remote.as_deref(), Some("lidzbark-vhf")); + } + + #[test] + fn audio_query_accepts_legacy_rig_id_alias() { + let query: AudioQuery = + serde_json::from_str(r#"{"rig_id":"gdansk"}"#).expect("query parse"); + assert_eq!(query.remote.as_deref(), Some("gdansk")); + } +}