diff --git a/public/app.js b/public/app.js
index 2100891..3984556 100644
--- a/public/app.js
+++ b/public/app.js
@@ -10,8 +10,6 @@
let joinFile = null; // File object for the joiner
let ignoreSync = false; // flag to avoid feedback loops
let syncTimeout = null;
- let driftInterval = null;
- let serverState = { playing: false, position: 0, speed: 1 };
// Reconnection state
let reconnectAttempts = 0;
@@ -73,7 +71,6 @@
const volumeSlider = $("volume-slider");
const currentTimeEl = $("current-time");
const durationEl = $("duration");
- const speedSelect = $("speed-select");
const fullscreenBtn = $("fullscreen-btn");
const userCountEl = $("user-count");
const usersList = $("users-list");
@@ -243,7 +240,6 @@
applySync(msg.state);
}
addSystemMessage("Reconnected");
- startDriftCorrection();
flushMessageQueue();
break;
@@ -298,82 +294,22 @@
// Load local file into video player
const file = localFile || joinFile;
if (file) {
- loadVideoSource(file);
+ if (currentBlobUrl) URL.revokeObjectURL(currentBlobUrl);
+ currentBlobUrl = URL.createObjectURL(file);
+ videoPlayer.src = currentBlobUrl;
}
- // --- Handle false "ended" events (Chromium MKV blob bug) ---
- // Chrome can't properly index MKV containers loaded via blob URLs.
- // After ~30s it may jump to the end and fire "ended" even though
- // the video isn't actually over. Recovery: reload the blob and seek.
- let recoveryAttempts = 0;
- const MAX_RECOVERY_ATTEMPTS = 5;
-
- videoPlayer.addEventListener("ended", () => {
- const duration = videoPlayer.duration || 0;
- // Only attempt recovery if we have server state showing we're not near the end
- if (duration > 0 && lastServerState.position < duration - 5 && recoveryAttempts < MAX_RECOVERY_ATTEMPTS) {
- recoveryAttempts++;
- console.log(`[MKV-RECOVERY] False ended detected. Server pos=${lastServerState.position.toFixed(1)}, duration=${duration.toFixed(1)}. Reloading source (attempt ${recoveryAttempts}/${MAX_RECOVERY_ATTEMPTS})`);
-
- const targetPos = lastServerState.position;
- const wasPlaying = lastServerState.playing;
- const currentFile = localFile || joinFile;
-
- if (currentFile) {
- // Reload the video with a fresh blob URL
- loadVideoSource(currentFile, targetPos, wasPlaying);
- }
- }
- });
-
- // Reset recovery counter when video plays successfully for a while
- let recoveryResetTimer = null;
- videoPlayer.addEventListener("timeupdate", () => {
- if (recoveryAttempts > 0) {
- clearTimeout(recoveryResetTimer);
- recoveryResetTimer = setTimeout(() => {
- recoveryAttempts = 0;
- }, 10000); // Reset after 10s of successful playback
- }
- });
-
// --- Resync on tab focus (handles background tab throttling) ---
document.addEventListener("visibilitychange", () => {
if (!document.hidden && roomCode && ws && ws.readyState === WebSocket.OPEN) {
- console.log("[SYNC] Tab became visible, requesting state resync");
send({ type: "request_state" });
}
});
-
- // Start drift correction
- startDriftCorrection();
}
- // --- Video Source Loading ---
+ // --- State tracking ---
let currentBlobUrl = null;
- let lastServerState = { playing: false, position: 0, speed: 1 };
-
- function loadVideoSource(file, seekTo, shouldPlay) {
- // Revoke old blob URL to free memory
- if (currentBlobUrl) {
- URL.revokeObjectURL(currentBlobUrl);
- }
- currentBlobUrl = URL.createObjectURL(file);
- videoPlayer.src = currentBlobUrl;
-
- if (seekTo !== undefined) {
- videoPlayer.addEventListener("loadedmetadata", function onMeta() {
- videoPlayer.removeEventListener("loadedmetadata", onMeta);
- videoPlayer.currentTime = seekTo;
- if (shouldPlay) {
- videoPlayer.play().then(() => {
- videoWrapper.classList.add("playing");
- updatePlayPauseIcon();
- }).catch(() => {});
- }
- });
- }
- }
+ let lastServerState = { playing: false, position: 0 };
// --- File Check Modal ---
let pendingRoomFileInfo = null;
@@ -459,12 +395,7 @@
// Track latest server state for recovery
if (data.position !== undefined) lastServerState.position = data.position;
if (data.playing !== undefined) lastServerState.playing = data.playing;
- if (data.speed !== undefined) lastServerState.speed = data.speed;
- if (data.speed !== undefined && videoPlayer.playbackRate !== data.speed) {
- videoPlayer.playbackRate = data.speed;
- speedSelect.value = String(data.speed);
- }
if (data.position !== undefined) {
const diff = Math.abs(videoPlayer.currentTime - data.position);
@@ -510,14 +441,7 @@
}
}
- function startDriftCorrection() {
- if (driftInterval) clearInterval(driftInterval);
- driftInterval = setInterval(() => {
- if (ws && ws.readyState === WebSocket.OPEN) {
- send({ type: "request_state" });
- }
- }, 5000);
- }
+
// --- Get video duration from a file (used for file info) ---
function getVideoDuration(file) {
@@ -544,6 +468,17 @@
container.classList.remove("hidden");
}
+ function isMkvFile(file) {
+ return file.name.toLowerCase().endsWith(".mkv");
+ }
+
+ function showFormatWarning(container) {
+ const warning = document.createElement("div");
+ warning.className = "format-warning";
+ warning.innerHTML = `⚠️ MKV files may not play correctly in browsers. Convert to MP4 for best results:ffmpeg -i file.mkv -c copy file.mp4`;
+ container.parentNode.insertBefore(warning, container.nextSibling);
+ }
+
// ===== EVENT LISTENERS =====
// --- Lobby: Username ---
@@ -560,7 +495,11 @@
localFile = file;
const duration = await getVideoDuration(file);
localFile._duration = duration;
+ // Remove any previous format warning
+ const oldWarning = createFileInfo.parentNode.querySelector(".format-warning");
+ if (oldWarning) oldWarning.remove();
renderFileInfo(createFileInfo, file, duration);
+ if (isMkvFile(file)) showFormatWarning(createFileInfo);
createRoomBtn.disabled = !usernameInput.value.trim();
});
@@ -679,7 +618,6 @@
joinFile = null;
roomFileInfo = null;
messageQueue = [];
- if (driftInterval) clearInterval(driftInterval);
videoPlayer.src = "";
chatMessages.innerHTML = '
Welcome to the room! 👋