Stabilize screen audio playback/reduce race conditions
Some checks are pending
build-docker / Build Image (push) Waiting to run

This commit is contained in:
mrkmntal 2026-04-12 17:42:18 -04:00
commit b0d9c95bf1
3 changed files with 87 additions and 18 deletions

View file

@ -385,6 +385,10 @@ const screenAudioMap = {
};
let screenAudioPlayer = null;
let audioStartTime = 0;
let audioStopTimeout = null;
const AUDIO_MIN_PLAY_TIME = 500; // 500ms minimum play time
let currentScreenId = null;
// Helper function to check if screen audio is enabled (always reads from localStorage)
const isScreenAudioEnabled = () => {
@ -394,17 +398,35 @@ const isScreenAudioEnabled = () => {
// Play screen audio
const playScreenAudio = async (screenId) => {
console.log(`[AUDIO] playScreenAudio called for ${screenId}`);
// Always check localStorage to ensure setting is current
if (!isScreenAudioEnabled()) return;
if (!isScreenAudioEnabled()) {
console.log(`[AUDIO] Aborting - screen audio disabled`);
return;
}
const fileName = screenAudioMap[screenId];
if (!fileName) return;
if (!fileName) {
console.log(`[AUDIO] No audio file mapped for ${screenId}`);
return;
}
// Cancel any pending stop timeout from previous audio
if (audioStopTimeout) {
console.log(`[AUDIO] Cancelling pending stop timeout for new audio`);
clearTimeout(audioStopTimeout);
audioStopTimeout = null;
}
// Stop any existing screen audio first
stopScreenAudio();
// Don't play if alert tone is active
if (alertToneActive || alertTonePending) return;
if (alertToneActive || alertTonePending) {
console.log(`[AUDIO] Aborting - alert tone active/pending`);
return;
}
// Duck background music to 10%
if (player && !player.paused) {
@ -414,17 +436,22 @@ const playScreenAudio = async (screenId) => {
// Create and play audio
screenAudioPlayer = new Audio(withBasePath(`alert/${fileName}`));
screenAudioPlayer.type = 'audio/mpeg';
currentScreenId = screenId;
screenAudioPlayer.addEventListener('ended', () => {
console.log(`[AUDIO] 'ended' event fired for ${currentScreenId}`);
screenAudioPlayer = null;
currentScreenId = null;
// Only restore if alert isn't playing
if (!alertToneActive && !alertTonePending) {
restoreMediaAfterAlert();
}
});
screenAudioPlayer.addEventListener('error', () => {
screenAudioPlayer.addEventListener('error', (e) => {
console.log(`[AUDIO] 'error' event fired for ${currentScreenId}:`, e.message || e);
screenAudioPlayer = null;
currentScreenId = null;
if (!alertToneActive && !alertTonePending) {
restoreMediaAfterAlert();
}
@ -432,21 +459,69 @@ const playScreenAudio = async (screenId) => {
try {
await screenAudioPlayer.play();
audioStartTime = Date.now();
console.log(`[AUDIO] Audio started for ${screenId} at ${audioStartTime}`);
} catch (e) {
console.log(`[AUDIO] Failed to play for ${screenId}:`, e.message);
screenAudioPlayer = null;
currentScreenId = null;
if (!alertToneActive && !alertTonePending) {
restoreMediaAfterAlert();
}
}
};
// Stop screen audio immediately
const stopScreenAudio = () => {
// Actually stop the audio (internal helper)
const actuallyStopAudio = () => {
console.log(`[AUDIO] Actually stopping audio for ${currentScreenId}`);
if (screenAudioPlayer) {
screenAudioPlayer.pause();
screenAudioPlayer.currentTime = 0;
screenAudioPlayer = null;
}
currentScreenId = null;
audioStartTime = 0;
audioStopTimeout = null;
};
// Stop screen audio (with minimum play time protection)
const stopScreenAudio = () => {
if (!screenAudioPlayer) {
console.log(`[AUDIO] stopScreenAudio called but no audio playing`);
return;
}
const elapsed = Date.now() - audioStartTime;
console.log(`[AUDIO] stopScreenAudio called for ${currentScreenId}, elapsed: ${elapsed}ms`);
// If we haven't played for the minimum time, delay the stop
if (elapsed < AUDIO_MIN_PLAY_TIME) {
const delay = AUDIO_MIN_PLAY_TIME - elapsed;
console.log(`[AUDIO] Delaying stop for ${delay}ms (minimum play time: ${AUDIO_MIN_PLAY_TIME}ms)`);
// Clear any existing timeout first
if (audioStopTimeout) {
clearTimeout(audioStopTimeout);
}
audioStopTimeout = setTimeout(() => {
// Check if this audio is still the current one (might have been replaced)
if (screenAudioPlayer && Date.now() - audioStartTime >= AUDIO_MIN_PLAY_TIME) {
console.log(`[AUDIO] Executing delayed stop for ${currentScreenId}`);
actuallyStopAudio();
// Restore music volume after delayed stop
if (!alertToneActive && !alertTonePending) {
restoreMediaAfterAlert();
}
} else {
console.log(`[AUDIO] Delayed stop cancelled - audio already changed or stopped`);
}
}, delay);
} else {
// Minimum play time met, stop immediately
console.log(`[AUDIO] Minimum play time met, stopping immediately`);
actuallyStopAudio();
}
};
const playerCanPlay = async () => {

View file

@ -6,6 +6,7 @@ import { safeJson } from './utils/fetch.mjs';
import { getPoint, getOpenMeteoForecast, aggregateWeatherForecastData } from './utils/weather.mjs';
import { debugFlag } from './utils/debug.mjs';
import settings from './settings.mjs';
import { stopScreenAudio } from './media.mjs';
document.addEventListener('DOMContentLoaded', () => {
init();
@ -386,17 +387,13 @@ const handleNavButton = (button) => {
case 'next':
setPlaying(false);
// Stop screen audio immediately when navigating
import('./media.mjs').then((media) => {
media.stopScreenAudio();
});
stopScreenAudio();
navTo(msg.command.nextFrame);
break;
case 'previous':
setPlaying(false);
// Stop screen audio immediately when navigating
import('./media.mjs').then((media) => {
media.stopScreenAudio();
});
stopScreenAudio();
navTo(msg.command.previousFrame);
break;
case 'menu':

View file

@ -9,6 +9,7 @@ import { parseQueryString } from './utils/setting.mjs';
import settings from './settings.mjs';
import { elemForEach } from './utils/elem.mjs';
import { debugFlag } from './utils/debug.mjs';
import { playScreenAudio, stopScreenAudio } from './media.mjs';
class WeatherDisplay {
constructor(navId, elemId, name, defaultEnabled) {
@ -228,9 +229,7 @@ class WeatherDisplay {
// Play screen-specific audio only if display was not already active
// This prevents audio restart on frame changes (e.g., Local Radar animation)
if (!wasActive) {
import('./media.mjs').then((media) => {
media.playScreenAudio(this.elemId);
});
playScreenAudio(this.elemId);
}
}
@ -241,9 +240,7 @@ class WeatherDisplay {
document.querySelector('#divTwc').classList.remove(this.elemId);
// Stop screen audio when leaving
import('./media.mjs').then((media) => {
media.stopScreenAudio();
});
stopScreenAudio();
}
get active() {