diff --git a/src/trx-client/trx-frontend/src/lib.rs b/src/trx-client/trx-frontend/src/lib.rs index f206dff..be25491 100644 --- a/src/trx-client/trx-frontend/src/lib.rs +++ b/src/trx-client/trx-frontend/src/lib.rs @@ -224,6 +224,8 @@ pub struct FrontendRuntimeContext { pub sse_clients: Arc, /// Active rigctl TCP clients. pub rigctl_clients: Arc, + /// Active audio WebSocket streams. + pub audio_clients: Arc, /// rigctl listen endpoint, if enabled. pub rigctl_listen_addr: Arc>>, /// Guard to avoid spawning duplicate decode collectors. @@ -367,6 +369,7 @@ impl FrontendRuntimeContext { auth_tokens: HashSet::new(), sse_clients: Arc::new(AtomicUsize::new(0)), rigctl_clients: Arc::new(AtomicUsize::new(0)), + audio_clients: Arc::new(AtomicUsize::new(0)), rigctl_listen_addr: Arc::new(Mutex::new(None)), decode_collector_started: AtomicBool::new(false), http_auth_enabled: false, diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index f2a61d2..d528d04 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -3268,11 +3268,18 @@ function render(update) { document.getElementById("about-audio-codec").textContent = "Opus"; document.getElementById("about-audio-samplerate").textContent = `${(streamInfo.sample_rate || 48000).toLocaleString()} Hz`; document.getElementById("about-audio-channels").textContent = (streamInfo.channels || 1) === 1 ? "Mono" : "Stereo"; + if (streamInfo.bitrate_bps) { + const kbps = (streamInfo.bitrate_bps / 1000).toFixed(0); + document.getElementById("about-audio-bitrate").textContent = `${kbps} kbps`; + } if (streamInfo.frame_duration_ms) { document.getElementById("about-audio-frame").textContent = `${streamInfo.frame_duration_ms} ms`; } } document.getElementById("about-audio-rx").textContent = rxActive ? "Active" : "Off"; + if (typeof update.audio_clients === "number") { + document.getElementById("about-audio-streams").textContent = update.audio_clients; + } // About — Decoders card const decMap = [ diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index 15a7c8a..cb93c8d 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -1056,8 +1056,10 @@ Codec-- Sample rate-- Channels-- + Bitrate-- Frame duration-- RX statusOff + Active streams-- diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs index 265dea8..421984d 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs @@ -94,6 +94,7 @@ struct FrontendMeta { #[serde(rename = "clients")] http_clients: usize, rigctl_clients: usize, + audio_clients: usize, #[serde(skip_serializing_if = "Option::is_none")] rigctl_addr: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -217,6 +218,7 @@ fn frontend_meta_from_context( FrontendMeta { http_clients, rigctl_clients: context.rigctl_clients.load(Ordering::Relaxed), + audio_clients: context.audio_clients.load(Ordering::Relaxed), rigctl_addr: rigctl_addr_from_context(context), active_remote: active_rig_id_from_context(context), remotes: rig_ids_from_context(context), 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 92f6f6d..de6ff89 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 @@ -650,6 +650,9 @@ pub async fn audio_ws( let (response, mut session, mut msg_stream) = actix_ws::handle(&req, body)?; + let audio_clients = context.audio_clients.clone(); + audio_clients.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + actix_web::rt::spawn(async move { let mut current_info = loop { if let Some(info) = info_rx.borrow().clone() { @@ -682,7 +685,8 @@ pub async fn audio_ws( }; let changed = next_info.sample_rate != current_info.sample_rate || next_info.channels != current_info.channels - || next_info.frame_duration_ms != current_info.frame_duration_ms; + || next_info.frame_duration_ms != current_info.frame_duration_ms + || next_info.bitrate_bps != current_info.bitrate_bps; if changed { current_info = next_info; let info_json = match serde_json::to_string(¤t_info) { @@ -723,6 +727,7 @@ pub async fn audio_ws( } } let _ = session.close(None).await; + audio_clients.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); }); Ok(response) diff --git a/src/trx-core/src/audio.rs b/src/trx-core/src/audio.rs index a33c49e..69a07cf 100644 --- a/src/trx-core/src/audio.rs +++ b/src/trx-core/src/audio.rs @@ -78,6 +78,12 @@ pub struct AudioStreamInfo { pub sample_rate: u32, pub channels: u8, pub frame_duration_ms: u16, + #[serde(default, skip_serializing_if = "is_zero_u32")] + pub bitrate_bps: u32, +} + +fn is_zero_u32(v: &u32) -> bool { + *v == 0 } /// Write a length-prefixed audio message. diff --git a/src/trx-server/src/main.rs b/src/trx-server/src/main.rs index 88cf286..cbcc9f9 100644 --- a/src/trx-server/src/main.rs +++ b/src/trx-server/src/main.rs @@ -484,6 +484,7 @@ fn spawn_rig_audio_stack( sample_rate: rig_cfg.audio.sample_rate, channels: rig_cfg.audio.channels, frame_duration_ms: rig_cfg.audio.frame_duration_ms, + bitrate_bps: rig_cfg.audio.bitrate_bps, }; let (rx_audio_tx, _) = broadcast::channel::(256);