From 2ae268456724a57020821283ab7ac8801c30b8d6 Mon Sep 17 00:00:00 2001 From: Dorian Zedler Date: Fri, 13 May 2022 19:14:55 +0200 Subject: [PATCH] Fix: Timer not working on Apple devices, Sound not preloaded, Sound played multiple times --- README.md | 0 src/index.php | 2 +- src/static/js/synced-timer.js | 151 ++++++++++++++++++++++++---------- src/theme.php | 10 +-- 4 files changed, 114 insertions(+), 49 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/src/index.php b/src/index.php index 3c799c7..5d9dfeb 100644 --- a/src/index.php +++ b/src/index.php @@ -98,7 +98,7 @@ class SyncedTimer if (($this->_basepath !== '' && strpos($_SERVER['REQUEST_URI'], $this->_basepath) === false) || $_SERVER['REQUEST_URI'] === $this->_basepath) $this->_path = "/"; else - $this->_path = str_replace($this->_basepath, "", $_SERVER['REQUEST_URI']); + $this->_path = str_replace($this->_basepath, "", parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH)); } private function _redirect($path) diff --git a/src/static/js/synced-timer.js b/src/static/js/synced-timer.js index f27878f..9cc9995 100644 --- a/src/static/js/synced-timer.js +++ b/src/static/js/synced-timer.js @@ -1,9 +1,9 @@ -document.addEventListener('alpine:init' , () => { +document.addEventListener('alpine:init', () => { console.log("Synced-timer is starting") Alpine.store("siteData", { lastResponse: null, - currentData: null, + currentData: null, time: '00:00:00', header: '...', @@ -14,24 +14,24 @@ document.addEventListener('alpine:init' , () => { init() { Alpine.effect(() => { const response = this.lastResponse - if(!response) return; - - if(response.status !== 200) { + if (!response) return; + + if (response.status !== 200) { this.header = `Error: ${response.status}` return; } - + this.currentData = response.data Alpine.store("audioPlayback").isDisabledByServer = !this.currentData.soundEnabled this.header = this.currentData.header }) setInterval(() => { - if(!this.currentData) return; + if (!this.currentData) return; - const passedSeconds = parseInt(((performance.now()-this.timeOffsetToServer) / 1000) - this.currentData.startedAt); + const passedSeconds = parseInt(((performance.now() - this.timeOffsetToServer) / 1000) - this.currentData.startedAt); let remaningSeconds; - if(this.currentData.repeatEnabled) { + if (this.currentData.repeatEnabled) { remaningSeconds = parseInt(this.currentData.time * 60 - (passedSeconds % (this.currentData.time * 60))); } else { @@ -47,26 +47,38 @@ document.addEventListener('alpine:init' , () => { scheduleNextRequest() { setTimeout(() => { let requestUrl; - const requestId = Date.now(); - fetch(`/api/${username}#${requestId}`) - .then((response) => { - requestUrl = response.url - return response.json() - }) - .then((data) => { - if(data && data.status === 200) { - const performanceEntry = performance.getEntriesByName(`${requestUrl}#${requestId}`)[0]; - console.log(performanceEntry) - const requestDuration = performanceEntry.responseEnd - performanceEntry.requestStart; - this.timeOffsetToServer = performanceEntry.responseEnd - data.data.currentServerTime + (requestDuration / 2); - console.log(requestDuration, "offset: ", this.timeOffsetToServer) - } - - this.lastResponse = data - this.scheduleNextRequest() - }); + // Add a request Id to be able to identify this request later on + fetch(`/api/${username}?id=${parseInt(performance.now())}`) + .then((response) => { + requestUrl = response.url + return response.json() + }) + .then((data) => { + this.handleRequestFinished(data, requestUrl) + + this.lastResponse = data + this.scheduleNextRequest() + }); }, 1000) - } + }, + + handleRequestFinished(data, requestName) { + if (!data || data.status !== 200) return; + + // identify request using its id + const performanceEntry = performance.getEntriesByName(requestName)[0]; + if (!performanceEntry) { + // retry + console.warn("Could not find performance entry, discarding...") + return + } + + console.log(performanceEntry) + const requestDuration = performanceEntry.responseEnd - performanceEntry.requestStart; + this.timeOffsetToServer = performanceEntry.responseEnd - data.data.currentServerTime + (requestDuration / 2); + console.log(requestDuration, "offset: ", this.timeOffsetToServer) + + }, }) Alpine.store("audioPlayback", { @@ -75,13 +87,19 @@ document.addEventListener('alpine:init' , () => { isDisabledByUser: false, isDisabledByServer: true, audioPlayer: null, + replayLocked: false, + sounds: { + silence: '/static/sound/silence.mp3', + countdown: '/static/sound/countdown.mp3', + beep: '/static/sound/beep.mp3' + }, init() { this.audioPlayer = new Audio('/static/sound/silence.mp3'); this.testPermission() const audioPlaybackStore = this - + Alpine.bind('audioPlaybackEnableButton', () => ({ '@click'() { audioPlaybackStore.testPermission() @@ -95,15 +113,19 @@ document.addEventListener('alpine:init' , () => { })) Alpine.effect(() => { - if(Alpine.store("audioPlayback").isDisabledByUser) this.isAvailable = false; + if (Alpine.store("audioPlayback").isDisabledByUser) this.isAvailable = false; }) }, - + testPermission() { this.audioPlayer.src = '/static/sound/silence.mp3'; this.audioPlayer.play().then(() => { this.isAvailable = true this.hasBeenTested = true + + this.preloadSound(this.sounds.beep) + this.preloadSound(this.sounds.countdown) + }).catch((error) => { console.warn("Audio permission not granted!") this.isAvailable = false @@ -112,11 +134,41 @@ document.addEventListener('alpine:init' , () => { }, playSound(soundFile) { - if(!this.audioPlayer.paused || !this.isAvailable || this.isDisabledByUser || this.isDisabledByServer) { + if (!this.audioPlayer.paused || !this.isAvailable || this.isDisabledByUser || this.isDisabledByServer || this.replayLocked) { return; } - this.audioPlayer.src = soundFile; + if (this.audioPlayer.src !== soundFile) { + this.audioPlayer.src = soundFile; + } + this.replayLocked = true; this.audioPlayer.play(); + }, + + preloadSound(soundFile) { + if (this.audioPlayer.src === soundFile) { + return; + } + console.log(`Preloading sound ${soundFile}`) + this.audioPlayer.src = soundFile; + this.audioPlayer.load(); + }, + + playCountdown() { + this.playSound(this.sounds.countdown) + }, + preloadCountdown() { + this.preloadSound(this.sounds.countdown) + }, + + playBeep() { + this.playSound(this.sounds.beep); + }, + preloadBeep() { + this.preloadSound(this.sounds.beep); + }, + + unlockReplay() { + this.replayLocked = false; } }) @@ -127,9 +179,9 @@ document.addEventListener('alpine:init' , () => { document.documentElement.requestFullscreen(); this.enabled = true } else { - if (document.exitFullscreen) { - document.exitFullscreen(); - } + if (document.exitFullscreen) { + document.exitFullscreen(); + } } } }) @@ -159,7 +211,7 @@ function getTimerText(remaningSeconds) { var remaningMinutes = zeroPad(parseInt(remaningSeconds / 60) % 60, 2) var remaningSeconds = zeroPad(remaningSeconds % 60, 2) - if(parseInt(remaningHours) === 0) { + if (parseInt(remaningHours) === 0) { return " " + remaningMinutes + ":" + remaningSeconds + " " } @@ -167,10 +219,25 @@ function getTimerText(remaningSeconds) { } function playTimerSound(remaningSeconds) { - if(parseInt(remaningSeconds) === 5) { - Alpine.store("audioPlayback").playSound('/static/sound/countdown.mp3') + switch (parseInt(remaningSeconds)) { + case 65: + Alpine.store("audioPlayback").preloadBeep() + break; + case 60: + Alpine.store("audioPlayback").playBeep() + break; + case 59: + Alpine.store("audioPlayback").unlockReplay() + break; + + case 10: + Alpine.store("audioPlayback").preloadCountdown() + break; + case 5: + Alpine.store("audioPlayback").playCountdown() + break; + case 4: + Alpine.store("audioPlayback").unlockReplay() + break; } - else if(parseInt(remaningSeconds) === 60){ - Alpine.store("audioPlayback").playSound('/static/sound/beep.mp3') - } } \ No newline at end of file diff --git a/src/theme.php b/src/theme.php index ba93a5c..71be349 100644 --- a/src/theme.php +++ b/src/theme.php @@ -424,9 +424,7 @@ class LandingpageTheme - - - +