UNPKG

@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.

767 lines (766 loc) 23.9 kB
import { Rect as v, Log as A, MediaStreamClip as _, Combinator as G, OffscreenSprite as J } from "@webav/av-cliper"; import { EventTool as B, debounce as Q, workerTimer as Z, throttle as K } from "@webav/internal-utils"; const I = [ "t", "b", "l", "r", "lt", "lb", "rt", "rb", "rotate" ]; function R(i) { return document.createElement(i); } const $ = /* @__PURE__ */ new WeakMap(); function V(i, t) { if ($.has(i)) return $.get(i)(t); let n = 10; new ResizeObserver((s) => { const a = s[0]; a != null && (n = 10 / (a.contentRect.width / i.width)); }).observe(i); function r(s) { const { w: a, h } = s, o = n, c = o / 2, d = a / 2, l = h / 2, u = o * 1.5, p = u / 2; return { ...s.fixedAspectRatio ? {} : { t: new v(-c, -l - c, o, o, s), b: new v(-c, l - c, o, o, s), l: new v(-d - c, -c, o, o, s), r: new v(d - c, -c, o, o, s) }, lt: new v(-d - c, -l - c, o, o, s), lb: new v(-d - c, l - c, o, o, s), rt: new v(d - c, -l - c, o, o, s), rb: new v(d - c, l - c, o, o, s), rotate: new v(-p, -l - o * 2 - p, u, u, s) }; } return $.set(i, r), r(t); } const W = /* @__PURE__ */ new WeakMap(); function L(i) { if (W.has(i)) return W.get(i); const t = { w: i.clientWidth / i.width, h: i.clientHeight / i.height }; return new ResizeObserver(() => { t.w = i.clientWidth / i.width, t.h = i.clientHeight / i.height; }).observe(i), W.set(i, t), t; } var z = /* @__PURE__ */ ((i) => (i.ActiveSpriteChange = "activeSpriteChange", i.AddSprite = "addSprite", i))(z || {}); class tt { #t = []; #e = null; #i = new B(); on = this.#i.on; get activeSprite() { return this.#e; } set activeSprite(t) { t === this.#e || t?.interactable === "disabled" || (this.#e = t, this.#i.emit("activeSpriteChange", t)); } activeSpriteByCoord(t, n) { this.activeSprite = this.getSprites().reverse().find( (e) => e.visible && e.interactable !== "disabled" && e.rect.checkHit(t, n) ) ?? null; } async addSprite(t) { await t.ready, this.#t.push(t), this.#t = this.#t.sort((n, e) => n.zIndex - e.zIndex), t.on("propsChange", (n) => { n.zIndex != null && (this.#t = this.#t.sort((e, r) => e.zIndex - r.zIndex)); }), this.#i.emit("addSprite", t); } removeSprite(t) { this.#e === t && (this.activeSprite = null), this.#t = this.#t.filter((n) => n !== t), t.destroy(); } getSprites(t = { time: !0 }) { return this.#t.filter( (n) => n.visible && (t.time ? this.#s >= n.time.offset && this.#s <= n.time.offset + n.time.duration : !0) ); } #s = 0; updateRenderTime(t) { this.#s = t; const n = this.activeSprite; n != null && (t < n.time.offset || t > n.time.offset + n.time.duration) && (this.activeSprite = null); } destroy() { this.#i.destroy(), this.#t.forEach((t) => t.destroy()), this.#t = []; } } const et = ` <svg t="1756779136804" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1456" width="16" height="16"> <path d="M1022.793875 170.063604L852.730271 0 511.396938 341.333333 170.063604 0 0 170.063604l341.333333 341.333334L0 852.730271l170.063604 170.063604 341.333334-340.127208 341.333333 340.127208 170.063604-170.063604-340.127208-341.333333 340.127208-341.333334z" fill="#bfbfbf" p-id="1457"></path> </svg> `; function nt(i, t, n) { const e = L(t), r = new ResizeObserver(() => { n.activeSprite != null && H(n.activeSprite, t, a, h); }); r.observe(t); let s = () => { }; const { rectEl: a, ctrlsEl: h } = it(i); a.addEventListener("pointerdown", (c) => { if (Object.values(h).includes(c.target)) return; const d = t.getBoundingClientRect(), l = (c.clientX - d.left) / e.w, u = (c.clientY - d.top) / e.h; n.activeSpriteByCoord(l, u); }); const o = n.on(z.ActiveSpriteChange, (c) => { if (s(), c == null) { a.style.display = "none"; return; } H(c, t, a, h), s = c.on("propsChange", () => { H(c, t, a, h); }); }); return () => { r.disconnect(), o(), a.remove(), s(); }; } function it(i) { 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 n = Object.fromEntries( I.map((e) => { const r = R("div"); return r.classList.add(`ctrl-key-${e}`), 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: ${e === "rotate" ? "crosshair" : "default"}; user-select: none; `, [e, r]; }) ); return Object.values(n).forEach((e) => t.appendChild(e)), i.appendChild(t), { rectEl: t, ctrlsEl: n }; } function H(i, t, n, e) { if (i.interactable === "disabled") { n.style.display = "none"; return; } n.style.display = ""; const r = L(t), { x: s, y: a, w: h, h: o, angle: c } = i.rect; Object.assign(n.style, { left: `${s * r.w}px`, top: `${a * r.h}px`, width: `${h * r.w}px`, height: `${o * r.h}px`, transform: `rotate(${c}rad)` }); const d = V(t, i.rect); for (const l in e) { const u = l, p = e[u], f = d[u]; if (f == null) { p.style.display = "none"; continue; } const w = { width: `${f.w * r.w}px`, height: `${f.h * r.h}px`, transform: `translate(${f.x * r.w}px, ${f.y * r.h}px)`, left: "50%", top: "50%" }; let b = { display: "none" }; switch (p.innerHTML = "", i.interactable) { case "interactive": b = { display: "block", backgroundColor: "#fff", border: "1px solid #3ee" }; break; case "selectable": u !== "rotate" && (b = { display: "flex", justifyContent: "center", alignItems: "center", backgroundColor: "transparent", border: "none" }, p.innerHTML = et); break; } Object.assign(p.style, w, b); } } function rt(i, t) { const n = (e) => { if (e.button !== 0 || e.target !== i) return; const r = L(i), { offsetX: s, offsetY: a } = e, h = s / r.w, o = a / r.h; t.activeSpriteByCoord(h, o); }; return i.addEventListener("pointerdown", n), () => { i.removeEventListener("pointerdown", n); }; } function st(i, t, n) { let e = 0, r = 0, s = null; const a = pt(i, n), h = n.querySelector(".sprite-rect"); if (!h) throw Error("sprite-rect DOM Node not found"); const o = (p) => { const f = t.activeSprite; if (p.button !== 0 || f == null || f.interactable !== "interactive") return; const { clientX: w, clientY: b } = p; s = f.rect.clone(), a.magneticEffect(f.rect.x, f.rect.y, f.rect), e = w, r = b, window.addEventListener("pointermove", d), window.addEventListener("pointerup", l), p.stopPropagation(); }, c = L(i), d = (p) => { const f = t.activeSprite; if (f == null || f.interactable !== "interactive" || s == null) return; const { clientX: w, clientY: b } = p; let m = s.x + (w - e) / c.w, y = s.y + (b - r) / c.h; N( f.rect, i, a.magneticEffect(m, y, f.rect) ); }, l = () => { a.hide(), window.removeEventListener("pointermove", d), window.removeEventListener("pointerup", l); }; h.addEventListener("pointerdown", o), i.addEventListener("pointerdown", o); const u = ot(i, h, t); return () => { a.destroy(), l(), h.removeEventListener("pointerdown", o), i.removeEventListener("pointerdown", o), u(); }; } function ot(i, t, n) { const e = Array.from(t.children), r = L(i); e.forEach((c, d) => { const l = I[d]; c.addEventListener("pointerdown", (u) => { const p = n.activeSprite; if (u.button !== 0 || p == null || p.interactable !== "interactive") return; const { clientX: f, clientY: w } = u; l === "rotate" ? lt( p.rect, dt(p.rect.center, r, i) ) : at({ sprRect: p.rect, ctrlKey: l, startX: f, startY: w, cvsRatio: r, cvsEl: i }), u.stopPropagation(); }); }), e[I.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 = n.on(z.ActiveSpriteChange, (c) => { if (h(), c == null) return; const d = Q(function() { const { angle: l } = c.rect, u = l < 0 ? l + 2 * Math.PI : l; e.forEach((p, f) => { const w = I[f]; if (w === "rotate") return; const b = (a[w] + Math.floor((u + Math.PI / 8) / (Math.PI / 4))) % 8; p.style.cursor = s[b]; }); }, 300); h = c.on("propsChange", (l) => { l.rect?.angle != null && d(); }), d(); }); return () => { h(), o(); }; } function at({ sprRect: i, startX: t, startY: n, ctrlKey: e, cvsRatio: r, cvsEl: s }) { const a = i.clone(), h = (c) => { const { clientX: d, clientY: l } = c, u = (d - t) / r.w, p = (l - n) / r.h, f = e.length === 1 ? ct : ht, { x: w, y: b, w: m, h: y } = a, T = Math.atan2(y, m), { incW: D, incH: Y, incS: C, rotateAngle: j } = f({ deltaX: u, deltaY: p, angle: i.angle, ctrlKey: e, diagonalAngle: T }), S = 10; let g = m, M = y, k = a.fixedScaleCenter ? D * 2 : D, O = a.fixedScaleCenter ? Y * 2 : Y, x = C; const F = Math.sqrt(y ** 2 + m ** 2), X = Math.sqrt((S * (y / m)) ** 2 + S ** 2); switch (e) { // 非等比例缩放时,变化的增量范围 由原宽高跟 minSize 的差值决定 // 非等比例缩放时,根据ctrlKey的不同,固定宽高中的一个,另一个根据增量计算,并考虑最小值限定 case "l": g = Math.max(m + k, S), x = Math.min(C, m - S); break; case "r": g = Math.max(m + k, S), x = Math.max(C, S - m); break; case "b": M = Math.max(y + O, S), x = Math.min(C, y - S); break; case "t": M = Math.max(y + O, S), x = Math.max(C, S - y); break; // 等比例缩放时,变化(对角线长度)的增量范围由原对角线长度跟 minSize 对角线的差值决定 // 等比例缩放时,某一边达到最小值时保持宽高比例不变 case "lt": case "lb": g = Math.max(m + k, S), M = g === S ? y / m * g : y + O, x = Math.min(C, F - X); break; case "rt": case "rb": g = Math.max(m + k, S), M = g === S ? y / m * g : y + O, x = Math.max(C, X - F); break; } let P = w, E = b; if (a.fixedScaleCenter) P = w + m / 2 - g / 2, E = b + y / 2 - M / 2; else { const q = x / 2 * Math.cos(j) + w + m / 2, U = x / 2 * Math.sin(j) + b + y / 2; P = q - g / 2, E = U - M / 2; } N(i, s, { x: P, y: E, w: g, h: M }); }, o = () => { window.removeEventListener("pointermove", h), window.removeEventListener("pointerup", o); }; window.addEventListener("pointermove", h), window.addEventListener("pointerup", o); } function ct({ deltaX: i, deltaY: t, angle: n, ctrlKey: e }) { let r = 0, s = 0, a = 0, h = n; return e === "l" || e === "r" ? (r = i * Math.cos(n) + t * Math.sin(n), s = r * (e === "l" ? -1 : 1)) : (e === "t" || e === "b") && (h = n - Math.PI / 2, r = i * Math.cos(h) + t * Math.sin(h), a = r * (e === "b" ? -1 : 1)), { incW: s, incH: a, incS: r, rotateAngle: h }; } function ht({ deltaX: i, deltaY: t, angle: n, ctrlKey: e, diagonalAngle: r }) { const s = (e === "lt" || e === "rb" ? 1 : -1) * r + n, a = i * Math.cos(s) + t * Math.sin(s), h = e === "lt" || e === "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 lt(i, t) { const n = ({ clientX: r, clientY: s }) => { const a = r - t.x, h = s - t.y, o = Math.atan2(h, a) + Math.PI / 2; i.angle = o; }, e = () => { window.removeEventListener("pointermove", n), window.removeEventListener("pointerup", e); }; window.addEventListener("pointermove", n), window.addEventListener("pointerup", e); } function dt(i, t, n) { const e = i.x * t.w, r = i.y * t.h, { left: s, top: a } = n.getBoundingClientRect(); return { x: e + s, y: r + a }; } function N(i, t, n) { const e = { x: i.x, y: i.y, w: i.w, h: i.h, ...n }, r = t.width * 0.05, s = t.height * 0.05; e.x < -e.w + r ? e.x = -e.w + r : e.x > t.width - r && (e.x = t.width - r), e.y < -e.h + s ? e.y = -e.h + s : e.y > t.height - s && (e.y = t.height - s), i.x = e.x, i.y = e.y, i.w = e.w, i.h = e.h; } function pt(i, t) { const n = "display: none; position: absolute;", e = { w: 0, h: 0, x: 0, y: 0 }, r = { vertMiddle: { ...e, h: 100, x: 50, ref: { prop: "x", val: ({ w: o }) => (i.width - o) / 2 } }, horMiddle: { ...e, w: 100, y: 50, ref: { prop: "y", val: ({ h: o }) => (i.height - o) / 2 } }, top: { ...e, w: 100, ref: { prop: "y", val: () => 0 } }, bottom: { ...e, w: 100, y: 100, ref: { prop: "y", val: ({ h: o }) => i.height - o } }, left: { ...e, h: 100, ref: { prop: "x", val: () => 0 } }, right: { ...e, h: 100, x: 100, ref: { prop: "x", val: ({ w: o }) => i.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: d, x: l, y: u }]) => { const p = R("div"); return p.style.cssText = ` ${n} border-${c > 0 ? "top" : "left"}: 1px solid #3ee; top: ${u}%; left: ${l}%; ${l === 100 ? "margin-left: -1px" : ""}; ${u === 100 ? "margin-top: -1px" : ""}; width: ${c}%; height: ${d}%; `, s.appendChild(p), [o, p]; }) ); t.appendChild(s); const h = 6 / (900 / i.width); return { magneticEffect(o, c, d) { const l = { x: o, y: c }, u = { x: h, y: h }, p = { x: "", y: "" }; Object.values(a).forEach((f) => f.style.display = "none"); for (const f in r) { const { prop: w, val: b } = r[f].ref, m = b(d), T = Math.abs((w === "x" ? o : c) - m); T <= h && T < u[w] && (u[w] = T, l[w] = m, p[w] = f); } return p.x && (a[p.x].style.display = "block"), p.y && (a[p.y].style.display = "block"), l; }, hide() { Object.values(a).forEach((o) => o.style.display = "none"); }, destroy() { s.remove(); } }; } const ft = { sampleRate: 48e3 }; function ut(i) { const t = R("canvas"); return t.style.cssText = ` width: 100%; height: 100%; display: block; touch-action: none; `, t.width = i.width, t.height = i.height, t; } class St { #t; #e; #i; #s = !1; #u = []; #w; #o = new B(); on = this.#o.on; #m; /** * 预览帧生成中 */ #l = !1; /** * 创建 `AVCanvas` 类的实例。 * @param attchEl - 要添加画布的元素。 * @param opts - 画布的选项 * @param opts.bgColor - 画布的背景颜色。 * @param opts.width - 画布的宽度。 * @param opts.height - 画布的高度。 */ constructor(t, n) { this.#m = n, this.#t = ut(n); const e = this.#t.getContext("2d", { alpha: !1 }); if (e == null) throw Error("canvas context is null"); this.#i = e; const r = R("div"); r.style.cssText = "width: 100%; height: 100%; position: relative;", r.appendChild(this.#t), t.appendChild(r), mt(this.#n).connect(this.#c), V(this.#t, { x: 0, y: 0, w: 0, h: 0 }), this.#e = new tt(), this.#u.push( // 鼠标样式、控制 sprite 依赖 activeSprite, // activeSprite 需要在他们之前监听到 mousedown 事件 (代码顺序需要靠前) rt(this.#t, this.#e), nt(r, this.#t, this.#e), st(this.#t, this.#e, r), this.#e.on(z.AddSprite, (c) => { const { rect: d } = c; d.x === 0 && d.y === 0 && (d.x = (this.#t.width - d.w) / 2, d.y = (this.#t.height - d.h) / 2); }), B.forwardEvent(this.#e, this.#o, [ z.ActiveSpriteChange ]) ); let s = this.#a, a = performance.now(), h = 0; const o = 1e3 / 30; this.#w = Z(() => { (performance.now() - a) / (o * h) < 1 || this.#l || (h += 1, this.#i.fillStyle = n.bgColor, this.#i.fillRect(0, 0, this.#t.width, this.#t.height), this.#b(), 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.#f.updateTime(t); } #p() { if (this.#r.step !== 0) { this.#r.step = 0, this.#o.emit("paused"), this.#n.suspend(); for (const t of this.#h) t.stop(), t.disconnect(); this.#h.clear(), this.#f.reset(); } } #n = new AudioContext(); #c = this.#n.createMediaStreamDestination(); #h = /* @__PURE__ */ new Set(); #b() { const t = this.#i; let n = this.#a; const { start: e, end: r, step: s, audioPlayAt: a } = this.#r; n += s, s !== 0 && n >= e && n < r ? this.#d(n) : this.#p(); const h = []; for (const o of this.#e.getSprites()) { t.save(); const { audio: c } = o.render(t, n - o.time.offset); t.restore(), h.push(c); } if (t.resetTransform(), s !== 0) { const o = Math.max(this.#n.currentTime, a), c = wt( h, this.#n ); let d = 0; for (const l of c) l.start(o), l.connect(this.#n.destination), l.connect(this.#c), this.#h.add(l), l.onended = () => { l.disconnect(), this.#h.delete(l); }, d = Math.max(d, l.buffer?.duration ?? 0); this.#r.audioPlayAt = o + d; } } #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 n = this.#e.getSprites({ time: !1 }).map((r) => r.time.offset + r.time.duration), e = t.end ?? (n.length > 0 ? Math.max(...n) : 1 / 0); if (t.start >= e || t.start < 0) throw Error( `Invalid time parameter, ${JSON.stringify({ start: t.start, end: e })}` ); this.#d(t.start), this.#f.reset(), this.#r.start = t.start, this.#r.end = e, this.#r.step = (t.playbackRate ?? 1) * (1e3 / 30) * 1e3, this.#n.resume(), this.#r.audioPlayAt = 0, this.#o.emit("playing"), A.info("AVCanvs play by:", this.#r); } #f = (() => { const t = /* @__PURE__ */ new Set(); return { reset() { t.clear(); }, updateTime: K((n) => { const r = this.#e.getSprites({ time: !1 }).filter((s) => { const { offset: a } = s.time; return a > n && a - 1e6 <= n; }); for (const s of r) t.has(s) || s.preFrame(0), t.add(s); }, 500) }; })(); /** * 暂停播放,画布内容不再更新 */ pause() { this.#p(); } /** * 预览 `AVCanvas` 指定时间的图像帧 */ async previewFrame(t) { this.#p(), this.#d(t), this.#l = !0; try { await Promise.all( this.#e.getSprites({ time: !1 }).map((n) => t >= n.time.offset && t <= n.time.offset + n.time.duration ? n.preFrame(t - n.time.offset) : null) ); } finally { this.#l = !1; } } /** * 获取当前帧的截图图像 返回的是一个base64 */ captureImage() { return this.#t.toDataURL(); } get activeSprite() { return this.#e.activeSprite; } set activeSprite(t) { this.#e.activeSprite = t; } #y = /* @__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.#n.state === "suspended" && this.#n.resume().catch(A.error); const n = t.getClip(); if (n instanceof _ && n.audioTrack != null) { const e = this.#n.createMediaStreamSource( new MediaStream([n.audioTrack]) ); e.connect(this.#c), this.#y.set(t, e); } await this.#e.addSprite(t); }; /** * 删除 {@link VisibleSprite} * @param args * @returns * @example * const sprite = new VisibleSprite(); * avCvs.removeSprite(sprite); */ removeSprite = (t) => { this.#y.get(t)?.disconnect(), this.#e.removeSprite(t); }; /** * 销毁实例 */ destroy() { this.#s || (this.#s = !0, this.#n.close(), this.#c.disconnect(), this.#o.destroy(), this.#w(), this.#t.parentElement?.remove(), this.#u.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.#n.state === "suspended" && this.#n.resume().catch(A.error); const t = new MediaStream( this.#t.captureStream().getTracks().concat(this.#c.stream.getTracks()) ); return A.info( "AVCanvas.captureStream, tracks:", t.getTracks().map((n) => n.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 = {}) { A.info("AVCanvas.createCombinator, opts:", t); const n = new G({ ...this.#m, ...t }), e = this.#e.getSprites({ time: !1 }); if (e.length === 0) throw Error("No sprite added"); for (const r of e) { const s = new J(r.getClip()); s.time = { ...r.time }, r.copyStateTo(s), await n.addSprite(s); } return n; } } function wt(i, t) { const n = []; if (i.length === 0) return n; for (const [e, r] of i) { if (e == null || e.length <= 0) continue; const s = t.createBuffer( 2, e.length, ft.sampleRate ); s.copyToChannel(e, 0), s.copyToChannel(r ?? e, 1); const a = t.createBufferSource(); a.buffer = s, n.push(a); } return n; } function mt(i) { const t = i.createOscillator(), n = new Float32Array([0, 0]), e = new Float32Array([0, 0]), r = i.createPeriodicWave(n, e, { disableNormalization: !0 }); return t.setPeriodicWave(r), t.start(), t; } export { St as AVCanvas }; //# sourceMappingURL=av-canvas.js.map