UNPKG

@fly-cut/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.

857 lines (856 loc) 28.6 kB
var vt = Object.defineProperty; var gt = (e) => { throw TypeError(e); }; var Ct = (e, t, n) => t in e ? vt(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n; var Q = (e, t, n) => Ct(e, typeof t != "symbol" ? t + "" : t, n), ft = (e, t, n) => t.has(e) || gt("Cannot " + n); var r = (e, t, n) => (ft(e, t, "read from private field"), n ? n.call(e) : t.get(e)), g = (e, t, n) => t.has(e) ? gt("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(e) : t.set(e, n), y = (e, t, n, i) => (ft(e, t, "write to private field"), i ? i.call(e, n) : t.set(e, n), n), R = (e, t, n) => (ft(e, t, "access private method"), n); import { Rect as Y, Log as Z, MediaStreamClip as Mt, Combinator as It, OffscreenSprite as kt } from "@fly-cut/av-cliper"; import { EventTool as pt, workerTimer as Tt } from "@fly-cut/internal-utils"; const At = [ "t", "b", "l", "r", "lt", "lb", "rt", "rb", "rotate" ]; function J(e) { return document.createElement(e); } function zt(e) { let t = 16; const n = new ResizeObserver((s) => { const o = s[0]; o != null && (t = 10 / (o.contentRect.width / e.width)); }); n.observe(e); function i(s) { const { w: o, h } = s, a = t, c = a / 2, l = o / 2, d = h / 2, u = a * 1.5, f = u / 2; return { ...s.fixedAspectRatio ? {} : { t: new Y(-c, -d - c, a, a, s), b: new Y(-c, d - c, a, a, s), l: new Y(-l - c, -c, a, a, s), r: new Y(l - c, -c, a, a, s) }, lt: new Y(-l - c, -d - c, a, a, s), lb: new Y(-l - c, d - c, a, a, s), rt: new Y(l - c, -d - c, a, a, s), rb: new Y(l - c, d - c, a, a, s), rotate: new Y(-f, -d - a * 2 - f, u, u, s) }; } return { rectCtrlsGetter: i, destroy: () => { n.disconnect(); } }; } var ht = /* @__PURE__ */ ((e) => (e.ActiveSpriteChange = "activeSpriteChange", e.AddSprite = "addSprite", e))(ht || {}), A, V, N, q; class Lt { constructor() { g(this, A, []); g(this, V, null); g(this, N, new pt()); Q(this, "on", r(this, N).on); g(this, q, 0); } get activeSprite() { return r(this, V); } set activeSprite(t) { t !== r(this, V) && (y(this, V, t), r(this, N).emit("activeSpriteChange", t)); } async addSprite(t) { await t.ready, r(this, A).push(t), y(this, A, r(this, A).sort((n, i) => n.zIndex - i.zIndex)), t.on("propsChange", (n) => { n.zIndex != null && y(this, A, r(this, A).sort((i, s) => i.zIndex - s.zIndex)); }), r(this, N).emit("addSprite", t); } removeSprite(t) { r(this, V) === t && (this.activeSprite = null), y(this, A, r(this, A).filter((n) => n !== t)), t.destroy(); } getSprites(t = { time: !0 }) { return r(this, A).filter( (n) => n.visible && (t.time ? r(this, q) >= n.time.offset && r(this, q) <= n.time.offset + n.time.duration : !0) ); } updateRenderTime(t) { y(this, q, t); const n = this.activeSprite; n != null && (t < n.time.offset || t > n.time.offset + n.time.duration) && (this.activeSprite = null); } destroy() { r(this, N).destroy(), r(this, A).forEach((t) => t.destroy()), y(this, A, []); } } A = new WeakMap(), V = new WeakMap(), N = new WeakMap(), q = new WeakMap(); function Ot(e, t, n, i) { const s = { w: t.clientWidth / t.width, h: t.clientHeight / t.height }, o = new ResizeObserver(() => { s.w = t.clientWidth / t.width, s.h = t.clientHeight / t.height, n.activeSprite != null && ut( n.activeSprite, a, c, s, i, t ); }); o.observe(t); let h = () => { }; const { rectEl: a, ctrlsEl: c } = Rt(e), l = n.on(ht.ActiveSpriteChange, (d) => { if (h(), d == null) { a.style.display = "none"; return; } ut(d, a, c, s, i, t), h = d.on("propsChange", () => { ut(d, a, c, s, i, t); }), a.style.display = ""; }); return () => { o.disconnect(), l(), a.remove(), h(); }; } function Rt(e) { const t = J("div"); t.style.cssText = ` position: absolute; pointer-events: none; border: 1px solid #eee; box-sizing: border-box; display: none; `; const n = Object.fromEntries( At.map((i) => { const s = J("div"); return s.style.cssText = ` display: none; position: absolute; border: 1px solid #3ee; border-radius: 50%; box-sizing: border-box; background-color: #fff; `, [i, s]; }) ); return Object.values(n).forEach((i) => t.appendChild(i)), e.appendChild(t), { rectEl: t, ctrlsEl: n }; } function ut(e, t, n, i, s, o) { const { x: h, y: a, w: c, h: l, angle: d } = e.rect, u = o.width / 2 * i.w, f = o.height / 2 * i.h, p = h - c / 2, b = a - l / 2; Object.assign(t.style, { left: `${u + p * i.w}px`, top: `${f + b * i.h}px`, width: `${c * i.w}px`, height: `${l * i.h}px`, rotate: `${d}rad` }), Object.entries(s(e.rect)).forEach(([M, { x: v, y: m, w, h: U }]) => { Object.assign(n[M].style, { display: "block", left: "50%", top: "50%", width: `${w * i.w}px`, height: `${U * i.h}px`, // border 1px, 所以要 -1 transform: `translate(${v * i.w}px, ${m * i.h}px)` }); }); } function Wt(e, t, n) { const i = { w: e.clientWidth / e.width, h: e.clientHeight / e.height }, s = new ResizeObserver(() => { i.w = e.clientWidth / e.width, i.h = e.clientHeight / e.height; }); s.observe(e); const o = (h) => { if (h.button !== 0) return; const { offsetX: a, offsetY: c } = h, l = a / i.w, d = c / i.h, u = l - e.width / 2, f = d - e.height / 2; if (t.activeSprite != null) { const [p] = Object.entries(n(t.activeSprite.rect)).find( ([, b]) => b.checkHit(u, f) ) ?? []; if (p != null) return; } t.activeSprite = t.getSprites().reverse().find((p) => p.visible && p.selectable && p.rect.checkHit(u, f)) ?? null; }; return e.addEventListener("pointerdown", o), () => { s.disconnect(), e.removeEventListener("pointerdown", o); }; } function Ht(e, t, n, i) { const s = { w: e.clientWidth / e.width, h: e.clientHeight / e.height }, o = new ResizeObserver(() => { s.w = e.clientWidth / e.width, s.h = e.clientHeight / e.height; }); o.observe(e); let h = 0, a = 0, c = null; const l = jt(e, n); let d = null; const u = (b) => { if (b.button !== 0 || t.activeSprite == null) return; d = t.activeSprite; const { offsetX: M, offsetY: v, clientX: m, clientY: w } = b; Bt({ rect: d.rect, offsetX: M, offsetY: v, clientX: m, clientY: w, cvsRatio: s, cvsEl: e, rectCtrlsGetter: i }) || (c = d.rect.clone(), l.magneticEffect(d.rect.x, d.rect.y, d.rect), h = m, a = w, window.addEventListener("pointermove", f), window.addEventListener("pointerup", p)); }, f = (b) => { if (d == null || c == null) return; const { clientX: M, clientY: v } = b; let m = c.x + (M - h) / s.w, w = c.y + (v - a) / s.h; yt( d.rect, e, l.magneticEffect(m, w, d.rect) ); }; e.addEventListener("pointerdown", u); const p = () => { l.hide(), window.removeEventListener("pointermove", f), window.removeEventListener("pointerup", p); }; return () => { o.disconnect(), l.destroy(), p(), e.removeEventListener("pointerdown", u); }; } function $t({ sprRect: e, startX: t, startY: n, ctrlKey: i, cvsRatio: s, cvsEl: o }) { const h = e.clone(), a = (l) => { const { clientX: d, clientY: u } = l, f = (d - t) / s.w, p = (u - n) / s.h, b = i.length === 1 ? Yt : Xt, { x: M, y: v, w: m, h: w } = h, U = Math.atan2(w, m), { incW: _, incH: B, incS: $, rotateAngle: ot } = b({ deltaX: f, deltaY: p, angle: e.angle, ctrlKey: i, diagonalAngle: U }), k = 10; let O = m, F = w, at = h.fixedScaleCenter ? _ * 2 : _, ct = h.fixedScaleCenter ? B * 2 : B, P = $; const mt = Math.sqrt(w ** 2 + m ** 2), bt = Math.sqrt((k * (w / m)) ** 2 + k ** 2); switch (i) { case "l": O = Math.max(m + at, k), P = Math.min($, m - k); break; case "r": O = Math.max(m + at, k), P = Math.max($, k - m); break; case "b": F = Math.max(w + ct, k), P = Math.min($, w - k); break; case "t": F = Math.max(w + ct, k), P = Math.max($, k - w); break; case "lt": case "lb": O = Math.max(m + at, k), F = O === k ? w / m * O : w + ct, P = Math.min($, mt - bt); break; case "rt": case "rb": O = Math.max(m + at, k), F = O === k ? w / m * O : w + ct, P = Math.max($, bt - mt); break; } let dt = M, lt = v; if (h.fixedScaleCenter) dt = M + m / 2 - O / 2, lt = v + w / 2 - F / 2; else { const xt = P / 2 * Math.cos(ot) + M + m / 2, St = P / 2 * Math.sin(ot) + v + w / 2; dt = xt - O / 2, lt = St - F / 2; } yt(e, o, { x: dt, y: lt, w: O, h: F }); }, c = () => { window.removeEventListener("pointermove", a), window.removeEventListener("pointerup", c); }; window.addEventListener("pointermove", a), window.addEventListener("pointerup", c); } function Yt({ deltaX: e, deltaY: t, angle: n, ctrlKey: i }) { let s = 0, o = 0, h = 0, a = n; return i === "l" || i === "r" ? (s = e * Math.cos(n) + t * Math.sin(n), o = s * (i === "l" ? -1 : 1)) : (i === "t" || i === "b") && (a = n - Math.PI / 2, s = e * Math.cos(a) + t * Math.sin(a), h = s * (i === "b" ? -1 : 1)), { incW: o, incH: h, incS: s, rotateAngle: a }; } function Xt({ deltaX: e, deltaY: t, angle: n, ctrlKey: i, diagonalAngle: s }) { const o = (i === "lt" || i === "rb" ? 1 : -1) * s + n, h = e * Math.cos(o) + t * Math.sin(o), a = i === "lt" || i === "lb" ? -1 : 1, c = h * Math.cos(s) * a, l = h * Math.sin(s) * a; return { incW: c, incH: l, incS: h, rotateAngle: o }; } function Bt({ rect: e, cvsRatio: t, offsetX: n, offsetY: i, clientX: s, clientY: o, cvsEl: h, rectCtrlsGetter: a }) { const c = n / t.w, l = i / t.h, d = c - h.width / 2, u = l - h.height / 2, [f] = Object.entries(a(e)).find( ([, p]) => p.checkHit(d, u) ) ?? []; return f == null ? !1 : (f === "rotate" ? Pt(e, Dt(e.center, t, h)) : $t({ sprRect: e, ctrlKey: f, startX: s, startY: o, cvsRatio: t, cvsEl: h }), !0); } function Pt(e, t) { const n = ({ clientX: s, clientY: o }) => { const h = s - t.x, a = o - t.y, c = Math.atan2(a, h) + Math.PI / 2; e.angle = c; }, i = () => { window.removeEventListener("pointermove", n), window.removeEventListener("pointerup", i); }; window.addEventListener("pointermove", n), window.addEventListener("pointerup", i); } function Dt(e, t, n) { const i = e.x * t.w, s = e.y * t.h, { left: o, top: h } = n.getBoundingClientRect(); return { x: i + o, y: s + h }; } function yt(e, t, n) { const i = { x: e.x, y: e.y, w: e.w, h: e.h, ...n }, s = 0.05, o = i.w * s, h = i.h * s, a = -t.width / 2, c = t.width / 2, l = -t.height / 2, d = t.height / 2, u = a - i.w / 2 + o, f = c + i.w / 2 - o, p = l - i.h / 2 + h, b = d + i.h / 2 - h; i.x < u ? i.x = u : i.x > f && (i.x = f), i.y < p ? i.y = p : i.y > b && (i.y = b), e.x = i.x, e.y = i.y, e.w = i.w, e.h = i.h; } function jt(e, t) { const n = "display: none; position: absolute;", i = { w: 0, h: 0, x: 0, y: 0 }, s = { vertMiddle: { ...i, h: 100, x: 50, ref: { prop: "x", val: ({ w: c }) => (e.width - c) / 2 } }, horMiddle: { ...i, w: 100, y: 50, ref: { prop: "y", val: ({ h: c }) => (e.height - c) / 2 } }, top: { ...i, w: 100, ref: { prop: "y", val: () => 0 } }, bottom: { ...i, w: 100, y: 100, ref: { prop: "y", val: ({ h: c }) => e.height - c } }, left: { ...i, h: 100, ref: { prop: "x", val: () => 0 } }, right: { ...i, h: 100, x: 100, ref: { prop: "x", val: ({ w: c }) => e.width - c } } }, o = J("div"); o.style.cssText = ` position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; box-sizing: border-box; `; const h = Object.fromEntries( Object.entries(s).map(([c, { w: l, h: d, x: u, y: f }]) => { const p = J("div"); return p.style.cssText = ` ${n} border-${l > 0 ? "top" : "left"}: 1px solid #3ee; top: ${f}%; left: ${u}%; ${u === 100 ? "margin-left: -1px" : ""}; ${f === 100 ? "margin-top: -1px" : ""}; width: ${l}%; height: ${d}%; `, o.appendChild(p), [c, p]; }) ); t.appendChild(o); const a = 6 / (900 / e.width); return { magneticEffect(c, l, d) { const u = { x: c, y: l }; let f, p = { x: !1, y: !1 }; for (f in s) { const { prop: b, val: M } = s[f].ref; if (p[b]) continue; const v = M(d); Math.abs(d[b] - v) <= a && Math.abs(d[b] - (b === "x" ? c : l)) <= a ? (u[b] = v, h[f].style.display = "block", p[b] = !0) : h[f].style.display = "none"; } return u; }, hide() { Object.values(h).forEach((c) => c.style.display = "none"); }, destroy() { o.remove(); } }; } function Ft(e, t, n) { const i = { w: e.clientWidth / e.width, h: e.clientHeight / e.height }, s = new ResizeObserver(() => { i.w = e.clientWidth / e.width, i.h = e.clientHeight / e.height; }); s.observe(e); const o = e.style; let h = t.activeSprite; t.on(ht.ActiveSpriteChange, (p) => { h = p, p == null && (o.cursor = ""); }); let a = !1; const c = ({ offsetX: p, offsetY: b }) => { a = !0; const M = p / i.w, v = b / i.h, m = M - e.width / 2, w = v - e.height / 2; (h == null ? void 0 : h.rect.checkHit(m, w)) === !0 && o.cursor === "" && (o.cursor = "move"); }, l = () => { a = !1; }, d = [ "ns-resize", "nesw-resize", "ew-resize", "nwse-resize", "ns-resize", "nesw-resize", "ew-resize", "nwse-resize" ], u = { t: 0, rt: 1, r: 2, rb: 3, b: 4, lb: 5, l: 6, lt: 7 }, f = (p) => { if (h == null || a) return; const { offsetX: b, offsetY: M } = p, v = b / i.w, m = M / i.h, w = v - e.width / 2, U = m - e.height / 2, [_] = Object.entries(n(h.rect)).find( ([, B]) => B.checkHit(w, U) ) ?? []; if (_ != null) { if (_ === "rotate") { o.cursor = "crosshair"; return; } const B = h.rect.angle, $ = B < 0 ? B + 2 * Math.PI : B, ot = (u[_] + Math.floor(($ + Math.PI / 8) / (Math.PI / 4))) % 8; o.cursor = d[ot]; return; } if (h.rect.checkHit(w, U)) { o.cursor = "move"; return; } o.cursor = ""; }; return e.addEventListener("pointermove", f), e.addEventListener("pointerdown", c), window.addEventListener("pointerup", l), () => { s.disconnect(), e.removeEventListener("pointermove", f), e.removeEventListener("pointerdown", c), window.removeEventListener("pointerup", l); }; } const Vt = { sampleRate: 48e3, channelCount: 2, codec: "mp4a.40.2" }; function Nt(e) { const t = J("canvas"); return t.style.cssText = ` width: 100%; height: 100%; display: block; touch-action: none; `, t.width = e.width, t.height = e.height, t; } var x, S, tt, et, it, nt, X, G, z, T, W, rt, H, I, K, E, C, D, j, wt, L, st; class Kt { /** * 创建 `AVCanvas` 类的实例。 * @param attchEl - 要添加画布的元素。 * @param opts - 画布的选项 * @param opts.bgColor - 画布的背景颜色。 * @param opts.width - 画布的宽度。 * @param opts.height - 画布的高度。 */ constructor(t, n) { g(this, I); g(this, x); g(this, S); g(this, tt); g(this, et, !1); g(this, it, []); g(this, nt); g(this, X, new pt()); Q(this, "on", r(this, X).on); g(this, G); g(this, z, { mode: "cover", opacity: 1, blur: 0 }); // 在 AVCanvas 类中添加 g(this, T, null); g(this, W, null); // 添加锚点属性 g(this, rt, { x: 0, y: 0 }); g(this, H, 0); g(this, C, new AudioContext()); g(this, D, r(this, C).createMediaStreamDestination()); g(this, j, /* @__PURE__ */ new Set()); g(this, L, { start: 0, end: 0, // paused state when step equal 0 step: 0, // step: (1000 / 30) * 1000, audioPlayAt: 0 }); g(this, st, /* @__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!, * }), * ); */ Q(this, "addSprite", async (t) => { r(this, C).state === "suspended" && r(this, C).resume().catch(Z.error); const n = t.getClip(); if (n instanceof Mt && n.audioTrack != null) { const i = r(this, C).createMediaStreamSource( new MediaStream([n.audioTrack]) ); i.connect(r(this, D)), r(this, st).set(t, i); } await r(this, S).addSprite(t), t.preFrame(0); }); /** * 删除 {@link VisibleSprite} * @param args * @returns * @example * const sprite = new VisibleSprite(); * avCvs.removeSprite(sprite); */ Q(this, "removeSprite", (t) => { var n; (n = r(this, st).get(t)) == null || n.disconnect(), r(this, S).removeSprite(t); }); y(this, G, n), y(this, x, Nt(n)); const i = r(this, x).getContext("2d", { alpha: !1 }); if (i == null) throw Error("canvas context is null"); y(this, tt, i); const s = J("div"); s.style.cssText = "width: 100%; height: 100%; position: relative; overflow: hidden;", s.appendChild(r(this, x)), t.appendChild(s), _t(r(this, C)).connect(r(this, D)), y(this, S, new Lt()); const { rectCtrlsGetter: o, destroy: h } = zt( r(this, x) ); r(this, it).push( h, // 鼠标样式、控制 sprite 依赖 activeSprite, // activeSprite 需要在他们之前监听到 mousedown 事件 (代码顺序需要靠前) Wt(r(this, x), r(this, S), o), Ft(r(this, x), r(this, S), o), Ht( r(this, x), r(this, S), s, o ), Ot(s, r(this, x), r(this, S), o), // 因为默认为中心对齐,所以可以不用考虑居中的问题,0,0就是居中 // this.#spriteManager.on(ESpriteManagerEvt.AddSprite, (s) => { // const { rect } = s; // // 默认居中 // if (rect.x === 0 && rect.y === 0) { // // 考虑锚点的情况 // rect.x = (this.#cvsEl.width - rect.w) / 2; // rect.y = (this.#cvsEl.height - rect.h) / 2; // } // }), pt.forwardEvent(r(this, S), r(this, X), [ ht.ActiveSpriteChange ]) ); let a = r(this, H), c = performance.now(), l = 0; const d = 1e3 / 30; y(this, nt, Tt(() => { (performance.now() - c) / (d * l) < 1 || (l += 1, R(this, I, wt).call(this), a !== r(this, H) && (a = r(this, H), r(this, X).emit("timeupdate", Math.round(a)))); }, d)); } /** * 每 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 = r(this, S).getSprites({ time: !1 }).map((s) => s.time.offset + s.time.duration), i = t.end ?? (n.length > 0 ? Math.max(...n) : 1 / 0); if (t.start >= i || t.start < 0) throw Error( `Invalid time parameter, ${JSON.stringify({ start: t.start, end: i })}` ); R(this, I, K).call(this, t.start), r(this, S).getSprites({ time: !1 }).forEach((s) => { const { offset: o, duration: h } = s.time, a = r(this, H) - o; s.preFrame(a > 0 && a < h ? a : 0); }), r(this, L).start = t.start, r(this, L).end = i, r(this, L).step = (t.playbackRate ?? 1) * (1e3 / 30) * 1e3, r(this, C).resume(), r(this, L).audioPlayAt = 0, r(this, X).emit("playing"), Z.info("AVCanvs play by:", r(this, L)); } /** * 暂停播放,画布内容不再更新 */ pause() { R(this, I, E).call(this); } /** * 预览 `AVCanvas` 指定时间的图像帧 */ previewFrame(t) { R(this, I, K).call(this, t), R(this, I, E).call(this); } /** * 获取当前帧的截图图像 返回的是一个base64 */ captureImage() { return r(this, x).toDataURL(); } get activeSprite() { return r(this, S).activeSprite; } set activeSprite(t) { r(this, S).activeSprite = t; } /** * 销毁实例 */ destroy() { var t; r(this, et) || (y(this, et, !0), r(this, C).close(), r(this, D).disconnect(), r(this, X).destroy(), r(this, nt).call(this), (t = r(this, x).parentElement) == null || t.remove(), r(this, it).forEach((n) => n()), r(this, j).clear(), r(this, S).destroy()); } /** * 合成所有素材的图像与音频,返回实时媒体流 `MediaStream` * * 可用于 WebRTC 推流,或由 {@link [AVRecorder](../../av-recorder/classes/AVRecorder.html)} 录制生成视频文件 * * @see [直播录制](https://webav-tech.github.io/WebAV/demo/4_2-recorder-avcanvas) * */ captureStream() { r(this, C).state === "suspended" && r(this, C).resume().catch(Z.error); const t = new MediaStream( r(this, x).captureStream().getTracks().concat(r(this, D).stream.getTracks()) ); return Z.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 = {}) { Z.info("AVCanvas.createCombinator, opts:", t); const n = new It({ ...r(this, G), ...t }), i = r(this, S).getSprites({ time: !1 }); if (i.length === 0) throw Error("No sprite added"); for (const s of i) { const o = new kt(s.getClip()); o.time = { ...s.time }, s.copyStateTo(o), await n.addSprite(o); } return n; } /** * 设置背景图片 * @param image 背景图片(ImageBitmap、HTMLImageElement 或 URL) * @param options 可选配置(如拉伸模式、透明度等) */ async setBackgroundImage(t, n = {}) { let i; if (typeof t == "string") { const o = await (await fetch(t)).blob(); i = await createImageBitmap(o); } else t instanceof HTMLImageElement ? i = await createImageBitmap(t) : i = t; if (y(this, W, i), y(this, z, { mode: n.mode || "cover", opacity: n.opacity !== void 0 ? n.opacity : 1, blur: n.blur !== void 0 ? n.blur : 0 }), r(this, z).blur > 0) { const s = new OffscreenCanvas( i.width, i.height ), o = s.getContext("2d"); o ? (o.filter = `blur(${r(this, z).blur}px)`, o.drawImage(i, 0, 0), y(this, T, await createImageBitmap(s))) : y(this, T, i); } else y(this, T, i); } /** * 更新背景图片的模糊效果或透明度 * @param options 可选配置(模式、透明度、模糊度) */ async updateBackgroundOptions(t = {}) { if (r(this, W) && (t.mode !== void 0 && (r(this, z).mode = t.mode), t.opacity !== void 0 && (r(this, z).opacity = t.opacity), t.blur !== void 0 && (r(this, z).blur = t.blur), t.blur !== void 0)) if (r(this, z).blur > 0) { const n = new OffscreenCanvas( r(this, W).width, r(this, W).height ), i = n.getContext("2d"); i && (i.filter = `blur(${r(this, z).blur}px)`, i.drawImage(r(this, W), 0, 0), y(this, T, await createImageBitmap(n))); } else y(this, T, r(this, W)); } /** * 清除背景图片,恢复使用纯色背景 */ clearBackgroundImage() { y(this, T, null), y(this, W, null); } /** * 刷新当前画布内容 * @description 强制重新渲染当前画布的所有内容,包括背景和所有精灵 */ refresh() { R(this, I, K).call(this, r(this, H)), R(this, I, E).call(this); } /** * 设置画布的坐标原点 * @param x - 原点的 x 坐标(0-1 之间表示百分比,大于 1 表示具体像素值) * @param y - 原点的 y 坐标(0-1 之间表示百分比,大于 1 表示具体像素值) */ setAnchor(t, n) { const i = r(this, x).width, s = r(this, x).height, o = t >= 0 && t <= 1 ? t * i : t, h = n >= 0 && n <= 1 ? n * s : n; y(this, rt, { x: o, y: h }), R(this, I, wt).call(this); } } x = new WeakMap(), S = new WeakMap(), tt = new WeakMap(), et = new WeakMap(), it = new WeakMap(), nt = new WeakMap(), X = new WeakMap(), G = new WeakMap(), z = new WeakMap(), T = new WeakMap(), W = new WeakMap(), rt = new WeakMap(), H = new WeakMap(), I = new WeakSet(), K = function(t) { y(this, H, t), r(this, S).updateRenderTime(t); }, E = function() { const t = r(this, L).step !== 0; r(this, L).step = 0, t && (r(this, X).emit("paused"), r(this, C).suspend()); for (const n of r(this, j)) n.stop(), n.disconnect(); r(this, j).clear(); }, C = new WeakMap(), D = new WeakMap(), j = new WeakMap(), wt = function() { var c; const t = r(this, tt); let n = r(this, H); const { start: i, end: s, step: o, audioPlayAt: h } = r(this, L); if (o !== 0 && n >= i && n < s ? n += o : R(this, I, E).call(this), R(this, I, K).call(this, n), t.fillStyle = r(this, G).bgColor, t.fillRect(0, 0, r(this, x).width, r(this, x).height), r(this, T)) { const { width: l, height: d } = r(this, x), { mode: u, opacity: f } = r(this, z); switch (t.save(), f !== 1 && (t.globalAlpha = f), u) { case "cover": qt(t, r(this, T), 0, 0, l, d); break; case "contain": Gt(t, r(this, T), 0, 0, l, d); break; case "stretch": t.drawImage(r(this, T), 0, 0, l, d); break; case "repeat": const p = t.createPattern(r(this, T), "repeat"); p && (t.fillStyle = p, t.fillRect(0, 0, l, d)); break; } t.restore(); } const a = []; for (const l of r(this, S).getSprites()) { t.save(); const { audio: d } = l.render(t, n - l.time.offset, r(this, rt)); t.restore(), a.push(d); } if (t.resetTransform(), o !== 0) { const l = Math.max(r(this, C).currentTime, h), d = Ut( a, r(this, C) ); let u = 0; for (const f of d) f.start(l), f.connect(r(this, C).destination), f.connect(r(this, D)), r(this, j).add(f), f.onended = () => { f.disconnect(), r(this, j).delete(f); }, u = Math.max(u, ((c = f.buffer) == null ? void 0 : c.duration) ?? 0); r(this, L).audioPlayAt = l + u; } }, L = new WeakMap(), st = new WeakMap(); function Ut(e, t) { const n = []; if (e.length === 0) return n; for (const [i, s] of e) { if (i == null || i.length <= 0) continue; const o = t.createBuffer( 2, i.length, Vt.sampleRate ); o.copyToChannel(i, 0), o.copyToChannel(s ?? i, 1); const h = t.createBufferSource(); h.buffer = o, n.push(h); } return n; } function _t(e) { const t = e.createOscillator(), n = new Float32Array([0, 0]), i = new Float32Array([0, 0]), s = e.createPeriodicWave(n, i, { disableNormalization: !0 }); return t.setPeriodicWave(s), t.start(), t; } function qt(e, t, n, i, s, o) { const h = t.width / t.height, a = s / o; let c = s, l = o, d = 0, u = 0; a > h ? (l = s / t.width * t.height, u = (o - l) / 2) : (c = o / t.height * t.width, d = (s - c) / 2), e.drawImage(t, n + d, i + u, c, l); } function Gt(e, t, n, i, s, o) { const h = t.width / t.height, a = s / o; let c = s, l = o, d = 0, u = 0; a < h ? (l = s / t.width * t.height, u = (o - l) / 2) : (c = o / t.height * t.width, d = (s - c) / 2), e.drawImage(t, n + d, i + u, c, l); } export { Kt as AVCanvas }; //# sourceMappingURL=av-canvas.js.map