Modify the media/hazards module to only play alerts once
Some checks are pending
build-docker / Build Image (push) Waiting to run

This commit is contained in:
mrkmntal 2026-04-10 15:02:23 -04:00
commit cd2cc65040
4 changed files with 61 additions and 55 deletions

Binary file not shown.

View file

@ -1,7 +1,7 @@
// hourly forecast list // hourly forecast list
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import { setAlertToneActive } from './media.mjs'; import { playAlertTone, stopAlertTone } from './media.mjs';
import { safeJson } from './utils/fetch.mjs'; import { safeJson } from './utils/fetch.mjs';
import deriveHazards from './utils/derived-hazards.mjs'; import deriveHazards from './utils/derived-hazards.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
@ -98,12 +98,14 @@ class Hazards extends WeatherDisplay {
} }
} }
this.alertSignature = getAlertSignature(this.data); this.alertSignature = getAlertSignature(this.data);
setAlertToneActive(this.data.length > 0);
const alertsChanged = previousSignature !== this.alertSignature; const alertsChanged = previousSignature !== this.alertSignature;
if (alertsChanged) { if (alertsChanged) {
this.viewedAlerts.clear(); this.viewedAlerts.clear();
if (this.data.length > 0) { if (this.data.length > 0) {
playAlertTone();
postMessage({ type: 'current-weather-scroll', method: 'reload' }); postMessage({ type: 'current-weather-scroll', method: 'reload' });
} else {
stopAlertTone();
} }
} }
@ -129,7 +131,7 @@ class Hazards extends WeatherDisplay {
} }
} catch (error) { } catch (error) {
console.error(`Unexpected Active Alerts error: ${error.message}`); console.error(`Unexpected Active Alerts error: ${error.message}`);
setAlertToneActive(false); stopAlertTone();
if (this.isEnabled) this.setStatus(STATUS.failed); if (this.isEnabled) this.setStatus(STATUS.failed);
// return undefined to other subscribers // return undefined to other subscribers
this.getDataCallback(undefined); this.getDataCallback(undefined);

View file

@ -12,11 +12,21 @@ let volumeSlider = null;
let volumeSliderInput = null; let volumeSliderInput = null;
let alertToneActive = false; let alertToneActive = false;
let alertTonePending = false; let alertTonePending = false;
let resumeMediaAfterAlertTone = false;
let audioUnlocked = false; let audioUnlocked = false;
let alertToneTimeout = null;
const ALERT_TONE_DURATION_MS = 30_000; const ALERT_DUCK_VOLUME = 0.05;
const MAX_MEDIA_VOLUME = 0.20;
const isAlertToneBlockingMedia = () => alertToneActive || alertTonePending;
const clampMediaVolume = (value) => Math.min(Math.max(value, ALERT_DUCK_VOLUME), MAX_MEDIA_VOLUME);
const getActiveMediaVolume = () => {
if (isAlertToneBlockingMedia()) {
return ALERT_DUCK_VOLUME;
}
return clampMediaVolume(mediaVolume.value);
};
const mediaPlaying = new Setting('mediaPlaying', { const mediaPlaying = new Setting('mediaPlaying', {
name: 'Media Playing', name: 'Media Playing',
@ -46,12 +56,15 @@ document.addEventListener('DOMContentLoaded', () => {
// register the volume setting // register the volume setting
registerHiddenSetting(mediaVolume.elemId, mediaVolume); registerHiddenSetting(mediaVolume.elemId, mediaVolume);
if (mediaVolume.value !== clampMediaVolume(mediaVolume.value)) {
mediaVolume.value = clampMediaVolume(mediaVolume.value);
}
}); });
const unlockAudio = () => { const unlockAudio = () => {
if (audioUnlocked) return; if (audioUnlocked) return;
audioUnlocked = true; audioUnlocked = true;
if (alertToneActive && alertTonePending) { if (alertTonePending) {
startAlertTone(); startAlertTone();
} }
}; };
@ -194,8 +207,8 @@ const startMedia = async () => {
if (!player) { if (!player) {
initializePlayer(); initializePlayer();
} else { } else {
if (alertToneActive) return;
try { try {
player.volume = getActiveMediaVolume();
await player.play(); await player.play();
setTrackName(playlist.availableFiles[currentTrack]); setTrackName(playlist.availableFiles[currentTrack]);
} catch (e) { } catch (e) {
@ -245,8 +258,9 @@ const randomizePlaylist = () => {
}; };
const setVolume = (newVolume) => { const setVolume = (newVolume) => {
const clampedVolume = clampMediaVolume(newVolume);
if (player) { if (player) {
player.volume = newVolume; player.volume = isAlertToneBlockingMedia() ? ALERT_DUCK_VOLUME : clampedVolume;
} }
}; };
@ -254,8 +268,7 @@ const sliderChanged = () => {
// get the value of the slider // get the value of the slider
if (volumeSlider) { if (volumeSlider) {
const newValue = volumeSliderInput.value; const newValue = volumeSliderInput.value;
const cleanValue = parseFloat(newValue) / 100; const cleanValue = clampMediaVolume(parseFloat(newValue) / 100);
setVolume(cleanValue);
mediaVolume.value = cleanValue; mediaVolume.value = cleanValue;
} }
}; };
@ -263,12 +276,12 @@ const sliderChanged = () => {
const mediaVolume = new Setting('mediaVolume', { const mediaVolume = new Setting('mediaVolume', {
name: 'Volume', name: 'Volume',
type: 'select', type: 'select',
defaultValue: 0.75, defaultValue: 0.15,
values: [ values: [
[1, '100%'], [0.20, '20%'],
[0.75, '75%'], [0.15, '15%'],
[0.50, '50%'], [0.10, '10%'],
[0.25, '25%'], [0.05, '5%'],
], ],
changeAction: setVolume, changeAction: setVolume,
}); });
@ -296,7 +309,7 @@ const initializePlayer = () => {
setTrackName(playlist.availableFiles[currentTrack]); setTrackName(playlist.availableFiles[currentTrack]);
player.type = 'audio/mpeg'; player.type = 'audio/mpeg';
// set volume and slider indicator // set volume and slider indicator
setVolume(mediaVolume.value); setVolume(getActiveMediaVolume());
volumeSliderInput.value = Math.round(mediaVolume.value * 100); volumeSliderInput.value = Math.round(mediaVolume.value * 100);
}; };
@ -305,15 +318,23 @@ const initializeAlertTonePlayer = () => {
alertTonePlayer = new Audio(withBasePath('alert/tone.mp3')); alertTonePlayer = new Audio(withBasePath('alert/tone.mp3'));
alertTonePlayer.type = 'audio/mpeg'; alertTonePlayer.type = 'audio/mpeg';
alertTonePlayer.preload = 'auto'; alertTonePlayer.preload = 'auto';
alertTonePlayer.addEventListener('ended', () => { alertTonePlayer.addEventListener('ended', finishAlertTone);
if (alertToneActive) { };
alertTonePlayer.currentTime = 0;
alertTonePlayer.play().catch((e) => { const duckMediaForAlert = () => {
console.error('Couldn\'t continue alert tone'); if (!player || player.paused) return;
console.error(e); player.volume = ALERT_DUCK_VOLUME;
}); };
}
}); const restoreMediaAfterAlert = () => {
if (!player) return;
player.volume = clampMediaVolume(mediaVolume.value);
};
const finishAlertTone = () => {
alertToneActive = false;
alertTonePending = false;
restoreMediaAfterAlert();
}; };
const startAlertTone = async () => { const startAlertTone = async () => {
@ -323,19 +344,16 @@ const startAlertTone = async () => {
} }
initializeAlertTonePlayer(); initializeAlertTonePlayer();
try { try {
alertTonePending = true;
duckMediaForAlert();
alertToneActive = true;
alertTonePlayer.currentTime = 0;
await alertTonePlayer.play(); await alertTonePlayer.play();
alertTonePending = false; alertTonePending = false;
resumeMediaAfterAlertTone = mediaPlaying.value === true;
if (alertToneTimeout) clearTimeout(alertToneTimeout);
alertToneTimeout = setTimeout(() => {
if (alertToneActive) {
setAlertToneActive(false);
}
}, ALERT_TONE_DURATION_MS);
if (player && !player.paused) {
player.pause();
}
} catch (e) { } catch (e) {
alertToneActive = false;
alertTonePending = false;
restoreMediaAfterAlert();
console.error('Couldn\'t play alert tone'); console.error('Couldn\'t play alert tone');
console.error(e); console.error(e);
} }
@ -343,29 +361,14 @@ const startAlertTone = async () => {
const stopAlertTone = () => { const stopAlertTone = () => {
alertTonePending = false; alertTonePending = false;
if (alertToneTimeout) {
clearTimeout(alertToneTimeout);
alertToneTimeout = null;
}
if (alertTonePlayer) { if (alertTonePlayer) {
alertTonePlayer.pause(); alertTonePlayer.pause();
alertTonePlayer.currentTime = 0; alertTonePlayer.currentTime = 0;
} }
if (resumeMediaAfterAlertTone && mediaPlaying.value === true) { finishAlertTone();
startMedia();
}
resumeMediaAfterAlertTone = false;
}; };
const setAlertToneActive = (active) => { const playAlertTone = () => startAlertTone();
if (active === alertToneActive) return;
alertToneActive = active;
if (alertToneActive) {
startAlertTone();
return;
}
stopAlertTone();
};
const playerCanPlay = async () => { const playerCanPlay = async () => {
// check to make sure they user still wants music (protect against slow loading music) // check to make sure they user still wants music (protect against slow loading music)
@ -397,5 +400,6 @@ const setTrackName = (fileName) => {
export { export {
handleClick, handleClick,
setAlertToneActive, playAlertTone,
stopAlertTone,
}; };

View file

@ -165,7 +165,7 @@
<img class="navButton on" src="images/nav/ic_volume_on_white_24dp_2x.png" title="Volume" /> <img class="navButton on" src="images/nav/ic_volume_on_white_24dp_2x.png" title="Volume" />
</div> </div>
<div class="volume-slider"> <div class="volume-slider">
<input type="range" min="1" max="100" value="75" /><br> <input type="range" min="1" max="20" value="15" /><br>
<img class="navButton" src="images/nav/ic_volume_off_white_24dp_2x.png" title="Mute" /> <img class="navButton" src="images/nav/ic_volume_off_white_24dp_2x.png" title="Mute" />
</div> </div>
</div> </div>