music player fix

建立於 差異永不過期
17 刪除
559
12 新增
553
//this widget is created in 2025 by elsie
//this widget is created in 2025 by elsie
//check out my website at elswhere.neocities.org
//check out my website at elswhere.neocities.org
//released under the MIT lisence
//released under the MIT lisence




//actual code below
//actual code below
//inject html
//inject html


document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("DOMContentLoaded", () => {
mountPlayer(); // put the HTML into #player
mountPlayer(); // put the HTML into #player
initPlayerUI(); // query elements & attach listeners
initPlayerUI(); // query elements & attach listeners
});
});



//if you want any parts of the playlist to look differently, change it in the section below
const playerHTML = `
const playerHTML = `
<button id="prev" aria-label="Previous">⏮</button>
<button id="prev" aria-label="Previous">⏮</button>
<button id="play" aria-label="Play/Pause">▶</button>
<button id="play" aria-label="Play/Pause">▶</button>
<button id="next" aria-label="Next">⏭</button>
<button id="next" aria-label="Next">⏭</button>
<button id="shuffle" aria-label="Shuffle">🔀</button>
<button id="shuffle" aria-label="Shuffle">🔀</button>
<button id="repeat" aria-label="Repeat">🔁</button>
<button id="repeat" aria-label="Repeat">🔁</button>
<div class="track">
<div class="track">
<div class="title-wrap">
<div class="title-wrap">
<div class="title" id="title">-</div>
<div class="title" id="title" style="font-family: 'VT323';">-</div>
</div>
</div>
<div class="progress" id="progress">
<div class="progress" id="progress">
<div class="progress-bar" id="progressBar"></div>
<div class="progress-bar" id="progressBar"></div>
</div>
</div>
<div class="time" >
<div class="time" >
<span id="currentTime" style="font-family: 'VT323';">0:00</span>
<span id="currentTime" style="font-family: 'VT323';">0:00</span>
<span id="duration" style="font-family: 'VT323';">0:00</span>
<span id="duration" style="font-family: 'VT323';">0:00</span>
</div>
</div>
</div>
</div>


<button id="btnPlaylist" aria-haspopup="listbox" aria-expanded="false" aria-controls="playlistPanel">🎵</button>
<button id="btnPlaylist" aria-haspopup="listbox" aria-expanded="false" aria-controls="playlistPanel">🎵</button>
<div id="playlistPanel" class="playlist" role="listbox" tabindex="-1" hidden style="font-family: 'VT323';"></div>
<div id="playlistPanel" class="playlist" role="listbox" tabindex="-1" hidden style="font-family: 'VT323';"></div>




<input id="volume" type="range" min="0" max="1" step="0.01" value="0.5" aria-label="Volume">`;
<input id="volume" type="range" min="0" max="1" step="0.01" value="0.5" aria-label="Volume">`;


function mountPlayer(opts={}) {
function mountPlayer(opts={}) {
const root = document.getElementById("player");
const root = document.getElementById("player");
if (!root) return; // page without a player
if (!root) return; // page without a player
root.innerHTML = playerHTML;
root.innerHTML = playerHTML;
}
}



function initPlayerUI(opts={}) {
function initPlayerUI(opts={}) {


showConsoleCredit(opts);
showConsoleCredit(opts);
let path = window.location.pathname;
let path = window.location.pathname;


//IMPORTANT, LOOK HERE!
//the below section is to make sure that this player can work on every page on your website, not just the root pages!
//to make it work for your site simply change the name of the folders in path.includes.
//for example, if you have sub pages, put it in the path.includes above rootPath="../", sub sub pages and then you put it above rootPath="../../", so on and so forth.

let rootPath = "";
let rootPath = "";
if( //sub sub sub pages
if(
path.includes("extra/mediarec/")
path.includes("extra/mediarec/")
) {
) {
rootPath = "../../../";
rootPath = "../../../";
} else if ( //sub sub pages
} else if (
path.includes("blog/posts/")
path.includes("blog/posts/") ||
path.includes ("extra/screenshots/")
) {
) {
rootPath = "../../";
rootPath = "../../";
} else if ( //sub pages
} else if (
path.includes("blog/") ||
path.includes("blog/") ||
path.includes("artworks/") ||
path.includes("artworks/") ||
path.includes("about/") ||
path.includes("about/") ||
path.includes("extra/")
path.includes("extra/") ||
path.includes('coding-proj/')
) {
) {
rootPath = "../";
rootPath = "../";
} else {
} else {
rootPath = ""; //main index.html
rootPath = ""; //main index.html
}
}




// oh my lord so many consts
// oh my lord so many consts
const audio = document.getElementById("audio");
const audio = document.getElementById("audio");
const btnPlay = document.getElementById("play");
const btnPlay = document.getElementById("play");
const btnNext = document.getElementById("next");
const btnNext = document.getElementById("next");
const btnPrev = document.getElementById("prev");
const btnPrev = document.getElementById("prev");
const btnShuffle = document.getElementById("shuffle");
const btnShuffle = document.getElementById("shuffle");
const btnRepeat = document.getElementById("repeat");
const btnRepeat = document.getElementById("repeat");
const titleEl = document.getElementById("title");
const titleEl = document.getElementById("title");
const progress = document.getElementById("progress");
const progress = document.getElementById("progress");
const progressBar = document.getElementById("progressBar");
const progressBar = document.getElementById("progressBar");
const volume = document.getElementById("volume");
const volume = document.getElementById("volume");


let list = [];
let list = [];
let index = 0;
let index = 0;
let repeat = "all";
let repeat = "all";
let shuffle = false;
let shuffle = false;

const BASE_URL = "";
function resolveSrc(p) {
return /^https?:\/\//.test(p) ? p : BASE_URL + p;
}
// 1) fetch playlist.json
// 1) fetch playlist.json
fetch(`${rootPath}music/playlist.json`, {cache: 'no-store'})
fetch(`${rootPath}music-player/playlist.json`, {cache: 'no-store'})
.then((r) => r.json())
.then((r) => r.json())
.then((tracks) => {
.then((tracks) => {
list = tracks;
list = tracks;
//load playlist
//load playlist
list = tracks;
list = tracks;
renderPlaylist();
renderPlaylist();
restoreStateThenLoad();
restoreStateThenLoad();


load(index);
load(index);

audio.crossOrigin = "anonymous";
audio.src = resolveSrc(t.src);
audio.load();
})
})
.catch((err) => console.error("Playlist load failed:", err));
.catch((err) => console.error("Playlist load failed:", err));




//1.1) restore state
//1.1) restore state
function restoreStateThenLoad() {
function restoreStateThenLoad() {
let saved = null;
let saved = null;
try { saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null'); } catch {}
try { saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null'); } catch {}
// Defaults if nothing saved yet
// Defaults if nothing saved yet
let i = 0, resumeTime = 0, resumePlay = false;
let i = 0, resumeTime = 0, resumePlay = false;


if (saved) {
if (saved) {
// Prefer matching by src (safer if playlist order changed)
// Prefer matching by src (safer if playlist order changed)
const bySrc = saved.src ? list.findIndex(t => t.src === saved.src) : -1;
const bySrc = saved.src ? list.findIndex(t => t.src === saved.src) : -1;
i = bySrc >= 0 ? bySrc : (
i = bySrc >= 0 ? bySrc : (
Number.isInteger(saved.index) && saved.index >= 0 && saved.index < list.length
Number.isInteger(saved.index) && saved.index >= 0 && saved.index < list.length
? saved.index : 0
? saved.index : 0
);
);


// restore modes + volume
// restore modes + volume
if (typeof saved.shuffle === 'boolean') shuffle = saved.shuffle;
if (typeof saved.shuffle === 'boolean') shuffle = saved.shuffle;
if (saved.repeat) repeat = saved.repeat;
if (saved.repeat) repeat = saved.repeat;
if (typeof saved.vol === 'number') {
if (typeof saved.vol === 'number') {
audio.volume = Math.min(1, Math.max(0, saved.vol));
audio.volume = Math.min(1, Math.max(0, saved.vol));
volume && (volume.value = audio.volume);
volume && (volume.value = audio.volume);
}
}


resumeTime = Number(saved.time) || 0;
resumeTime = Number(saved.time) || 0;
resumePlay = !!saved.playing;
resumePlay = !!saved.playing;
}
}


// Load the track now
// Load the track now
load(i);
load(i);


const onMeta = () => {
const onMeta = () => {
audio.removeEventListener('loadedmetadata', onMeta);
audio.removeEventListener('loadedmetadata', onMeta);
if (isFinite(audio.duration)) {
if (isFinite(audio.duration)) {
audio.currentTime = Math.min(resumeTime, audio.duration - 0.25);
audio.currentTime = Math.min(resumeTime, audio.duration - 0.25);
}
}
if (resumePlay) {
if (resumePlay) {
const resumeOnce = () => {
const resumeOnce = () => {
play(); // your existing play()
play(); // your existing play()
document.removeEventListener('click', resumeOnce, true);
document.removeEventListener('click', resumeOnce, true);
document.removeEventListener('keydown', resumeOnce, true);
document.removeEventListener('keydown', resumeOnce, true);
};
};
// any click or keypress will resume
// any click or keypress will resume
document.addEventListener('click', resumeOnce, true);
document.addEventListener('click', resumeOnce, true);
document.addEventListener('keydown', resumeOnce, true);
document.addEventListener('keydown', resumeOnce, true);
// Optional: show a tiny “tap to resume ▶” hint near the player
// Optional: show a tiny “tap to resume ▶” hint near the player
}
}
};
};
audio.addEventListener('loadedmetadata', onMeta);
audio.addEventListener('loadedmetadata', onMeta);
}
}


//1.5) title scrolling
//1.5) title scrolling


const titleWrap = document.querySelector(".title-wrap");
const titleWrap = document.querySelector(".title-wrap");
const titleMarquee = document.getElementById("title");
const titleMarquee = document.getElementById("title");


function setTitle(text) {
function setTitle(text) {
// reset
// reset
titleMarquee.classList.remove("scroll");
titleMarquee.classList.remove("scroll");
titleMarquee.style.removeProperty("--scroll-distance");
titleMarquee.style.removeProperty("--scroll-distance");
titleMarquee.style.removeProperty("--duration");
titleMarquee.style.removeProperty("--duration");


// set fresh text
// set fresh text
titleMarquee.textContent = text;
titleMarquee.textContent = text;


// If it fits, don't animate
// If it fits, don't animate
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const needsScroll = titleMarquee.scrollWidth > titleWrap.clientWidth;
const needsScroll = titleMarquee.scrollWidth > titleWrap.clientWidth;
if (!needsScroll) return;
if (!needsScroll) return;


// Duplicate the text so it loops smoothly
// Duplicate the text so it loops smoothly
const gap = " \u00A0\u00A0\u00A0 ";
const gap = " \u00A0\u00A0\u00A0 ";
titleMarquee.innerHTML = `${text}${gap}${text}`;
titleMarquee.innerHTML = `${text}${gap}${text}`;


// Distance to travel = width of one copy
// Distance to travel = width of one copy
const distance = titleMarquee.scrollWidth / 2;
const distance = titleMarquee.scrollWidth / 2;


// Speed: ~40px/sec (tweak to taste)
// Speed: ~40px/sec (tweak to taste)
const duration = Math.max(10, distance / 40);
const duration = Math.max(10, distance / 40);


titleMarquee.style.setProperty("--scroll-distance", distance + "px");
titleMarquee.style.setProperty("--scroll-distance", distance + "px");
titleMarquee.style.setProperty("--duration", duration + "s");
titleMarquee.style.setProperty("--duration", duration + "s");
titleMarquee.classList.add("scroll");
titleMarquee.classList.add("scroll");
});
});
}
}


//1.6) time marker
//1.6) time marker
const curEl = document.getElementById("currentTime");
const curEl = document.getElementById("currentTime");
const durEl = document.getElementById("duration");
const durEl = document.getElementById("duration");


// mm:ss (or h:mm:ss if long)
// mm:ss (or h:mm:ss if long)
function fmtTime(s) {
function fmtTime(s) {
if (!isFinite(s)) return "0:00";
if (!isFinite(s)) return "0:00";
s = Math.floor(s);
s = Math.floor(s);
const h = Math.floor(s / 3600);
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
const m = Math.floor((s % 3600) / 60);
const sec = String(s % 60).padStart(2, "0");
const sec = String(s % 60).padStart(2, "0");
return h ? `${h}:${String(m).padStart(2, "0")}:${sec}` : `${m}:${sec}`;
return h ? `${h}:${String(m).padStart(2, "0")}:${sec}` : `${m}:${sec}`;
}
}


//1.7) Playlist panel
//1.7) Playlist panel


const btnPlaylist = document.getElementById("btnPlaylist");
const btnPlaylist = document.getElementById("btnPlaylist");
const panel = document.getElementById("playlistPanel");
const panel = document.getElementById("playlistPanel");


function renderPlaylist() {
function renderPlaylist() {
if (!panel) return;
if (!panel) return;
panel.innerHTML = list
panel.innerHTML = list
.map(
.map(
(t, i) => `
(t, i) => `
<div class="playlist_item" role="option"
<div class="playlist_item" role="option"
data-index="${i}"
data-index="${i}"
aria-selected="${i === index}">
aria-selected="${i === index}">
<div class="playlist_title">${t.artist}${t.title ? " — " + t.title : ""}</div>
<div class="playlist_title">${t.artist}${t.title ? " — " + t.title : ""}</div>
</div>
</div>
`
`
)
)
.join("");
.join("");
}
}


function prefetchDurations() {
function prefetchDurations() {
list.forEach((t, i) => {
list.forEach((t, i) => {
if (t._durFetched) return;
if (t._durFetched) return;
t._durFetched = true;
t._durFetched = true;
const a = new Audio();
const a = new Audio();
a.crossOrigin = "anonymous"; // helps metadata on some hosts
a.crossOrigin = "anonymous"; // helps metadata on some hosts
a.preload = "metadata";
a.preload = "metadata";
a.src = resolveSrc(t.src);
a.src = resolveSrc(t.src);
a.src = t.src;
a.addEventListener(
a.addEventListener(
"loadedmetadata",
"loadedmetadata",
() => {
() => {
t._dur = fmtTime(a.duration);
t._dur = fmtTime(a.duration);
const row = panel.querySelector(`.playlist_item[data-index="${i}"]`);
const row = panel.querySelector(`.playlist_item[data-index="${i}"]`);
if (row && !row.querySelector(".playlist_dur")) {
if (row && !row.querySelector(".playlist_dur")) {
row.insertAdjacentHTML(
row.insertAdjacentHTML(
"beforeend",
"beforeend",
`<div class="playlist_dur">${t._dur}</div>`
`<div class="playlist_dur">${t._dur}</div>`
);
);
}
}
},
},
{ once: true }
{ once: true }
);
);
});
});
}
}


function openPlaylist() {
function openPlaylist() {
panel.hidden = false;
panel.hidden = false;
panel.setAttribute("aria-open", "true");
panel.setAttribute("aria-open", "true");
btnPlaylist.setAttribute("aria-expanded", "true");
btnPlaylist.setAttribute("aria-expanded", "true");
panel.focus();
panel.focus();
}
}
function closePlaylist() {
function closePlaylist() {
panel.hidden = true;
panel.hidden = true;
panel.setAttribute("aria-open", "false");
panel.setAttribute("aria-open", "false");
btnPlaylist.setAttribute("aria-expanded", "false");
btnPlaylist.setAttribute("aria-expanded", "false");
setTimeout(() => (panel.hidden = true), 150);
setTimeout(() => (panel.hidden = true), 150);
}
}


// Button click toggles
// Button click toggles
btnPlaylist.addEventListener("click", () => {
btnPlaylist.addEventListener("click", () => {
panel.hidden ? openPlaylist() : closePlaylist();
panel.hidden ? openPlaylist() : closePlaylist();
});
});


// Close on outside click
// Close on outside click
document.addEventListener("click", (e) => {
document.addEventListener("click", (e) => {
if (panel.hidden) return;
if (panel.hidden) return;
const within = panel.contains(e.target) || btnPlaylist.contains(e.target);
const within = panel.contains(e.target) || btnPlaylist.contains(e.target);
if (!within) closePlaylist();
if (!within) closePlaylist();
});
});


// Close on Esc
// Close on Esc
panel.addEventListener("keydown", (e) => {
panel.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
if (e.key === "Escape") {
closePlaylist();
closePlaylist();
btnPlaylist.focus();
btnPlaylist.focus();
}
}
});
});


panel.addEventListener("click", (e) => {
panel.addEventListener("click", (e) => {
const row = e.target.closest(".playlist_item");
const row = e.target.closest(".playlist_item");
if (!row) return;
if (!row) return;
const i = +row.dataset.index;
const i = +row.dataset.index;
load(i);
load(i);
play();
play();
renderPlaylist(); // refresh highlight
renderPlaylist(); // refresh highlight
closePlaylist();
closePlaylist();
});
});


function focusActiveRow() {
function focusActiveRow() {
const row = panel.querySelector(`.playlist_item[data-index="${index}"]`);
const row = panel.querySelector(`.playlist_item[data-index="${index}"]`);
row?.scrollIntoView({ block: "nearest" });
row?.scrollIntoView({ block: "nearest" });
}
}




//1.9) save state
//1.9) save state
//9) remember play progress
//9) remember play progress


function throttle (fn, ms) {
function throttle (fn, ms) {
let t = 0;
let t = 0;
return (...args) => {
return (...args) => {
const now = Date.now();
const now = Date.now();
if (now - t >= ms) {
if (now - t >= ms) {
t = now;
t = now;
fn(...args);
fn(...args);
}
}
}
}
}
}


const STORAGE_KEY = 'localPlayer';
const STORAGE_KEY = 'localPlayer';


const saveState = throttle(() => {
const saveState = throttle(() => {
try {
try {
localStorage.setItem(
localStorage.setItem(
STORAGE_KEY,
STORAGE_KEY,
JSON.stringify({
JSON.stringify({
index: index,
index: index,
src: list[index]?.src || null,
src: list[index]?.src || null,
time: audio.currentTime || 0,
time: audio.currentTime || 0,
vol: audio.volume,
vol: audio.volume,
shuffle,
shuffle,
repeat,
repeat,
playing: !audio.paused,
playing: !audio.paused,
updatedAT: Date.now()
updatedAT: Date.now()
})
})
);
);
} catch (e) {
} catch (e) {
console.warn("Failed to save player state:", e);
console.warn("Failed to save player state:", e);
}
}
}, 500);
}, 500);






// 2) load a track into the audio tag
// 2) load a track into the audio tag
function load(i) {
function load(i) {
index = (i + list.length) % list.length;
index = (i + list.length) % list.length;
const t = list[index];
const t = list[index];
audio.src = t.src; // direct MP3/OGG URL
audio.src = t.src; // direct MP3/OGG URL
setTitle(`${t.artist} - ${t.title ?? ""}`.trim());
setTitle(`${t.artist} - ${t.title ?? ""}`.trim());
curEl.textContent = "0:00";
curEl.textContent = "0:00";
durEl.textContent = "0:00";
durEl.textContent = "0:00";
saveState();// save current state
saveState();// save current state
// load playlist
// load playlist
renderPlaylist();
renderPlaylist();
prefetchDurations();
prefetchDurations();
focusActiveRow();
focusActiveRow();
}
}


// 2.5) update time markers when loaded
// 2.5) update time markers when loaded


audio.addEventListener("loadedmetadata", () => {
audio.addEventListener("loadedmetadata", () => {
durEl.textContent = fmtTime(audio.duration);
durEl.textContent = fmtTime(audio.duration);
curEl.textContent = fmtTime(audio.currentTime || 0);
curEl.textContent = fmtTime(audio.currentTime || 0);
});
});


audio.addEventListener("timeupdate", () => {
audio.addEventListener("timeupdate", () => {
curEl.textContent = fmtTime(audio.currentTime);
curEl.textContent = fmtTime(audio.currentTime);
});
});


progress?.addEventListener("click", () => {
progress?.addEventListener("click", () => {
curEl.textContent = fmtTime(audio.currentTime);
curEl.textContent = fmtTime(audio.currentTime);
});
});


audio.addEventListener("ended", () => {
audio.addEventListener("ended", () => {
curEl.textContent = fmtTime(audio.duration);
curEl.textContent = fmtTime(audio.duration);
});
});




// 2.7) when to save state
// 2.7) when to save state


// when to save?
// when to save?


audio.addEventListener('timeupdate', saveState);
audio.addEventListener('timeupdate', saveState);
progress?.addEventListener('click', () => saveState());
progress?.addEventListener('click', () => saveState());
btnPlay.addEventListener('click', () => saveState());
btnPlay.addEventListener('click', () => saveState());
volume.addEventListener('input', () => saveState());
volume.addEventListener('input', () => saveState());
btnShuffle.addEventListener('click', () => { shuffle = !shuffle; saveState(); });
btnShuffle.addEventListener('click', () => { shuffle = !shuffle; saveState(); });
btnRepeat.addEventListener('click', () => { /* cycle repeat */ saveState(); });
btnRepeat.addEventListener('click', () => { /* cycle repeat */ saveState(); });
window.addEventListener('pagehide', saveState);
window.addEventListener('pagehide', saveState);






// 3) play/pause logic
// 3) play/pause logic
function play() {
function play() {
audio
audio
.play()
.play()
.then(() => {
.then(() => {
btnPlay.textContent = "⏸";
btnPlay.textContent = "⏸";
})
})
.catch((err) => {
.catch((err) => {
console.warn("Play failed (likely autoplay policy):", err);
console.warn("Play failed (likely autoplay policy):", err);
});
});
}
}
function pause() {
function pause() {
audio.pause();
audio.pause();
btnPlay.textContent = "▶";
btnPlay.textContent = "▶";
}
}


// 4) wire up buttons
// 4) wire up buttons
btnPlay.addEventListener("click", () => (audio.paused ? play() : pause()));
btnPlay.addEventListener("click", () => (audio.paused ? play() : pause()));
btnNext.addEventListener("click", () => {
btnNext.addEventListener("click", () => {
next();
next();
play();
play();
});
});
btnPrev.addEventListener("click", () => {
btnPrev.addEventListener("click", () => {
prev();
prev();
play();
play();
});
});


function next() {
function next() {
if (shuffle) {
if (shuffle) {
let j;
let j;
do {
do {
j = Math.floor(Math.random() * list.length);
j = Math.floor(Math.random() * list.length);
} while (j === index && list.length > 1);
} while (j === index && list.length > 1);
load(j);
load(j);
} else {
} else {
load(index + 1);
load(index + 1);
}
}
}
}
function prev() {
function prev() {
load(index - 1);
load(index - 1);
}
}


// 5) time/progress UI
// 5) time/progress UI


audio.addEventListener("timeupdate", () => {
audio.addEventListener("timeupdate", () => {
if (!isFinite(audio.duration)) return;
if (!isFinite(audio.duration)) return;
const pct = (audio.currentTime / audio.duration) * 100;
const pct = (audio.currentTime / audio.duration) * 100;
progressBar.style.width = `${pct}%`;
progressBar.style.width = `${pct}%`;
});
});


// click to seek
// click to seek
progress.addEventListener("click", (e) => {
progress.addEventListener("click", (e) => {
const rect = progress.getBoundingClientRect();
const rect = progress.getBoundingClientRect();
const pct = (e.clientX - rect.left) / rect.width;
const pct = (e.clientX - rect.left) / rect.width;
if (isFinite(audio.duration)) audio.currentTime = pct * audio.duration;
if (isFinite(audio.duration)) audio.currentTime = pct * audio.duration;
});
});


// 6) when a song ends → next depending on repeat mode
// 6) when a song ends → next depending on repeat mode
audio.addEventListener("ended", () => {
audio.addEventListener("ended", () => {
if (repeat === "one") {
if (repeat === "one") {
audio.currentTime = 0;
audio.currentTime = 0;
play();
play();
return;
return;
}
}
if (repeat === "all") {
if (repeat === "all") {
next();
next();
play();
play();
return;
return;
}
}
// 'off'
// 'off'
btnPlay.textContent = "▶️";
btnPlay.textContent = "▶️";
});
});


// 7) volume (and remember it)
// 7) volume (and remember it)
const savedVol = +localStorage.getItem("vol");
const savedVol = +localStorage.getItem("vol");
if (!Number.isNaN(savedVol)) {
if (!Number.isNaN(savedVol)) {
volume.value = savedVol;
volume.value = savedVol;
audio.volume = savedVol;
audio.volume = savedVol;
}
}
volume.addEventListener("input", () => {
volume.addEventListener("input", () => {
audio.volume = +volume.value;
audio.volume = +volume.value;
localStorage.setItem("vol", volume.value);
localStorage.setItem("vol", volume.value);
});
});


//8) shuffle and repeat
//8) shuffle and repeat


btnShuffle.addEventListener("click", () => {
btnShuffle.addEventListener("click", () => {
shuffle = !shuffle;
shuffle = !shuffle;
btnShuffle.style.opacity = shuffle ? "1" : ".5";
btnShuffle.style.opacity = shuffle ? "1" : ".5";
});
});


btnRepeat.addEventListener("click", () => {
btnRepeat.addEventListener("click", () => {
repeat = repeat === "all" ? "one" : repeat === "one" ? "off" : "all";
repeat = repeat === "all" ? "one" : repeat === "one" ? "off" : "all";
btnRepeat.textContent = repeat === "one" ? "🔂" : "🔁";
btnRepeat.textContent = repeat === "one" ? "🔂" : "🔁";
btnRepeat.style.opacity = repeat === "off" ? ".5" : "1";
btnRepeat.style.opacity = repeat === "off" ? ".5" : "1";
});
});
} //end of initPlayerUI
} //end of initPlayerUI




//9) remember play progress
//9) remember play progress


function throttle (fn, ms) {
function throttle (fn, ms) {
let t = 0;
let t = 0;
return (...args) => {
return (...args) => {
const now = Date.now();
const now = Date.now();
if (now - t >= ms) {
if (now - t >= ms) {
t = now;
t = now;
fn(...args);
fn(...args);
}
}
}
}
}
}


//don't change the section below or else i will b really sad ------
//don't change the section below or else i will b really sad ------


function showConsoleCredit(opts = {}) {
function showConsoleCredit(opts = {}) {
// allow turning it off: init({ credit:false })
// allow turning it off: init({ credit:false })
if (opts.credit === false) return;
if (opts.credit === false) return;


// avoid duplicates (per page load)
// avoid duplicates (per page load)
if (window.__lilacCreditShown) return;
if (window.__lilacCreditShown) return;
window.__lilacCreditShown = true;
window.__lilacCreditShown = true;




const NAME = "elsie's music player widget";
const NAME = "elsie's music player widget";
const VERSION = '1.0.0';
const VERSION = '1.0.0';
const URL = 'https://github.com/elsieeeyjd/music-player-bar';
const URL = 'https://github.com/elsieeeyjd/music-player-bar';


const credStart = "this lovely site uses: ";
const credStart = "this lovely site uses: ";
const tag = `${NAME} v${VERSION} `;
const tag = `${NAME} v${VERSION} `;
const style = 'background:#8b5cf6;color:#fff;padding:2px 8px;border-radius:8px;font-weight:600';
const style = 'background:#8b5cf6;color:#fff;padding:2px 8px;border-radius:8px;font-weight:600';
const msg = `by elsie — MIT — ${URL}`;
const msg = `by elsie — MIT — ${URL}`;


console.info(`${credStart}%c${tag}%c ${msg}`, style, '');
console.info(`${credStart}%c${tag}%c ${msg}`, style, '');
}
}




//------------------
//------------------


const STORAGE_KEY = 'elsiePlayer';
const STORAGE_KEY = 'elsiePlayer';


const saveState = throttle(() => {
const saveState = throttle(() => {
try {
try {
localStorage.setItem(
localStorage.setItem(
STORAGE_KEY,
STORAGE_KEY,
JSON.stringify({
JSON.stringify({
index: index,
index: index,
src: list[index]?.src || null,
src: list[index]?.src || null,
time: audio.currentTime || 0,
time: audio.currentTime || 0,
vol: audio.volume,
vol: audio.volume,
shuffle,
shuffle,
repeat,
repeat,
playing: !audio.paused,
playing: !audio.paused,
updatedAT: Date.now()
updatedAT: Date.now()
})
})
);
);
} catch (e) {
} catch (e) {
console.warn("Failed to save player state:", e);
console.warn("Failed to save player state:", e);
}
}
}, 500);
}, 500);