[fix](trx-frontend-http): reconfigure audio stream on rig change

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-02-28 22:23:03 +01:00
parent b6053729a4
commit f06dbc921a
2 changed files with 80 additions and 34 deletions
@@ -2642,6 +2642,40 @@ function clearTxTimeout() {
txTimeoutRemaining = 0; txTimeoutRemaining = 0;
} }
function resetRxDecoder() {
if (opusDecoder) {
try { opusDecoder.close(); } catch (e) {}
opusDecoder = null;
}
nextPlayTime = 0;
}
function configureRxStream(nextInfo) {
const nextSampleRate = (nextInfo && nextInfo.sample_rate) || 48000;
const sampleRateChanged = !audioCtx || audioCtx.sampleRate !== nextSampleRate;
streamInfo = nextInfo;
updateWfmControls();
resetRxDecoder();
if (sampleRateChanged && audioCtx) {
audioCtx.close().catch(() => {});
audioCtx = null;
rxGainNode = null;
}
if (!audioCtx) {
audioCtx = new AudioContext({ sampleRate: nextSampleRate });
audioCtx.resume().catch(() => {});
}
if (!rxGainNode) {
rxGainNode = audioCtx.createGain();
rxGainNode.connect(audioCtx.destination);
}
rxGainNode.gain.value = rxVolSlider.value / 100;
rxActive = true;
rxAudioBtn.style.borderColor = "#00d17f";
rxAudioBtn.style.color = "#00d17f";
audioStatus.textContent = "RX";
}
function startRxAudio() { function startRxAudio() {
if (rxActive) { stopRxAudio(); return; } if (rxActive) { stopRxAudio(); return; }
if (!hasWebCodecs) { if (!hasWebCodecs) {
@@ -2661,17 +2695,7 @@ function startRxAudio() {
if (typeof evt.data === "string") { if (typeof evt.data === "string") {
// Stream info JSON // Stream info JSON
try { try {
streamInfo = JSON.parse(evt.data); configureRxStream(JSON.parse(evt.data));
updateWfmControls();
audioCtx = new AudioContext({ sampleRate: streamInfo.sample_rate || 48000 });
audioCtx.resume().catch(() => {});
rxGainNode = audioCtx.createGain();
rxGainNode.gain.value = rxVolSlider.value / 100;
rxGainNode.connect(audioCtx.destination);
rxActive = true;
rxAudioBtn.style.borderColor = "#00d17f";
rxAudioBtn.style.color = "#00d17f";
audioStatus.textContent = "RX";
} catch (e) { } catch (e) {
console.error("Audio stream info parse error", e); console.error("Audio stream info parse error", e);
} }
@@ -226,7 +226,7 @@ pub async fn audio_ws(
let (response, mut session, mut msg_stream) = actix_ws::handle(&req, body)?; let (response, mut session, mut msg_stream) = actix_ws::handle(&req, body)?;
actix_web::rt::spawn(async move { actix_web::rt::spawn(async move {
let info = loop { let mut current_info = loop {
if let Some(info) = info_rx.borrow().clone() { if let Some(info) = info_rx.borrow().clone() {
break info; break info;
} }
@@ -236,7 +236,7 @@ pub async fn audio_ws(
} }
}; };
let info_json = match serde_json::to_string(&info) { let info_json = match serde_json::to_string(&current_info) {
Ok(j) => j, Ok(j) => j,
Err(_) => { Err(_) => {
let _ = session.close(None).await; let _ = session.close(None).await;
@@ -247,34 +247,56 @@ pub async fn audio_ws(
return; return;
} }
let mut rx_session = session.clone(); loop {
let rx_handle = actix_web::rt::spawn(async move { tokio::select! {
loop { changed = info_rx.changed() => {
match rx_sub.recv().await { match changed {
Ok(packet) => { Ok(()) => {
if rx_session.binary(packet).await.is_err() { let Some(next_info) = info_rx.borrow().clone() else {
break; continue;
};
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;
if changed {
current_info = next_info;
let info_json = match serde_json::to_string(&current_info) {
Ok(j) => j,
Err(_) => break,
};
if session.text(info_json).await.is_err() {
break;
}
}
} }
Err(_) => break,
} }
Err(broadcast::error::RecvError::Lagged(n)) => { }
warn!("Audio WS: dropped {} RX frames", n); packet = rx_sub.recv() => {
match packet {
Ok(packet) => {
if session.binary(packet).await.is_err() {
break;
}
}
Err(broadcast::error::RecvError::Lagged(n)) => {
warn!("Audio WS: dropped {} RX frames", n);
}
Err(broadcast::error::RecvError::Closed) => break,
} }
Err(broadcast::error::RecvError::Closed) => break,
} }
} msg = msg_stream.recv() => {
}); match msg {
Some(Ok(Message::Binary(data))) => {
while let Some(Ok(msg)) = msg_stream.recv().await { let _ = tx_sender.send(Bytes::from(data.to_vec())).await;
match msg { }
Message::Binary(data) => { Some(Ok(Message::Close(_))) => break,
let _ = tx_sender.send(Bytes::from(data.to_vec())).await; Some(Ok(_)) => {}
Some(Err(_)) | None => break,
}
} }
Message::Close(_) => break,
_ => {}
} }
} }
rx_handle.abort();
let _ = session.close(None).await; let _ = session.close(None).await;
}); });