VELTRON.NET/javascript/music-player.js
2026-02-27 17:51:45 -05:00

197 lines
5.2 KiB
JavaScript

// ===============================
// 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 }))
});
}
});