Remove speed control
This commit is contained in:
110
public/app.js
110
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 = `⚠️ <strong>MKV files may not play correctly</strong> in browsers. Convert to MP4 for best results:<br><code>ffmpeg -i file.mkv -c copy file.mp4</code>`;
|
||||
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 = '<div class="chat-welcome" id="chat-welcome"><p>Welcome to the room! 👋</p></div>';
|
||||
createFileInfo.classList.add("hidden");
|
||||
@@ -766,12 +704,6 @@
|
||||
volOffIcon.classList.toggle("hidden", !videoPlayer.muted);
|
||||
});
|
||||
|
||||
// Speed
|
||||
speedSelect.addEventListener("change", () => {
|
||||
const speed = parseFloat(speedSelect.value);
|
||||
videoPlayer.playbackRate = speed;
|
||||
send({ type: "sync", action: "speed", speed });
|
||||
});
|
||||
|
||||
// Fullscreen
|
||||
fullscreenBtn.addEventListener("click", () => {
|
||||
|
||||
Reference in New Issue
Block a user