Adds screen audio/narration voices to the screens (Can be turned off in settings)
Some checks are pending
build-docker / Build Image (push) Waiting to run

This commit is contained in:
mrkmntal 2026-04-12 16:23:16 -04:00
commit 0afd3f14a0
10 changed files with 131 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -59,6 +59,8 @@ document.addEventListener('DOMContentLoaded', () => {
if (mediaVolume.value !== clampMediaVolume(mediaVolume.value)) {
mediaVolume.value = clampMediaVolume(mediaVolume.value);
}
// Screen audio setting is managed via localStorage and checked directly in playScreenAudio()
});
const unlockAudio = () => {
@ -342,6 +344,7 @@ const startAlertTone = async () => {
alertTonePending = true;
return;
}
stopScreenAudio(); // Stop screen audio when alert plays
initializeAlertTonePlayer();
try {
alertTonePending = true;
@ -370,6 +373,82 @@ const stopAlertTone = () => {
const playAlertTone = () => startAlertTone();
// Screen audio constants and variables
const SCREEN_AUDIO_DUCK_VOLUME = 0.10; // 10% ducking (vs 5% for alerts)
const screenAudioMap = {
'radar': 'local-radar.mp3',
'regional-forecast': 'regional-observations.mp3',
'travel': 'travel-forecast.mp3',
'hourly-graph': 'hourly-graph.mp3',
'hourly': 'hourly-forecast.mp3',
'current-weather': 'current-conditions.mp3',
};
let screenAudioPlayer = null;
// Helper function to check if screen audio is enabled (always reads from localStorage)
const isScreenAudioEnabled = () => {
const saved = window.localStorage.getItem('screenAudioEnabled');
return saved !== null ? saved === 'true' : true; // Default: enabled
};
// Play screen audio
const playScreenAudio = async (screenId) => {
// Always check localStorage to ensure setting is current
if (!isScreenAudioEnabled()) return;
const fileName = screenAudioMap[screenId];
if (!fileName) return;
// Stop any existing screen audio first
stopScreenAudio();
// Don't play if alert tone is active
if (alertToneActive || alertTonePending) return;
// Duck background music to 10%
if (player && !player.paused) {
player.volume = SCREEN_AUDIO_DUCK_VOLUME;
}
// Create and play audio
screenAudioPlayer = new Audio(withBasePath(`alert/${fileName}`));
screenAudioPlayer.type = 'audio/mpeg';
screenAudioPlayer.addEventListener('ended', () => {
screenAudioPlayer = null;
// Only restore if alert isn't playing
if (!alertToneActive && !alertTonePending) {
restoreMediaAfterAlert();
}
});
screenAudioPlayer.addEventListener('error', () => {
screenAudioPlayer = null;
if (!alertToneActive && !alertTonePending) {
restoreMediaAfterAlert();
}
});
try {
await screenAudioPlayer.play();
} catch (e) {
screenAudioPlayer = null;
if (!alertToneActive && !alertTonePending) {
restoreMediaAfterAlert();
}
}
};
// Stop screen audio immediately
const stopScreenAudio = () => {
if (screenAudioPlayer) {
screenAudioPlayer.pause();
screenAudioPlayer.currentTime = 0;
screenAudioPlayer = null;
}
};
const playerCanPlay = async () => {
// check to make sure they user still wants music (protect against slow loading music)
if (!mediaPlaying.value) return;
@ -402,4 +481,6 @@ export {
handleClick,
playAlertTone,
stopAlertTone,
playScreenAudio,
stopScreenAudio,
};

View file

@ -385,10 +385,18 @@ const handleNavButton = (button) => {
break;
case 'next':
setPlaying(false);
// Stop screen audio immediately when navigating
import('./media.mjs').then((media) => {
media.stopScreenAudio();
});
navTo(msg.command.nextFrame);
break;
case 'previous':
setPlaying(false);
// Stop screen audio immediately when navigating
import('./media.mjs').then((media) => {
media.stopScreenAudio();
});
navTo(msg.command.previousFrame);
break;
case 'menu':

View file

@ -231,6 +231,32 @@ document.addEventListener('DOMContentLoaded', () => {
settingsSection.innerHTML = '';
settingsSection.append(...settingHtml);
// Add screen audio toggle checkbox
const screenAudioContainer = document.createElement('div');
screenAudioContainer.className = 'info';
const screenAudioLabel = document.createElement('label');
screenAudioLabel.htmlFor = 'screen-audio-checkbox';
const screenAudioCheckbox = document.createElement('input');
screenAudioCheckbox.type = 'checkbox';
screenAudioCheckbox.id = 'screen-audio-checkbox';
// Load saved preference or default to enabled
const savedScreenAudio = window.localStorage.getItem('screenAudioEnabled');
screenAudioCheckbox.checked = savedScreenAudio !== null ? savedScreenAudio === 'true' : true;
screenAudioCheckbox.addEventListener('change', () => {
window.localStorage.setItem('screenAudioEnabled', screenAudioCheckbox.checked);
// Import media module and update setting
import('./media.mjs').then((media) => {
// Media module will read the localStorage value
if (!screenAudioCheckbox.checked) {
media.stopScreenAudio();
}
});
});
screenAudioLabel.appendChild(screenAudioCheckbox);
screenAudioLabel.appendChild(document.createTextNode(' Play screen audio'));
screenAudioContainer.appendChild(screenAudioLabel);
settingsSection.appendChild(screenAudioContainer);
// update visibility on some settings
const modeSelect = document.getElementById('settings-scanLineMode-label');
const { value } = settings.scanLines;

View file

@ -219,8 +219,19 @@ class WeatherDisplay {
this.startNavCount();
// Check if display was already active before showing
const wasActive = this.active;
this.elem.classList.add('show');
document.querySelector('#divTwc').classList.add(this.elemId);
// 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);
});
}
}
hideCanvas() {
@ -228,6 +239,11 @@ class WeatherDisplay {
this.elem.classList.remove('show');
// used to change backgrounds for widescreen
document.querySelector('#divTwc').classList.remove(this.elemId);
// Stop screen audio when leaving
import('./media.mjs').then((media) => {
media.stopScreenAudio();
});
}
get active() {