Remove delay between host and viewer
All checks were successful
Deploy Feature / deploy-feature (push) Successful in 26s
All checks were successful
Deploy Feature / deploy-feature (push) Successful in 26s
This commit is contained in:
65
src/App.vue
65
src/App.vue
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user