@webav/av-canvas
Version:
Combine Text, Image, Video, Audio, UserMedia, DisplayMedia to generate MediaStream. With [AVRcorder](../av-recorder/README.md) you can output MP4 streams and save them as local files or push them to the server.
713 lines (712 loc) • 22.3 kB
JavaScript
import { Rect as b, Log as T, MediaStreamClip as _, Combinator as G, OffscreenSprite as J } from "@webav/av-cliper";
import { EventTool as Y, debounce as Q, workerTimer as Z, throttle as K } from "@webav/internal-utils";
const k = [
"t",
"b",
"l",
"r",
"lt",
"lb",
"rt",
"rb",
"rotate"
];
function R(n) {
return document.createElement(n);
}
const P = /* @__PURE__ */ new WeakMap();
function V(n, t) {
if (P.has(n))
return P.get(n)(t);
let e = 10;
new ResizeObserver((s) => {
const a = s[0];
a != null && (e = 10 / (a.contentRect.width / n.width));
}).observe(n);
function r(s) {
const { w: a, h } = s, o = e, c = o / 2, l = a / 2, d = h / 2, f = o * 1.5, p = f / 2;
return {
...s.fixedAspectRatio ? {} : {
t: new b(-c, -d - c, o, o, s),
b: new b(-c, d - c, o, o, s),
l: new b(-l - c, -c, o, o, s),
r: new b(l - c, -c, o, o, s)
},
lt: new b(-l - c, -d - c, o, o, s),
lb: new b(-l - c, d - c, o, o, s),
rt: new b(l - c, -d - c, o, o, s),
rb: new b(l - c, d - c, o, o, s),
rotate: new b(-p, -d - o * 2 - p, f, f, s)
};
}
return P.set(n, r), r(t);
}
const W = /* @__PURE__ */ new WeakMap();
function L(n) {
if (W.has(n))
return W.get(n);
const t = {
w: n.clientWidth / n.width,
h: n.clientHeight / n.height
};
return new ResizeObserver(() => {
t.w = n.clientWidth / n.width, t.h = n.clientHeight / n.height;
}).observe(n), W.set(n, t), t;
}
var z = /* @__PURE__ */ ((n) => (n.ActiveSpriteChange = "activeSpriteChange", n.AddSprite = "addSprite", n))(z || {});
class tt {
#t = [];
#e = null;
#n = new Y();
on = this.#n.on;
get activeSprite() {
return this.#e;
}
set activeSprite(t) {
t !== this.#e && (this.#e = t, this.#n.emit("activeSpriteChange", t));
}
activeSpriteByCoord(t, e) {
this.activeSprite = this.getSprites().reverse().find((i) => i.visible && i.rect.checkHit(t, e)) ?? null;
}
async addSprite(t) {
await t.ready, this.#t.push(t), this.#t = this.#t.sort((e, i) => e.zIndex - i.zIndex), t.on("propsChange", (e) => {
e.zIndex != null && (this.#t = this.#t.sort((i, r) => i.zIndex - r.zIndex));
}), this.#n.emit("addSprite", t);
}
removeSprite(t) {
this.#e === t && (this.activeSprite = null), this.#t = this.#t.filter((e) => e !== t), t.destroy();
}
getSprites(t = { time: !0 }) {
return this.#t.filter(
(e) => e.visible && (t.time ? this.#s >= e.time.offset && this.#s <= e.time.offset + e.time.duration : !0)
);
}
#s = 0;
updateRenderTime(t) {
this.#s = t;
const e = this.activeSprite;
e != null && (t < e.time.offset || t > e.time.offset + e.time.duration) && (this.activeSprite = null);
}
destroy() {
this.#n.destroy(), this.#t.forEach((t) => t.destroy()), this.#t = [];
}
}
function et(n, t, e) {
const i = L(t), r = new ResizeObserver(() => {
e.activeSprite != null && D(e.activeSprite, t, a, h);
});
r.observe(t);
let s = () => {
};
const { rectEl: a, ctrlsEl: h } = it(n);
a.addEventListener("pointerdown", (c) => {
if (Object.values(h).includes(c.target))
return;
const l = t.getBoundingClientRect(), d = (c.clientX - l.left) / i.w, f = (c.clientY - l.top) / i.h;
e.activeSpriteByCoord(d, f);
});
const o = e.on(z.ActiveSpriteChange, (c) => {
if (s(), c == null) {
a.style.display = "none";
return;
}
D(c, t, a, h), s = c.on("propsChange", () => {
D(c, t, a, h);
}), a.style.display = "";
});
return () => {
r.disconnect(), o(), a.remove(), s();
};
}
function it(n) {
const t = R("div");
t.classList.add("sprite-rect"), t.style.cssText = `
position: absolute;
z-index: 3;
pointer-events: auto;
border: 1px solid #eee;
box-sizing: border-box;
display: none;
cursor: move;
`;
const e = Object.fromEntries(
k.map((i) => {
const r = R("div");
return r.classList.add(`ctrl-key-${i}`), r.style.cssText = `
display: none;
position: absolute;
border: 1px solid #3ee; border-radius: 50%;
box-sizing: border-box;
background-color: #fff;
pointer-events: auto;
cursor: ${i === "rotate" ? "crosshair" : "default"};
user-select: none;
`, [i, r];
})
);
return Object.values(e).forEach((i) => t.appendChild(i)), n.appendChild(t), {
rectEl: t,
ctrlsEl: e
};
}
function D(n, t, e, i) {
const r = L(t), { x: s, y: a, w: h, h: o, angle: c } = n.rect;
Object.assign(e.style, {
left: `${s * r.w}px`,
top: `${a * r.h}px`,
width: `${h * r.w}px`,
height: `${o * r.h}px`,
rotate: `${c}rad`
}), Object.entries(V(t, n.rect)).forEach(([l, { x: d, y: f, w: p, h: u }]) => {
Object.assign(i[l].style, {
display: "block",
left: "50%",
top: "50%",
width: `${p * r.w}px`,
height: `${u * r.h}px`,
// border 1px, 所以要 -1
transform: `translate(${d * r.w}px, ${f * r.h}px)`
});
});
}
function nt(n, t) {
const e = (i) => {
if (i.button !== 0 || i.target !== n) return;
const r = L(n), { offsetX: s, offsetY: a } = i, h = s / r.w, o = a / r.h;
t.activeSpriteByCoord(h, o);
};
return n.addEventListener("pointerdown", e), () => {
n.removeEventListener("pointerdown", e);
};
}
function rt(n, t, e) {
let i = 0, r = 0, s = null;
const a = lt(n, e), h = e.querySelector(".sprite-rect");
if (!h) throw Error("sprite-rect DOM Node not found");
const o = (p) => {
if (p.button !== 0 || t.activeSprite == null) return;
const u = t.activeSprite, { clientX: m, clientY: v } = p;
s = u.rect.clone(), a.magneticEffect(u.rect.x, u.rect.y, u.rect), i = m, r = v, window.addEventListener("pointermove", l), window.addEventListener("pointerup", d), p.stopPropagation();
}, c = L(n), l = (p) => {
if (t.activeSprite == null || s == null) return;
const { clientX: u, clientY: m } = p;
let v = s.x + (u - i) / c.w, w = s.y + (m - r) / c.h;
N(
t.activeSprite.rect,
n,
a.magneticEffect(v, w, t.activeSprite.rect)
);
}, d = () => {
a.hide(), window.removeEventListener("pointermove", l), window.removeEventListener("pointerup", d);
};
h.addEventListener("pointerdown", o), n.addEventListener("pointerdown", o);
const f = st(n, h, t);
return () => {
a.destroy(), d(), h.removeEventListener("pointerdown", o), n.removeEventListener("pointerdown", o), f();
};
}
function st(n, t, e) {
const i = Array.from(t.children), r = L(n);
i.forEach((c, l) => {
const d = k[l];
c.addEventListener("pointerdown", (f) => {
if (f.button !== 0 || e.activeSprite == null) return;
const { clientX: p, clientY: u } = f;
d === "rotate" ? ht(
e.activeSprite.rect,
dt(e.activeSprite.rect.center, r, n)
) : ot({
sprRect: e.activeSprite.rect,
ctrlKey: d,
startX: p,
startY: u,
cvsRatio: r,
cvsEl: n
}), f.stopPropagation();
});
}), i[k.indexOf("rotate")].style.cursor = "crosshair";
const s = [
"ns-resize",
"nesw-resize",
"ew-resize",
"nwse-resize",
"ns-resize",
"nesw-resize",
"ew-resize",
"nwse-resize"
], a = {
t: 0,
rt: 1,
r: 2,
rb: 3,
b: 4,
lb: 5,
l: 6,
lt: 7
};
let h = () => {
};
const o = e.on(z.ActiveSpriteChange, (c) => {
if (h(), c == null) return;
const l = Q(function() {
const { angle: d } = c.rect, f = d < 0 ? d + 2 * Math.PI : d;
i.forEach((p, u) => {
const m = k[u];
if (m === "rotate") return;
const v = (a[m] + Math.floor((f + Math.PI / 8) / (Math.PI / 4))) % 8;
p.style.cursor = s[v];
});
}, 300);
h = c.on("propsChange", (d) => {
d.rect?.angle != null && l();
}), l();
});
return () => {
h(), o();
};
}
function ot({
sprRect: n,
startX: t,
startY: e,
ctrlKey: i,
cvsRatio: r,
cvsEl: s
}) {
const a = n.clone(), h = (c) => {
const { clientX: l, clientY: d } = c, f = (l - t) / r.w, p = (d - e) / r.h, u = i.length === 1 ? at : ct, { x: m, y: v, w, h: S } = a, A = Math.atan2(S, w), { incW: j, incH: B, incS: C, rotateAngle: H } = u({
deltaX: f,
deltaY: p,
angle: n.angle,
ctrlKey: i,
diagonalAngle: A
}), y = 10;
let x = w, M = S, E = a.fixedScaleCenter ? j * 2 : j, O = a.fixedScaleCenter ? B * 2 : B, g = C;
const X = Math.sqrt(S ** 2 + w ** 2), F = Math.sqrt((y * (S / w)) ** 2 + y ** 2);
switch (i) {
// 非等比例缩放时,变化的增量范围 由原宽高跟 minSize 的差值决定
// 非等比例缩放时,根据ctrlKey的不同,固定宽高中的一个,另一个根据增量计算,并考虑最小值限定
case "l":
x = Math.max(w + E, y), g = Math.min(C, w - y);
break;
case "r":
x = Math.max(w + E, y), g = Math.max(C, y - w);
break;
case "b":
M = Math.max(S + O, y), g = Math.min(C, S - y);
break;
case "t":
M = Math.max(S + O, y), g = Math.max(C, y - S);
break;
// 等比例缩放时,变化(对角线长度)的增量范围由原对角线长度跟 minSize 对角线的差值决定
// 等比例缩放时,某一边达到最小值时保持宽高比例不变
case "lt":
case "lb":
x = Math.max(w + E, y), M = x === y ? S / w * x : S + O, g = Math.min(C, X - F);
break;
case "rt":
case "rb":
x = Math.max(w + E, y), M = x === y ? S / w * x : S + O, g = Math.max(C, F - X);
break;
}
let I = m, $ = v;
if (a.fixedScaleCenter)
I = m + w / 2 - x / 2, $ = v + S / 2 - M / 2;
else {
const q = g / 2 * Math.cos(H) + m + w / 2, U = g / 2 * Math.sin(H) + v + S / 2;
I = q - x / 2, $ = U - M / 2;
}
N(n, s, {
x: I,
y: $,
w: x,
h: M
});
}, o = () => {
window.removeEventListener("pointermove", h), window.removeEventListener("pointerup", o);
};
window.addEventListener("pointermove", h), window.addEventListener("pointerup", o);
}
function at({
deltaX: n,
deltaY: t,
angle: e,
ctrlKey: i
}) {
let r = 0, s = 0, a = 0, h = e;
return i === "l" || i === "r" ? (r = n * Math.cos(e) + t * Math.sin(e), s = r * (i === "l" ? -1 : 1)) : (i === "t" || i === "b") && (h = e - Math.PI / 2, r = n * Math.cos(h) + t * Math.sin(h), a = r * (i === "b" ? -1 : 1)), { incW: s, incH: a, incS: r, rotateAngle: h };
}
function ct({
deltaX: n,
deltaY: t,
angle: e,
ctrlKey: i,
diagonalAngle: r
}) {
const s = (i === "lt" || i === "rb" ? 1 : -1) * r + e, a = n * Math.cos(s) + t * Math.sin(s), h = i === "lt" || i === "lb" ? -1 : 1, o = a * Math.cos(r) * h, c = a * Math.sin(r) * h;
return { incW: o, incH: c, incS: a, rotateAngle: s };
}
function ht(n, t) {
const e = ({ clientX: r, clientY: s }) => {
const a = r - t.x, h = s - t.y, o = Math.atan2(h, a) + Math.PI / 2;
n.angle = o;
}, i = () => {
window.removeEventListener("pointermove", e), window.removeEventListener("pointerup", i);
};
window.addEventListener("pointermove", e), window.addEventListener("pointerup", i);
}
function dt(n, t, e) {
const i = n.x * t.w, r = n.y * t.h, { left: s, top: a } = e.getBoundingClientRect();
return {
x: i + s,
y: r + a
};
}
function N(n, t, e) {
const i = { x: n.x, y: n.y, w: n.w, h: n.h, ...e }, r = t.width * 0.05, s = t.height * 0.05;
i.x < -i.w + r ? i.x = -i.w + r : i.x > t.width - r && (i.x = t.width - r), i.y < -i.h + s ? i.y = -i.h + s : i.y > t.height - s && (i.y = t.height - s), n.x = i.x, n.y = i.y, n.w = i.w, n.h = i.h;
}
function lt(n, t) {
const e = "display: none; position: absolute;", i = { w: 0, h: 0, x: 0, y: 0 }, r = {
vertMiddle: {
...i,
h: 100,
x: 50,
ref: { prop: "x", val: ({ w: o }) => (n.width - o) / 2 }
},
horMiddle: {
...i,
w: 100,
y: 50,
ref: { prop: "y", val: ({ h: o }) => (n.height - o) / 2 }
},
top: {
...i,
w: 100,
ref: { prop: "y", val: () => 0 }
},
bottom: {
...i,
w: 100,
y: 100,
ref: { prop: "y", val: ({ h: o }) => n.height - o }
},
left: {
...i,
h: 100,
ref: { prop: "x", val: () => 0 }
},
right: {
...i,
h: 100,
x: 100,
ref: { prop: "x", val: ({ w: o }) => n.width - o }
}
}, s = R("div");
s.style.cssText = `
position: absolute;
z-index: 4;
top: 0; left: 0;
width: 100%; height: 100%;
pointer-events: none;
box-sizing: border-box;
`;
const a = Object.fromEntries(
Object.entries(r).map(([o, { w: c, h: l, x: d, y: f }]) => {
const p = R("div");
return p.style.cssText = `
${e}
border-${c > 0 ? "top" : "left"}: 1px solid #3ee;
top: ${f}%; left: ${d}%;
${d === 100 ? "margin-left: -1px" : ""};
${f === 100 ? "margin-top: -1px" : ""};
width: ${c}%; height: ${l}%;
`, s.appendChild(p), [o, p];
})
);
t.appendChild(s);
const h = 6 / (900 / n.width);
return {
magneticEffect(o, c, l) {
const d = { x: o, y: c }, f = { x: h, y: h }, p = { x: "", y: "" };
Object.values(a).forEach((u) => u.style.display = "none");
for (const u in r) {
const { prop: m, val: v } = r[u].ref, w = v(l), A = Math.abs((m === "x" ? o : c) - w);
A <= h && A < f[m] && (f[m] = A, d[m] = w, p[m] = u);
}
return p.x && (a[p.x].style.display = "block"), p.y && (a[p.y].style.display = "block"), d;
},
hide() {
Object.values(a).forEach((o) => o.style.display = "none");
},
destroy() {
s.remove();
}
};
}
const pt = {
sampleRate: 48e3
};
function ft(n) {
const t = R("canvas");
return t.style.cssText = `
width: 100%;
height: 100%;
display: block;
touch-action: none;
`, t.width = n.width, t.height = n.height, t;
}
class yt {
#t;
#e;
#n;
#s = !1;
#f = [];
#u;
#o = new Y();
on = this.#o.on;
#w;
/**
* 创建 `AVCanvas` 类的实例。
* @param attchEl - 要添加画布的元素。
* @param opts - 画布的选项
* @param opts.bgColor - 画布的背景颜色。
* @param opts.width - 画布的宽度。
* @param opts.height - 画布的高度。
*/
constructor(t, e) {
this.#w = e, this.#t = ft(e);
const i = this.#t.getContext("2d", { alpha: !1 });
if (i == null) throw Error("canvas context is null");
this.#n = i;
const r = R("div");
r.style.cssText = "width: 100%; height: 100%; position: relative;", r.appendChild(this.#t), t.appendChild(r), wt(this.#i).connect(this.#c), V(this.#t, { x: 0, y: 0, w: 0, h: 0 }), this.#e = new tt(), this.#f.push(
// 鼠标样式、控制 sprite 依赖 activeSprite,
// activeSprite 需要在他们之前监听到 mousedown 事件 (代码顺序需要靠前)
nt(this.#t, this.#e),
et(r, this.#t, this.#e),
rt(this.#t, this.#e, r),
this.#e.on(z.AddSprite, (c) => {
const { rect: l } = c;
l.x === 0 && l.y === 0 && (l.x = (this.#t.width - l.w) / 2, l.y = (this.#t.height - l.h) / 2);
}),
Y.forwardEvent(this.#e, this.#o, [
z.ActiveSpriteChange
])
);
let s = this.#a, a = performance.now(), h = 0;
const o = 1e3 / 30;
this.#u = Z(() => {
(performance.now() - a) / (o * h) < 1 || (h += 1, this.#n.fillStyle = e.bgColor, this.#n.fillRect(0, 0, this.#t.width, this.#t.height), this.#S(), s !== this.#a && (s = this.#a, this.#o.emit("timeupdate", Math.round(s))));
}, o);
}
#a = 0;
#d(t) {
this.#a = t, this.#e.updateRenderTime(t), this.#p.updateTime(t);
}
#l() {
const t = this.#r.step !== 0;
this.#r.step = 0, t && (this.#o.emit("paused"), this.#i.suspend());
for (const e of this.#h)
e.stop(), e.disconnect();
this.#h.clear(), this.#p.reset();
}
#i = new AudioContext();
#c = this.#i.createMediaStreamDestination();
#h = /* @__PURE__ */ new Set();
#S() {
const t = this.#n;
let e = this.#a;
const { start: i, end: r, step: s, audioPlayAt: a } = this.#r;
e += s, s !== 0 && e >= i && e < r ? this.#d(e) : this.#l();
const h = [];
for (const o of this.#e.getSprites()) {
t.save();
const { audio: c } = o.render(t, e - o.time.offset);
t.restore(), h.push(c);
}
if (t.resetTransform(), s !== 0) {
const o = Math.max(this.#i.currentTime, a), c = ut(
h,
this.#i
);
let l = 0;
for (const d of c)
d.start(o), d.connect(this.#i.destination), d.connect(this.#c), this.#h.add(d), d.onended = () => {
d.disconnect(), this.#h.delete(d);
}, l = Math.max(l, d.buffer?.duration ?? 0);
this.#r.audioPlayAt = o + l;
}
}
#r = {
start: 0,
end: 0,
// paused state when step equal 0
step: 0,
// step: (1000 / 30) * 1000,
audioPlayAt: 0
};
/**
* 每 33ms 更新一次画布,绘制已添加的 Sprite
* @param opts - 播放选项
* @param opts.start - 开始播放的时间(单位:微秒)
* @param [opts.end] - 结束播放的时间(单位:微秒)。如果未指定,则播放到最后一个 Sprite 的结束时间
* @param [opts.playbackRate] - 播放速率。1 表示正常速度,2 表示两倍速度,0.5 表示半速等。如果未指定,则默认为 1
* @throws 如果开始时间大于等于结束时间或小于 0,则抛出错误
*/
play(t) {
const e = this.#e.getSprites({ time: !1 }).map((r) => r.time.offset + r.time.duration), i = t.end ?? (e.length > 0 ? Math.max(...e) : 1 / 0);
if (t.start >= i || t.start < 0)
throw Error(
`Invalid time parameter, ${JSON.stringify({ start: t.start, end: i })}`
);
this.#d(t.start), this.#p.reset(), this.#r.start = t.start, this.#r.end = i, this.#r.step = (t.playbackRate ?? 1) * (1e3 / 30) * 1e3, this.#i.resume(), this.#r.audioPlayAt = 0, this.#o.emit("playing"), T.info("AVCanvs play by:", this.#r);
}
#p = (() => {
const t = /* @__PURE__ */ new Set();
return {
reset() {
t.clear();
},
updateTime: K((e) => {
const r = this.#e.getSprites({ time: !1 }).filter((s) => {
const { offset: a } = s.time;
return a > e && a - 1e6 <= e;
});
for (const s of r)
t.has(s) || s.preFrame(0), t.add(s);
}, 500)
};
})();
/**
* 暂停播放,画布内容不再更新
*/
pause() {
this.#l();
}
/**
* 预览 `AVCanvas` 指定时间的图像帧
*/
previewFrame(t) {
this.#e.getSprites().forEach((e) => {
e.preFrame(t - e.time.offset);
}), this.#d(t), this.#l();
}
/**
* 获取当前帧的截图图像 返回的是一个base64
*/
captureImage() {
return this.#t.toDataURL();
}
get activeSprite() {
return this.#e.activeSprite;
}
set activeSprite(t) {
this.#e.activeSprite = t;
}
#m = /* @__PURE__ */ new WeakMap();
/**
* 添加 {@link VisibleSprite}
* @param args {@link VisibleSprite}
* @example
* const sprite = new VisibleSprite(
* new ImgClip({
* type: 'image/gif',
* stream: (await fetch('https://xx.gif')).body!,
* }),
* );
*/
addSprite = async (t) => {
this.#i.state === "suspended" && this.#i.resume().catch(T.error);
const e = t.getClip();
if (e instanceof _ && e.audioTrack != null) {
const i = this.#i.createMediaStreamSource(
new MediaStream([e.audioTrack])
);
i.connect(this.#c), this.#m.set(t, i);
}
await this.#e.addSprite(t);
};
/**
* 删除 {@link VisibleSprite}
* @param args
* @returns
* @example
* const sprite = new VisibleSprite();
* avCvs.removeSprite(sprite);
*/
removeSprite = (t) => {
this.#m.get(t)?.disconnect(), this.#e.removeSprite(t);
};
/**
* 销毁实例
*/
destroy() {
this.#s || (this.#s = !0, this.#i.close(), this.#c.disconnect(), this.#o.destroy(), this.#u(), this.#t.parentElement?.remove(), this.#f.forEach((t) => t()), this.#h.clear(), this.#e.destroy());
}
/**
* 合成所有素材的图像与音频,返回实时媒体流 `MediaStream`
*
* 可用于 WebRTC 推流,或由 {@link [AVRecorder](../../av-recorder/classes/AVRecorder.html)} 录制生成视频文件
*
* @see [直播录制](https://webav-tech.github.io/WebAV/demo/4_2-recorder-avcanvas)
*
*/
captureStream() {
this.#i.state === "suspended" && this.#i.resume().catch(T.error);
const t = new MediaStream(
this.#t.captureStream().getTracks().concat(this.#c.stream.getTracks())
);
return T.info(
"AVCanvas.captureStream, tracks:",
t.getTracks().map((e) => e.kind)
), t;
}
/**
* 创建一个视频合成器 {@link [Combinator](../../av-cliper/classes/Combinator.html)} 实例,用于将当前画布添加的 Sprite 导出为视频文件流
*
* @param opts - 创建 Combinator 的可选参数
* @throws 如果没有添加素材,会抛出错误
*
* @example
* avCvs.createCombinator().output() // => ReadableStream
*
* @see [视频剪辑](https://webav-tech.github.io/WebAV/demo/6_4-video-editor)
*/
async createCombinator(t = {}) {
T.info("AVCanvas.createCombinator, opts:", t);
const e = new G({ ...this.#w, ...t }), i = this.#e.getSprites({ time: !1 });
if (i.length === 0) throw Error("No sprite added");
for (const r of i) {
const s = new J(r.getClip());
s.time = { ...r.time }, r.copyStateTo(s), await e.addSprite(s);
}
return e;
}
}
function ut(n, t) {
const e = [];
if (n.length === 0) return e;
for (const [i, r] of n) {
if (i == null || i.length <= 0) continue;
const s = t.createBuffer(
2,
i.length,
pt.sampleRate
);
s.copyToChannel(i, 0), s.copyToChannel(r ?? i, 1);
const a = t.createBufferSource();
a.buffer = s, e.push(a);
}
return e;
}
function wt(n) {
const t = n.createOscillator(), e = new Float32Array([0, 0]), i = new Float32Array([0, 0]), r = n.createPeriodicWave(e, i, {
disableNormalization: !0
});
return t.setPeriodicWave(r), t.start(), t;
}
export {
yt as AVCanvas
};
//# sourceMappingURL=av-canvas.js.map