function initCustomAudioPlayers() { // =================== Config & Helpers =================== const CONFIG = { LEFT_MARGIN_PERCENT: 6, RIGHT_MARGIN_PERCENT: 9, PROGRESS_BAR_UPDATE_INTERVAL: 20, BUFFER_TIME: 0.1, }; const debounce = (func, wait) => { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; }; const icons = { play: ` `, pause: ` `, dots: ` `, spinner: `
`, error: ` `, }; // For user preferences (gain, filters, etc.) const safeGet = (k, fb) => { try { return localStorage.getItem(k) || fb; } catch { return fb; } }; const safeSet = (k, v) => { try { localStorage.setItem(k, v); } catch {} }; // Helper for readable/friendly display of numbers const compactFormatter = new Intl.NumberFormat(undefined, { notation: 'compact' }); const formatCompactOrEcho = (value) => { const n = (typeof value === 'number') ? value : Number(value); if (!Number.isFinite(n)) { return String(value); } const out = compactFormatter.format(n); return out; }; // Retrieve saved user preferences const savedGain = safeGet("customAudioPlayerGain", "Off"); const savedHighpass = safeGet("customAudioPlayerFilterHigh", "Off"); const savedLowpass = safeGet("customAudioPlayerFilterLow", "Off"); // Helper to apply multiple style properties const applyStyles = (elem, styles) => Object.assign(elem.style, styles); // Basic styling for small control buttons const styleButton = (btn, styles = {}) => { applyStyles(btn, styles); // Subtle hover highlight btn.addEventListener("mouseover", () => (btn.style.background = "rgba(255,255,255,0.1)")); btn.addEventListener("mouseout", () => (btn.style.background = "transparent")); }; // Helper to create a button const createButton = ( parent, { text = "", html = "", styles = {}, data = {}, onClick = null } = {} ) => { const btn = document.createElement("button"); btn.type = "button"; if (text) btn.textContent = text; if (html) btn.innerHTML = html; Object.entries(data).forEach(([k, v]) => (btn.dataset[k] = v)); styleButton(btn, styles); if (onClick) btn.addEventListener("click", onClick); parent.appendChild(btn); return btn; }; // Common small‐button styling const iconBtnStyle = { background: "transparent", border: "none", cursor: "pointer", width: "36px", height: "36px", display: "flex", alignItems: "center", justifyContent: "center", marginRight: "0.6rem", padding: "0", borderRadius: "50%", }; const textBtnStyle = { background: "none", border: "none", cursor: "pointer", color: "white", fontSize: "14px", textAlign: "right", width: "100%", padding: "6px 12px", margin: "2px 0", borderRadius: "4px", }; const optionBtnStyle = { background: "none", border: "none", cursor: "pointer", color: "white", fontSize: "14px", textAlign: "center", width: "auto", padding: "6px 8px", margin: "2px 4px", borderRadius: "4px", }; // =================== Main Loop over all .custom-audio-player =================== document.querySelectorAll(".custom-audio-player").forEach((player) => { let hasLoaded = false; // set to true once metadata is available // Audio/player data const audioSrc = player.dataset.audioSrc; const imageSrc = player.dataset.imageSrc; //