Implement severe weather alert tone
This commit is contained in:
parent
2e304d41c6
commit
d3643d3a9a
4 changed files with 98 additions and 1 deletions
|
|
@ -134,6 +134,7 @@ const compressHtml = async () => src(htmlSources)
|
||||||
const otherFiles = [
|
const otherFiles = [
|
||||||
'server/robots.txt',
|
'server/robots.txt',
|
||||||
'server/manifest.json',
|
'server/manifest.json',
|
||||||
|
'server/alert/**/*.mp3',
|
||||||
'server/music/**/*.mp3',
|
'server/music/**/*.mp3',
|
||||||
];
|
];
|
||||||
const copyOtherFiles = () => src(otherFiles, { base: 'server/', encoding: false })
|
const copyOtherFiles = () => src(otherFiles, { base: 'server/', encoding: false })
|
||||||
|
|
|
||||||
BIN
server/alert/tone.mp3
Normal file
BIN
server/alert/tone.mp3
Normal file
Binary file not shown.
|
|
@ -1,6 +1,7 @@
|
||||||
// hourly forecast list
|
// hourly forecast list
|
||||||
|
|
||||||
import STATUS from './status.mjs';
|
import STATUS from './status.mjs';
|
||||||
|
import { setAlertToneActive } from './media.mjs';
|
||||||
import { safeJson } from './utils/fetch.mjs';
|
import { safeJson } from './utils/fetch.mjs';
|
||||||
import WeatherDisplay from './weatherdisplay.mjs';
|
import WeatherDisplay from './weatherdisplay.mjs';
|
||||||
import { registerDisplay } from './navigation.mjs';
|
import { registerDisplay } from './navigation.mjs';
|
||||||
|
|
@ -57,6 +58,7 @@ class Hazards extends WeatherDisplay {
|
||||||
const superResult = super.getData(weatherParameters, refresh);
|
const superResult = super.getData(weatherParameters, refresh);
|
||||||
if (!this.weatherParameters?.supportsNoaaAlerts) {
|
if (!this.weatherParameters?.supportsNoaaAlerts) {
|
||||||
this.data = [];
|
this.data = [];
|
||||||
|
setAlertToneActive(false);
|
||||||
this.timing.totalScreens = 0;
|
this.timing.totalScreens = 0;
|
||||||
this.getDataCallback();
|
this.getDataCallback();
|
||||||
this.setStatus(STATUS.loaded);
|
this.setStatus(STATUS.loaded);
|
||||||
|
|
@ -99,6 +101,7 @@ class Hazards extends WeatherDisplay {
|
||||||
this.data = filteredAlerts;
|
this.data = filteredAlerts;
|
||||||
}
|
}
|
||||||
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();
|
||||||
|
|
@ -129,6 +132,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);
|
||||||
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);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,22 @@
|
||||||
import { text } from './utils/fetch.mjs';
|
import { text } from './utils/fetch.mjs';
|
||||||
import Setting from './utils/setting.mjs';
|
import Setting from './utils/setting.mjs';
|
||||||
import { registerHiddenSetting } from './share.mjs';
|
import { registerHiddenSetting } from './share.mjs';
|
||||||
|
import { withBasePath } from './utils/base-path.mjs';
|
||||||
|
|
||||||
let playlist;
|
let playlist;
|
||||||
let currentTrack = 0;
|
let currentTrack = 0;
|
||||||
let player;
|
let player;
|
||||||
|
let alertTonePlayer;
|
||||||
let sliderTimeout = null;
|
let sliderTimeout = null;
|
||||||
let volumeSlider = null;
|
let volumeSlider = null;
|
||||||
let volumeSliderInput = 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', {
|
const mediaPlaying = new Setting('mediaPlaying', {
|
||||||
name: 'Media Playing',
|
name: 'Media Playing',
|
||||||
|
|
@ -33,11 +42,26 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
// get the playlist
|
// get the playlist
|
||||||
getMedia();
|
getMedia();
|
||||||
|
registerAudioUnlockHandlers();
|
||||||
|
|
||||||
// register the volume setting
|
// register the volume setting
|
||||||
registerHiddenSetting(mediaVolume.elemId, mediaVolume);
|
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 scanMusicDirectory = async () => {
|
||||||
const parseDirectory = async (path, prefix = '') => {
|
const parseDirectory = async (path, prefix = '') => {
|
||||||
const listing = await text(path);
|
const listing = await text(path);
|
||||||
|
|
@ -170,6 +194,7 @@ const startMedia = async () => {
|
||||||
if (!player) {
|
if (!player) {
|
||||||
initializePlayer();
|
initializePlayer();
|
||||||
} else {
|
} else {
|
||||||
|
if (alertToneActive) return;
|
||||||
try {
|
try {
|
||||||
await player.play();
|
await player.play();
|
||||||
setTrackName(playlist.availableFiles[currentTrack]);
|
setTrackName(playlist.availableFiles[currentTrack]);
|
||||||
|
|
@ -275,6 +300,73 @@ const initializePlayer = () => {
|
||||||
volumeSliderInput.value = Math.round(mediaVolume.value * 100);
|
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 () => {
|
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)
|
||||||
if (!mediaPlaying.value) return;
|
if (!mediaPlaying.value) return;
|
||||||
|
|
@ -304,6 +396,6 @@ const setTrackName = (fileName) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
|
||||||
handleClick,
|
handleClick,
|
||||||
|
setAlertToneActive,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue