wave-audio-path-player
Version:
Simple audio player webcomponent customizable with waveform.
450 lines (445 loc) • 19.2 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
const getAudioData = (e) => {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
const a = new AudioContext();
return fetch(e).then((e2) => e2.arrayBuffer()).then((e2) => a.decodeAudioData(e2)).catch((e2) => {
console.error(e2);
});
}, linearPath = (e, a) => {
var _a, _b, _c;
const { channel: t = 0, samples: v = e.length, height: h = 100, width: r = 800, top: s = 0, left: l = 0, type: i = "steps", paths: $ = [{ d: "Q", sx: 0, sy: 0, x: 50, y: 100, ex: 100, ey: 0 }], animation: o = false, animationframes: n = 10, normalize: b = true } = a;
var a = getFramesData(e, t, o, n), e = getFilterData(a, v), w = b ? getNormalizeData(e) : e;
let c = "";
var p, f, g = i != "bars" ? (h + 2 * s) / 2 : h + s, m = r / v, y = $.length, u = i == "mirror" ? 2 * y : y, A = w.length;
for (let n2 = 0; n2 < A; n2++) {
0 < n2 && (p = c.length, f = c.charAt(p - 1), c += f == ";" || p === 0 ? " M 0 0 ;" : ";");
let s2 = -9999, o2 = -9999;
for (let r2 = 0; r2 < v; r2++) {
var M = i == "bars" || r2 % 2 ? 1 : -1;
let t2 = 1;
for (let e2 = 0; e2 < u; e2++) {
let a2 = e2;
e2 >= y && (a2 = e2 - y, t2 = -1), $[a2].minshow = (_a = $[a2].minshow) != null ? _a : 0, $[a2].maxshow = (_b = $[a2].maxshow) != null ? _b : 1, $[a2].normalize = (_c = $[a2].normalize) != null ? _c : false;
var d = $[a2].normalize ? 1 : w[n2][r2];
if ($[a2].minshow <= w[n2][r2] && $[a2].maxshow >= w[n2][r2])
switch ($[a2].d) {
case "L":
var D = r2 * m + m * $[a2].sx / 100 + l, x = g + d * $[a2].sy / 100 * (i != "bars" ? h / 2 : h) * -M * t2, k = r2 * m + m * $[a2].ex / 100 + l, z = g + d * $[a2].ey / 100 * (i != "bars" ? h / 2 : h) * -M * t2;
D === s2 && x === o2 || (c += `M ${D} ${x} `), c += `L ${k} ${z} `, s2 = k, o2 = z;
break;
case "H":
D = r2 * m + m * $[a2].sx / 100 + l, x = g + d * $[a2].y / 100 * (i != "bars" ? h / 2 : h) * -M * t2, k = r2 * m + m * $[a2].ex / 100 + l;
D === s2 && x === o2 || (c += `M ${D} ${x} `), c += `H ${k} `, s2 = k, o2 = x;
break;
case "V":
var z = r2 * m + m * $[a2].x / 100 + l, C = g + d * $[a2].sy / 100 * (i != "bars" ? h / 2 : h) * -M * t2, F = g + d * $[a2].ey / 100 * (i != "bars" ? h / 2 : h) * -M * t2;
z === s2 && C === o2 || (c += `M ${z} ${C} `), c += `V ${F} `, s2 = z, o2 = F;
break;
case "C":
var C = r2 * m + m * $[a2].sx / 100 + l, F = g - g * $[a2].sy / 100 * M, Q = r2 * m + m * $[a2].x / 100 + l, P = g + d * $[a2].y / 100 * (i != "bars" ? h : 2 * h) * -M * t2, L = r2 * m + m * $[a2].ex / 100 + l, Z = g - g * $[a2].ey / 100 * M;
C === s2 && F === o2 || (c += `M ${C} ${F} `), c += `C ${C} ${F} ${Q} ${P} ${L} ${Z} `, s2 = L, o2 = Z;
break;
case "Q":
var Q = r2 * m + m * $[a2].sx / 100 + l, P = g + d * $[a2].sy / 100 * (i != "bars" ? h / 2 : h) * -M * t2, L = r2 * m + m * $[a2].x / 100 + l, Z = g + d * $[a2].y / 100 * (i != "bars" ? h : 2 * h) * -M * t2, N = r2 * m + m * $[a2].ex / 100 + l, H = g + d * $[a2].ey / 100 * (i != "bars" ? h / 2 : h) * -M * t2;
Q === s2 && P === o2 || (c += `M ${Q} ${P} `), c += `Q ${L} ${Z} ${N} ${H} `, s2 = N, o2 = H;
break;
case "A": {
var N = r2 * m + m * $[a2].sx / 100 + l, H = g + d * $[a2].sy / 100 * (i != "bars" ? h / 2 : h) * -M * t2, V = r2 * m + m * $[a2].ex / 100 + l, B = g + d * $[a2].ey / 100 * (i != "bars" ? h / 2 : h) * -M * t2, I = (N === s2 && H === o2 || (c += `M ${N} ${H} `), $[a2].rx * m / 100), R = $[a2].ry * m / 100;
let e3 = $[a2].sweep;
M == -1 && (e3 = e3 == 1 ? 0 : 1), t2 == -1 && (e3 = e3 == 1 ? 0 : 1), c += `A ${I} ${R} ${$[a2].angle} ${$[a2].arc} ${e3} ${V} ${B} `, s2 = V, o2 = B;
break;
}
case "Z":
c += "Z ";
}
}
}
}
return c;
}, getFramesData = (e, a, t, r) => {
const s = e.getChannelData(a), o = [];
if (t) {
var n = e.sampleRate / r;
for (let e2 = 0; e2 < s.length; e2 += n) {
var h = s.slice(e2, e2 + n);
o.push(h);
}
} else
o.push(s);
return o;
}, getFilterData = (r, a) => {
const e = [];
var s = r.length;
for (let t = 0; t < s; t++) {
var o = Math.floor(r[t].length / a);
const h = [];
for (let e2 = 0; e2 < a; e2++) {
var n = o * e2;
let a2 = 0;
for (let e3 = 0; e3 < o; e3++)
a2 += Math.abs(r[t][n + e3]);
h.push(a2 / o);
}
e.push(h);
}
return e;
}, getNormalizeData = (a) => {
const t = [];
var r = a.length;
for (let e = 0; e < r; e++) {
var s = Math.max(...a[e]);
t.push(s);
}
const o = Math.pow(Math.max(...t), -1), n = [];
for (let e = 0; e < r; e++) {
var h = a[e].map((e2) => e2 * o);
n.push(h);
}
return n;
};
class AudioPathPlayer extends HTMLElement {
constructor() {
super();
__publicField(this, "loadSong", () => {
this.durationContainer.textContent = this.calculateTime(this.audio.duration);
this.seekSlider.max = this.audio.duration;
this.svg.unpauseAnimations();
this.animationsvg.setAttribute("dur", "" + this.audio.duration + "s");
if (!this.animation) {
this.animationsvgx.setAttribute("dur", "" + this.audio.duration + "s");
}
this.svg.pauseAnimations();
this.svg.setCurrentTime(0);
});
__publicField(this, "playPause", () => {
if (this.audio.paused) {
this.audio.play();
this.svg.unpauseAnimations();
this.path2.style.display = "block";
this.playPathButton.setAttribute("d", this.pausePath);
this.raf = requestAnimationFrame(this.whilePlaying);
} else {
this.audio.pause();
this.svg.pauseAnimations();
this.playPathButton.setAttribute("d", this.playPath);
cancelAnimationFrame(this.raf);
}
});
__publicField(this, "sliderInput", () => {
this.path2.style.display = "block";
this.currentTimeContainer.textContent = this.calculateTime(this.seekSlider.value);
this.svg.setCurrentTime(this.seekSlider.value);
if (!this.audio.paused) {
cancelAnimationFrame(this.raf);
}
});
__publicField(this, "sliderChange", () => {
this.audio.currentTime = this.seekSlider.value;
this.path2.style.display = "block";
this.svg.setCurrentTime(this.seekSlider.value);
if (!this.audio.paused) {
this.raf = requestAnimationFrame(this.whilePlaying);
}
});
__publicField(this, "onFinish", () => {
this.seekSlider.value = this.seekSlider.max;
this.svg.setCurrentTime(this.audio.duration);
this.svg.pauseAnimations();
this.playPathButton.setAttribute("d", this.playPath);
cancelAnimationFrame(this.raf);
});
__publicField(this, "whilePlaying", () => {
this.seekSlider.value = this.audio.currentTime;
this.currentTimeContainer.textContent = this.calculateTime(this.seekSlider.value);
this.svg.setCurrentTime(this.seekSlider.value);
this.raf = requestAnimationFrame(this.whilePlaying);
});
__publicField(this, "svgDraw", () => {
const path = linearPath(this.audioData, this.options);
if (!this.animation) {
this.path1.setAttribute("d", path);
this.path2.setAttribute("d", path);
} else {
this.animationsvg.setAttribute("values", path);
}
this.svg.setCurrentTime(this.seekSlider.value);
});
__publicField(this, "calculateTime", (secs) => {
const minutes = Math.floor(secs / 60);
const seconds = Math.floor(secs % 60);
const returnedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`;
return `${minutes}:${returnedSeconds}`;
});
this.shadowDOM = this.attachShadow({ mode: "open" });
this.audioData = null;
if (this.hasAttribute("wave-options")) {
this.options = JSON.parse(this.attributes["wave-options"].value);
this.options.width = parseInt(this.attributes["wave-width"].value);
this.options.height = parseInt(this.attributes["wave-height"].value);
} else {
this.options = {
samples: 40,
type: "mirror",
width: parseInt(this.attributes["wave-width"].value),
height: parseInt(this.attributes["wave-height"].value),
paths: [
{ d: "V", sy: 0, x: 50, ey: 100 }
]
};
}
if (this.options.hasOwnProperty("animation")) {
this.animation = this.options.animation;
} else {
this.animation = false;
}
this.playPath = "M8.5 8.7c0-1.7 1.2-2.4 2.6-1.5l14.4 8.3c1.4.8 1.4 2.2 0 3l-14.4 8.3c-1.4.8-2.6.2-2.6-1.5V8.7z";
this.pausePath = "M9.2 25c0 .5.4 1 .9 1h3.6c.5 0 .9-.4.9-1V9c0-.5-.4-.9-.9-.9h-3.6c-.4-.1-.9.3-.9.9v16zm11-17c-.5 0-1 .4-1 .9V25c0 .5.4 1 1 1h3.6c.5 0 1-.4 1-1V9c0-.5-.4-.9-1-.9 0-.1-3.6-.1-3.6-.1z";
this.playPathButton = null;
this.svg = null;
this.path1 = null;
this.path2 = null;
this.animationsvg = null;
this.animationsvgx = null;
this.audio = null;
this.durationContainer = null;
this.seekSlider = null;
this.currentTimeContainer = null;
this.playIconContainer = null;
this.raf = null;
}
initComponent() {
this.playPathButton = this.shadowDOM.getElementById("playPathButton");
this.svg = this.shadowDOM.getElementById("svg");
this.path1 = this.shadowDOM.getElementById("path1");
this.path2 = this.shadowDOM.getElementById("path2");
this.animationsvg = this.shadowDOM.getElementById("animationsvg");
this.animationsvgx = this.shadowDOM.getElementById("animationsvgx");
this.audio = this.shadowDOM.querySelector("audio");
this.durationContainer = this.shadowDOM.getElementById("duration");
this.seekSlider = this.shadowDOM.getElementById("seek-slider");
this.currentTimeContainer = this.shadowDOM.getElementById("current-time");
this.playIconContainer = this.shadowDOM.getElementById("play");
this.svg.pauseAnimations();
if (this.audio.readyState > 0) {
this.loadSong();
} else {
this.audio.addEventListener("loadedmetadata", this.loadSong);
}
this.audio.addEventListener("ended", this.onFinish);
this.seekSlider.addEventListener("input", this.sliderInput);
this.seekSlider.addEventListener("change", this.sliderChange);
this.playIconContainer.addEventListener("click", this.playPause);
}
async audioPath() {
this.audioData = await getAudioData(this.attributes.src.value);
this.svgDraw();
}
mapComponentAttributes() {
const attributesMapping = [
"src",
"wave-height",
"wave-width",
"color",
"wave-options",
"wave-color",
"wave-progress-color",
"wave-slider"
];
attributesMapping.forEach((key) => {
if (!this.attributes[key]) {
this.attributes[key] = { value: null };
}
});
}
connectedCallback() {
this.mapComponentAttributes();
this.render();
this.initComponent();
this.audioPath();
}
render() {
this.shadowDOM.innerHTML = `
${this.templateCss()}
${this.template()}
`;
}
template() {
let html = `
<div part="player" class="player">
<button id="play" part="play">
<svg viewBox="0 0 34 34" width="34" height="34" part="button">
<path id="playPathButton" d="M8.5 8.7c0-1.7 1.2-2.4 2.6-1.5l14.4 8.3c1.4.8 1.4 2.2 0 3l-14.4 8.3c-1.4.8-2.6.2-2.6-1.5V8.7z"></path>
<!--<path fill="currentColor" d="M9.2 25c0 .5.4 1 .9 1h3.6c.5 0 .9-.4.9-1V9c0-.5-.4-.9-.9-.9h-3.6c-.4-.1-.9.3-.9.9v16zm11-17c-.5 0-1 .4-1 .9V25c0 .5.4 1 1 1h3.6c.5 0 1-.4 1-1V9c0-.5-.4-.9-1-.9 0-.1-3.6-.1-3.6-.1z"></path>-->
</svg>
</button>
<div id="current-time" part="currenttime">0:00</div>
<div id="slider" part="slider">
<svg id="svg" part="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${this.attributes["wave-width"].value} ${this.attributes["wave-height"].value}" width="${this.attributes["wave-width"].value}" height="${this.attributes["wave-height"].value}">
`;
if (!this.animation) {
html += `
<defs>
<clipPath id="left-to-right-x">
<rect x="-1" y="-100" width="${parseInt(this.attributes["wave-width"].value) + 2}" height="${parseInt(this.attributes["wave-height"].value) + 200}" >
<animate id="animationsvgx" attributeName="x" values="-1;${parseInt(this.attributes["wave-width"].value) + 2}" dur="99999s" fill="freeze" />
</rect>
</clipPath>
<clipPath id="left-to-right">
<rect x="-${parseInt(this.attributes["wave-width"].value) + 2}" y="-100" width="${parseInt(this.attributes["wave-width"].value) + 2}" height="${parseInt(this.attributes["wave-height"].value) + 200}" >
<animate id="animationsvg" attributeName="x" values="-${parseInt(this.attributes["wave-width"].value) + 2};-1" dur="99999s" fill="freeze" />
</rect>
</clipPath>
</defs>
<path id="path1" part="path1" stroke-width="2" d="" clip-path="url(#left-to-right-x)"></path>
<path id="path2" part="path2" stroke-width="2" d="" clip-path="url(#left-to-right)" style="display:none;"></path>`;
} else {
html += `
<path id="path1" part="path1" stroke-width="2" style="display:none;"></path>
<path id="path2" part="path2" stroke-width="2" style="display:block;">
<animate id="animationsvg" attributeName="d" dur="99999s" calcMode="linear" values="" fill="freeze"></animate>
</path>
`;
}
html += `
</svg>
<input type="range" part="input" id="seek-slider" max="100" value="0" step="any">
</div>
<div id="duration" part="duration">0:00</div>
</div>
<audio src="${this.attributes.src.value}"></audio>
`;
return html;
}
templateCss() {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
return `
<style>
*, :after, :before {
box-sizing: border-box;
margin: 0;
}
:host {
display: flex;
}
.player {
display: flex;
justify-content: space-between;
align-items: center;
}
#play {
background: transparent;
border: none;
cursor:pointer;
padding: 0 0 0 10px;
margin: 0px;
}
#play svg {
fill: ${(_b = (_a = this.attributes["color"]) == null ? void 0 : _a.value) != null ? _b : "#858a8d"};
position:relative;
transition: transform 0.3s;
top: -0.5px;
}
#play svg:hover {
transform: scale(1.2);
}
#play svg path {
stroke-linecap: round;
stroke-linejoin: round;
transition: 0.2s;
}
#svg {
margin: 0 10px;
overflow: visible;
stroke-width: 1px;
fill: none;
}
#path1 {
stroke: ${(_d = (_c = this.attributes["wave-color"]) == null ? void 0 : _c.value) != null ? _d : "#dadcdd"};
overflow: visible;
stroke-linecap: round;
}
#path2 {
stroke: ${(_f = (_e = this.attributes["wave-progress-color"]) == null ? void 0 : _e.value) != null ? _f : "#858a8d"};
overflow: visible;
stroke-linecap: round;
}
#slider {
position:relative;
}
#duration, #current-time {
position: relative;
top:-1.1px;
color: ${(_h = (_g = this.attributes["color"]) == null ? void 0 : _g.value) != null ? _h : "#858a8d"};
margin: 0px 10px;
font-size: 16px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
min-width:32px;
}
#seek-slider {
position: absolute;
width: 100%;
left: 0;
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
padding: 0px;
margin: 0px;
border: 0px;
height: ${parseInt(this.attributes["wave-height"].value)}px;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-ms-track {
width: 100%;
cursor: pointer;
/* Hides the slider so custom styles can be added */
background: transparent;
border-color: transparent;
color: transparent;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
position:relative;
/*top: -1.5px;*/
height: 12.5px;
width: 12.5px;
border-radius: 50%;
background: ${(_j = (_i = this.attributes["wave-slider"]) == null ? void 0 : _i.value) != null ? _j : "#4fc3f7"};
cursor: pointer;
box-shadow: none;
}
input[type="range"]::-webkit-slider-thumb {
transition: transform 0.3s;
}
input[type="range"]:active::-webkit-slider-thumb {
transform: scale(1.5);
}
input[type="range"]::-moz-range-thumb {
height: 12.5px;
width: 12.5px;
border-radius: 50%;
background: ${(_l = (_k = this.attributes["wave-slider"]) == null ? void 0 : _k.value) != null ? _l : "#4fc3f7"};
cursor: pointer;
box-shadow: none;
border: 0px;
}
input[type="range"]:active::-moz-range-thumb {
transform: scale(1.5);
}
</style>
`;
}
}
window.customElements.define("wave-audio-path-player", AudioPathPlayer);
export { AudioPathPlayer };