Remove delay between host and viewer
All checks were successful
Deploy Feature / deploy-feature (push) Successful in 26s

This commit is contained in:
Johnny322
2026-02-24 21:52:07 +01:00
parent a16d340cb7
commit 7c12d25367

View File

@@ -316,6 +316,8 @@ type RealtimeState = {
currentSelectorId: string | null currentSelectorId: string | null
lastAwardedTeamId: string | null lastAwardedTeamId: string | null
isAnswerClip: boolean isAnswerClip: boolean
playbackPosition: number
playbackCapturedAt: number
} }
type RealtimeMessage = { type RealtimeMessage = {
@@ -377,7 +379,8 @@ export default {
syncError: '', syncError: '',
isApplyingRemote: false, isApplyingRemote: false,
queuedRemoteState: null as RealtimeState | null, queuedRemoteState: null as RealtimeState | null,
syncTimer: 0 syncTimer: 0,
playbackSyncInterval: 0
} }
}, },
async mounted() { async mounted() {
@@ -528,6 +531,7 @@ export default {
this.gameIdInput = '' this.gameIdInput = ''
this.isHost = false this.isHost = false
this.syncError = '' this.syncError = ''
this.stopHostPlaybackSync()
this.setGameInUrl('') this.setGameInUrl('')
}, },
async connectSession() { async connectSession() {
@@ -576,6 +580,7 @@ export default {
}).catch(() => {}) }).catch(() => {})
}, },
buildRealtimeState(): RealtimeState { buildRealtimeState(): RealtimeState {
const player = this.getPlayer()
return { return {
step: this.step, step: this.step,
teams: this.teams.map((team) => ({ ...team })), teams: this.teams.map((team) => ({ ...team })),
@@ -585,7 +590,9 @@ export default {
currentClipUrl: this.currentClipUrl, currentClipUrl: this.currentClipUrl,
currentSelectorId: this.currentSelectorId, currentSelectorId: this.currentSelectorId,
lastAwardedTeamId: this.lastAwardedTeamId, lastAwardedTeamId: this.lastAwardedTeamId,
isAnswerClip: this.isAnswerClip isAnswerClip: this.isAnswerClip,
playbackPosition: player?.currentTime || 0,
playbackCapturedAt: Date.now()
} }
}, },
publishState() { publishState() {
@@ -630,13 +637,27 @@ export default {
this.isApplyingRemote = false this.isApplyingRemote = false
} }
await nextTick() await nextTick()
this.syncRemotePlayback(previousClipUrl !== this.currentClipUrl) this.syncRemotePlayback(previousClipUrl !== this.currentClipUrl, state)
}, },
getCurrentTileStatus() { getCurrentTileStatus() {
if (!this.currentTileKey) return 'available' if (!this.currentTileKey) return 'available'
return this.tiles[this.currentTileKey]?.status || 'available' return this.tiles[this.currentTileKey]?.status || 'available'
}, },
syncRemotePlayback(forceReload: boolean) { setPlayerCurrentTime(player: HTMLAudioElement, targetTime: number) {
const seek = () => {
try {
player.currentTime = targetTime
} catch {
// ignore seek failures on unsupported ranges
}
}
if (player.readyState > 0) {
seek()
return
}
player.addEventListener('loadedmetadata', seek, { once: true })
},
syncRemotePlayback(forceReload: boolean, state: RealtimeState) {
if (this.canControlGame) return if (this.canControlGame) return
const player = this.getPlayer() const player = this.getPlayer()
if (!player) return if (!player) return
@@ -647,11 +668,23 @@ export default {
return return
} }
const elapsedSeconds = Math.max(0, (Date.now() - (state.playbackCapturedAt || Date.now())) / 1000)
let targetTime = state.playbackPosition || 0
if (tileStatus === 'playing' || tileStatus === 'guessed') {
targetTime += elapsedSeconds
}
if (Number.isFinite(player.duration) && player.duration > 0) {
targetTime = Math.min(targetTime, Math.max(0, player.duration - 0.05))
}
if (forceReload) { if (forceReload) {
player.currentTime = 0
player.load() player.load()
} }
if (Math.abs((player.currentTime || 0) - targetTime) > 0.2) {
this.setPlayerCurrentTime(player, targetTime)
}
if (tileStatus === 'paused') { if (tileStatus === 'paused') {
player.pause() player.pause()
return return
@@ -707,6 +740,7 @@ export default {
this.tentacleLevels = this.tentacleLevels.map(() => 0) this.tentacleLevels = this.tentacleLevels.map(() => 0)
}, },
teardownAudio() { teardownAudio() {
this.stopHostPlaybackSync()
if (this.rafId) { if (this.rafId) {
cancelAnimationFrame(this.rafId) cancelAnimationFrame(this.rafId)
this.rafId = 0 this.rafId = 0
@@ -785,9 +819,25 @@ export default {
if (this.rafId) cancelAnimationFrame(this.rafId) if (this.rafId) cancelAnimationFrame(this.rafId)
this.rafId = requestAnimationFrame(tick) this.rafId = requestAnimationFrame(tick)
}, },
startHostPlaybackSync() {
if (!this.isHost || !this.socketConnected || !this.socket) return
this.stopHostPlaybackSync()
this.playbackSyncInterval = window.setInterval(() => {
this.queueStateSync()
}, 400)
},
stopHostPlaybackSync() {
if (!this.playbackSyncInterval) return
window.clearInterval(this.playbackSyncInterval)
this.playbackSyncInterval = 0
},
handlePlayerPlay() { handlePlayerPlay() {
this.isPlaying = true this.isPlaying = true
this.startPulse() this.startPulse()
if (this.isHost) {
this.startHostPlaybackSync()
this.queueStateSync()
}
}, },
handlePlayerPause() { handlePlayerPause() {
this.isPlaying = false this.isPlaying = false
@@ -796,6 +846,10 @@ export default {
this.rafId = 0 this.rafId = 0
} }
this.pulseLevel = 0 this.pulseLevel = 0
if (this.isHost) {
this.stopHostPlaybackSync()
this.queueStateSync()
}
}, },
togglePlayback() { togglePlayback() {
const player = this.getPlayer() const player = this.getPlayer()
@@ -1017,6 +1071,7 @@ export default {
this.teardownAudio() this.teardownAudio()
this.closeSocket() this.closeSocket()
if (this.syncTimer) window.clearTimeout(this.syncTimer) if (this.syncTimer) window.clearTimeout(this.syncTimer)
if (this.playbackSyncInterval) window.clearInterval(this.playbackSyncInterval)
} }
} }
</script> </script>