This commit is contained in:
parent
6bbfc671b3
commit
2e376269c2
28 changed files with 389 additions and 332 deletions
|
|
@ -19,11 +19,16 @@
|
|||
let currentPlaylistItemId = null; // Track playlist item ID to detect changes even with same video
|
||||
let durationReportedForItemId = null; // Track which playlist item we've reported duration for
|
||||
let lastControllerSeekTime = 0; // Debounce controller seek updates
|
||||
let lastSeekTime = 0; // Track last seek time for rate-limiting
|
||||
let leadInEndedAt = 0; // Track when lead-in ended for grace period
|
||||
let wasInLeadIn = false; // Track previous lead-in state
|
||||
|
||||
const SYNC_CHECK_INTERVAL = 1000; // Check sync every second (matches server sync interval)
|
||||
const DRIFT_THRESHOLD = 2; // Seek if drift > 2 seconds (CyTube-style tight sync)
|
||||
const MIN_SYNC_INTERVAL = 1000; // Minimum 1 second between sync checks (server pushes every 1s)
|
||||
const CONTROLLER_SEEK_DEBOUNCE = 2000; // Debounce controller seeks by 2 seconds
|
||||
const SEEK_RATE_LIMIT = 2000; // Minimum 2 seconds between seeks
|
||||
const POST_LEAD_IN_GRACE = 500; // 500ms grace period after lead-in ends
|
||||
|
||||
// Load YouTube IFrame API
|
||||
function loadYouTubeAPI() {
|
||||
|
|
@ -171,16 +176,28 @@
|
|||
// During lead-in period, let video buffer without seeking
|
||||
// Server sends leadIn=true for 3 seconds after play starts
|
||||
if (storeState.leadIn) {
|
||||
wasInLeadIn = true;
|
||||
// During lead-in, just ensure video is loading/buffering
|
||||
// Don't seek or sync position - wait for lead-in to complete
|
||||
if (shouldBePlaying && !isPlayerPlaying && !isBuffering && !hasVideoEnded) {
|
||||
ignoreStateChange = true;
|
||||
player.playVideo();
|
||||
setTimeout(() => { ignoreStateChange = false; }, 500);
|
||||
setTimeout(() => { ignoreStateChange = false; }, 1500);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Track when lead-in ends for grace period
|
||||
if (wasInLeadIn && !storeState.leadIn) {
|
||||
wasInLeadIn = false;
|
||||
leadInEndedAt = now;
|
||||
}
|
||||
|
||||
// Apply grace period after lead-in ends - don't seek immediately
|
||||
if (leadInEndedAt > 0 && (now - leadInEndedAt) < POST_LEAD_IN_GRACE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const expectedTime = watchSync.getExpectedTime();
|
||||
const currentTime = player.getCurrentTime();
|
||||
const drift = Math.abs(currentTime - expectedTime);
|
||||
|
|
@ -205,10 +222,15 @@
|
|||
}
|
||||
} else {
|
||||
// Non-controller - sync back to server time
|
||||
// Rate-limit seeks to prevent stuttering from consecutive seeks
|
||||
if (now - lastSeekTime < SEEK_RATE_LIMIT) {
|
||||
return; // Skip this seek, wait for rate limit
|
||||
}
|
||||
console.log(`Sync drift detected: ${drift.toFixed(2)}s, seeking to ${expectedTime.toFixed(2)}s`);
|
||||
ignoreStateChange = true;
|
||||
player.seekTo(expectedTime, true);
|
||||
setTimeout(() => { ignoreStateChange = false; }, 500);
|
||||
lastSeekTime = now;
|
||||
setTimeout(() => { ignoreStateChange = false; }, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -217,11 +239,11 @@
|
|||
if (shouldBePlaying && !isPlayerPlaying && !isBuffering && !hasVideoEnded) {
|
||||
ignoreStateChange = true;
|
||||
player.playVideo();
|
||||
setTimeout(() => { ignoreStateChange = false; }, 500);
|
||||
setTimeout(() => { ignoreStateChange = false; }, 1500);
|
||||
} else if (!shouldBePlaying && isPlayerPlaying) {
|
||||
ignoreStateChange = true;
|
||||
player.pauseVideo();
|
||||
setTimeout(() => { ignoreStateChange = false; }, 500);
|
||||
setTimeout(() => { ignoreStateChange = false; }, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -245,11 +267,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
// React to state changes from the store
|
||||
$: if (playerReady && player && $watchSync.serverTime > lastSyncTime) {
|
||||
lastSyncTime = $watchSync.serverTime;
|
||||
checkAndSync();
|
||||
}
|
||||
// Note: Sync is handled by the interval at onPlayerReady (line 99)
|
||||
// We don't trigger sync on every serverTime update to avoid double-syncing
|
||||
// which causes stuttering. The interval runs every 1 second which is sufficient.
|
||||
|
||||
// Handle window event for state changes from other users
|
||||
function handleStateChange(event) {
|
||||
|
|
@ -270,6 +290,11 @@
|
|||
} else if (action === 'skip' || action === 'video_changed') {
|
||||
// Video change will be handled by the reactive statement above
|
||||
setTimeout(() => checkAndSync(true), 1000);
|
||||
} else if (action === 'locked_restart' || action === 'repeat') {
|
||||
// Locked video loop - seek to beginning and play
|
||||
player.seekTo(0, true);
|
||||
player.playVideo();
|
||||
setTimeout(() => checkAndSync(true), 1000);
|
||||
}
|
||||
|
||||
setTimeout(() => { ignoreStateChange = false; }, 1000);
|
||||
|
|
@ -280,7 +305,7 @@
|
|||
if (playerReady && player) {
|
||||
ignoreStateChange = true;
|
||||
player.seekTo(time, true);
|
||||
setTimeout(() => { ignoreStateChange = false; }, 500);
|
||||
setTimeout(() => { ignoreStateChange = false; }, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue