import "./style.css"; type HlsClass = (typeof import("hls.js"))["default"]; declare global { interface Window { switchRoom: (room: string) => void; sendMessage: () => void; togglePrivateDM: () => void; toggleChatPanel: () => void; } } let hls: InstanceType | null = null; let ws: WebSocket | null = null; let currentRoom = "main"; let username = "viewer" + Math.floor(Math.random() * 1000); let chatVisible = true; function resolveWebSocketBaseUrl() { const configuredUrl = import.meta.env.VITE_PUBLIC_WS_URL?.trim(); if (configuredUrl) { return configuredUrl; } const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; return `${protocol}//${window.location.host}/ws`; } function resolveManifestUrl() { const configuredUrl = import.meta.env.VITE_PUBLIC_HLS_URL?.trim(); const cacheBust = `stream=${Date.now()}`; const sameOriginManifestUrl = new URL( "/hls/playlist.m3u8", window.location.origin, ); sameOriginManifestUrl.searchParams.set("stream", cacheBust); const allowCrossOriginManifest = import.meta.env.VITE_PUBLIC_HLS_CROSS_ORIGIN?.trim().toLowerCase() === "true"; if (configuredUrl) { const manifestUrl = new URL(configuredUrl); manifestUrl.searchParams.set("stream", cacheBust); if ( !allowCrossOriginManifest && manifestUrl.origin !== window.location.origin ) { return sameOriginManifestUrl.toString(); } return manifestUrl.toString(); } return sameOriginManifestUrl.toString(); } function connectWebSocket(room: string) { if (ws) ws.close(); const socketUrl = new URL(resolveWebSocketBaseUrl()); socketUrl.searchParams.set("username", username); socketUrl.searchParams.set("room", room); ws = new WebSocket(socketUrl); ws.onopen = () => { console.log(`Connected to chat room: ${room}`); addMessage("system", "Connected to " + room); }; ws.onmessage = (event) => { const msg = JSON.parse(event.data); addMessage(msg.type || "public", msg.text, msg.username, msg.timestamp); }; ws.onclose = () => { console.log("Chat disconnected. Reconnecting in 3s..."); setTimeout(() => connectWebSocket(room), 3000); }; } function addMessage( type: string, text: string, sender = "", timestamp = new Date(), ) { const messages = document.getElementById("messages")!; const div = document.createElement("div"); div.className = "message-row"; if (type === "system") { div.classList.add("message-system-row"); div.innerHTML = `
${text}
`; } else { div.classList.add( sender === username ? "message-self-row" : "message-other-row", ); div.innerHTML = `
${sender && sender !== username ? `
${sender}
` : ""}
${text}
`; } messages.appendChild(div); messages.scrollTop = messages.scrollHeight; } async function initPlayerAndChat() { const video = document.getElementById("player") as HTMLVideoElement; const manifestUrl = resolveManifestUrl(); if (video.canPlayType("application/vnd.apple.mpegurl")) { video.src = manifestUrl; video.addEventListener("loadedmetadata", () => video.play()); } else { try { const { default: Hls } = await import("hls.js"); if (Hls.isSupported()) { hls = new Hls(); hls.loadSource(manifestUrl); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, () => video.play()); } } catch (error) { console.error("Failed to load HLS player", error); addMessage("system", "Unable to initialize video player."); } } connectWebSocket("main"); console.log( "Linear Void Frontend initialized - HLS Player + Chat Sidebar ready", ); } function toggleChatPanel() { const shell = document.querySelector(".viewer-shell"); const toggle = document.getElementById("chat-toggle"); if (!shell || !toggle) return; chatVisible = !chatVisible; shell.classList.toggle("chat-collapsed", !chatVisible); toggle.textContent = chatVisible ? "Hide Chat" : "Show Chat"; toggle.setAttribute("aria-expanded", String(chatVisible)); } function sendMessage() { const input = document.getElementById("message-input") as HTMLInputElement; const text = input.value.trim(); if (!text || !ws) return; const msg = { room: currentRoom, username: username, text: text, type: currentRoom.startsWith("dm:") ? "private" : "public", }; ws.send(JSON.stringify(msg)); input.value = ""; } function switchRoom(room: string) { currentRoom = room; document.getElementById("room-name")!.textContent = room === "main" ? "Main Channel" : room.charAt(0).toUpperCase() + room.slice(1) + " Room"; addMessage("system", `Switched to ${room} room`); connectWebSocket(room); } function togglePrivateDM() { const target = prompt("Enter username to start private chat with:"); if (target && target !== username) { const dmRoom = `dm:${[username, target].sort().join("-")}`; switchRoom(dmRoom); } } window.switchRoom = switchRoom; window.sendMessage = sendMessage; window.togglePrivateDM = togglePrivateDM; window.toggleChatPanel = toggleChatPanel; document.addEventListener("DOMContentLoaded", () => { void initPlayerAndChat(); });