Implement severe weather alert tone

This commit is contained in:
mrkmntal 2026-04-09 23:10:04 -04:00
commit d3643d3a9a
4 changed files with 98 additions and 1 deletions

BIN
server/alert/tone.mp3 Normal file

Binary file not shown.

View file

@ -1,6 +1,7 @@
// hourly forecast list
import STATUS from './status.mjs';
import { setAlertToneActive } from './media.mjs';
import { safeJson } from './utils/fetch.mjs';
import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
@ -57,6 +58,7 @@ class Hazards extends WeatherDisplay {
const superResult = super.getData(weatherParameters, refresh);
if (!this.weatherParameters?.supportsNoaaAlerts) {
this.data = [];
setAlertToneActive(false);
this.timing.totalScreens = 0;
this.getDataCallback();
this.setStatus(STATUS.loaded);
@ -99,6 +101,7 @@ class Hazards extends WeatherDisplay {
this.data = filteredAlerts;
}
this.alertSignature = getAlertSignature(this.data);
setAlertToneActive(this.data.length > 0);
const alertsChanged = previousSignature !== this.alertSignature;
if (alertsChanged) {
this.viewedAlerts.clear();
@ -129,6 +132,7 @@ class Hazards extends WeatherDisplay {
}
} catch (error) {
console.error(`Unexpected Active Alerts error: ${error.message}`);
setAlertToneActive(false);
if (this.isEnabled) this.setStatus(STATUS.failed);
// return undefined to other subscribers
this.getDataCallback(undefined);

View file

@ -1,13 +1,22 @@
import { text } from './utils/fetch.mjs';
import Setting from './utils/setting.mjs';
import { registerHiddenSetting } from './share.mjs';
import { withBasePath } from './utils/base-path.mjs';
let playlist;
let currentTrack = 0;
let player;
let alertTonePlayer;
let sliderTimeout = null;
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 mediaPlaying = new Setting('mediaPlaying', {
name: 'Media Playing',
@ -33,11 +42,26 @@ document.addEventListener('DOMContentLoaded', () => {
// get the playlist
getMedia();
registerAudioUnlockHandlers();
// register the volume setting
registerHiddenSetting(mediaVolume.elemId, mediaVolume);
});
const unlockAudio = () => {
if (audioUnlocked) return;
audioUnlocked = true;
if (alertToneActive && alertTonePending) {
startAlertTone();
}
};
const registerAudioUnlockHandlers = () => {
['pointerdown', 'keydown', 'touchstart'].forEach((eventName) => {
document.addEventListener(eventName, unlockAudio, { passive: true, once: true });
});
};
const scanMusicDirectory = async () => {
const parseDirectory = async (path, prefix = '') => {
const listing = await text(path);
@ -170,6 +194,7 @@ const startMedia = async () => {
if (!player) {
initializePlayer();
} else {
if (alertToneActive) return;
try {
await player.play();
setTrackName(playlist.availableFiles[currentTrack]);
@ -275,6 +300,73 @@ const initializePlayer = () => {
volumeSliderInput.value = Math.round(mediaVolume.value * 100);
};
const initializeAlertTonePlayer = () => {
if (alertTonePlayer) return;
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);
});
}
});
};
const startAlertTone = async () => {
if (!audioUnlocked) {
alertTonePending = true;
return;
}
initializeAlertTonePlayer();
try {
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) {
console.error('Couldn\'t play alert tone');
console.error(e);
}
};
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;
};
const setAlertToneActive = (active) => {
if (active === alertToneActive) return;
alertToneActive = active;
if (alertToneActive) {
startAlertTone();
return;
}
stopAlertTone();
};
const playerCanPlay = async () => {
// check to make sure they user still wants music (protect against slow loading music)
if (!mediaPlaying.value) return;
@ -304,6 +396,6 @@ const setTrackName = (fileName) => {
};
export {
// eslint-disable-next-line import/prefer-default-export
handleClick,
setAlertToneActive,
};