// issues: initial load fails when syncpoints match, init syncedvideos by checking actual time; unpausing video should also sync; sync max syncpoint const videos = [ { url: "vids/test.mp4", syncPoint: 10, }, { url: "vids/test.mp4", syncPoint: 60 + 10, }, { url: "vids/test.mp4", syncPoint: 120 + 10, }, ]; let videoElements = []; // Initialize the video grid function initializeVideos() { const container = document.getElementById("videoContainer"); // Set the appropriate grid class based on video count container.classList.add(`count-${videos.length}`); videos.forEach((videoConfig, index) => { const videoElement = document.createElement("video"); videoElement.src = videoConfig.url; videoElement.controls = true; videoElement.dataset.index = index; videoElement.preload = "auto"; videoElement.volume = 1 / videos.length; container.appendChild(videoElement); videoElements.push(videoElement); // Add event listeners videoElement.addEventListener("play", onPlay); videoElement.addEventListener("pause", onPause); videoElement.addEventListener("seeked", onSeek); videoElement.addEventListener("timeupdate", onTimeUpdate); videoElement.addEventListener("loadeddata", () => { console.log("Video loaded:", videoElement.dataset.index); }); }); // Initial sync when videos are loaded const minSyncVideoIndex = videos.reduce((minIndex, video, index) => { if (video.syncPoint < videos[minIndex].syncPoint) { return index; } return minIndex; }, 0); syncAllVideos( videoElements[minSyncVideoIndex], -videos[minSyncVideoIndex].syncPoint ); syncing = false; } function onPlay(event) { console.log("Received play event from", event.target.dataset.index); console.log("Playing all videos"); const relativeTime = event.target.currentTime - videos[event.target.dataset.index].syncPoint; for (const videoElement of videoElements) { if ( videoElement.currentTime < videoElement.duration - 1 && relativeTime + videos[videoElement.dataset.index].syncPoint > 0 ) { videoElement.play(); } } } function onPause(event) { console.log("Received pause event from", event.target.dataset.index); if (syncing) { console.log( "Ignoring pause event while syncing from", event.target.dataset.index ); return; } if (event.target.currentTime > event.target.duration - 1) { console.log(" Video ended naturally."); return; } else { console.log(" Pausing all videos"); for (const videoElement of videoElements) { if (videoElement === event.target) { continue; } videoElement.pause(); } } } let syncing = false; let syncedVideos = []; function onSeek(event) { console.log( "Received seeked event from", event.target.dataset.index, "at", event.target.currentTime ); if (syncing) { if (syncedVideos.includes(event.target.dataset.index)) { console.log(" Already synced"); return; } syncedVideos.push(event.target.dataset.index); if (syncedVideos.length === videoElements.length) { console.log("Syncing complete"); syncing = false; document.body.classList.remove("syncing"); } return; } else { console.log(" Received seek while not syncing, starting sync"); const syncPoint = videos[event.target.dataset.index].syncPoint; const relativeTime = event.target.currentTime - syncPoint; syncAllVideos(event.target, relativeTime); } } function syncAllVideos(sourceVideoElement, relativeTime) { syncing = true; document.body.classList.add("syncing"); syncedVideos = [sourceVideoElement.dataset.index]; console.log( "Syncing all videos from", sourceVideoElement.dataset.index, "at", sourceVideoElement.currentTime ); for (const videoElement of videoElements) { if (videoElement === sourceVideoElement) { continue; } const syncedTime = relativeTime + videos[videoElement.dataset.index].syncPoint; if (syncedTime < 0) { videoElement.currentTime = 0; videoElement.pause(); } else if ( syncedTime > videos[videoElement.dataset.index].duration - videos[videoElement.dataset.index].syncPoint ) { videoElement.currentTime = videos[videoElement.dataset.index].duration; } else { videoElement.currentTime = syncedTime; } } } function onTimeUpdate(event) { if (syncing || event.target.paused) { return; } const relativeTime = event.target.currentTime - videos[event.target.dataset.index].syncPoint; for (const videoElement of videoElements) { if ( videoElement.paused && relativeTime + videos[videoElement.dataset.index].syncPoint > 0 ) { console.log( "Reached sync point for", videoElement.dataset.index, "- playing" ); videoElement.play(); } } } // Initialize everything when the page loads document.addEventListener("DOMContentLoaded", () => { initializeVideos(); });