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
|
||||
lastAwardedTeamId: string | null
|
||||
isAnswerClip: boolean
|
||||
playbackPosition: number
|
||||
playbackCapturedAt: number
|
||||
}
|
||||
|
||||
type RealtimeMessage = {
|
||||
@@ -377,7 +379,8 @@ export default {
|
||||
syncError: '',
|
||||
isApplyingRemote: false,
|
||||
queuedRemoteState: null as RealtimeState | null,
|
||||
syncTimer: 0
|
||||
syncTimer: 0,
|
||||
playbackSyncInterval: 0
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -528,6 +531,7 @@ export default {
|
||||
this.gameIdInput = ''
|
||||
this.isHost = false
|
||||
this.syncError = ''
|
||||
this.stopHostPlaybackSync()
|
||||
this.setGameInUrl('')
|
||||
},
|
||||
async connectSession() {
|
||||
@@ -576,6 +580,7 @@ export default {
|
||||
}).catch(() => {})
|
||||
},
|
||||
buildRealtimeState(): RealtimeState {
|
||||
const player = this.getPlayer()
|
||||
return {
|
||||
step: this.step,
|
||||
teams: this.teams.map((team) => ({ ...team })),
|
||||
@@ -585,7 +590,9 @@ export default {
|
||||
currentClipUrl: this.currentClipUrl,
|
||||
currentSelectorId: this.currentSelectorId,
|
||||
lastAwardedTeamId: this.lastAwardedTeamId,
|
||||
isAnswerClip: this.isAnswerClip
|
||||
isAnswerClip: this.isAnswerClip,
|
||||
playbackPosition: player?.currentTime || 0,
|
||||
playbackCapturedAt: Date.now()
|
||||
}
|
||||
},
|
||||
publishState() {
|
||||
@@ -630,13 +637,27 @@ export default {
|
||||
this.isApplyingRemote = false
|
||||
}
|
||||
await nextTick()
|
||||
this.syncRemotePlayback(previousClipUrl !== this.currentClipUrl)
|
||||
this.syncRemotePlayback(previousClipUrl !== this.currentClipUrl, state)
|
||||
},
|
||||
getCurrentTileStatus() {
|
||||
if (!this.currentTileKey) return '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
|
||||
const player = this.getPlayer()
|
||||
if (!player) return
|
||||
@@ -647,11 +668,23 @@ export default {
|
||||
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) {
|
||||
player.currentTime = 0
|
||||
player.load()
|
||||
}
|
||||
|
||||
if (Math.abs((player.currentTime || 0) - targetTime) > 0.2) {
|
||||
this.setPlayerCurrentTime(player, targetTime)
|
||||
}
|
||||
|
||||
if (tileStatus === 'paused') {
|
||||
player.pause()
|
||||
return
|
||||
@@ -707,6 +740,7 @@ export default {
|
||||
this.tentacleLevels = this.tentacleLevels.map(() => 0)
|
||||
},
|
||||
teardownAudio() {
|
||||
this.stopHostPlaybackSync()
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId)
|
||||
this.rafId = 0
|
||||
@@ -785,9 +819,25 @@ export default {
|
||||
if (this.rafId) cancelAnimationFrame(this.rafId)
|
||||
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() {
|
||||
this.isPlaying = true
|
||||
this.startPulse()
|
||||
if (this.isHost) {
|
||||
this.startHostPlaybackSync()
|
||||
this.queueStateSync()
|
||||
}
|
||||
},
|
||||
handlePlayerPause() {
|
||||
this.isPlaying = false
|
||||
@@ -796,6 +846,10 @@ export default {
|
||||
this.rafId = 0
|
||||
}
|
||||
this.pulseLevel = 0
|
||||
if (this.isHost) {
|
||||
this.stopHostPlaybackSync()
|
||||
this.queueStateSync()
|
||||
}
|
||||
},
|
||||
togglePlayback() {
|
||||
const player = this.getPlayer()
|
||||
@@ -1017,6 +1071,7 @@ export default {
|
||||
this.teardownAudio()
|
||||
this.closeSocket()
|
||||
if (this.syncTimer) window.clearTimeout(this.syncTimer)
|
||||
if (this.playbackSyncInterval) window.clearInterval(this.playbackSyncInterval)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user