525 lines
10 KiB
JavaScript
525 lines
10 KiB
JavaScript
(() => {
|
|
const cfg = window.PLAYER_CONFIG || {};
|
|
|
|
const VIDEO_LIST = Array.isArray(cfg.videos) ? cfg.videos : [];
|
|
|
|
const DEF_VOL = Number.isFinite(cfg.defaultVolume)
|
|
? Math.max(0, Math.min(1, cfg.defaultVolume))
|
|
: 0.3;
|
|
|
|
const LSV = cfg.localStorageVolumeKey || "vpref";
|
|
const LSM = cfg.localStorageMutedKey || "mpref";
|
|
|
|
const $ = id => document.getElementById(id);
|
|
|
|
const v = $("video");
|
|
const p = $("play");
|
|
const m = $("mute");
|
|
const s = $("seek");
|
|
const o = $("volume");
|
|
const c = $("controls");
|
|
const r = $("player");
|
|
const t = $("tapUnmute");
|
|
const x = $("rotate");
|
|
const ba = $("prevBtn");
|
|
const bb = $("nextBtn");
|
|
|
|
const F = VIDEO_LIST.filter(item => item && item.src && item.chance > 0);
|
|
|
|
const D = F.length
|
|
? F
|
|
: [
|
|
{
|
|
src: "https://s1ne.com/buttery-stealthx.webm",
|
|
chance: 100
|
|
}
|
|
];
|
|
|
|
const HASLS = (() => {
|
|
try {
|
|
const key = "__video_player_storage_test__";
|
|
localStorage.setItem(key, "1");
|
|
localStorage.removeItem(key);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
})();
|
|
|
|
let i = 0;
|
|
let h;
|
|
let n;
|
|
let l = -1;
|
|
let u = false;
|
|
let y = false;
|
|
let q = [];
|
|
let blocked = false;
|
|
let sideControls = false;
|
|
let sideEdge = false;
|
|
|
|
const coarsePointer = matchMedia("(pointer:coarse)").matches;
|
|
const EDGE_HOVER = 92;
|
|
|
|
const storedVolume = HASLS ? Number(localStorage.getItem(LSV)) : NaN;
|
|
const storedMuted = HASLS ? localStorage.getItem(LSM) : null;
|
|
|
|
function isMobile() {
|
|
return coarsePointer || innerWidth <= 768;
|
|
}
|
|
|
|
function updateRangeProgress(el) {
|
|
const min = Number(el.min) || 0;
|
|
const max = Number(el.max) || 100;
|
|
const val = Number(el.value) || 0;
|
|
const progress = max === min ? 0 : ((val - min) * 100) / (max - min);
|
|
|
|
el.style.setProperty("--range-progress", `${progress}%`);
|
|
}
|
|
|
|
function weightedPick(seed) {
|
|
let total = 0;
|
|
|
|
for (const item of D) {
|
|
total += Number(item.chance) || 0;
|
|
}
|
|
|
|
if (total <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
let roll = seed == null ? Math.random() * total : seed % total;
|
|
|
|
for (let index = 0; index < D.length; index++) {
|
|
roll -= Number(D[index].chance) || 0;
|
|
|
|
if (roll < 0) {
|
|
return index;
|
|
}
|
|
}
|
|
|
|
return D.length - 1;
|
|
}
|
|
|
|
function loopIndex(index) {
|
|
return ((index % D.length) + D.length) % D.length;
|
|
}
|
|
|
|
function preferredVolume() {
|
|
return Number.isFinite(storedVolume)
|
|
? Math.max(0, Math.min(1, storedVolume))
|
|
: DEF_VOL;
|
|
}
|
|
|
|
function preferredMuted() {
|
|
return storedMuted === null ? false : storedMuted === "1";
|
|
}
|
|
|
|
function savePref() {
|
|
if (!HASLS) return;
|
|
|
|
try {
|
|
localStorage.setItem(LSV, String(v.volume || 0));
|
|
localStorage.setItem(LSM, v.muted ? "1" : "0");
|
|
} catch {}
|
|
}
|
|
|
|
function sideAllowed() {
|
|
return !isMobile() && innerWidth > 768;
|
|
}
|
|
|
|
function updateSide() {
|
|
r.classList.toggle("side-visible", sideAllowed() && (sideControls || sideEdge));
|
|
}
|
|
|
|
function setSideControls(show) {
|
|
sideControls = Boolean(show);
|
|
updateSide();
|
|
}
|
|
|
|
function setSideEdge(show) {
|
|
sideEdge = Boolean(show);
|
|
updateSide();
|
|
}
|
|
|
|
function edgeHover(event) {
|
|
if (!sideAllowed()) {
|
|
setSideEdge(false);
|
|
return;
|
|
}
|
|
|
|
const nearEdge =
|
|
event.clientX <= EDGE_HOVER ||
|
|
event.clientX >= innerWidth - EDGE_HOVER;
|
|
|
|
setSideEdge(nearEdge);
|
|
}
|
|
|
|
function setTap(show) {
|
|
t.classList.toggle("visible", Boolean(show));
|
|
}
|
|
|
|
function loadVideo(index) {
|
|
i = loopIndex(index);
|
|
v.src = D[i].src;
|
|
blocked = false;
|
|
setTap(false);
|
|
}
|
|
|
|
function trimQueue(arr) {
|
|
q = arr.slice(-3);
|
|
}
|
|
|
|
function nextCleanIndex(index) {
|
|
if (!D.length) return 0;
|
|
|
|
let candidate = loopIndex(index);
|
|
let tries = 0;
|
|
|
|
while (tries < D.length && q.includes(candidate)) {
|
|
candidate = loopIndex(candidate + 1);
|
|
tries++;
|
|
}
|
|
|
|
return candidate;
|
|
}
|
|
|
|
function updatePlayButton() {
|
|
p.textContent = v.paused ? "x" : "▶";
|
|
}
|
|
|
|
function updateRotateButton() {
|
|
x.textContent = y ? "⟲" : "⟳";
|
|
}
|
|
|
|
function updateMuteButton() {
|
|
m.textContent = v.muted || v.volume === 0 ? "u" : "a";
|
|
o.value = v.muted ? 0 : v.volume;
|
|
updateRangeProgress(o);
|
|
}
|
|
|
|
function showControls() {
|
|
if (!u) {
|
|
c.classList.add("visible");
|
|
u = true;
|
|
}
|
|
|
|
setSideControls(true);
|
|
clearTimeout(h);
|
|
|
|
h = setTimeout(() => {
|
|
c.classList.remove("visible");
|
|
u = false;
|
|
setSideControls(false);
|
|
}, isMobile() ? 2200 : 1700);
|
|
}
|
|
|
|
function resizeVideo() {
|
|
const videoWidth = v.videoWidth;
|
|
const videoHeight = v.videoHeight;
|
|
const windowWidth = innerWidth;
|
|
const windowHeight = innerHeight;
|
|
|
|
if (!videoWidth || !videoHeight) {
|
|
v.style.objectFit = "cover";
|
|
v.style.transform = "translateZ(0) scale(1.01)";
|
|
return;
|
|
}
|
|
|
|
if (y) {
|
|
v.style.objectFit = "contain";
|
|
v.style.transform = "translateZ(0) rotate(90deg) scale(1.15)";
|
|
return;
|
|
}
|
|
|
|
const videoRatio = videoWidth / videoHeight;
|
|
const windowRatio = windowWidth / windowHeight;
|
|
|
|
v.style.objectFit = videoRatio > windowRatio ? "contain" : "cover";
|
|
v.style.transform = "translateZ(0) scale(1)";
|
|
}
|
|
|
|
function toggleRotate() {
|
|
y = !y;
|
|
updateRotateButton();
|
|
resizeVideo();
|
|
showControls();
|
|
}
|
|
|
|
function initVolume() {
|
|
const volume = preferredVolume();
|
|
const muted = preferredMuted();
|
|
|
|
v.muted = muted;
|
|
v.volume = muted ? 0 : volume;
|
|
|
|
savePref();
|
|
}
|
|
|
|
async function start() {
|
|
blocked = false;
|
|
|
|
initVolume();
|
|
updateMuteButton();
|
|
updatePlayButton();
|
|
updateRotateButton();
|
|
|
|
try {
|
|
await v.play();
|
|
setTap(!(v.muted === false && v.volume > 0));
|
|
return true;
|
|
} catch {
|
|
v.muted = true;
|
|
v.volume = 0;
|
|
updateMuteButton();
|
|
|
|
try {
|
|
await v.play();
|
|
blocked = true;
|
|
setTap(true);
|
|
return false;
|
|
} catch {
|
|
blocked = true;
|
|
setTap(true);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
function unmuteFromTap() {
|
|
v.muted = false;
|
|
v.volume = Math.max(v.volume || 0, 0.5);
|
|
|
|
blocked = false;
|
|
|
|
setTap(false);
|
|
updateMuteButton();
|
|
updatePlayButton();
|
|
savePref();
|
|
|
|
v.play().catch(() => {});
|
|
}
|
|
|
|
function playNextByIndex(index) {
|
|
loadVideo(nextCleanIndex(index));
|
|
q.push(i);
|
|
trimQueue(q);
|
|
showControls();
|
|
start();
|
|
}
|
|
|
|
function loadRandomNext() {
|
|
let index = weightedPick();
|
|
|
|
for (
|
|
let attempt = 0;
|
|
attempt < 8 && (index === i || q.includes(index));
|
|
attempt++
|
|
) {
|
|
index = loopIndex(index + 1);
|
|
}
|
|
|
|
loadVideo(index);
|
|
q.push(i);
|
|
trimQueue(q);
|
|
showControls();
|
|
start();
|
|
}
|
|
|
|
v.addEventListener("error", () => {
|
|
playNextByIndex(i + 1);
|
|
});
|
|
|
|
v.addEventListener("ended", loadRandomNext);
|
|
|
|
v.addEventListener("play", () => {
|
|
updatePlayButton();
|
|
showControls();
|
|
setTap(!(v.muted === false && v.volume > 0));
|
|
});
|
|
|
|
v.addEventListener("pause", () => {
|
|
updatePlayButton();
|
|
showControls();
|
|
});
|
|
|
|
v.addEventListener("loadedmetadata", () => {
|
|
resizeVideo();
|
|
|
|
const clearText = () => {
|
|
const el = document.querySelector(".video-text");
|
|
if (el) {
|
|
el.textContent = "";
|
|
}
|
|
};
|
|
|
|
if (window.requestIdleCallback) {
|
|
window.requestIdleCallback(clearText);
|
|
} else {
|
|
setTimeout(clearText, 0);
|
|
}
|
|
|
|
updateMuteButton();
|
|
updatePlayButton();
|
|
});
|
|
|
|
v.addEventListener("loadeddata", () => {
|
|
setTap(!(v.muted === false && v.volume > 0));
|
|
});
|
|
|
|
v.addEventListener("volumechange", savePref);
|
|
|
|
v.addEventListener("timeupdate", () => {
|
|
if (!v.duration) return;
|
|
|
|
const percent = (100 * v.currentTime / v.duration) | 0;
|
|
|
|
if (percent !== l) {
|
|
l = percent;
|
|
s.value = percent;
|
|
updateRangeProgress(s);
|
|
}
|
|
});
|
|
|
|
p.addEventListener("click", async event => {
|
|
event.stopPropagation();
|
|
|
|
if (v.paused) {
|
|
await v.play().catch(() => {});
|
|
} else {
|
|
v.pause();
|
|
}
|
|
});
|
|
|
|
m.addEventListener("click", event => {
|
|
event.stopPropagation();
|
|
|
|
if (v.muted || v.volume === 0) {
|
|
v.muted = false;
|
|
|
|
if (v.volume === 0) {
|
|
v.volume = 0.5;
|
|
}
|
|
|
|
blocked = false;
|
|
setTap(false);
|
|
} else {
|
|
v.muted = true;
|
|
}
|
|
|
|
updateMuteButton();
|
|
savePref();
|
|
});
|
|
|
|
s.addEventListener("input", event => {
|
|
event.stopPropagation();
|
|
updateRangeProgress(event.target);
|
|
|
|
if (v.duration) {
|
|
v.currentTime = event.target.value / 100 * v.duration;
|
|
}
|
|
});
|
|
|
|
o.addEventListener("input", event => {
|
|
event.stopPropagation();
|
|
updateRangeProgress(event.target);
|
|
|
|
v.volume = parseFloat(event.target.value);
|
|
v.muted = v.volume === 0;
|
|
|
|
if (v.volume > 0) {
|
|
blocked = false;
|
|
setTap(false);
|
|
}
|
|
|
|
updateMuteButton();
|
|
savePref();
|
|
});
|
|
|
|
x.addEventListener("click", event => {
|
|
event.stopPropagation();
|
|
toggleRotate();
|
|
});
|
|
|
|
t.addEventListener("click", event => {
|
|
event.stopPropagation();
|
|
unmuteFromTap();
|
|
});
|
|
|
|
c.addEventListener("click", event => {
|
|
event.stopPropagation();
|
|
});
|
|
|
|
r.addEventListener(
|
|
"click",
|
|
() => {
|
|
showControls();
|
|
|
|
if (blocked && v.muted) {
|
|
unmuteFromTap();
|
|
}
|
|
},
|
|
{ passive: true }
|
|
);
|
|
|
|
r.addEventListener("mousemove", event => {
|
|
showControls();
|
|
edgeHover(event);
|
|
});
|
|
|
|
r.addEventListener("mouseenter", event => {
|
|
showControls();
|
|
edgeHover(event);
|
|
});
|
|
|
|
r.addEventListener("mouseleave", () => {
|
|
setSideEdge(false);
|
|
});
|
|
|
|
r.addEventListener("touchstart", showControls, { passive: true });
|
|
|
|
ba.addEventListener("mouseenter", () => {
|
|
setSideEdge(true);
|
|
});
|
|
|
|
bb.addEventListener("mouseenter", () => {
|
|
setSideEdge(true);
|
|
});
|
|
|
|
ba.addEventListener("click", event => {
|
|
event.stopPropagation();
|
|
playNextByIndex(i - 1);
|
|
});
|
|
|
|
bb.addEventListener("click", event => {
|
|
event.stopPropagation();
|
|
playNextByIndex(i + 1);
|
|
});
|
|
|
|
addEventListener("resize", () => {
|
|
clearTimeout(n);
|
|
|
|
n = setTimeout(() => {
|
|
resizeVideo();
|
|
updateSide();
|
|
}, 100);
|
|
});
|
|
|
|
addEventListener("orientationchange", () => {
|
|
resizeVideo();
|
|
updateSide();
|
|
});
|
|
|
|
updateRangeProgress(s);
|
|
updateRangeProgress(o);
|
|
|
|
loadVideo(weightedPick());
|
|
|
|
q = [i];
|
|
|
|
updatePlayButton();
|
|
updateMuteButton();
|
|
updateRotateButton();
|
|
updateSide();
|
|
|
|
start();
|
|
})(); |