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 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)
|
// Helper function to check if screen audio is enabled (always reads from localStorage)
|
||||||
const isScreenAudioEnabled = () => {
|
const isScreenAudioEnabled = () => {
|
||||||
|
|
@ -394,17 +398,35 @@ const isScreenAudioEnabled = () => {
|
||||||
|
|
||||||
// Play screen audio
|
// Play screen audio
|
||||||
const playScreenAudio = async (screenId) => {
|
const playScreenAudio = async (screenId) => {
|
||||||
|
console.log(`[AUDIO] playScreenAudio called for ${screenId}`);
|
||||||
|
|
||||||
// Always check localStorage to ensure setting is current
|
// 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];
|
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
|
// Stop any existing screen audio first
|
||||||
stopScreenAudio();
|
stopScreenAudio();
|
||||||
|
|
||||||
// Don't play if alert tone is active
|
// 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%
|
// Duck background music to 10%
|
||||||
if (player && !player.paused) {
|
if (player && !player.paused) {
|
||||||
|
|
@ -414,17 +436,22 @@ const playScreenAudio = async (screenId) => {
|
||||||
// Create and play audio
|
// Create and play audio
|
||||||
screenAudioPlayer = new Audio(withBasePath(`alert/${fileName}`));
|
screenAudioPlayer = new Audio(withBasePath(`alert/${fileName}`));
|
||||||
screenAudioPlayer.type = 'audio/mpeg';
|
screenAudioPlayer.type = 'audio/mpeg';
|
||||||
|
currentScreenId = screenId;
|
||||||
|
|
||||||
screenAudioPlayer.addEventListener('ended', () => {
|
screenAudioPlayer.addEventListener('ended', () => {
|
||||||
|
console.log(`[AUDIO] 'ended' event fired for ${currentScreenId}`);
|
||||||
screenAudioPlayer = null;
|
screenAudioPlayer = null;
|
||||||
|
currentScreenId = null;
|
||||||
// Only restore if alert isn't playing
|
// Only restore if alert isn't playing
|
||||||
if (!alertToneActive && !alertTonePending) {
|
if (!alertToneActive && !alertTonePending) {
|
||||||
restoreMediaAfterAlert();
|
restoreMediaAfterAlert();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
screenAudioPlayer.addEventListener('error', () => {
|
screenAudioPlayer.addEventListener('error', (e) => {
|
||||||
|
console.log(`[AUDIO] 'error' event fired for ${currentScreenId}:`, e.message || e);
|
||||||
screenAudioPlayer = null;
|
screenAudioPlayer = null;
|
||||||
|
currentScreenId = null;
|
||||||
if (!alertToneActive && !alertTonePending) {
|
if (!alertToneActive && !alertTonePending) {
|
||||||
restoreMediaAfterAlert();
|
restoreMediaAfterAlert();
|
||||||
}
|
}
|
||||||
|
|
@ -432,21 +459,69 @@ const playScreenAudio = async (screenId) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await screenAudioPlayer.play();
|
await screenAudioPlayer.play();
|
||||||
|
audioStartTime = Date.now();
|
||||||
|
console.log(`[AUDIO] Audio started for ${screenId} at ${audioStartTime}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log(`[AUDIO] Failed to play for ${screenId}:`, e.message);
|
||||||
screenAudioPlayer = null;
|
screenAudioPlayer = null;
|
||||||
|
currentScreenId = null;
|
||||||
if (!alertToneActive && !alertTonePending) {
|
if (!alertToneActive && !alertTonePending) {
|
||||||
restoreMediaAfterAlert();
|
restoreMediaAfterAlert();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Stop screen audio immediately
|
// Actually stop the audio (internal helper)
|
||||||
const stopScreenAudio = () => {
|
const actuallyStopAudio = () => {
|
||||||
|
console.log(`[AUDIO] Actually stopping audio for ${currentScreenId}`);
|
||||||
if (screenAudioPlayer) {
|
if (screenAudioPlayer) {
|
||||||
screenAudioPlayer.pause();
|
screenAudioPlayer.pause();
|
||||||
screenAudioPlayer.currentTime = 0;
|
screenAudioPlayer.currentTime = 0;
|
||||||
screenAudioPlayer = null;
|
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 () => {
|
const playerCanPlay = async () => {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { safeJson } from './utils/fetch.mjs';
|
||||||
import { getPoint, getOpenMeteoForecast, aggregateWeatherForecastData } from './utils/weather.mjs';
|
import { getPoint, getOpenMeteoForecast, aggregateWeatherForecastData } from './utils/weather.mjs';
|
||||||
import { debugFlag } from './utils/debug.mjs';
|
import { debugFlag } from './utils/debug.mjs';
|
||||||
import settings from './settings.mjs';
|
import settings from './settings.mjs';
|
||||||
|
import { stopScreenAudio } from './media.mjs';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
init();
|
init();
|
||||||
|
|
@ -386,17 +387,13 @@ const handleNavButton = (button) => {
|
||||||
case 'next':
|
case 'next':
|
||||||
setPlaying(false);
|
setPlaying(false);
|
||||||
// Stop screen audio immediately when navigating
|
// Stop screen audio immediately when navigating
|
||||||
import('./media.mjs').then((media) => {
|
stopScreenAudio();
|
||||||
media.stopScreenAudio();
|
|
||||||
});
|
|
||||||
navTo(msg.command.nextFrame);
|
navTo(msg.command.nextFrame);
|
||||||
break;
|
break;
|
||||||
case 'previous':
|
case 'previous':
|
||||||
setPlaying(false);
|
setPlaying(false);
|
||||||
// Stop screen audio immediately when navigating
|
// Stop screen audio immediately when navigating
|
||||||
import('./media.mjs').then((media) => {
|
stopScreenAudio();
|
||||||
media.stopScreenAudio();
|
|
||||||
});
|
|
||||||
navTo(msg.command.previousFrame);
|
navTo(msg.command.previousFrame);
|
||||||
break;
|
break;
|
||||||
case 'menu':
|
case 'menu':
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { parseQueryString } from './utils/setting.mjs';
|
||||||
import settings from './settings.mjs';
|
import settings from './settings.mjs';
|
||||||
import { elemForEach } from './utils/elem.mjs';
|
import { elemForEach } from './utils/elem.mjs';
|
||||||
import { debugFlag } from './utils/debug.mjs';
|
import { debugFlag } from './utils/debug.mjs';
|
||||||
|
import { playScreenAudio, stopScreenAudio } from './media.mjs';
|
||||||
|
|
||||||
class WeatherDisplay {
|
class WeatherDisplay {
|
||||||
constructor(navId, elemId, name, defaultEnabled) {
|
constructor(navId, elemId, name, defaultEnabled) {
|
||||||
|
|
@ -228,9 +229,7 @@ class WeatherDisplay {
|
||||||
// Play screen-specific audio only if display was not already active
|
// Play screen-specific audio only if display was not already active
|
||||||
// This prevents audio restart on frame changes (e.g., Local Radar animation)
|
// This prevents audio restart on frame changes (e.g., Local Radar animation)
|
||||||
if (!wasActive) {
|
if (!wasActive) {
|
||||||
import('./media.mjs').then((media) => {
|
playScreenAudio(this.elemId);
|
||||||
media.playScreenAudio(this.elemId);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,9 +240,7 @@ class WeatherDisplay {
|
||||||
document.querySelector('#divTwc').classList.remove(this.elemId);
|
document.querySelector('#divTwc').classList.remove(this.elemId);
|
||||||
|
|
||||||
// Stop screen audio when leaving
|
// Stop screen audio when leaving
|
||||||
import('./media.mjs').then((media) => {
|
stopScreenAudio();
|
||||||
media.stopScreenAudio();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get active() {
|
get active() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue