From 3fa5ec7146dabc78efacc5a215a5897dff22449e Mon Sep 17 00:00:00 2001 From: Aaron Meese Date: Mon, 16 Sep 2024 18:51:31 -0400 Subject: [PATCH] feat: moved audio to web worker Now sounds will still play when the tab is out of focus. --- website/index.html | 1 + website/js/app.js | 94 +++++++++++++++++++++++++++++---------- website/js/audioWorker.js | 37 +++++++++++++++ 3 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 website/js/audioWorker.js diff --git a/website/index.html b/website/index.html index 15606c9..bf152bc 100644 --- a/website/index.html +++ b/website/index.html @@ -43,6 +43,7 @@

Settings

+ diff --git a/website/js/app.js b/website/js/app.js index 1fdec3d..637f607 100644 --- a/website/js/app.js +++ b/website/js/app.js @@ -13,6 +13,9 @@ document.addEventListener("DOMContentLoaded", () => { this.themeToggle = document.getElementById("themeToggle"); this.timeFormatToggle = document.getElementById("timeFormatToggle"); this.use24HourFormat = false; + this.lastUpdateTime = Date.now(); + this.audioWorker = null; + this.audioContext = null; this.setupEventListeners(); this.start(); @@ -42,40 +45,40 @@ document.addEventListener("DOMContentLoaded", () => { } start() { + this.lastUpdateTime = Date.now(); + this.updateDisplay(); + } + + updateDisplay() { this.update(); - this.updateInterval = setInterval(() => this.update(), 1000); + this.animationFrame = requestAnimationFrame(() => this.updateDisplay()); } update() { const now = new Date(); - let hours = now.getHours(); - const minutes = now.getMinutes().toString().padStart(2, "0"); - const seconds = now.getSeconds().toString().padStart(2, "0"); - - if (!this.use24HourFormat) { - hours = hours % 12 || 12; - // Remove leading zero for 12-hour format, except for 12 (noon/midnight) - hours = hours === 12 ? "12" : hours.toString().padStart(1, " "); - } else { - hours = hours.toString().padStart(2, "0"); - } + const currentTime = now.getTime(); + const elapsedTime = currentTime - this.lastUpdateTime; - this.clockElement.textContent = `${hours}:${minutes}:${seconds}`; + if (elapsedTime >= 1000) { + this.lastUpdateTime = currentTime; - const currentMinutes = now.getHours() * 60 + now.getMinutes(); - const currentSeconds = now.getSeconds(); + let hours = now.getHours(); + const minutes = now.getMinutes().toString().padStart(2, "0"); + const seconds = now.getSeconds().toString().padStart(2, "0"); - if (currentSeconds === 0 && (currentMinutes % this.updateInterval === 0) && currentMinutes !== this.lastUpdate) { - this.lastUpdate = currentMinutes; - this.speakTime(hours, minutes); - } else { - this.playTickSound(); + if (!this.use24HourFormat) { + hours = hours % 12 || 12; + hours = hours === 12 ? "12" : hours.toString().padStart(1, " "); + } else { + hours = hours.toString().padStart(2, "0"); + } + + this.clockElement.textContent = `${hours}:${minutes}:${seconds}`; } } playTickSound() { - const now = new Date(); - if (now.getSeconds() === 0 && this.audioEnabled && this.tickSound && this.audioContext && this.audioContext.state === "running") { + if (this.audioEnabled && this.tickSound && this.audioContext && this.audioContext.state === "running") { const source = this.audioContext.createBufferSource(); source.buffer = this.tickSound; source.connect(this.audioContext.destination); @@ -143,6 +146,7 @@ document.addEventListener("DOMContentLoaded", () => { toggleAudio() { this.audioEnabled = !this.audioEnabled; + localStorage.setItem("audioEnabled", this.audioEnabled); this.audioToggle.textContent = this.audioEnabled ? "🔊" : "🔇"; if (this.audioEnabled) { if (!this.audioContext) { @@ -153,19 +157,42 @@ document.addEventListener("DOMContentLoaded", () => { if (!this.tickSound) { this.loadTickSound(); } + this.initAudioWorker(); + this.audioWorker.postMessage({ type: "start" }); }); } else { + if (this.audioWorker) { + this.audioWorker.postMessage({ type: "stop" }); + } if (this.audioContext) { this.audioContext.suspend(); } } } + initAudioWorker() { + if (typeof(Worker) !== "undefined") { + if (!this.audioWorker) { + this.audioWorker = new Worker("./js/audioWorker.js"); + this.audioWorker.onmessage = (e) => { + if (e.data.type === "tick") { + this.playTickSound(); + } else if (e.data.type === "update") { + const now = e.data.time; + this.speakTime(now.getHours(), now.getMinutes()); + } + }; + } + } else { + console.error("Web Workers are not supported in this browser."); + } + } + handleVisibilityChange() { if (document.hidden) { - clearInterval(this.updateInterval); + cancelAnimationFrame(this.animationFrame); } else { - this.updateInterval = setInterval(() => this.update(), 1000); + this.updateDisplay(); } } @@ -173,6 +200,10 @@ document.addEventListener("DOMContentLoaded", () => { const newInterval = parseInt(this.updateIntervalInput.value); if (newInterval >= 1 && newInterval <= 60) { this.updateInterval = newInterval; + localStorage.setItem("updateInterval", this.updateInterval); + if (this.audioWorker) { + this.audioWorker.postMessage({ type: "setUpdateInterval", interval: newInterval }); + } console.debug(`Update interval set to ${this.updateInterval} minutes`); this.closeSettingsModal(); } else { @@ -220,6 +251,21 @@ document.addEventListener("DOMContentLoaded", () => { const savedTimeFormat = localStorage.getItem("timeFormat") || "12"; this.timeFormatToggle.value = savedTimeFormat; this.switchTimeFormat(); + + const savedInterval = localStorage.getItem("updateInterval"); + if (savedInterval) { + this.updateInterval = parseInt(savedInterval); + this.updateIntervalInput.value = this.updateInterval; + } + + const savedAudioState = localStorage.getItem("audioEnabled"); + if (savedAudioState) { + this.audioEnabled = savedAudioState === "true"; + this.audioToggle.textContent = this.audioEnabled ? "🔊" : "🔇"; + if (this.audioEnabled) { + this.toggleAudio(); + } + } } } diff --git a/website/js/audioWorker.js b/website/js/audioWorker.js new file mode 100644 index 0000000..bf4f464 --- /dev/null +++ b/website/js/audioWorker.js @@ -0,0 +1,37 @@ +let intervalId; +let updateInterval = 15; + +self.onmessage = function(e) { + if (e.data.type === "start") { + startWorker(); + } else if (e.data.type === "stop") { + stopWorker(); + } else if (e.data.type === "setUpdateInterval") { + updateInterval = e.data.interval; + } +}; + +function startWorker() { + intervalId = setInterval(checkTime, 1000); +} + +function stopWorker() { + clearInterval(intervalId); +} + +function checkTime() { + const now = new Date(); + const minutes = now.getMinutes(); + const seconds = now.getSeconds(); + + if (seconds === 0) { + self.postMessage({ type: "tick" }); + + // Calculate the next update time + const nextUpdateMinute = Math.ceil(minutes / updateInterval) * updateInterval; + + if (minutes === nextUpdateMinute % 60) { + self.postMessage({ type: "update", time: now }); + } + } +}