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

View file

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