UNPKG

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
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 };