164 lines
5.9 KiB
JavaScript
164 lines
5.9 KiB
JavaScript
// MusicRoom - UI module
|
|
// Progress bar, buffer display, and UI state updates
|
|
|
|
(function() {
|
|
const M = window.MusicRoom;
|
|
|
|
// Create buffer segments on load
|
|
for (let i = 0; i < M.SEGMENTS; i++) {
|
|
const seg = document.createElement("div");
|
|
seg.className = "segment";
|
|
M.$("#buffer-bar").appendChild(seg);
|
|
}
|
|
|
|
// Update general UI state
|
|
M.updateUI = function() {
|
|
const isPlaying = M.synced ? !M.serverPaused : !M.audio.paused;
|
|
M.$("#btn-sync").classList.toggle("synced", M.wantSync);
|
|
M.$("#btn-sync").classList.toggle("connected", M.synced);
|
|
M.$("#btn-sync").title = M.wantSync ? "Unsync" : "Sync";
|
|
M.$("#status").textContent = M.synced ? "Synced" : (M.wantSync ? "Connecting..." : "Local");
|
|
M.$("#sync-indicator").classList.toggle("visible", M.synced);
|
|
M.$("#progress-bar").classList.toggle("synced", M.synced);
|
|
M.$("#progress-bar").classList.toggle("local", !M.synced);
|
|
M.$("#progress-bar").classList.toggle("muted", M.audio.volume === 0);
|
|
M.$("#btn-mute").textContent = M.audio.volume === 0 ? "🔇" : "🔊";
|
|
M.$("#status-icon").textContent = isPlaying ? "⏸" : "▶";
|
|
|
|
// Show/hide controls based on permissions
|
|
const hasControl = M.canControl();
|
|
M.$("#status-icon").style.cursor = hasControl || !M.synced ? "pointer" : "default";
|
|
};
|
|
|
|
// Update auth-related UI
|
|
M.updateAuthUI = function() {
|
|
if (M.currentUser) {
|
|
M.$("#login-panel").classList.add("hidden");
|
|
M.$("#player-content").classList.add("visible");
|
|
if (M.currentUser.isGuest) {
|
|
M.$("#current-username").textContent = "Guest";
|
|
M.$("#btn-logout").textContent = "Sign In";
|
|
} else {
|
|
M.$("#current-username").textContent = M.currentUser.username;
|
|
M.$("#btn-logout").textContent = "Logout";
|
|
}
|
|
M.$("#admin-badge").style.display = M.currentUser.isAdmin ? "inline" : "none";
|
|
} else {
|
|
M.$("#login-panel").classList.remove("hidden");
|
|
M.$("#player-content").classList.remove("visible");
|
|
// Pause and unsync when login panel is shown
|
|
if (!M.audio.paused) {
|
|
M.localTimestamp = M.audio.currentTime;
|
|
M.audio.pause();
|
|
}
|
|
if (M.synced && M.ws) {
|
|
M.synced = false;
|
|
M.ws.close();
|
|
M.ws = null;
|
|
}
|
|
// Show guest button if server allows guests
|
|
if (M.serverStatus?.allowGuests) {
|
|
M.$("#guest-section").classList.remove("hidden");
|
|
} else {
|
|
M.$("#guest-section").classList.add("hidden");
|
|
}
|
|
}
|
|
M.updateUI();
|
|
};
|
|
|
|
// Progress bar and buffer update loop (250ms interval)
|
|
setInterval(() => {
|
|
if (M.serverTrackDuration <= 0) return;
|
|
let t, dur;
|
|
if (M.synced) {
|
|
t = M.audio.paused ? M.getServerTime() : M.audio.currentTime;
|
|
dur = M.audio.duration || M.serverTrackDuration;
|
|
} else {
|
|
t = M.audio.paused ? M.localTimestamp : M.audio.currentTime;
|
|
dur = M.audio.duration || M.serverTrackDuration;
|
|
}
|
|
const pct = Math.min((t / dur) * 100, 100);
|
|
if (Math.abs(pct - M.lastProgressPct) > 0.1) {
|
|
M.$("#progress-bar").style.width = pct + "%";
|
|
M.lastProgressPct = pct;
|
|
}
|
|
|
|
const timeCurrent = M.fmt(t);
|
|
const timeTotal = M.fmt(dur);
|
|
if (timeCurrent !== M.lastTimeCurrent) {
|
|
M.$("#time-current").textContent = timeCurrent;
|
|
M.lastTimeCurrent = timeCurrent;
|
|
}
|
|
if (timeTotal !== M.lastTimeTotal) {
|
|
M.$("#time-total").textContent = timeTotal;
|
|
M.lastTimeTotal = timeTotal;
|
|
}
|
|
|
|
// Update buffer segments
|
|
const segments = M.$("#buffer-bar").children;
|
|
const segmentDur = dur / M.SEGMENTS;
|
|
let availableCount = 0;
|
|
const trackCache = M.getTrackCache(M.currentTrackId);
|
|
for (let i = 0; i < M.SEGMENTS; i++) {
|
|
const segStart = i * segmentDur;
|
|
const segEnd = (i + 1) * segmentDur;
|
|
let available = trackCache.has(i); // Check our cache first
|
|
if (!available) {
|
|
// Check browser's native buffer
|
|
for (let j = 0; j < M.audio.buffered.length; j++) {
|
|
const bufStart = M.audio.buffered.start(j);
|
|
const bufEnd = M.audio.buffered.end(j);
|
|
if (bufStart <= segStart && bufEnd >= segEnd) {
|
|
available = true;
|
|
// Sync browser buffer to our trackCache
|
|
trackCache.add(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (available) availableCount++;
|
|
const isAvailable = segments[i].classList.contains("available");
|
|
const isLoading = segments[i].classList.contains("loading");
|
|
const shouldBeLoading = !available && M.loadingSegments.has(i);
|
|
if (available !== isAvailable) segments[i].classList.toggle("available", available);
|
|
if (shouldBeLoading !== isLoading) segments[i].classList.toggle("loading", shouldBeLoading);
|
|
}
|
|
|
|
// Check if all segments now cached - trigger full cache
|
|
if (trackCache.size >= M.SEGMENTS && !M.cachedTracks.has(M.currentTrackId)) {
|
|
M.checkAndCacheComplete(M.currentTrackId);
|
|
}
|
|
|
|
// Update download speed display
|
|
const kbps = M.downloadSpeed > 0 ? M.downloadSpeed * 8 / 1000 : 0;
|
|
const bufferPct = Math.round(availableCount / M.SEGMENTS * 100);
|
|
let speedText = "";
|
|
if (kbps > 0) {
|
|
speedText = kbps >= 1024 ? ` | ${(kbps / 1024).toFixed(1)} Mb/s` : ` | ${Math.round(kbps)} kb/s`;
|
|
}
|
|
if (bufferPct !== M.lastBufferPct || speedText !== M.lastSpeedText) {
|
|
M.$("#download-speed").textContent = `${bufferPct}% buffered${speedText}`;
|
|
M.lastBufferPct = bufferPct;
|
|
M.lastSpeedText = speedText;
|
|
}
|
|
}, 250);
|
|
|
|
// Prefetch loop (1s interval)
|
|
setInterval(() => {
|
|
if (M.currentTrackId && M.audio.src) {
|
|
M.prefetchSegments();
|
|
}
|
|
}, 1000);
|
|
|
|
// Cache status check (5s interval) - updates indicators when tracks finish caching
|
|
let lastCacheSize = 0;
|
|
setInterval(async () => {
|
|
const currentSize = M.cachedTracks.size;
|
|
if (currentSize !== lastCacheSize) {
|
|
lastCacheSize = currentSize;
|
|
M.renderPlaylist();
|
|
M.renderLibrary();
|
|
}
|
|
}, 5000);
|
|
})();
|