Fix: Timer not working on Apple devices, Sound not preloaded, Sound

played multiple times
This commit is contained in:
Dorian Zedler 2022-05-13 19:14:55 +02:00
parent f418bb8543
commit 2ae2684567
Signed by: dorian
GPG key ID: 989DE36109AFA354
4 changed files with 114 additions and 49 deletions

0
README.md Normal file
View file

View file

@ -98,7 +98,7 @@ class SyncedTimer
if (($this->_basepath !== '' && strpos($_SERVER['REQUEST_URI'], $this->_basepath) === false) || $_SERVER['REQUEST_URI'] === $this->_basepath) if (($this->_basepath !== '' && strpos($_SERVER['REQUEST_URI'], $this->_basepath) === false) || $_SERVER['REQUEST_URI'] === $this->_basepath)
$this->_path = "/"; $this->_path = "/";
else 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) private function _redirect($path)

View file

@ -1,9 +1,9 @@
document.addEventListener('alpine:init' , () => { document.addEventListener('alpine:init', () => {
console.log("Synced-timer is starting") console.log("Synced-timer is starting")
Alpine.store("siteData", { Alpine.store("siteData", {
lastResponse: null, lastResponse: null,
currentData: null, currentData: null,
time: '00:00:00', time: '00:00:00',
header: '...', header: '...',
@ -14,24 +14,24 @@ document.addEventListener('alpine:init' , () => {
init() { init() {
Alpine.effect(() => { Alpine.effect(() => {
const response = this.lastResponse const response = this.lastResponse
if(!response) return; if (!response) return;
if(response.status !== 200) { if (response.status !== 200) {
this.header = `Error: ${response.status}` this.header = `Error: ${response.status}`
return; return;
} }
this.currentData = response.data this.currentData = response.data
Alpine.store("audioPlayback").isDisabledByServer = !this.currentData.soundEnabled Alpine.store("audioPlayback").isDisabledByServer = !this.currentData.soundEnabled
this.header = this.currentData.header this.header = this.currentData.header
}) })
setInterval(() => { 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; let remaningSeconds;
if(this.currentData.repeatEnabled) { if (this.currentData.repeatEnabled) {
remaningSeconds = parseInt(this.currentData.time * 60 - (passedSeconds % (this.currentData.time * 60))); remaningSeconds = parseInt(this.currentData.time * 60 - (passedSeconds % (this.currentData.time * 60)));
} }
else { else {
@ -47,26 +47,38 @@ document.addEventListener('alpine:init' , () => {
scheduleNextRequest() { scheduleNextRequest() {
setTimeout(() => { setTimeout(() => {
let requestUrl; let requestUrl;
const requestId = Date.now(); // Add a request Id to be able to identify this request later on
fetch(`/api/${username}#${requestId}`) fetch(`/api/${username}?id=${parseInt(performance.now())}`)
.then((response) => { .then((response) => {
requestUrl = response.url requestUrl = response.url
return response.json() return response.json()
}) })
.then((data) => { .then((data) => {
if(data && data.status === 200) { this.handleRequestFinished(data, requestUrl)
const performanceEntry = performance.getEntriesByName(`${requestUrl}#${requestId}`)[0];
console.log(performanceEntry) this.lastResponse = data
const requestDuration = performanceEntry.responseEnd - performanceEntry.requestStart; this.scheduleNextRequest()
this.timeOffsetToServer = performanceEntry.responseEnd - data.data.currentServerTime + (requestDuration / 2); });
console.log(requestDuration, "offset: ", this.timeOffsetToServer)
}
this.lastResponse = data
this.scheduleNextRequest()
});
}, 1000) }, 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", { Alpine.store("audioPlayback", {
@ -75,13 +87,19 @@ document.addEventListener('alpine:init' , () => {
isDisabledByUser: false, isDisabledByUser: false,
isDisabledByServer: true, isDisabledByServer: true,
audioPlayer: null, audioPlayer: null,
replayLocked: false,
sounds: {
silence: '/static/sound/silence.mp3',
countdown: '/static/sound/countdown.mp3',
beep: '/static/sound/beep.mp3'
},
init() { init() {
this.audioPlayer = new Audio('/static/sound/silence.mp3'); this.audioPlayer = new Audio('/static/sound/silence.mp3');
this.testPermission() this.testPermission()
const audioPlaybackStore = this const audioPlaybackStore = this
Alpine.bind('audioPlaybackEnableButton', () => ({ Alpine.bind('audioPlaybackEnableButton', () => ({
'@click'() { '@click'() {
audioPlaybackStore.testPermission() audioPlaybackStore.testPermission()
@ -95,15 +113,19 @@ document.addEventListener('alpine:init' , () => {
})) }))
Alpine.effect(() => { Alpine.effect(() => {
if(Alpine.store("audioPlayback").isDisabledByUser) this.isAvailable = false; if (Alpine.store("audioPlayback").isDisabledByUser) this.isAvailable = false;
}) })
}, },
testPermission() { testPermission() {
this.audioPlayer.src = '/static/sound/silence.mp3'; this.audioPlayer.src = '/static/sound/silence.mp3';
this.audioPlayer.play().then(() => { this.audioPlayer.play().then(() => {
this.isAvailable = true this.isAvailable = true
this.hasBeenTested = true this.hasBeenTested = true
this.preloadSound(this.sounds.beep)
this.preloadSound(this.sounds.countdown)
}).catch((error) => { }).catch((error) => {
console.warn("Audio permission not granted!") console.warn("Audio permission not granted!")
this.isAvailable = false this.isAvailable = false
@ -112,11 +134,41 @@ document.addEventListener('alpine:init' , () => {
}, },
playSound(soundFile) { 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; return;
} }
this.audioPlayer.src = soundFile; if (this.audioPlayer.src !== soundFile) {
this.audioPlayer.src = soundFile;
}
this.replayLocked = true;
this.audioPlayer.play(); 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(); document.documentElement.requestFullscreen();
this.enabled = true this.enabled = true
} else { } else {
if (document.exitFullscreen) { if (document.exitFullscreen) {
document.exitFullscreen(); document.exitFullscreen();
} }
} }
} }
}) })
@ -159,7 +211,7 @@ function getTimerText(remaningSeconds) {
var remaningMinutes = zeroPad(parseInt(remaningSeconds / 60) % 60, 2) var remaningMinutes = zeroPad(parseInt(remaningSeconds / 60) % 60, 2)
var remaningSeconds = zeroPad(remaningSeconds % 60, 2) var remaningSeconds = zeroPad(remaningSeconds % 60, 2)
if(parseInt(remaningHours) === 0) { if (parseInt(remaningHours) === 0) {
return " " + remaningMinutes + ":" + remaningSeconds + " " return " " + remaningMinutes + ":" + remaningSeconds + " "
} }
@ -167,10 +219,25 @@ function getTimerText(remaningSeconds) {
} }
function playTimerSound(remaningSeconds) { function playTimerSound(remaningSeconds) {
if(parseInt(remaningSeconds) === 5) { switch (parseInt(remaningSeconds)) {
Alpine.store("audioPlayback").playSound('/static/sound/countdown.mp3') 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')
}
} }

View file

@ -424,9 +424,7 @@ class LandingpageTheme
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
<!-- JavaScript --> <!-- JavaScript -->
<script src="/static/js/lib/apline.min.js" defer></script> <script src="/static/js/lib/alpine.min.js" defer></script>
<script src="/static/js/lib/purify.min.js" defer></script>
<script src="/static/js/synced-timer.js" defer></script>
<style> <style>
:root { :root {
@ -510,10 +508,10 @@ class LandingpageTheme
}) })
} }
} }
</script>
<script src="/static/js/lib/alpine.min.js" defer></script>
<script src="/static/js/synced-timer.js"></script> <script src="/static/js/synced-timer.js"></script>
</script>
</html> </html>
<?php <?php