musicos / static /script.js
triflix's picture
Create script.js
9f2941b verified
document.addEventListener("DOMContentLoaded", () => {
// --- DOM Elements ---
const joinScreen = document.getElementById("join-screen");
const roomScreen = document.getElementById("room-screen");
const roomNameInput = document.getElementById("room-name-input");
const userNameInput = document.getElementById("user-name-input");
const joinBtn = document.getElementById("join-btn");
const errorMessage = document.getElementById("error-message");
const roomHeader = document.getElementById("room-header");
const userList = document.getElementById("user-list");
const nowPlaying = document.getElementById("now-playing");
const audioPlayer = document.getElementById("audio-player");
const playPauseBtn = document.getElementById("play-pause-btn");
const seekBar = document.getElementById("seek-bar");
const timeDisplay = document.getElementById("time-display");
const playlistElement = document.getElementById("playlist");
const adminControls = document.getElementById("admin-controls");
const fileInput = document.getElementById("file-input");
const uploadBtn = document.getElementById("upload-btn");
const uploadStatus = document.getElementById("upload-status");
// --- State ---
let ws = null;
let roomName = "";
let userName = "";
let userId = "";
let isAdmin = false;
let isSeeking = false;
// --- Event Listeners ---
joinBtn.addEventListener("click", joinRoom);
playPauseBtn.addEventListener("click", togglePlayPause);
uploadBtn.addEventListener("click", uploadFile);
seekBar.addEventListener("input", () => { isSeeking = true; });
seekBar.addEventListener("change", () => {
isSeeking = false;
if (isAdmin) {
const newPosition = parseFloat(seekBar.value);
audioPlayer.currentTime = newPosition;
sendMessage({ action: "seek", position: newPosition });
}
});
audioPlayer.addEventListener("timeupdate", updateSeekBar);
audioPlayer.addEventListener("loadedmetadata", updateSeekBar);
// --- Functions ---
function joinRoom() {
roomName = roomNameInput.value.trim();
userName = userNameInput.value.trim();
if (!roomName || !userName) {
errorMessage.textContent = "Room and User name are required.";
return;
}
errorMessage.textContent = "";
const wsProtocol = window.location.protocol === "https:" ? "wss://" : "ws://";
const wsURL = `${wsProtocol}${window.location.host}/ws/${encodeURIComponent(roomName)}/${encodeURIComponent(userName)}`;
ws = new WebSocket(wsURL);
ws.onopen = () => {
console.log("WebSocket connection established.");
joinScreen.classList.remove("active");
roomScreen.classList.add("active");
roomHeader.textContent = `Room: ${roomName}`;
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
handleWebSocketMessage(message);
};
ws.onerror = (error) => {
console.error("WebSocket error:", error);
errorMessage.textContent = "Failed to connect to the room.";
};
ws.onclose = () => {
console.log("WebSocket connection closed.");
alert("Connection lost. Please refresh the page to reconnect.");
roomScreen.classList.remove("active");
joinScreen.classList.add("active");
};
}
function handleWebSocketMessage(message) {
console.log("Received message:", message);
switch (message.type) {
case "initial_state":
userId = message.user_id;
updateState(message.payload);
break;
case "state_update":
updateState(message.payload);
break;
case "user_list_update": // Handles only user changes without interrupting playback
updateUI(message.payload);
break;
}
}
function updateState(state) {
// Determine admin status before updating the UI
isAdmin = state.admin_user_id === userId;
updateUI(state);
// Sync audio player
const currentTrackUrl = state.current_track ? `/tmp/${encodeURIComponent(state.current_track)}` : "";
if (state.current_track && audioPlayer.src.endsWith(currentTrackUrl) === false) {
audioPlayer.src = currentTrackUrl;
}
if (!state.current_track) {
audioPlayer.src = "";
nowPlaying.textContent = "Now Playing: Nothing";
return;
}
// Avoid race conditions by only applying server state if not currently seeking
if (!isSeeking) {
// Correct for latency by gently nudging the time
const timeDifference = Math.abs(audioPlayer.currentTime - state.position);
if (timeDifference > 1.5) { // Resync if more than 1.5s out of sync
audioPlayer.currentTime = state.position;
}
}
if (state.is_playing && audioPlayer.paused) {
audioPlayer.play().catch(e => console.error("Autoplay failed:", e));
} else if (!state.is_playing && !audioPlayer.paused) {
audioPlayer.pause();
}
}
function updateUI(state) {
// Update User List
userList.innerHTML = "Users: " + Object.values(state.users)
.map(u => `<span class="${state.admin_user_id === Object.keys(state.users).find(key => state.users[key] === u) ? 'admin-badge' : ''}">${u.name}${state.admin_user_id === Object.keys(state.users).find(key => state.users[key] === u) ? ' (Admin)' : ''}</span>`)
.join(", ");
// Update Now Playing
nowPlaying.textContent = state.current_track ? `Now Playing: ${state.current_track}` : "Now Playing: Nothing";
// Update Playlist
playlistElement.innerHTML = "";
state.playlist.forEach(track => {
const li = document.createElement("li");
li.textContent = track;
if (track === state.current_track) {
li.classList.add("active");
}
if (isAdmin) {
const playTrackBtn = document.createElement("button");
playTrackBtn.textContent = "Play";
playTrackBtn.className = "play-track-btn";
playTrackBtn.onclick = () => {
sendMessage({ action: "change_track", track: track });
};
li.appendChild(playTrackBtn);
}
playlistElement.appendChild(li);
});
// Toggle Admin Controls
const isCurrentlyAdmin = state.admin_user_id === userId;
if (isCurrentlyAdmin) {
adminControls.style.display = "block";
playPauseBtn.disabled = false;
seekBar.disabled = false;
} else {
adminControls.style.display = "none";
playPauseBtn.disabled = true;
seekBar.disabled = true;
}
// Update Play/Pause button text
playPauseBtn.textContent = state.is_playing ? "Pause" : "Play";
}
function togglePlayPause() {
if (!isAdmin) return;
const isPlaying = !audioPlayer.paused;
const action = isPlaying ? "pause" : "play";
sendMessage({ action: action, position: audioPlayer.currentTime });
}
function updateSeekBar() {
if (!isSeeking) {
seekBar.max = audioPlayer.duration || 0;
seekBar.value = audioPlayer.currentTime;
}
timeDisplay.textContent = `${formatTime(audioPlayer.currentTime)} / ${formatTime(audioPlayer.duration || 0)}`;
}
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
async function uploadFile() {
if (!fileInput.files.length) {
uploadStatus.textContent = "Please select a file.";
return;
}
const file = fileInput.files[0];
const formData = new FormData();
formData.append("file", file);
uploadStatus.textContent = "Uploading...";
try {
const response = await fetch(`/upload/${encodeURIComponent(roomName)}`, {
method: "POST",
body: formData,
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || "Upload failed");
}
const result = await response.json();
uploadStatus.textContent = `Upload successful: ${result.filename}`;
fileInput.value = ""; // Reset file input
} catch (error) {
uploadStatus.textContent = `Error: ${error.message}`;
console.error("Upload error:", error);
}
}
function sendMessage(message) {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(message));
}
}
});