better sync

This commit is contained in:
2026-04-13 02:14:30 -07:00
parent 8aa84ea446
commit acb0cd3161

View File

@@ -15,14 +15,25 @@
let videoElement: HTMLVideoElement | undefined = $state();
let newUrl = $state("");
let ignoreNextEvent = false;
let isSyncing = false;
let syncTimeout: ReturnType<typeof setTimeout> | undefined;
function syncAction(fn: () => void) {
isSyncing = true;
clearTimeout(syncTimeout);
fn();
syncTimeout = setTimeout(() => {
isSyncing = false;
}, 50);
}
$effect(() => {
if (!videoElement || !videoState) return;
const el = videoElement;
const state = videoState;
if (videoElement.src !== state.url) {
videoElement.src = state.url;
if (el.src !== state.url) {
el.src = state.url;
}
// Account for server to client clock drift slightly, but MVP assumes relatively synced clocks.
@@ -31,47 +42,66 @@
? state.timePosition + Number(timeElapsedMicros) / 1_000_000
: state.timePosition;
const diff = Math.abs(videoElement.currentTime - expectedTime);
const diff = Math.abs(el.currentTime - expectedTime);
// Allow up to 2 seconds drift without snapping
if (diff > 2.0) {
ignoreNextEvent = true;
videoElement.currentTime = expectedTime;
}
if (state.isPlaying && videoElement.paused) {
ignoreNextEvent = true;
videoElement.play().catch(console.error);
} else if (!state.isPlaying && !videoElement.paused) {
ignoreNextEvent = true;
videoElement.pause();
if (!state.isPlaying) {
if (el.currentTime < expectedTime && diff <= 2 && !el.paused) {
// Behind pause point. Keep playing to catch up. (timeupdate will pause it)
} else {
// Ahead or reached pause point. Pause and rewind if needed.
syncAction(() => {
el.pause();
el.currentTime = expectedTime;
});
}
} else {
// Sync on play by unpausing slightly in future
if (el.paused) {
syncAction(() => {
el.currentTime = expectedTime;
el.play().catch(console.error);
});
} else if (diff > 2.0) {
// Allow up to 2s drift while playing
syncAction(() => {
el.currentTime = expectedTime;
});
}
}
});
function handlePlay() {
if (ignoreNextEvent) {
ignoreNextEvent = false;
return;
}
if (isSyncing) return;
playReducer({ timePosition: videoElement?.currentTime ?? 0 });
}
function handlePause() {
if (ignoreNextEvent) {
ignoreNextEvent = false;
return;
}
if (isSyncing) return;
pauseReducer({ timePosition: videoElement?.currentTime ?? 0 });
}
function handleSeeked() {
if (ignoreNextEvent) {
ignoreNextEvent = false;
return;
}
if (isSyncing) return;
seekReducer({ timePosition: videoElement?.currentTime ?? 0 });
}
function handleTimeUpdate() {
if (!videoElement || !videoState) return;
const el = videoElement;
if (!videoState.isPlaying) {
const expectedTime = videoState.timePosition;
if (!el.paused && el.currentTime >= expectedTime) {
syncAction(() => {
el.pause();
if (el.currentTime - expectedTime > 0.01) {
el.currentTime = expectedTime;
}
});
}
}
}
function handleSetUrl(e: SubmitEvent) {
e.preventDefault();
if (!newUrl.trim() || !$conn.isActive) return;
@@ -109,6 +139,7 @@
onplay={handlePlay}
onpause={handlePause}
onseeked={handleSeeked}
ontimeupdate={handleTimeUpdate}
class="w-full max-w-2xl bg-black"
>
Your browser does not support the video tag.