Stabilize screen audio playback/reduce race conditions
Some checks are pending
build-docker / Build Image (push) Waiting to run
Some checks are pending
build-docker / Build Image (push) Waiting to run
This commit is contained in:
parent
0afd3f14a0
commit
b0d9c95bf1
3 changed files with 87 additions and 18 deletions
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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':
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue