224 lines
No EOL
8.1 KiB
JavaScript
224 lines
No EOL
8.1 KiB
JavaScript
document.addEventListener("DOMContentLoaded", async () => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const room = params.get("room");
|
|
const username = localStorage.getItem("username");
|
|
const userKey = localStorage.getItem("userKey");
|
|
|
|
if (!room || !username || !userKey) {
|
|
alert("Missing room or user credentials.");
|
|
window.location.href = `/index.html${room ? `?room=${encodeURIComponent(room)}&watchparty=1` : ""}`;
|
|
return;
|
|
}
|
|
|
|
// Note: We rely on chat.js (loaded in the HTML) to handle the server-side
|
|
// /validate call and the rendering of the #room-title header.
|
|
// This script focuses solely on the video synchronization logic.
|
|
|
|
const videoControls = document.getElementById("video-controls");
|
|
const videoInput = document.getElementById("video-url-input");
|
|
const videoButton = document.getElementById("start-video-button");
|
|
const pauseButton = document.getElementById("pause-video-button");
|
|
const resumeButton = document.getElementById("resume-video-button");
|
|
const clearButton = document.getElementById("clear-video-button");
|
|
const iframe = document.getElementById("video-iframe");
|
|
const pausedOverlay = document.getElementById("paused-overlay");
|
|
|
|
let videoExpireAt = null;
|
|
let videoStarted = false;
|
|
let lastVideoId = null;
|
|
let isRoomOwner = false;
|
|
|
|
function extractYouTubeId(url) {
|
|
const match = url.match(/(?:youtube\.com\/.*v=|youtu\.be\/)([\w-]{11})/);
|
|
return match ? match[1] : null;
|
|
}
|
|
|
|
async function fetchYouTubeDuration(videoId) {
|
|
return 24 * 60 * 60; // 24 hours in seconds
|
|
}
|
|
|
|
function loadYouTubeVideo(videoId, startSeconds = 0) {
|
|
if (lastVideoId === videoId) return;
|
|
iframe.src = `https://www.youtube.com/embed/${videoId}?autoplay=1&start=${startSeconds}`;
|
|
iframe.classList.remove("hidden");
|
|
lastVideoId = videoId;
|
|
}
|
|
|
|
function clearVideoUI() {
|
|
iframe.src = "";
|
|
iframe.classList.add("hidden");
|
|
videoExpireAt = null;
|
|
videoStarted = false;
|
|
lastVideoId = null;
|
|
pausedOverlay.classList.add("hidden");
|
|
updateControlVisibility({ hasVideo: false, isPaused: false, isOwner: isRoomOwner });
|
|
}
|
|
|
|
function updateControlVisibility({ hasVideo, isPaused, isOwner }) {
|
|
if (isOwner) {
|
|
if (hasVideo) {
|
|
videoInput.classList.add("hidden");
|
|
videoButton.classList.add("hidden");
|
|
clearButton.classList.remove("hidden");
|
|
|
|
if (isPaused) {
|
|
pauseButton.classList.add("hidden");
|
|
resumeButton.classList.remove("hidden");
|
|
} else {
|
|
pauseButton.classList.remove("hidden");
|
|
resumeButton.classList.add("hidden");
|
|
}
|
|
} else {
|
|
videoInput.classList.remove("hidden");
|
|
videoButton.classList.remove("hidden");
|
|
clearButton.classList.add("hidden");
|
|
pauseButton.classList.add("hidden");
|
|
resumeButton.classList.add("hidden");
|
|
}
|
|
videoControls.classList.remove("hidden");
|
|
} else {
|
|
videoControls.classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
async function checkRoomOwnership() {
|
|
try {
|
|
const res = await fetch(`/chat/${encodeURIComponent(room)}/users`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username }),
|
|
});
|
|
const data = await res.json();
|
|
isRoomOwner = data.owner && data.owner === username;
|
|
} catch (err) {
|
|
console.error("Failed to determine ownership", err);
|
|
isRoomOwner = false;
|
|
}
|
|
}
|
|
|
|
videoButton.addEventListener("click", async () => {
|
|
const url = videoInput.value.trim();
|
|
const videoId = extractYouTubeId(url);
|
|
if (!videoId) return alert("Invalid YouTube URL.");
|
|
|
|
await fetch(`/watchparty/${encodeURIComponent(room)}/video`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ url }),
|
|
});
|
|
|
|
const duration = await fetchYouTubeDuration(videoId);
|
|
videoExpireAt = Date.now() + duration * 1000;
|
|
videoStarted = true;
|
|
|
|
loadYouTubeVideo(videoId);
|
|
updateControlVisibility({ hasVideo: true, isPaused: false, isOwner: isRoomOwner });
|
|
});
|
|
|
|
pauseButton.addEventListener("click", async () => {
|
|
try {
|
|
await fetch(`/watchparty/${encodeURIComponent(room)}/video/pause`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username }),
|
|
});
|
|
} catch (err) {
|
|
console.error("Failed to pause video", err);
|
|
}
|
|
});
|
|
|
|
resumeButton.addEventListener("click", async () => {
|
|
try {
|
|
await fetch(`/watchparty/${encodeURIComponent(room)}/video/resume`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username }),
|
|
});
|
|
} catch (err) {
|
|
console.error("Failed to resume video", err);
|
|
}
|
|
});
|
|
|
|
clearButton.addEventListener("click", async () => {
|
|
try {
|
|
await fetch(`/watchparty/${encodeURIComponent(room)}/video/clear`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username }),
|
|
});
|
|
|
|
clearVideoUI();
|
|
} catch (err) {
|
|
console.error("Failed to clear video", err);
|
|
}
|
|
});
|
|
|
|
async function syncVideoFromServer() {
|
|
try {
|
|
const res = await fetch(`/watchparty/${encodeURIComponent(room)}/video`);
|
|
if (!res.ok) throw new Error("video fetch failed");
|
|
|
|
const { url, elapsed, is_paused } = await res.json();
|
|
const videoId = extractYouTubeId(url);
|
|
if (!videoId) {
|
|
clearVideoUI();
|
|
return;
|
|
}
|
|
|
|
if (is_paused) {
|
|
if (!iframe.classList.contains("hidden")) {
|
|
iframe.classList.add("hidden");
|
|
iframe.src = ""; // Stop audio
|
|
pausedOverlay.classList.remove("hidden");
|
|
lastVideoId = null; // Force reload on resume
|
|
}
|
|
videoStarted = true;
|
|
updateControlVisibility({ hasVideo: true, isPaused: true, isOwner: isRoomOwner });
|
|
return;
|
|
}
|
|
|
|
pausedOverlay.classList.add("hidden");
|
|
const duration = await fetchYouTubeDuration(videoId);
|
|
videoExpireAt = Date.now() + ((duration - elapsed) * 1000);
|
|
videoStarted = true;
|
|
|
|
loadYouTubeVideo(videoId, elapsed);
|
|
updateControlVisibility({ hasVideo: true, isPaused: false, isOwner: isRoomOwner });
|
|
} catch (err) {
|
|
// Fallback: video was likely cleared
|
|
if (videoStarted || !iframe.classList.contains("hidden")) {
|
|
clearVideoUI();
|
|
}
|
|
}
|
|
}
|
|
|
|
setInterval(async () => {
|
|
if (videoExpireAt && Date.now() > videoExpireAt) {
|
|
clearVideoUI();
|
|
|
|
if (isRoomOwner) {
|
|
try {
|
|
await fetch(`/watchparty/${encodeURIComponent(room)}/video/clear`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username }),
|
|
});
|
|
} catch (err) {
|
|
console.error("Failed to auto-clear video", err);
|
|
}
|
|
}
|
|
} else {
|
|
await syncVideoFromServer();
|
|
}
|
|
}, 1000);
|
|
|
|
async function initialize() {
|
|
await checkRoomOwnership();
|
|
await syncVideoFromServer();
|
|
if (!videoStarted) {
|
|
updateControlVisibility({ hasVideo: false, isPaused: false, isOwner: isRoomOwner });
|
|
}
|
|
}
|
|
|
|
await initialize();
|
|
}); |