diff --git a/javascript/nav-music.js b/javascript/nav-music.js index 069343c..9571ae6 100644 --- a/javascript/nav-music.js +++ b/javascript/nav-music.js @@ -1,12 +1,12 @@ // ============================ -// Volume normalization +// Volume control // ============================ document.querySelectorAll("audio").forEach((audio) => { audio.volume = 0.5; }); // ============================ -// Fisher-Yates Shuffle +// Shuffle (safe) // ============================ function fisherYatesShuffle(arr) { if (!Array.isArray(arr)) return []; @@ -15,66 +15,81 @@ function fisherYatesShuffle(arr) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } - return arr; } // ============================ -// Fetch music data (SAFE) +// SAFE FETCH (fixes JSON crash) // ============================ -async function loadMusicData(category = "frutigerAeroBliss") { +async function safeFetchJSON(url) { try { - const response = await fetch("https://api.veltron.net"); + const res = await fetch(url); - if (!response.ok) { - console.error("HTTP error:", response.status); - return []; + if (!res.ok) { + console.error("HTTP error:", res.status); + return null; } - const data = await response.json(); + const text = await res.text(); - if (!data?.ols || !Array.isArray(data.ols)) { - console.error("Invalid API structure:", data); - return []; + // Reject non-JSON responses (HTML, error pages, etc.) + const trimmed = text.trim(); + if (!trimmed || (trimmed[0] !== "{" && trimmed[0] !== "[")) { + console.error("Non-JSON response detected:", trimmed.slice(0, 120)); + return null; } - const section = - data.ols.find((item) => item?.["@id"] === category) || data.ols[0]; - - if (!section?.li || !Array.isArray(section.li)) { - console.error("Missing playlist section:", category); - return []; - } - - const baseUrl = "https://cdn.veltron.net/aero"; - - const playlist = section.li - .map((item) => { - const a = item?.a; - const mp3 = a?.["@data-src-mp3"]; - const title = a?.title; - - if (!mp3 || !title) return null; - - return { - title, - url: baseUrl + mp3.split("/").pop(), - background: a["@data-background"] || "", - artist: a["@data-artist"] || "", - youtubeLink: a["@data-youtube"] || "" - }; - }) - .filter(Boolean); - - return fisherYatesShuffle(playlist); + return JSON.parse(text); } catch (err) { - console.error("Failed to load music:", err); - return []; + console.error("Fetch failed:", err); + return null; } } // ============================ -// DOM Elements +// Load music data +// ============================ +async function loadMusicData(category = "frutigerAeroBliss") { + const data = await safeFetchJSON("https://api.veltron.net"); + + if (!data?.ols || !Array.isArray(data.ols)) { + console.error("Invalid API structure"); + return []; + } + + const section = + data.ols.find((x) => x?.["@id"] === category) || data.ols[0]; + + if (!section?.li || !Array.isArray(section.li)) { + console.error("Missing playlist section"); + return []; + } + + const baseUrl = "https://cdn.veltron.net/aero"; + + const playlist = section.li + .map((item) => { + const a = item?.a; + const mp3 = a?.["@data-src-mp3"]; + const title = a?.title; + + if (!mp3 || !title) return null; + + return { + title, + url: baseUrl + mp3.split("/").pop(), + background: a["@data-background"] || "", + artist: a["@data-artist"] || "", + youtube: a["@data-youtube"] || "" + }; + }) + .filter(Boolean); + + return fisherYatesShuffle(playlist); +} + +// ============================ +// DOM // ============================ const NAV_MUSIC = document.getElementById("navMusic"); const BUTTON_PLAY = document.getElementById("navMusicPlay"); @@ -91,7 +106,7 @@ let currentIndex = 0; let isPlaying = false; // ============================ -// Session persistence +// Save / Load state // ============================ function saveState() { sessionStorage.setItem("isPlaying", JSON.stringify(isPlaying)); @@ -99,9 +114,7 @@ function saveState() { function loadState() { const saved = sessionStorage.getItem("isPlaying"); - if (saved !== null) { - isPlaying = JSON.parse(saved); - } + if (saved !== null) isPlaying = JSON.parse(saved); } // ============================ @@ -109,25 +122,20 @@ function loadState() { // ============================ function loadSong(index) { if (!Array.isArray(songs) || songs.length === 0) return; - if (index < 0 || index >= songs.length) return; const song = songs[index]; - - if (!song?.url) { - console.warn("Skipping invalid song at index:", index); - return; - } + if (!song?.url) return; NAV_MUSIC.src = song.url; - CURRENT_SONG_INFO.innerText = `Featured song: ${song.title}`; + CURRENT_SONG_INFO.innerText = `Now playing: ${song.title}`; NAV_MUSIC.load(); } // ============================ -// UI state sync +// UI state // ============================ -function updateButtonStates() { +function updateUI() { BUTTON_PLAY.classList.toggle("active", isPlaying); BUTTON_PAUSE.classList.toggle("active", !isPlaying); } @@ -140,14 +148,14 @@ BUTTON_PLAY.addEventListener("click", () => { isPlaying = true; NAV_MUSIC.play().catch(() => {}); - updateButtonStates(); + updateUI(); saveState(); }); BUTTON_PAUSE.addEventListener("click", () => { isPlaying = false; NAV_MUSIC.pause(); - updateButtonStates(); + updateUI(); saveState(); }); @@ -172,7 +180,7 @@ BUTTON_NEXT.addEventListener("click", () => { }); // ============================ -// Auto next track +// Auto next // ============================ NAV_MUSIC.addEventListener("ended", () => { if (!songs.length) return; @@ -192,18 +200,17 @@ window.addEventListener("load", async () => { if (!songs.length) { CURRENT_SONG_INFO.innerText = "No music available"; - console.error("Playlist empty or failed to load"); + console.error("Playlist failed to load"); return; } loadState(); loadSong(currentIndex); + updateUI(); if (isPlaying) { NAV_MUSIC.play().catch(() => {}); } - - updateButtonStates(); }); // ============================