sonix-player
Version:
A modern, customizable audio player web component with support for thumbnails, speed control, and volume management
305 lines (304 loc) • 17.8 kB
JavaScript
const Y = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' class='ionicon'><path d='M472.7 189.5c-13.26-8.43-29.83-14.56-48.08-17.93A16 16 0 0 1 412 159.28c-7.86-34.51-24.6-64.13-49.15-86.58C334.15 46.45 296.21 32 256 32c-35.35 0-68 11.08-94.37 32a150.1 150.1 0 0 0-41.95 52.83A16.05 16.05 0 0 1 108 125.8c-27.13 4.9-50.53 14.68-68.41 28.7C13.7 174.83 0 203.56 0 237.6 0 305 55.93 352 136 352h104V224.45c0-8.61 6.62-16 15.23-16.43A16 16 0 0 1 272 224v128h124c72.64 0 116-34.24 116-91.6 0-30.05-13.59-54.57-39.3-70.9M240 425.42l-36.7-36.64a16 16 0 0 0-22.6 22.65l64 63.89a16 16 0 0 0 22.6 0l64-63.89a16 16 0 0 0-22.6-22.65L272 425.42V352h-32Z'/></svg>", _ = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' class='ionicon'><path d='M208 432h-48a16 16 0 0 1-16-16V96a16 16 0 0 1 16-16h48a16 16 0 0 1 16 16v320a16 16 0 0 1-16 16M352 432h-48a16 16 0 0 1-16-16V96a16 16 0 0 1 16-16h48a16 16 0 0 1 16 16v320a16 16 0 0 1-16 16'/></svg>", ee = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' class='ionicon'><path d='M133 440a35.37 35.37 0 0 1-17.5-4.67c-12-6.8-19.46-20-19.46-34.33V111c0-14.37 7.46-27.53 19.46-34.33a35.13 35.13 0 0 1 35.77.45l247.85 148.36a36 36 0 0 1 0 61l-247.89 148.4A35.5 35.5 0 0 1 133 440'/></svg>", te = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' class='ionicon'><path d='M32 145.52v221c0 13.28 13 21.72 23.63 15.35l188.87-113c9.24-5.53 9.24-20.07 0-25.6l-188.87-113C45 123.8 32 132.24 32 145.52ZM260.57 145.52v221c0 13.28 13 21.72 23.63 15.35l188.87-113c9.24-5.53 9.24-20.07 0-25.6l-188.87-113c-10.64-6.47-23.63 1.97-23.63 15.25Z' stroke-miterlimit='10' class='ionicon-fill-none ionicon-stroke-width'/></svg>", ne = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' class='ionicon'><path d='m320 120 48 48-48 48' stroke-linecap='round' stroke-linejoin='round' class='ionicon-fill-none ionicon-stroke-width'/><path d='M352 168H144a80.24 80.24 0 0 0-80 80v16M192 392l-48-48 48-48' stroke-linecap='round' stroke-linejoin='round' class='ionicon-fill-none ionicon-stroke-width'/><path d='M160 344h208a80.24 80.24 0 0 0 80-80v-16' stroke-linecap='round' stroke-linejoin='round' class='ionicon-fill-none ionicon-stroke-width'/></svg>", A = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' class='ionicon'><path d='M232 416a23.88 23.88 0 0 1-14.2-4.68 8 8 0 0 1-.66-.51L125.76 336H56a24 24 0 0 1-24-24V200a24 24 0 0 1 24-24h69.75l91.37-74.81a8 8 0 0 1 .66-.51A24 24 0 0 1 256 120v272a24 24 0 0 1-24 24M320 336a16 16 0 0 1-14.29-23.19c9.49-18.87 14.3-38 14.3-56.81 0-19.38-4.66-37.94-14.25-56.73a16 16 0 0 1 28.5-14.54C346.19 208.12 352 231.44 352 256c0 23.86-6 47.81-17.7 71.19A16 16 0 0 1 320 336'/><path d='M368 384a16 16 0 0 1-13.86-24C373.05 327.09 384 299.51 384 256c0-44.17-10.93-71.56-29.82-103.94a16 16 0 0 1 27.64-16.12C402.92 172.11 416 204.81 416 256c0 50.43-13.06 83.29-34.13 120a16 16 0 0 1-13.87 8'/><path d='M416 432a16 16 0 0 1-13.39-24.74C429.85 365.47 448 323.76 448 256c0-66.5-18.18-108.62-45.49-151.39a16 16 0 1 1 27-17.22C459.81 134.89 480 181.74 480 256c0 64.75-14.66 113.63-50.6 168.74A16 16 0 0 1 416 432'/></svg>", se = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' class='ionicon'><path d='M264 416.19a23.92 23.92 0 0 1-14.21-4.69l-.66-.51-91.46-75H88a24 24 0 0 1-24-24V200a24 24 0 0 1 24-24h69.65l91.46-75 .66-.51A24 24 0 0 1 288 119.83v272.34a24 24 0 0 1-24 24ZM352 336a16 16 0 0 1-14.29-23.18c9.49-18.9 14.3-38 14.3-56.82 0-19.36-4.66-37.92-14.25-56.73a16 16 0 0 1 28.5-14.54C378.2 208.16 384 231.47 384 256c0 23.83-6 47.78-17.7 71.18A16 16 0 0 1 352 336'/><path d='M400 384a16 16 0 0 1-13.87-24C405 327.05 416 299.45 416 256c0-44.12-10.94-71.52-29.83-103.95A16 16 0 0 1 413.83 136C434.92 172.16 448 204.88 448 256c0 50.36-13.06 83.24-34.12 120a16 16 0 0 1-13.88 8'/></svg>", F = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' class='ionicon'><path d='M416 432 64 80' stroke-linecap='round' stroke-miterlimit='10' class='ionicon-fill-none ionicon-stroke-width'/><path d='M243.33 98.86a23.89 23.89 0 0 0-25.55 1.82l-.66.51-28.52 23.35a8 8 0 0 0-.59 11.85l54.33 54.33a8 8 0 0 0 13.66-5.66v-64.49a24.51 24.51 0 0 0-12.67-21.71M251.33 335.29 96.69 180.69A16 16 0 0 0 85.38 176H56a24 24 0 0 0-24 24v112a24 24 0 0 0 24 24h69.76l92 75.31a23.9 23.9 0 0 0 25.87 1.69A24.51 24.51 0 0 0 256 391.45v-44.86a16 16 0 0 0-4.67-11.3M352 256c0-24.56-5.81-47.87-17.75-71.27a16 16 0 1 0-28.5 14.55C315.34 218.06 320 236.62 320 256q0 4-.31 8.13a8 8 0 0 0 2.32 6.25l14.36 14.36a8 8 0 0 0 13.55-4.31A146 146 0 0 0 352 256M416 256c0-51.18-13.08-83.89-34.18-120.06a16 16 0 0 0-27.64 16.12C373.07 184.44 384 211.83 384 256c0 23.83-3.29 42.88-9.37 60.65a8 8 0 0 0 1.9 8.26L389 337.4a8 8 0 0 0 13.13-2.79C411 311.76 416 287.26 416 256'/><path d='M480 256c0-74.25-20.19-121.11-50.51-168.61a16 16 0 1 0-27 17.22C429.82 147.38 448 189.5 448 256c0 46.19-8.43 80.27-22.43 110.53a8 8 0 0 0 1.59 9l11.92 11.92a8 8 0 0 0 12.92-2.16C471.6 344.9 480 305 480 256'/></svg>";
function c(V) {
return V.split(",")[1];
}
let M = null, I = [], x = !1, p = !1;
class ie extends HTMLElement {
connectedCallback() {
this.dataset.auto === "true" && (M && M !== this && (M.dataset.auto = "false", console.warn("only one sonix-player element in current page can have data-auto attribute, please fix it")), M = this);
const Z = this.dataset.src || "", e = document.createElement("audio");
e.src = Z, I.push(e);
function E() {
if (e.duration) {
const s = Math.floor(e.duration), i = Math.floor(s / 60), r = s % 60;
return `${i.toString().padStart(2, "0")}:${r.toString().padStart(2, "0")}`;
} else
return console.warn("No audio duration found. Please check data-src attribute"), "00:00";
}
e.addEventListener("loadedmetadata", () => {
const s = this.querySelector(".sonix-duration-value");
s && (s.innerHTML = E());
}), e.addEventListener("durationchange", () => {
const s = this.querySelector(".sonix-duration-value");
s && (s.innerHTML = E());
});
const f = {
thumbnail: this.dataset.thumbnail || "",
figure: this.dataset.figure || ""
}, m = {
artist: this.dataset.artist || "",
name: this.dataset.name || ""
}, D = {
autoPlay: this.dataset.auto || "false",
volumeControl: this.dataset.volume || "true",
repeat: this.dataset.repeat || "true"
}, k = {
speed: this.dataset.speed || "true",
minSpeed: 1,
maxSpeed: this.dataset.max || "2"
}, R = this.dataset.download || "true";
(() => {
const s = this.dataset.black || document.documentElement.style.getPropertyValue("--color-sonix-black"), i = this.dataset.accent || document.documentElement.style.getPropertyValue("--color-sonix-accent"), r = this.dataset.primary || document.documentElement.style.getPropertyValue("--color-sonix-primary"), S = this.dataset.secondary || document.documentElement.style.getPropertyValue("--color-sonix-secondary");
this.style.setProperty("--color-sonix-black", s), this.style.setProperty("--color-sonix-accent", i), this.style.setProperty("--color-sonix-primary", r), this.style.setProperty("--color-sonix-secondary", S);
})(), this.innerHTML = /*html*/
`
<div class="sonix-player">
<!-- -->
${f.thumbnail !== "" ? `
<div class="sonix-thumbnail-container">
<div class="sonix-thumbnail-filter" style="background-image:url(${f.thumbnail})"></div>
<img class="sonix-thumbnail-img" src="${f.thumbnail}" alt="${m.name}" loading="lazy"/>
<div class="sonix-loader">
<div class="sonix-loader-inner">
<div class="sonix-line h-7"></div>
<div class="sonix-line h-4"></div>
<div class="sonix-line h-8"></div>
<div class="sonix-line h-10"></div>
<div class="sonix-line h-8"></div>
<div class="sonix-line h-4"></div>
<div class="sonix-line h-7"></div>
</div>
</div>
</div>
` : ""}
<!-- -->
<div class="sonix-inner">
<div class="flex items-center">
<div class="sonix-btn-holder">
<button
class="sonix-play-btn"
title="Play current"
aria-label="Play current"
tabindex="0">
<span class="sonix-icon">
${c(ee)}
</span>
<span class="sonix-icon">
${c(_)}
</span>
</button>
</div>
<!-- -->
${D.repeat == "true" ? `
<div class="sonix-btn-holder col-span-1">
<button
class="sonix-repeat-btn"
title="Repeat current"
aria-label="Repeat current"
tabindex="0">
<span class="sonix-icon">
${c(ne)}
</span>
</button>
</div>
` : ""}
</div>
<!-- -->
<div class="sonix-timeline-container flex-2">
<div class="sonix-current-time-container">
<span class="sonix-current-time-value">${e.currentTime}:00</span>
</div>
<div class="sonix-timeline-outer">
<div class="sonix-timeline-inner"></div>
</div>
<div class="sonix-duration-container">
<span class="sonix-duration-value">00:00</span>
</div>
</div>
<!-- -->
<div class="flex items-center sonix-vol-speed">
${D.volumeControl == "true" ? `
<div class="sonix-btn-holder flex">
<button
class="sonix-volume-btn"
title="Control volume"
aria-label="Control volume"
tabindex="0">
<span class="sonix-icon">
${c(A)}
</span>
</button>
<div
class="sonix-custom-range"
data-value="100">
<div class="sonix-custom-range-outer">
<div class="sonix-custom-range-inner"></div>
</div>
</div>
</div>
` : ""}
<!-- -->
${k.speed == "true" ? `
<div class="sonix-btn-holder">
<button
class="sonix-speed-btn"
title="Control speed"
aria-label="Control speed"
tabindex="0"
data-value="1">
<span class="sonix-icon">
<span class="sonix-speed-value">1x</span>
${c(te)}
</span>
</button>
</div>
` : ""}
</div>
<!-- -->
<div class="flex justify-between items-center flex-1">
<div class="sonix-info ${R == "false" ? "lg:justify-center w-full" : ""}">
${f.figure !== "" ? `
<div class="sonix-figure-container">
<img class="sonix-figure-img" src="${f.figure}" alt="${m.name || ""}" loading="lazy" />
<div class="sonix-loader">
<div class="sonix-loader-inner">
<div class="sonix-line h-7"></div>
<div class="sonix-line h-4"></div>
<div class="sonix-line h-8"></div>
<div class="sonix-line h-10"></div>
<div class="sonix-line h-8"></div>
<div class="sonix-line h-4"></div>
<div class="sonix-line h-7"></div>
</div>
</div>
</div>
` : ""}
<div class="sonix-text-info">
${m.artist !== "" ? `<span class="sonix-artist">${m.artist}</span>` : ""}
${m.name !== "" ? `<span class="sonix-name">${m.name}</span>` : ""}
</div>
</div>
<!-- -->
${R == "true" ? `
<div class="sonix-btn-holder">
<button
class="sonix-download-btn"
title="Download audio"
aria-label="Download audio"
tabindex="0">
<span class="sonix-icon">
${c(Y)}
</span>
</button>
</div>
` : ""}
</div>
</div>
</div>
`, window.addEventListener("load", () => {
var j;
const s = this.querySelector(".sonix-duration-value");
s && (s.innerHTML = E());
const i = this.querySelector(".sonix-play-btn");
i.addEventListener("click", S);
function r(t, n) {
i.title = `${n} current`, i.ariaLabel = `${n} current`;
}
function S() {
const t = i.classList.contains("sonix-is-playing");
if (this.dataset.auto == "false" || !this.dataset.auto) {
const n = document.querySelector("[data-auto='true']");
n == null || n.setAttribute("data-auto", "false"), n && console.warn("auto-play disabled");
}
e.duration && (t ? (i.classList.remove("sonix-is-playing"), e.pause(), r(!1, "Play")) : (I.forEach((n) => {
n !== e && n.pause();
}), document.querySelectorAll(".sonix-player .sonix-play-btn").forEach((n) => {
n.classList.remove("sonix-is-playing");
}), i.classList.add("sonix-is-playing"), e.play(), r(!0, "Pause")));
}
const w = this.querySelector(".sonix-repeat-btn");
w == null || w.addEventListener("click", O);
function O() {
w.classList.toggle("sonix-active") ? e.loop = !0 : e.loop = !1;
}
e.addEventListener("timeupdate", z), e.addEventListener("timeupdate", U);
const X = this.querySelector(".sonix-current-time-value"), d = this.querySelector(".sonix-timeline-outer"), B = this.querySelector(".sonix-timeline-inner");
function z() {
let t = e.currentTime / e.duration * 100;
const n = Math.floor(e.currentTime), a = Math.floor(n / 60), o = Math.floor(n % 60);
if (n) {
const l = o < 10 ? `0${o}` : o;
X.innerHTML = a + ":" + l;
}
B.style.width = `${t}%`;
}
function U() {
e.currentTime === e.duration && (e.loop === !0 ? r(!0, "Pause") : r(!1, "Play"), i.classList.remove("sonix-is-playing"));
}
const g = this.querySelector(".sonix-volume-btn"), y = g == null ? void 0 : g.querySelector(".sonix-icon"), $ = this.querySelector(".sonix-custom-range"), u = this.querySelector(".sonix-custom-range-inner");
if (g) {
let t = function() {
e.muted ? (e.volume = 1, e.muted = !1, u.style.width = "100%", y.innerHTML = c(A)) : (e.volume = 0, e.muted = !0, u.style.width = "0%", y.innerHTML = c(F));
}, K = function(v) {
p = !0, b(v), n = (q) => {
p && b(q);
}, a = () => {
p = !1, document.removeEventListener("mousemove", n), document.removeEventListener("mouseup", a);
}, document.addEventListener("mousemove", n), document.addEventListener("mouseup", a);
}, Q = function(v) {
v.preventDefault(), p = !0, b(v.touches[0]), o = (q) => {
p && b(q.touches[0]);
}, l = () => {
p = !1, u.removeEventListener("touchmove", o), u.removeEventListener("touchend", l);
}, u.addEventListener("touchmove", o), u.addEventListener("touchend", l);
};
e.volume = 1, u.style.width = "100%", g.addEventListener("click", t);
let n, a, o, l;
$.addEventListener("mousedown", (v) => K(v)), $.addEventListener("touchstart", (v) => Q(v));
}
function b(t) {
const n = $.getBoundingClientRect(), a = n.width, o = Math.max(0, Math.min(t.clientX - n.left, a)), l = Math.max(0, Math.min(1, o / a));
isFinite(l) && (e.volume = l, u.style.width = `${l * 100}%`, l === 0 ? (y.innerHTML = c(F), e.muted = !0) : l < 0.5 ? y.innerHTML = c(se) : (e.muted = !1, y.innerHTML = c(A)));
}
const h = this.querySelector(".sonix-speed-btn");
h == null || h.addEventListener("click", W);
function W() {
let t = parseFloat(h.dataset.value);
t = t >= parseFloat(k.maxSpeed) ? k.minSpeed : t + 0.5, h.dataset.value = t, e.playbackRate = t, h.querySelector(".sonix-speed-value").textContent = `${t}x`;
}
(j = this.querySelector(".sonix-download-btn")) == null || j.addEventListener("click", N);
function N() {
if (e.duration) {
const t = document.createElement("a");
t.href = e.src, t.download = m.name, t.click();
} else
console.warn("audio source not found check data-src please");
}
let C, T, P, H;
d.addEventListener("mousedown", (t) => G(t));
function G(t) {
x = !0, L(t), C = (n) => {
x && L(n);
}, T = () => {
x = !1, document.removeEventListener("mousemove", C), document.removeEventListener("mouseup", T);
}, document.addEventListener("mousemove", C), document.addEventListener("mouseup", T);
}
d.addEventListener("touchstart", (t) => J(t));
function J(t) {
x = !0, L(t.touches[0]), P = (n) => {
x && n.cancelable && (n.preventDefault(), L(n.touches[0]));
}, H = () => {
x = !1, d.removeEventListener("touchmove", P), d.removeEventListener("touchend", H);
}, d.addEventListener("touchmove", P), d.removeEventListener("touchend", H);
}
function L(t) {
if (!e.duration) return;
const n = d.getBoundingClientRect();
let a = t.clientX - n.left;
a = Math.max(0, Math.min(a, d.offsetWidth));
const o = a / d.offsetWidth;
e.currentTime = o * e.duration, B.style.width = `${o * 100}%`;
}
setTimeout(() => {
const t = () => {
this.dataset.auto == "true" && e.duration && (e.play(), i.classList.add("sonix-is-playing"), r(!0, "Pause")), window.removeEventListener("click", t);
};
window.addEventListener("click", t);
}, 100), document.querySelector(".sonix-loader") && document.querySelectorAll(".sonix-loader").forEach((t) => t.remove());
});
}
}
customElements.define("sonix-player", ie);
export {
ie as default
};