1050 lines
30 KiB
JavaScript
1050 lines
30 KiB
JavaScript
let mediaDir = "media/";
|
|
let mainContainerEl = document.getElementById("mainContainer");
|
|
let playerEl = document.getElementById("player");
|
|
let tracksEl = document.getElementById("tracks");
|
|
let descriptionEl = document.getElementById("description");
|
|
let bigButtonEl = document.getElementById("bigButton");
|
|
let controlsEl = document.getElementById("controls");
|
|
let scrubberTrackContainerEl = document.getElementById(
|
|
"scrubberTrackContainer"
|
|
);
|
|
let scrubberTrackPreviewEl = document.getElementById("scrubberTrackPreview");
|
|
let scrubberTrackEl = document.getElementById("scrubberTrack");
|
|
let scrubberFillEl = document.getElementById("scrubberFill");
|
|
let scrubberEl = document.getElementById("scrubber");
|
|
let nextTrackEl = document.getElementById("nextTrack");
|
|
let prevTrackEl = document.getElementById("prevTrack");
|
|
let loopSwitchEl = document.getElementById("loopSwitch");
|
|
|
|
let titleEl = document.getElementById("title");
|
|
let titleContainerEl = document.getElementById("titleContainer");
|
|
let mediaColumnEl = document.getElementById("mediaColumn");
|
|
let mediaContainerEl = document.getElementById("mediaContainer");
|
|
let mediaInfoEl = document.getElementById("mediaInfo");
|
|
let mediaImageEl = document.getElementById("mediaImage");
|
|
let mediaVideoEl = document.getElementById("mediaVideo");
|
|
|
|
let volumeContainerEl = document.getElementById("volumeContainer");
|
|
let volumeTrackContainerEl = document.getElementById("volumeTrackContainer");
|
|
let volumeTrackEl = document.getElementById("volumeTrack");
|
|
let volumeFillEl = document.getElementById("volumeFill");
|
|
let volumeIconEl = document.getElementById("volumeIcon");
|
|
|
|
let currentEntry = {};
|
|
let media = [];
|
|
|
|
let scrubPosition = 0;
|
|
|
|
let volumeIcons = ["icon-volume-none", "icon-volume-low", "icon-volume-high"];
|
|
let volume = 0.8;
|
|
let muted = false;
|
|
|
|
let loadedFirst = false;
|
|
let autoPlay = false;
|
|
let loopMode = "none";
|
|
|
|
let config = {};
|
|
|
|
function init() {
|
|
fetch("config.json")
|
|
.then((res) => res.json())
|
|
.then((data) => {
|
|
update(data);
|
|
})
|
|
.catch(console.error);
|
|
}
|
|
init();
|
|
|
|
function updateTheme(theme) {
|
|
config.theme = theme;
|
|
let rootEl = document.documentElement;
|
|
if (theme.primaryColor)
|
|
rootEl.style.setProperty("--primaryColor", theme.primaryColor);
|
|
if (theme.primaryAltColor)
|
|
rootEl.style.setProperty("--primaryAltColor", theme.primaryAltColor);
|
|
if (theme.primaryTextColor)
|
|
rootEl.style.setProperty("--primaryTextColor", theme.primaryTextColor);
|
|
if (theme.primaryAltTextColor)
|
|
rootEl.style.setProperty(
|
|
"--primaryAltTextColor",
|
|
theme.primaryAltTextColor
|
|
);
|
|
if (theme.secondaryColor)
|
|
rootEl.style.setProperty("--secondaryColor", theme.secondaryColor);
|
|
if (theme.highlightColor)
|
|
rootEl.style.setProperty("--highlightColor", theme.highlightColor);
|
|
if (theme.backgroundColor)
|
|
rootEl.style.setProperty("--backgroundColor", theme.backgroundColor);
|
|
if (theme.previewStripeColor1)
|
|
rootEl.style.setProperty(
|
|
"--previewStripeColor1",
|
|
theme.previewStripeColor1
|
|
);
|
|
if (theme.previewStripeColor2)
|
|
rootEl.style.setProperty(
|
|
"--previewStripeColor2",
|
|
theme.previewStripeColor2
|
|
);
|
|
if (theme.linkColor) rootEl.style.setProperty("--linkColor", theme.linkColor);
|
|
if (theme.overlayTextColor)
|
|
rootEl.style.setProperty("--overlayTextColor", theme.overlayTextColor);
|
|
if (theme.overlayBackgroundColor)
|
|
rootEl.style.setProperty(
|
|
"--overlayBackgroundColor",
|
|
theme.overlayBackgroundColor
|
|
);
|
|
if (theme.layoutStyle) {
|
|
mainContainerEl.classList.remove("vertical", "horizontal");
|
|
mainContainerEl.classList.add(theme.layoutStyle);
|
|
}
|
|
if (theme.infoStyle) {
|
|
mainContainerEl.classList.remove(
|
|
"info-none",
|
|
"info-overlaid",
|
|
"info-below",
|
|
"info-overlaid-toggle"
|
|
);
|
|
mainContainerEl.classList.add("info-" + theme.infoStyle);
|
|
}
|
|
if (theme.titleStyle) {
|
|
mainContainerEl.classList.remove("title-none", "title-span");
|
|
mainContainerEl.classList.add("title-" + theme.titleStyle);
|
|
}
|
|
if (theme.contentWidth)
|
|
document.getElementById("contentContainer").style.maxWidth =
|
|
theme.contentWidth + "px";
|
|
if (theme.nativePlayer) {
|
|
document.getElementById("audio").classList.add("native");
|
|
mediaVideoEl.setAttribute("controls", "");
|
|
} else {
|
|
document.getElementById("audio").classList.remove("native");
|
|
mediaVideoEl.removeAttribute("controls");
|
|
}
|
|
if (theme.hideInfoDropdown) {
|
|
mainContainerEl.classList.add("hide-info");
|
|
} else {
|
|
mainContainerEl.classList.remove("hide-info");
|
|
}
|
|
mediaContainerEl.style.maxWidth = theme.coverSize + "px";
|
|
mediaContainerEl.style.maxHeight = theme.coverSize + "px";
|
|
mediaColumnEl.style.maxWidth = theme.coverSize + "px";
|
|
|
|
if (theme.customCSS !== undefined) {
|
|
document.querySelectorAll("#customCSS").forEach((el) => el.remove());
|
|
let styleEl = document.createElement("style");
|
|
styleEl.setAttribute("id", "customCSS");
|
|
styleEl.innerText = theme.customCSS;
|
|
document.head.appendChild(styleEl);
|
|
}
|
|
}
|
|
|
|
function loadContent(data) {
|
|
loadCover(data.cover);
|
|
mediaImageEl.style.width = config.theme.coverSize + "px";
|
|
mediaImageEl.style.height = config.theme.coverSize + "px";
|
|
if (data.media) loadMedia(data.media);
|
|
}
|
|
function updateTitle(title) {
|
|
if (title) {
|
|
titleContainerEl.classList.add("active");
|
|
config.title = title;
|
|
titleEl.innerHTML = title;
|
|
} else {
|
|
titleContainerEl.classList.remove("active");
|
|
}
|
|
}
|
|
function updateDescription(description) {
|
|
if (description === undefined || description === "") {
|
|
descriptionEl.classList.remove("active");
|
|
} else {
|
|
descriptionEl.classList.add("active");
|
|
config.description = description;
|
|
descriptionEl.innerHTML = description;
|
|
}
|
|
}
|
|
function updateLoopMode(mode) {
|
|
if (mode === undefined || mode === "") {
|
|
config.loopModeDefault = "none";
|
|
} else {
|
|
config.loopModeDefault = mode;
|
|
}
|
|
loopSwitch(config.loopModeDefault);
|
|
}
|
|
|
|
function localStorageSet(key, value) {
|
|
try {
|
|
localStorage.setItem(key, value);
|
|
} catch {}
|
|
}
|
|
|
|
function localStorageGet(key) {
|
|
let value;
|
|
try {
|
|
value = localStorage.getItem(key);
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function update(data) {
|
|
if (!data) {
|
|
data = config;
|
|
}
|
|
if (!data.theme) {
|
|
data.theme = {};
|
|
}
|
|
let title = data.title;
|
|
let theme = data.theme;
|
|
let description = data.description;
|
|
config = data;
|
|
|
|
loadContent(data);
|
|
updateTitle(title);
|
|
updateDescription(description);
|
|
updateLoopMode(data.loopModeDefault);
|
|
updateTheme(theme);
|
|
|
|
updateTrackPreview();
|
|
|
|
document.title = titleEl.textContent;
|
|
|
|
volume = parseFloat(localStorageGet("volume"));
|
|
muted = parseInt(localStorageGet("muted"));
|
|
if (volume && typeof volume === "number") {
|
|
setVolume(volume, muted);
|
|
} else {
|
|
volume = 0.8;
|
|
muted = false;
|
|
localStorageSet("volume", volume);
|
|
}
|
|
}
|
|
|
|
function loadMedia(list) {
|
|
if (list.length === 0) {
|
|
return;
|
|
}
|
|
loadedFirst = false;
|
|
autoPlay = false;
|
|
bigButtonEl.classList.remove("pause");
|
|
media = list.map((entry) => {
|
|
//return {file: entry.file, title: entry.title};
|
|
if (!/:\/\//.test(entry.file)) {
|
|
//if not a url
|
|
entry.file = mediaDir + entry.file;
|
|
}
|
|
entry.title = entry.title || entry.file;
|
|
return entry;
|
|
});
|
|
let featureIndex = 0;
|
|
tracksEl.textContent = "";
|
|
media.forEach((entry, i) => {
|
|
tracksEl.insertAdjacentHTML(
|
|
"beforeend",
|
|
`<div class="track ${entry.locked ? "locked" : ""}" ${
|
|
entry.locked
|
|
? 'title="This file is available in the full download"'
|
|
: ""
|
|
}>
|
|
<div class="main">
|
|
<button class="playButton ${
|
|
entry.locked ? "locked" : "loading"
|
|
}"></button>
|
|
<div class="details">
|
|
<div class="number">${i + 1}. </div>
|
|
<div class="title">${entry.title}</div>
|
|
<div class="duration"></div>
|
|
<div class="spacer"></div>
|
|
${
|
|
entry.info
|
|
? `<button class="toggleInfo icon-info"></button>`
|
|
: ""
|
|
}
|
|
</div>
|
|
</div>
|
|
${
|
|
entry.info
|
|
? `
|
|
<div class="infoContainer" inert>
|
|
<div class="info">
|
|
${entry.info}
|
|
</div>
|
|
</div>
|
|
`
|
|
: ""
|
|
}
|
|
</div>`
|
|
);
|
|
let trackEl = tracksEl.lastChild;
|
|
entry.trackEl = trackEl;
|
|
let buttonEl = trackEl.querySelector("button");
|
|
if (entry.feature) {
|
|
featureIndex = i;
|
|
}
|
|
if (entry.locked && featureIndex === i) {
|
|
featureIndex++;
|
|
}
|
|
if (entry.info) {
|
|
let infoContainerEl = trackEl.querySelector(".infoContainer");
|
|
trackEl.querySelector(".toggleInfo").addEventListener("click", (e) => {
|
|
infoContainerEl.style.height =
|
|
trackEl.querySelector(".info").clientHeight + "px";
|
|
entry.infoToggled = infoContainerEl.classList.toggle("active");
|
|
if (entry.info && entry.infoToggled) {
|
|
infoContainerEl.removeAttribute("inert");
|
|
} else {
|
|
infoContainerEl.setAttribute("inert", "");
|
|
}
|
|
if (config.theme.infoStyle === "overlaid-toggle") {
|
|
if (entry.info && entry.infoToggled) {
|
|
mediaInfoEl.innerHTML = entry.info;
|
|
mediaInfoContainer.classList.add("active");
|
|
} else {
|
|
mediaInfoEl.innerHTML = "";
|
|
mediaInfoContainer.classList.remove("active");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (!entry.previewFade && entry.previewFade !== false) {
|
|
entry.previewFade = true; //default true
|
|
}
|
|
if (typeof entry.loopCount === "number" && entry.loopCount > 0) {
|
|
entry.remainingLoops = entry.loopCount;
|
|
trackEl.classList.add("looped");
|
|
buttonEl.setAttribute("title", "loop count");
|
|
updateLoops(entry);
|
|
}
|
|
if (!entry.locked) {
|
|
buttonEl.onclick = () => {
|
|
loadedFirst = true;
|
|
autoPlay = true;
|
|
playEntry(entry);
|
|
};
|
|
let loaderEl;
|
|
if (entry.type === "video") {
|
|
loaderEl = document.createElement("video");
|
|
} else {
|
|
loaderEl = document.createElement("audio");
|
|
}
|
|
if (entry.previewStart || entry.previewEnd) {
|
|
entry.preview = true;
|
|
}
|
|
let needsPreviewEnd = false;
|
|
if (!entry.previewEnd) {
|
|
needsPreviewEnd = true;
|
|
}
|
|
entry.originalDuration =
|
|
(entry.originalDuration && fromHMS(entry.originalDuration)) || 1;
|
|
entry.previewStart =
|
|
(entry.previewStart && fromHMS(entry.previewStart)) || 0;
|
|
entry.previewEnd =
|
|
(entry.previewEnd && fromHMS(entry.previewEnd)) ||
|
|
entry.originalDuration;
|
|
entry.previewDuration = entry.previewEnd - entry.previewStart;
|
|
loaderEl.addEventListener("loadedmetadata", (e) => {
|
|
entry.duration = e.target.duration;
|
|
entry.needsDuration = false;
|
|
entry.previewStart = entry.previewStart || 0;
|
|
if (needsPreviewEnd) {
|
|
entry.previewEnd = entry.duration;
|
|
}
|
|
entry.originalDuration = Math.max(
|
|
entry.originalDuration,
|
|
entry.duration,
|
|
entry.previewEnd
|
|
);
|
|
entry.previewDuration = entry.previewEnd - entry.previewStart;
|
|
|
|
//make assumptions about the audio start points
|
|
if (entry.duration < entry.previewDuration) {
|
|
//smaller than given preview duration
|
|
//left align to preview
|
|
entry.audioStart = entry.previewStart;
|
|
} else if (
|
|
entry.duration <
|
|
entry.originalDuration - entry.previewStart
|
|
) {
|
|
//else, smaller than region between preview start and original end
|
|
//left align to preview
|
|
entry.audioStart = entry.previewStart;
|
|
} else if (entry.duration < entry.originalDuration) {
|
|
//else, smaller than original duration
|
|
//right align to original
|
|
entry.audioStart = entry.originalDuration - entry.duration;
|
|
} else {
|
|
//larger than original duration
|
|
//left align to original
|
|
entry.audioStart = 0;
|
|
}
|
|
entry.audioEnd = entry.audioStart + entry.duration;
|
|
|
|
let durationText = toHMS(entry.duration);
|
|
if (entry.preview) {
|
|
durationText += " (preview)";
|
|
}
|
|
trackEl.querySelector(".duration").textContent = durationText;
|
|
if (entry === currentEntry) {
|
|
updateTrackPreview();
|
|
// updateScrubPosition(entry.previewStart / entry.originalDuration);
|
|
updateScrubPosition(0);
|
|
updatePreviewVolume(0);
|
|
}
|
|
});
|
|
loaderEl.addEventListener("error", (e) => {
|
|
entry.error = true;
|
|
entry.errorMessage = e.target.error;
|
|
removeLoading(entry, true);
|
|
buttonEl.classList.add("error", "icon-warning");
|
|
buttonEl.setAttribute("title", "error loading file");
|
|
});
|
|
loaderEl.addEventListener("canplay", (e) => {
|
|
removeLoading(entry, true);
|
|
|
|
if (entry.feature && entry.autoplay) {
|
|
playerEl.play();
|
|
}
|
|
});
|
|
loaderEl.volume = 0;
|
|
loaderEl.src = entry.file;
|
|
entry.started = false;
|
|
entry.loading = true;
|
|
entry.loaderEl = loaderEl;
|
|
}
|
|
});
|
|
loadEntry(media[mod(featureIndex, media.length)]);
|
|
}
|
|
|
|
function loadCover(cover) {
|
|
if (cover) {
|
|
let src = cover;
|
|
if (mediaImageEl.src == src) {
|
|
return; //already loaded
|
|
}
|
|
if (!/:\/\//.test(src)) {
|
|
//if not a url
|
|
src = mediaDir + src;
|
|
}
|
|
mediaImageEl.src = src;
|
|
mediaImageEl.classList.add("active");
|
|
mediaImageEl.style.visibility = "visible";
|
|
} else {
|
|
mediaImageEl.style.visibility = "hidden";
|
|
}
|
|
}
|
|
|
|
function setCover(cover, size) {
|
|
if (cover) {
|
|
config.cover = cover;
|
|
size && (config.coverSize = size);
|
|
loadCover(cover);
|
|
}
|
|
}
|
|
|
|
playerEl.addEventListener("canplay", (e) => {
|
|
removeLoading(currentEntry);
|
|
if (currentEntry.needsDuration) {
|
|
currentEntry.duration = playerEl.duration;
|
|
currentEntry.needsDuration = false;
|
|
}
|
|
if (autoPlay && !currentEntry.started) {
|
|
currentEntry.started = true;
|
|
if (!currentEntry.needsDuration) {
|
|
playerEl.currentTime =
|
|
currentEntry.previewStart - currentEntry.audioStart;
|
|
}
|
|
playerEl.play();
|
|
}
|
|
if (!loadedFirst) {
|
|
autoPlay = true;
|
|
}
|
|
});
|
|
playerEl.addEventListener("waiting", (e) => {
|
|
setLoading(currentEntry, 0);
|
|
});
|
|
playerEl.addEventListener("play", (e) => {
|
|
if (
|
|
currentEntry.preview &&
|
|
playerEl.currentTime < currentEntry.previewStart - currentEntry.audioStart
|
|
) {
|
|
playerEl.currentTime = currentEntry.previewStart - currentEntry.audioStart;
|
|
}
|
|
bigButtonEl.classList.add("pause");
|
|
currentEntry.started = true;
|
|
currentEntry.trackEl.querySelector("button").classList.add("pause");
|
|
if (currentEntry.type === "video") {
|
|
mediaVideoEl.play();
|
|
}
|
|
});
|
|
playerEl.addEventListener("pause", (e) => {
|
|
bigButtonEl.classList.remove("pause");
|
|
currentEntry.trackEl.querySelector("button").classList.remove("pause");
|
|
if (currentEntry.type === "video") {
|
|
mediaVideoEl.pause();
|
|
}
|
|
});
|
|
playerEl.addEventListener("volumechange", (e) => {
|
|
if (config.theme.nativePlayer) {
|
|
volume = e.target.volume;
|
|
localStorageSet("volume", volume);
|
|
}
|
|
if (currentEntry.type === "video") {
|
|
if (config.theme.nativePlayer) {
|
|
mediaVideoEl.volume = e.target.volume;
|
|
} else {
|
|
mediaVideoEl.volume = 0;
|
|
}
|
|
}
|
|
});
|
|
mediaVideoEl.addEventListener("volumechange", (e) => {
|
|
if (currentEntry.type === "video" && config.theme.nativePlayer) {
|
|
volume = e.target.volume;
|
|
localStorageSet("volume", volume);
|
|
}
|
|
});
|
|
playerEl.addEventListener("timeupdate", (e) => {
|
|
let buffer = 0.18;
|
|
if (!scrubberDragged) {
|
|
if (currentEntry.duration && !currentEntry.needsDuration) {
|
|
if (currentEntry.started) {
|
|
let time = e.target.currentTime;
|
|
if (time > currentEntry.previewEnd - currentEntry.audioStart - buffer) {
|
|
nextTrack(false);
|
|
} else {
|
|
let t = time / currentEntry.duration;
|
|
updatePreviewVolume(t);
|
|
updateScrubPosition(t);
|
|
}
|
|
} else {
|
|
}
|
|
} else {
|
|
let time = e.target.currentTime;
|
|
if (time > e.target.duration - buffer) {
|
|
nextTrack(false);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
playerEl.addEventListener("ended", (e) => {
|
|
if (!scrubberDragged) {
|
|
nextTrack(false);
|
|
}
|
|
});
|
|
mediaVideoEl.addEventListener("ended", (e) => {
|
|
if (currentEntry.type === "video" && config.theme.nativePlayer) {
|
|
nextTrack(false);
|
|
}
|
|
});
|
|
|
|
bigButtonEl.addEventListener("click", (e) => {
|
|
playEntry(currentEntry);
|
|
});
|
|
|
|
volumeIconEl.addEventListener("click", (e) => {
|
|
toggleMute();
|
|
});
|
|
|
|
let volumeDragged = false;
|
|
volumeTrackContainerEl.addEventListener("mousedown", (e) => {
|
|
volumeDragged = true;
|
|
scrubVolume(e.clientX);
|
|
});
|
|
|
|
let volumeHovered = false;
|
|
volumeContainerEl.addEventListener("mouseenter", (e) => {
|
|
if (!scrubberDragged) {
|
|
volumeHovered = true;
|
|
volumeContainerEl.classList.add("hover");
|
|
}
|
|
});
|
|
volumeContainerEl.addEventListener("mouseleave", (e) => {
|
|
volumeHovered = false;
|
|
if (!volumeDragged) {
|
|
volumeContainerEl.classList.remove("hover");
|
|
}
|
|
});
|
|
|
|
function toggleMute() {
|
|
muted = !muted;
|
|
setVolume(volume, muted);
|
|
}
|
|
|
|
function scrubVolume(x) {
|
|
if (volumeDragged) {
|
|
let trackX = volumeTrackEl.getBoundingClientRect().x;
|
|
x -= trackX;
|
|
let scrubberWidth = 6;
|
|
let trackWidth = volumeTrackEl.clientWidth;
|
|
let t =
|
|
1 -
|
|
Math.max(
|
|
0,
|
|
Math.min(1, (x - scrubberWidth / 2) / (trackWidth - scrubberWidth))
|
|
);
|
|
setVolume(t);
|
|
}
|
|
}
|
|
|
|
function endVolumeScrub() {
|
|
if (!volumeHovered) {
|
|
volumeContainerEl.classList.remove("hover");
|
|
}
|
|
}
|
|
|
|
function setVolume(vol, mute = false) {
|
|
volume = vol;
|
|
localStorageSet("volume", volume);
|
|
localStorageSet("muted", mute ? 1 : 0);
|
|
vol = mute ? 0 : vol;
|
|
if (currentEntry.type === "video") {
|
|
if (config.theme.nativePlayer) {
|
|
mediaVideoEl.volume = volume;
|
|
playerEl.volume = 0;
|
|
} else {
|
|
mediaVideoEl.volume = 0;
|
|
playerEl.volume = vol;
|
|
}
|
|
} else {
|
|
playerEl.volume = vol;
|
|
}
|
|
let scrubberWidth = 6;
|
|
let trackWidth = volumeTrackEl.clientWidth;
|
|
volumeFillEl.style.left = (1 - vol) * (trackWidth - scrubberWidth) + "px";
|
|
volumeIconEl.classList.remove(...volumeIcons);
|
|
if (vol <= 0) {
|
|
volumeIconEl.classList.add(volumeIcons[0]);
|
|
} else if (vol <= 0.5) {
|
|
volumeIconEl.classList.add(volumeIcons[1]);
|
|
} else {
|
|
volumeIconEl.classList.add(volumeIcons[2]);
|
|
}
|
|
}
|
|
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.key === " " && e.target === document.body) {
|
|
playEntry(currentEntry);
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
let scrubberDragged = false;
|
|
scrubberTrackContainerEl.addEventListener("pointerdown", (e) => {
|
|
scrubberDragged = true;
|
|
currentEntry.started = true;
|
|
scrub(e.clientX);
|
|
});
|
|
document.addEventListener("pointerup", (e) => {
|
|
if (scrubberDragged) {
|
|
scrubberDragged = false;
|
|
endScrub();
|
|
}
|
|
if (volumeDragged) {
|
|
volumeDragged = false;
|
|
endVolumeScrub();
|
|
}
|
|
});
|
|
document.addEventListener("pointermove", (e) => {
|
|
scrub(e.clientX);
|
|
scrubVolume(e.clientX);
|
|
});
|
|
document.addEventListener("pointercancel", (e) => {
|
|
console.log(e);
|
|
});
|
|
|
|
nextTrackEl.addEventListener("click", (e) => {
|
|
nextTrack();
|
|
});
|
|
prevTrackEl.addEventListener("click", (e) => {
|
|
prevTrack();
|
|
});
|
|
loopSwitchEl.addEventListener("click", (e) => {
|
|
loopSwitch();
|
|
});
|
|
|
|
function updateTrackPreview() {
|
|
if (currentEntry.needsDuration) {
|
|
return;
|
|
}
|
|
//reset to prevent compounding width calculation issues
|
|
scrubberTrackPreviewEl.style.removeProperty("margin-left");
|
|
scrubberTrackPreviewEl.style.removeProperty("width");
|
|
scrubberEl.style.removeProperty("left");
|
|
scrubberFillEl.style.removeProperty("width");
|
|
if (currentEntry.preview) {
|
|
let fullWidth = scrubberEl.parentElement.parentElement.clientWidth;
|
|
scrubberTrackPreviewEl.style.marginLeft =
|
|
(currentEntry.previewStart / currentEntry.originalDuration) * 100 + "%";
|
|
scrubberTrackPreviewEl.style.width =
|
|
((currentEntry.previewEnd - currentEntry.previewStart) /
|
|
currentEntry.originalDuration) *
|
|
100 +
|
|
"%";
|
|
}
|
|
}
|
|
|
|
function updatePreviewVolume(t) {
|
|
if (currentEntry.preview && currentEntry.previewFade && !muted) {
|
|
if (currentEntry.needsDuration) {
|
|
playEntry.volume = 0;
|
|
} else {
|
|
//rebase t to full (assumed) duration
|
|
t = currentEntry.audioStart + t * currentEntry.duration;
|
|
//clip to preview
|
|
t = Math.max(
|
|
currentEntry.previewStart,
|
|
Math.min(currentEntry.previewEnd, t)
|
|
);
|
|
let inTime = t - currentEntry.previewStart;
|
|
let outTime = currentEntry.previewEnd - t;
|
|
playerEl.volume =
|
|
volume * Math.max(0, Math.min(1, inTime / 1, outTime / 1)); //1 second fade in/out
|
|
}
|
|
}
|
|
}
|
|
|
|
//t: 0-1 value in relation to the actual audio start/end
|
|
function updateScrubPosition(t) {
|
|
if (currentEntry.needsDuration) {
|
|
return;
|
|
}
|
|
//rebase t to full (assumed) duration
|
|
t = currentEntry.audioStart + t * currentEntry.duration;
|
|
//clip to preview
|
|
t = Math.max(currentEntry.previewStart, Math.min(currentEntry.previewEnd, t));
|
|
scrubPosition = (t - currentEntry.audioStart) / currentEntry.duration;
|
|
let time = t;
|
|
//rebase
|
|
t = t / currentEntry.originalDuration;
|
|
//relative to preview region
|
|
let scrubberT =
|
|
(t * currentEntry.originalDuration - currentEntry.previewStart) /
|
|
currentEntry.previewDuration;
|
|
let scrubberWidth = scrubberEl.clientWidth;
|
|
let trackWidth = scrubberEl.parentElement.clientWidth;
|
|
|
|
scrubberEl.style.left =
|
|
((scrubberT * (trackWidth - scrubberWidth)) / trackWidth) * 100 + "%";
|
|
scrubberFillEl.style.width = scrubberT * 100 + "%";
|
|
|
|
//let time = timePosition * currentEntry.duration;
|
|
let timeText = "";
|
|
if (currentEntry.preview) {
|
|
timeText += "(preview)<br/>";
|
|
}
|
|
timeText += `${toHMS(time)} / ${
|
|
currentEntry.originalDuration
|
|
? toHMS(currentEntry.originalDuration)
|
|
: "0:00"
|
|
}`;
|
|
controlsEl.querySelector(".time").innerHTML = timeText;
|
|
}
|
|
|
|
function scrub(x) {
|
|
if (scrubberDragged) {
|
|
let trackX = scrubberTrackEl.getBoundingClientRect().x;
|
|
x -= trackX;
|
|
let scrubberWidth = scrubberEl.clientWidth;
|
|
let trackWidth = scrubberEl.parentElement.clientWidth;
|
|
let t = Math.max(
|
|
0,
|
|
Math.min(1, (x - scrubberWidth / 2) / (trackWidth - scrubberWidth))
|
|
);
|
|
if (currentEntry.preview) {
|
|
//rebase t from preview to audio start/end
|
|
t =
|
|
(t * currentEntry.previewDuration +
|
|
(currentEntry.previewStart - currentEntry.audioStart)) /
|
|
currentEntry.duration;
|
|
}
|
|
updateScrubPosition(t);
|
|
}
|
|
}
|
|
|
|
function endScrub() {
|
|
if (currentEntry.loading) {
|
|
return;
|
|
}
|
|
playerEl.currentTime = scrubPosition * playerEl.duration;
|
|
if (currentEntry.type === "video") {
|
|
mediaVideoEl.currentTime = scrubPosition * playerEl.duration;
|
|
}
|
|
}
|
|
|
|
function canPlay(entry) {
|
|
return !entry.error && !entry.locked;
|
|
}
|
|
|
|
function nextTrack(manual = true) {
|
|
if (!manual && loopMode === "track") {
|
|
loadEntry(currentEntry);
|
|
} else if (!manual && currentEntry.remainingLoops > 0) {
|
|
currentEntry.remainingLoops--;
|
|
updateLoops(currentEntry);
|
|
loadEntry(currentEntry);
|
|
} else {
|
|
let loop = manual || loopMode === "playlist";
|
|
let idx = media.indexOf(currentEntry) + 1;
|
|
if (!loop && idx >= media.length) {
|
|
return;
|
|
}
|
|
let entry = media[mod(idx, media.length)];
|
|
let i = 0;
|
|
while (!canPlay(entry) && i < media.length) {
|
|
i++;
|
|
idx++;
|
|
if (!loop && idx >= media.length) {
|
|
return;
|
|
}
|
|
entry = media[mod(idx, media.length)];
|
|
}
|
|
loadEntry(entry);
|
|
}
|
|
}
|
|
function prevTrack() {
|
|
if (
|
|
playerEl.currentTime >
|
|
currentEntry.previewStart - currentEntry.audioStart + 5
|
|
) {
|
|
playerEl.currentTime = currentEntry.previewStart - currentEntry.audioStart;
|
|
} else {
|
|
let idx = media.indexOf(currentEntry) - 1;
|
|
let entry = media[mod(idx, media.length)];
|
|
let i = 0;
|
|
while (!canPlay(entry) && i < media.length) {
|
|
i++;
|
|
idx--;
|
|
entry = media[mod(idx, media.length)];
|
|
}
|
|
loadEntry(entry);
|
|
}
|
|
}
|
|
function loopSwitch(mode) {
|
|
loopSwitchEl.classList.remove("icon-loop", "icon-loop-1");
|
|
loopSwitchEl.style.opacity = 1;
|
|
if (mode) {
|
|
loopMode = mode;
|
|
} else {
|
|
if (loopMode === "none") {
|
|
loopMode = "playlist";
|
|
} else if (loopMode === "playlist") {
|
|
loopMode = "track";
|
|
} else {
|
|
loopMode = "none";
|
|
}
|
|
}
|
|
|
|
if (loopMode === "none") {
|
|
loopSwitchEl.classList.add("icon-loop");
|
|
loopSwitchEl.style.opacity = 0.5;
|
|
loopSwitchEl.setAttribute("title", "loop off");
|
|
} else if (loopMode === "playlist") {
|
|
loopSwitchEl.classList.add("icon-loop");
|
|
loopSwitchEl.setAttribute("title", "loop playlist");
|
|
} else {
|
|
loopSwitchEl.classList.add("icon-loop-1");
|
|
loopSwitchEl.setAttribute("title", "loop track");
|
|
}
|
|
}
|
|
|
|
function setLoading(entry, delay = 150) {
|
|
if (entry.loading) {
|
|
delay = 0;
|
|
}
|
|
if (entry === currentEntry) {
|
|
bigButtonEl.classList.add("preloading");
|
|
setTimeout(() => {
|
|
if (bigButtonEl.classList.contains("preloading")) {
|
|
bigButtonEl.classList.remove("preloading");
|
|
bigButtonEl.classList.add("loading");
|
|
}
|
|
}, delay);
|
|
}
|
|
entry.loading = true;
|
|
}
|
|
|
|
function removeLoading(entry, track = false) {
|
|
if (entry === currentEntry) {
|
|
bigButtonEl.classList.remove("loading", "preloading");
|
|
}
|
|
if (track) {
|
|
entry.trackEl
|
|
.querySelector("button")
|
|
.classList.remove("loading", "preloading");
|
|
}
|
|
entry.loading = false;
|
|
}
|
|
|
|
function updateLoops(entry) {
|
|
if (entry.loopCount > 0) {
|
|
let buttonEl = entry.trackEl.querySelector("button");
|
|
buttonEl.setAttribute("loopcount", entry.remainingLoops);
|
|
// buttonEl.setAttribute('title', 'loop count');
|
|
}
|
|
if (entry === currentEntry) {
|
|
if (entry.loopCount > 0) {
|
|
bigButtonEl.setAttribute("loopcount", entry.remainingLoops);
|
|
} else {
|
|
bigButtonEl.removeAttribute("loopcount");
|
|
}
|
|
}
|
|
}
|
|
function resetLoops(entry) {
|
|
entry.remainingLoops = entry.loopCount;
|
|
updateLoops(entry);
|
|
}
|
|
|
|
function loadEntry(entry) {
|
|
if (entry.error) {
|
|
return;
|
|
}
|
|
if (entry.locked) {
|
|
return;
|
|
}
|
|
if (entry === currentEntry) {
|
|
if (entry.type === "video") {
|
|
mediaVideoEl.currentTime = 0;
|
|
} else {
|
|
}
|
|
playerEl.currentTime = 0;
|
|
playerEl.play();
|
|
return;
|
|
}
|
|
currentEntry = entry;
|
|
media.forEach((thisEntry) => {
|
|
thisEntry.trackEl.classList.remove("active");
|
|
thisEntry.trackEl.querySelector("button").classList.remove("pause");
|
|
if (thisEntry !== entry) {
|
|
resetLoops(thisEntry);
|
|
}
|
|
updateLoops(entry);
|
|
});
|
|
if (!currentEntry.duration) {
|
|
currentEntry.duration = 999;
|
|
currentEntry.needsDuration = true;
|
|
}
|
|
updateTrackPreview();
|
|
if (entry.preview) {
|
|
scrubberTrackContainerEl.classList.add("preview");
|
|
updatePreviewVolume(0);
|
|
} else {
|
|
scrubberTrackContainerEl.classList.remove("preview");
|
|
}
|
|
if (entry.loopCount > 0) {
|
|
bigButtonEl.classList.add("looped");
|
|
} else {
|
|
bigButtonEl.classList.remove("looped");
|
|
}
|
|
Array.from(mediaContainerEl.children).forEach((el) => {
|
|
el.classList.remove("active");
|
|
});
|
|
if (entry.type === "video") {
|
|
mediaVideoEl.src = entry.file;
|
|
mediaVideoEl.classList.add("active");
|
|
mediaImageEl.style.visibility = "hidden";
|
|
playerEl.classList.add("video");
|
|
|
|
if (config.theme.nativePlayer) {
|
|
mediaVideoEl.volume = volume;
|
|
playerEl.volume = 0;
|
|
} else {
|
|
mediaVideoEl.volume = 0;
|
|
playerEl.volume = volume;
|
|
}
|
|
} else {
|
|
mediaImageEl.classList.add("active");
|
|
playerEl.classList.remove("video");
|
|
|
|
playerEl.volume = volume;
|
|
mediaVideoEl.pause();
|
|
mediaVideoEl.removeAttribute("src");
|
|
mediaVideoEl.load();
|
|
|
|
if (entry.cover) {
|
|
loadCover(entry.cover);
|
|
} else {
|
|
loadCover(config.cover);
|
|
}
|
|
}
|
|
playerEl.src = entry.file;
|
|
entry.started = false;
|
|
setLoading(currentEntry);
|
|
entry.trackEl.classList.add("active");
|
|
controlsEl.querySelector(".title").innerHTML = entry.title;
|
|
if (
|
|
entry.info &&
|
|
(config.theme.infoStyle !== "overlaid-toggle" || entry.infoToggled)
|
|
) {
|
|
mediaInfoEl.innerHTML = entry.info;
|
|
mediaInfoContainer.classList.add("active");
|
|
} else {
|
|
mediaInfoEl.innerHTML = "";
|
|
mediaInfoContainer.classList.remove("active");
|
|
}
|
|
}
|
|
|
|
function playEntry(entry) {
|
|
if (currentEntry === entry) {
|
|
if (entry.loading) {
|
|
return;
|
|
}
|
|
if (playerEl.paused) {
|
|
playerEl.play();
|
|
entry.trackEl.querySelector("button").classList.add("pause");
|
|
} else {
|
|
playerEl.pause();
|
|
}
|
|
} else {
|
|
loadEntry(entry);
|
|
}
|
|
}
|
|
|
|
function toHMS(secs) {
|
|
if (isNaN(secs)) {
|
|
return "00:00";
|
|
}
|
|
|
|
let sec_num = Math.max(0, Math.round(secs)); //flooring is more correct, but rounding gives nicer results due to playback event buffering
|
|
let hours = Math.floor(sec_num / 3600);
|
|
let minutes = Math.floor(sec_num / 60) % 60;
|
|
let seconds = sec_num % 60;
|
|
|
|
return [hours, minutes, seconds]
|
|
.map((v) => (v < 10 ? "0" + v : v))
|
|
.filter((v, i) => v !== "00" || i > 0)
|
|
.join(":");
|
|
}
|
|
|
|
function fromHMS(hms) {
|
|
if (!hms) {
|
|
return 0;
|
|
}
|
|
let groups = ("" + hms).split(":");
|
|
if (groups.length < 1) {
|
|
return 0;
|
|
}
|
|
for (let i = 0; i < groups.length; i++) {
|
|
if (groups[i] === "") {
|
|
groups[i] = "0";
|
|
continue;
|
|
}
|
|
if (isNaN(groups[i])) {
|
|
return 0;
|
|
}
|
|
}
|
|
let seconds = 0;
|
|
let minutes = 0;
|
|
let hours = 0;
|
|
seconds = parseFloat(groups[groups.length - 1]);
|
|
if (groups.length >= 2) {
|
|
minutes = parseFloat(groups[groups.length - 2]);
|
|
}
|
|
if (groups.length >= 3) {
|
|
hours = parseFloat(groups[groups.length - 3]);
|
|
}
|
|
|
|
return (hours * 60 + minutes) * 60 + seconds;
|
|
}
|
|
|
|
function mod(x, n) {
|
|
return ((x % n) + n) % n;
|
|
}
|