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