chore: update stuff
This commit is contained in:
parent
8b02a6ee14
commit
ee454cca8d
2 changed files with 135 additions and 365 deletions
|
|
@ -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 }))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue