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