[feat](trx-rs): show audio bitrate and active stream count on About page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-26 20:18:19 +01:00
parent f31fbecca6
commit c8de54d85e
7 changed files with 27 additions and 1 deletions
+3
View File
@@ -224,6 +224,8 @@ pub struct FrontendRuntimeContext {
pub sse_clients: Arc<AtomicUsize>,
/// Active rigctl TCP clients.
pub rigctl_clients: Arc<AtomicUsize>,
/// Active audio WebSocket streams.
pub audio_clients: Arc<AtomicUsize>,
/// rigctl listen endpoint, if enabled.
pub rigctl_listen_addr: Arc<Mutex<Option<SocketAddr>>>,
/// 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,
@@ -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 = [
@@ -1056,8 +1056,10 @@
<tr><td>Codec</td><td id="about-audio-codec">--</td></tr>
<tr><td>Sample rate</td><td id="about-audio-samplerate">--</td></tr>
<tr><td>Channels</td><td id="about-audio-channels">--</td></tr>
<tr><td>Bitrate</td><td id="about-audio-bitrate">--</td></tr>
<tr><td>Frame duration</td><td id="about-audio-frame">--</td></tr>
<tr><td>RX status</td><td id="about-audio-rx">Off</td></tr>
<tr><td>Active streams</td><td id="about-audio-streams">--</td></tr>
</table>
</div>
<!-- Decoders -->
@@ -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<String>,
#[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),
@@ -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(&current_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)
+6
View File
@@ -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.
+1
View File
@@ -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::<Bytes>(256);