197 lines
5.5 KiB
TypeScript
197 lines
5.5 KiB
TypeScript
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<HlsClass> | 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 = `<div class="message-system">${text}</div>`;
|
|
} else {
|
|
div.classList.add(
|
|
sender === username ? "message-self-row" : "message-other-row",
|
|
);
|
|
div.innerHTML = `
|
|
<article class="message-bubble ${sender === username ? "message-self" : "message-other"}">
|
|
${sender && sender !== username ? `<div class="message-sender">${sender}</div>` : ""}
|
|
<div class="message-text">${text}</div>
|
|
<time class="message-time">${new Date(timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}</time>
|
|
</article>
|
|
`;
|
|
}
|
|
|
|
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();
|
|
});
|