diff --git a/javascript/music-player.js b/javascript/music-player.js deleted file mode 100644 index 0a89266..0000000 --- a/javascript/music-player.js +++ /dev/null @@ -1,197 +0,0 @@ -// =============================== -// INITIAL SETUP -// =============================== -let currentSongIndex = 0; -let SONG_LINKS = []; -let PLAYLIST = document.getElementById("playlistLeft"); -let AUDIO_PLAYER = document.getElementById("navMusic"); -let TRACK_NAME_DISPLAY = document.getElementById("currentSongInfo"); - -let NAV_PLAY = document.getElementById("navMusicPlay"); -let NAV_PAUSE = document.getElementById("navMusicPause"); -let NAV_NEXT = document.getElementById("navMusicNext"); -let NAV_PREV = document.getElementById("navMusicPrevious"); - - -// =============================== -// UNIVERSAL AUDIO URL SANITIZER -// =============================== -function sanitizeAudioUrl(raw) { - if (!raw) return null; - - // Reject placeholders or HTML anchors - if (raw === "#" || raw === "/" || raw === "./" || raw === "../") return null; - - // Allowed audio extensions - const audioExtensions = [ - ".mp3", ".ogg", ".wav", ".m4a", ".aac", ".flac", ".opus", ".webm" - ]; - - const lower = raw.toLowerCase(); - const isAudio = audioExtensions.some(ext => lower.endsWith(ext)); - if (!isAudio) return null; - - // Absolute URL - if (raw.startsWith("http")) return raw; - - // JSON gives /music/... or /audio/... - if (raw.startsWith("/")) { - return "https://frutigeraeroarchive.org" + raw; - } - - // Otherwise treat as relative filename - return "https://frutigeraeroarchive.org/music/" + raw; -} - - -// =============================== -// LOAD MUSIC DATA (CORS PROXY) -// =============================== -async function loadMusicData() { - const response = await fetch( - "http://chat.veltron.net" - ); - - const data = await response.json(); - - // Safety check to prevent crashes - if (!data || !data.ols) { - console.error("Music JSON is invalid or blocked by CORS:", data); - return []; - } - - const section = data.ols.find(item => item["@id"] === "frutigerAeroBliss"); - if (!section) return []; - - // KEEP JSON ORDER EXACTLY, but filter invalid URLs - return section.li - .map(item => { - const fixedUrl = sanitizeAudioUrl(item.a["@data-src-mp3"]); - if (!fixedUrl) return null; - - return { - ...item.a, - "@data-src-mp3": fixedUrl - }; - }) - .filter(Boolean); -} - - -// =============================== -// CREATE PLAYLIST -// =============================== -function createAlbum(album) { - if (!PLAYLIST) return; - - const existing = PLAYLIST.querySelector("ol"); - if (existing) existing.remove(); - - const list = document.createElement("ol"); - list.id = album["@id"]; - - const title = document.createElement("span"); - title.textContent = album.span; - list.appendChild(title); - - const small = document.createElement("small"); - small.textContent = album.small; - list.appendChild(small); - - // Build playlist ONLY from valid songs - album.li.forEach((song, index) => { - const li = document.createElement("li"); - const a = document.createElement("a"); - - a.textContent = song.a.title; - a.href = "#"; - - for (const key in song.a) { - if (key.startsWith("@data-")) { - a.setAttribute(key.replace("@", ""), song.a[key]); - } - } - - li.appendChild(a); - list.appendChild(li); - }); - - PLAYLIST.appendChild(list); - - SONG_LINKS = PLAYLIST.querySelectorAll("a[data-src-mp3]"); - - SONG_LINKS.forEach((song, index) => { - song.addEventListener("click", (e) => { - e.preventDefault(); - currentSongIndex = index; - updateSong(index); - }); - }); - - currentSongIndex = 0; - updateSong(0); -} - - -// =============================== -// UPDATE SONG -// =============================== -function updateSong(index) { - SONG_LINKS.forEach((song, i) => { - song.classList.toggle("activeSong", i === index); - }); - - const song = SONG_LINKS[index]; - const src = song.getAttribute("data-src-mp3"); - - if (!src) { - console.warn("Invalid song URL, skipping."); - return; - } - - AUDIO_PLAYER.src = src; - TRACK_NAME_DISPLAY.textContent = song.getAttribute("data-title"); - - AUDIO_PLAYER.play().catch(console.error); -} - - -// =============================== -// NEXT / PREVIOUS -// =============================== -function nextTrack() { - currentSongIndex = (currentSongIndex + 1) % SONG_LINKS.length; - updateSong(currentSongIndex); -} - -function previousTrack() { - currentSongIndex = (currentSongIndex - 1 + SONG_LINKS.length) % SONG_LINKS.length; - updateSong(currentSongIndex); -} - -AUDIO_PLAYER.addEventListener("ended", nextTrack); - - -// =============================== -// NAV BUTTONS -// =============================== -NAV_PLAY.addEventListener("click", () => AUDIO_PLAYER.play()); -NAV_PAUSE.addEventListener("click", () => AUDIO_PLAYER.pause()); -NAV_NEXT.addEventListener("click", nextTrack); -NAV_PREV.addEventListener("click", previousTrack); - - -// =============================== -// LOAD ON STARTUP -// =============================== -window.addEventListener("load", async () => { - const songs = await loadMusicData(); - if (songs.length > 0) { - createAlbum({ - "@id": "frutigerAeroBliss", - span: "Frutiger Aero Bliss", - small: "Music that fits well with Frutiger Aero.", - li: songs.map(song => ({ a: song })) - }); - } -}); diff --git a/javascript/nav-music.js b/javascript/nav-music.js index 0a89266..df8f9cf 100644 --- a/javascript/nav-music.js +++ b/javascript/nav-music.js @@ -1,197 +1,164 @@ -// =============================== -// INITIAL SETUP -// =============================== -let currentSongIndex = 0; -let SONG_LINKS = []; -let PLAYLIST = document.getElementById("playlistLeft"); -let AUDIO_PLAYER = document.getElementById("navMusic"); -let TRACK_NAME_DISPLAY = document.getElementById("currentSongInfo"); +// Volume adjustment for audio elements +document.querySelectorAll("audio").forEach((audio) => { + audio.volume = 0.5; // Volume range is from 0.0 (silent) to 1.0 (maximum) +}); -let NAV_PLAY = document.getElementById("navMusicPlay"); -let NAV_PAUSE = document.getElementById("navMusicPause"); -let NAV_NEXT = document.getElementById("navMusicNext"); -let NAV_PREV = document.getElementById("navMusicPrevious"); - - -// =============================== -// UNIVERSAL AUDIO URL SANITIZER -// =============================== -function sanitizeAudioUrl(raw) { - if (!raw) return null; - - // Reject placeholders or HTML anchors - if (raw === "#" || raw === "/" || raw === "./" || raw === "../") return null; - - // Allowed audio extensions - const audioExtensions = [ - ".mp3", ".ogg", ".wav", ".m4a", ".aac", ".flac", ".opus", ".webm" - ]; - - const lower = raw.toLowerCase(); - const isAudio = audioExtensions.some(ext => lower.endsWith(ext)); - if (!isAudio) return null; - - // Absolute URL - if (raw.startsWith("http")) return raw; - - // JSON gives /music/... or /audio/... - if (raw.startsWith("/")) { - return "https://frutigeraeroarchive.org" + raw; +// Shuffle Playlist (Fisher-Yates) +function fisherYatesShuffle(songs) { + for (let i = songs.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [songs[i], songs[j]] = [songs[j], songs[i]]; } - - // Otherwise treat as relative filename - return "https://frutigeraeroarchive.org/music/" + raw; + return songs; } - -// =============================== -// LOAD MUSIC DATA (CORS PROXY) -// =============================== +// Load the JSON file containing music data (using CORS proxy) async function loadMusicData() { - const response = await fetch( - "http://chat.veltron.net" - ); - + const corsProxyUrl = 'https://corsproxy.io/?url='; + const targetUrl = 'https://chat.veltron.net'; + const response = await fetch(corsProxyUrl + encodeURIComponent(targetUrl)); const data = await response.json(); - // Safety check to prevent crashes - if (!data || !data.ols) { - console.error("Music JSON is invalid or blocked by CORS:", data); + // Get the "frutigerAeroBliss" section from the JSON + const section = data.ols.find(item => item["@id"] === "frutigerAeroBliss"); + + if (!section) { + console.error('Section "frutigerAeroBliss" not found.'); return []; } - const section = data.ols.find(item => item["@id"] === "frutigerAeroBliss"); - if (!section) return []; + // Map the songs into a simplified format with direct audio URLs + const baseUrl = "https://frutigeraeroarchive.org/music/"; // Base URL for the music files + const playlist = section.li.map(item => ({ + title: item.a.title, + url: baseUrl + item.a["@data-src-mp3"].split("/").pop(), // Build the full URL from the base path + background: item.a["@data-background"] || '', // Optional background if available + artist: item.a["@data-artist"] || '', + spotifyLink: item.a["@data-spotify"] || '', + youtubeLink: item.a["@data-youtube"] || '' + })); - // KEEP JSON ORDER EXACTLY, but filter invalid URLs - return section.li - .map(item => { - const fixedUrl = sanitizeAudioUrl(item.a["@data-src-mp3"]); - if (!fixedUrl) return null; - - return { - ...item.a, - "@data-src-mp3": fixedUrl - }; - }) - .filter(Boolean); + // Shuffle the playlist + return fisherYatesShuffle(playlist); } +// Define Music Player +const NAV_MUSIC = document.getElementById("navMusic"); +const BUTTON_PLAY = document.getElementById("navMusicPlay"); +const BUTTON_PAUSE = document.getElementById("navMusicPause"); +const BUTTON_PREVIOUS = document.getElementById("navMusicPrevious"); +const BUTTON_NEXT = document.getElementById("navMusicNext"); +const CURRENT_SONG_INFO = document.getElementById("currentSongInfo"); -// =============================== -// CREATE PLAYLIST -// =============================== -function createAlbum(album) { - if (!PLAYLIST) return; +let songs = []; +let currentIndex = 0; +let currentTime = 0; +let isPlaying = false; // Track play/pause state - const existing = PLAYLIST.querySelector("ol"); - if (existing) existing.remove(); - - const list = document.createElement("ol"); - list.id = album["@id"]; - - const title = document.createElement("span"); - title.textContent = album.span; - list.appendChild(title); - - const small = document.createElement("small"); - small.textContent = album.small; - list.appendChild(small); - - // Build playlist ONLY from valid songs - album.li.forEach((song, index) => { - const li = document.createElement("li"); - const a = document.createElement("a"); - - a.textContent = song.a.title; - a.href = "#"; - - for (const key in song.a) { - if (key.startsWith("@data-")) { - a.setAttribute(key.replace("@", ""), song.a[key]); - } - } - - li.appendChild(a); - list.appendChild(li); - }); - - PLAYLIST.appendChild(list); - - SONG_LINKS = PLAYLIST.querySelectorAll("a[data-src-mp3]"); - - SONG_LINKS.forEach((song, index) => { - song.addEventListener("click", (e) => { - e.preventDefault(); - currentSongIndex = index; - updateSong(index); - }); - }); - - currentSongIndex = 0; - updateSong(0); +// Save state to sessionStorage +function saveState() { + sessionStorage.setItem("isPlaying", isPlaying); } +// Load state from sessionStorage +function loadState() { + const savedIsPlaying = sessionStorage.getItem("isPlaying"); -// =============================== -// UPDATE SONG -// =============================== -function updateSong(index) { - SONG_LINKS.forEach((song, i) => { - song.classList.toggle("activeSong", i === index); - }); - - const song = SONG_LINKS[index]; - const src = song.getAttribute("data-src-mp3"); - - if (!src) { - console.warn("Invalid song URL, skipping."); - return; + if (savedIsPlaying !== null) { + isPlaying = savedIsPlaying === "true"; } - - AUDIO_PLAYER.src = src; - TRACK_NAME_DISPLAY.textContent = song.getAttribute("data-title"); - - AUDIO_PLAYER.play().catch(console.error); } - -// =============================== -// NEXT / PREVIOUS -// =============================== -function nextTrack() { - currentSongIndex = (currentSongIndex + 1) % SONG_LINKS.length; - updateSong(currentSongIndex); +// Load the current song +function loadSong(index) { + if (index < 0 || index >= songs.length) return; + NAV_MUSIC.src = songs[index].url; // Directly set the audio URL (no proxy needed) + CURRENT_SONG_INFO.innerText = `Featured song: ${songs[index].title}`; + NAV_MUSIC.load(); } -function previousTrack() { - currentSongIndex = (currentSongIndex - 1 + SONG_LINKS.length) % SONG_LINKS.length; - updateSong(currentSongIndex); +// Update button states based on `isPlaying` +function updateButtonStates() { + if (isPlaying) { + BUTTON_PLAY.classList.add("active"); + BUTTON_PAUSE.classList.remove("active"); + } else { + BUTTON_PAUSE.classList.add("active"); + BUTTON_PLAY.classList.remove("active"); + } } -AUDIO_PLAYER.addEventListener("ended", nextTrack); +// Event listeners for playback controls +BUTTON_PLAY.addEventListener("click", function () { + isPlaying = true; + NAV_MUSIC.play(); + updateButtonStates(); + saveState(); +}); +BUTTON_PAUSE.addEventListener("click", function () { + isPlaying = false; + NAV_MUSIC.pause(); + updateButtonStates(); + saveState(); +}); -// =============================== -// NAV BUTTONS -// =============================== -NAV_PLAY.addEventListener("click", () => AUDIO_PLAYER.play()); -NAV_PAUSE.addEventListener("click", () => AUDIO_PLAYER.pause()); -NAV_NEXT.addEventListener("click", nextTrack); -NAV_PREV.addEventListener("click", previousTrack); +BUTTON_PREVIOUS.addEventListener("click", function () { + currentIndex = currentIndex === 0 ? songs.length - 1 : currentIndex - 1; + currentTime = 0; // Reset time when switching songs + loadSong(currentIndex); + if (isPlaying) { + NAV_MUSIC.play(); + } + updateButtonStates(); + saveState(); +}); +BUTTON_NEXT.addEventListener("click", function () { + currentIndex = (currentIndex + 1) % songs.length; + currentTime = 0; // Reset time when switching songs + loadSong(currentIndex); + if (isPlaying) { + NAV_MUSIC.play(); + } + updateButtonStates(); + saveState(); +}); -// =============================== -// LOAD ON STARTUP -// =============================== +// Autoplay next song when current ends +NAV_MUSIC.addEventListener("ended", function () { + currentIndex = (currentIndex + 1) % songs.length; + currentTime = 0; // Reset time when a new song starts + loadSong(currentIndex); + if (isPlaying) { + NAV_MUSIC.play(); + } + saveState(); +}); + +// Initial load window.addEventListener("load", async () => { - const songs = await loadMusicData(); + songs = await loadMusicData(); if (songs.length > 0) { - createAlbum({ - "@id": "frutigerAeroBliss", - span: "Frutiger Aero Bliss", - small: "Music that fits well with Frutiger Aero.", - li: songs.map(song => ({ a: song })) - }); + loadState(); + loadSong(currentIndex); + if (isPlaying) { + NAV_MUSIC.play(); + } + updateButtonStates(); } }); + +// Pause all audio if user presses play on another audio element +document.addEventListener( + "play", + function (e) { + var audios = document.getElementsByTagName("audio"); + for (var i = 0, len = audios.length; i < len; i++) { + if (audios[i] != e.target) { + audios[i].pause(); + } + } + }, + true +);