blastoise/public/core.js

134 lines
3.5 KiB
JavaScript

// MusicRoom - Core module
// Shared state and namespace setup
window.MusicRoom = {
// Audio element
audio: new Audio(),
// WebSocket and channel state
ws: null,
currentChannelId: null,
currentTrackId: null,
currentTitle: null,
serverTimestamp: 0,
serverTrackDuration: 0,
lastServerUpdate: 0,
serverPaused: true,
// Current track cache (for ReplayGain re-application across graph init)
currentTrack: null,
// Channels list
channels: [],
// Sync state
wantSync: true, // User intent - do they want to be synced?
synced: false, // Actual state - are we currently synced?
// Volume
preMuteVolume: 1,
STORAGE_KEY: "blastoise_volume",
CHANNEL_STORAGE_KEY: "blastoise_channel",
THEME_STORAGE_KEY: "blastoise_theme",
VISUALIZER_STORAGE_KEY: "blastoise_visualizer",
// Playback state
localTimestamp: 0,
queue: [],
currentIndex: 0,
playbackMode: "repeat-all",
// User state
currentUser: null,
serverStatus: null,
// Library (all discovered tracks)
library: [],
// Caching state
prefetchController: null,
loadingSegments: new Set(),
trackCaches: new Map(), // Map of filename -> Set of cached segment indices
trackBlobs: new Map(), // Map of filename -> Blob URL for fully cached tracks
bulkDownloadStarted: new Map(),
cachedTracks: new Set(), // Set of track IDs that are fully cached locally
streamOnly: false, // When true, skip bulk downloads (still cache played tracks)
cacheLimit: 100 * 1024 * 1024, // 100MB default cache limit
// Download metrics
audioBytesPerSecond: 20000, // Audio bitrate estimate for range requests
downloadSpeed: 0, // Actual network download speed
recentDownloads: [], // Track recent downloads for speed calculation
// Constants
SEGMENTS: 20,
FAST_THRESHOLD: 10 * 1024 * 1024, // 10 MB/s
// UI update tracking (to avoid unnecessary DOM updates)
lastProgressPct: -1,
lastTimeCurrent: "",
lastTimeTotal: "",
lastBufferPct: -1,
lastSpeedText: ""
};
(function() {
const M = window.MusicRoom;
const CHANNEL_COOKIE_MAX_AGE = 60 * 60 * 24 * 400;
M.getCookieValue = function(name) {
const prefix = `${name}=`;
return document.cookie
.split(";")
.map(cookie => cookie.trim())
.find(cookie => cookie.startsWith(prefix))
?.slice(prefix.length) || null;
};
M.setCookieValue = function(name, value, maxAge = CHANNEL_COOKIE_MAX_AGE) {
document.cookie = `${name}=${encodeURIComponent(value)}; Path=/; SameSite=Lax; Max-Age=${maxAge}`;
};
M.clearCookieValue = function(name) {
document.cookie = `${name}=; Path=/; SameSite=Lax; Max-Age=0`;
};
M.getSavedChannelId = function() {
const cookieValue = M.getCookieValue(M.CHANNEL_STORAGE_KEY);
if (cookieValue) {
try {
return decodeURIComponent(cookieValue);
} catch {
M.clearRememberedChannel();
}
}
try {
const storedValue = localStorage.getItem(M.CHANNEL_STORAGE_KEY);
if (storedValue) {
M.rememberChannel(storedValue);
return storedValue;
}
} catch {}
return null;
};
M.rememberChannel = function(channelId) {
if (!channelId) return;
M.setCookieValue(M.CHANNEL_STORAGE_KEY, channelId);
try {
localStorage.setItem(M.CHANNEL_STORAGE_KEY, channelId);
} catch {}
};
M.clearRememberedChannel = function() {
M.clearCookieValue(M.CHANNEL_STORAGE_KEY);
try {
localStorage.removeItem(M.CHANNEL_STORAGE_KEY);
} catch {}
};
})();