[fix](trx-frontend-http): start browser audio on user gesture
Create and resume the RX AudioContext from the audio button click so Chromium does not leave playback suspended until a later interaction. Reuse that context when stream metadata arrives instead of recreating it from the WebSocket message path. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -7352,6 +7352,25 @@ function setAudioLevel(levelPct) {
|
|||||||
audioLevelFill.style.width = `${clamped}%`;
|
audioLevelFill.style.width = `${clamped}%`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create/resume the output context from a direct user gesture so Chromium
|
||||||
|
// does not leave playback suspended until a later click.
|
||||||
|
function ensureRxAudioContext(preferredSampleRate) {
|
||||||
|
if (!audioCtx) {
|
||||||
|
try {
|
||||||
|
audioCtx = Number.isFinite(preferredSampleRate) && preferredSampleRate > 0
|
||||||
|
? new AudioContext({ sampleRate: preferredSampleRate })
|
||||||
|
: new AudioContext();
|
||||||
|
} catch (e) {
|
||||||
|
audioCtx = new AudioContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
audioCtx.resume().catch(() => {});
|
||||||
|
if (!rxGainNode) {
|
||||||
|
rxGainNode = audioCtx.createGain();
|
||||||
|
rxGainNode.connect(audioCtx.destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function levelFromChannels(channels, frameCount) {
|
function levelFromChannels(channels, frameCount) {
|
||||||
if (!Array.isArray(channels) || channels.length === 0 || !Number.isFinite(frameCount) || frameCount <= 0) {
|
if (!Array.isArray(channels) || channels.length === 0 || !Number.isFinite(frameCount) || frameCount <= 0) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -7596,23 +7615,10 @@ function resetRxDecoder() {
|
|||||||
|
|
||||||
function configureRxStream(nextInfo) {
|
function configureRxStream(nextInfo) {
|
||||||
const nextSampleRate = (nextInfo && nextInfo.sample_rate) || 48000;
|
const nextSampleRate = (nextInfo && nextInfo.sample_rate) || 48000;
|
||||||
const sampleRateChanged = !audioCtx || audioCtx.sampleRate !== nextSampleRate;
|
|
||||||
streamInfo = nextInfo;
|
streamInfo = nextInfo;
|
||||||
updateWfmControls();
|
updateWfmControls();
|
||||||
resetRxDecoder();
|
resetRxDecoder();
|
||||||
if (sampleRateChanged && audioCtx) {
|
ensureRxAudioContext(nextSampleRate);
|
||||||
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;
|
rxGainNode.gain.value = rxVolSlider.value / 100;
|
||||||
rxActive = true;
|
rxActive = true;
|
||||||
setAudioLevel(0);
|
setAudioLevel(0);
|
||||||
@@ -7662,6 +7668,7 @@ function startRxAudio() {
|
|||||||
audioStatus.textContent = "Audio requires Chrome/Edge";
|
audioStatus.textContent = "Audio requires Chrome/Edge";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ensureRxAudioContext((streamInfo && streamInfo.sample_rate) || 48000);
|
||||||
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
let audioPath;
|
let audioPath;
|
||||||
if (_audioChannelOverride) {
|
if (_audioChannelOverride) {
|
||||||
|
|||||||
Reference in New Issue
Block a user