diff --git a/server/alert/tone.mp3 b/server/alert/tone.mp3
index 790d2b4..6c956f8 100644
Binary files a/server/alert/tone.mp3 and b/server/alert/tone.mp3 differ
diff --git a/server/scripts/modules/hazards.mjs b/server/scripts/modules/hazards.mjs
index 174295c..2048e7e 100644
--- a/server/scripts/modules/hazards.mjs
+++ b/server/scripts/modules/hazards.mjs
@@ -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);
diff --git a/server/scripts/modules/media.mjs b/server/scripts/modules/media.mjs
index abf4155..51c53f9 100644
--- a/server/scripts/modules/media.mjs
+++ b/server/scripts/modules/media.mjs
@@ -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,
};
diff --git a/views/index.ejs b/views/index.ejs
index 8354a03..edcf622 100644
--- a/views/index.ejs
+++ b/views/index.ejs
@@ -165,7 +165,7 @@