@webrock/av-cliper
Version:
WebCodecs-based, combine video, audio, images, text, with animation support 基于 WebCodecs 合成 视频、音频、图片、文字,支持动画
1,646 lines (1,642 loc) • 88.4 kB
JavaScript
var Ci = Object.defineProperty;
var Je = (i) => {
throw TypeError(i);
};
var vi = (i, t, e) => t in i ? Ci(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e;
var v = (i, t, e) => vi(i, typeof t != "symbol" ? t + "" : t, e), Ie = (i, t, e) => t.has(i) || Je("Cannot " + e);
var s = (i, t, e) => (Ie(i, t, "read from private field"), e ? e.call(i) : t.get(i)), d = (i, t, e) => t.has(i) ? Je("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(i) : t.set(i, e), h = (i, t, e, n) => (Ie(i, t, "write to private field"), n ? n.call(i, e) : t.set(i, e), e), B = (i, t, e) => (Ie(i, t, "access private method"), e);
import mt from "@webav/mp4box.js";
import { workerTimer as Si, Log as k, autoReadStream as ze, file2stream as oi, EventTool as Le, recodemux as Ti } from "@webrock/internal-utils";
import { Log as On } from "@webrock/internal-utils";
import { tmpfile as Ee, write as Pe } from "opfs-tools";
import * as Ai from "wave-resampler";
const ki = `#version 300 es
layout (location = 0) in vec4 a_position;
layout (location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
void main () {
gl_Position = a_position;
v_texCoord = a_texCoord;
}
`, Fi = `#version 300 es
precision mediump float;
out vec4 FragColor;
in vec2 v_texCoord;
uniform sampler2D frameTexture;
uniform vec3 keyColor;
// 色度的相似度计算
uniform float similarity;
// 透明度的平滑度计算
uniform float smoothness;
// 降低绿幕饱和度,提高抠图准确度
uniform float spill;
vec2 RGBtoUV(vec3 rgb) {
return vec2(
rgb.r * -0.169 + rgb.g * -0.331 + rgb.b * 0.5 + 0.5,
rgb.r * 0.5 + rgb.g * -0.419 + rgb.b * -0.081 + 0.5
);
}
void main() {
// 获取当前像素的rgba值
vec4 rgba = texture(frameTexture, v_texCoord);
// 计算当前像素与绿幕像素的色度差值
vec2 chromaVec = RGBtoUV(rgba.rgb) - RGBtoUV(keyColor);
// 计算当前像素与绿幕像素的色度距离(向量长度), 越相像则色度距离越小
float chromaDist = sqrt(dot(chromaVec, chromaVec));
// 设置了一个相似度阈值,baseMask为负,则表明是绿幕,为正则表明不是绿幕
float baseMask = chromaDist - similarity;
// 如果baseMask为负数,fullMask等于0;baseMask为正数,越大,则透明度越低
float fullMask = pow(clamp(baseMask / smoothness, 0., 1.), 1.5);
rgba.a = fullMask; // 设置透明度
// 如果baseMask为负数,spillVal等于0;baseMask为整数,越小,饱和度越低
float spillVal = pow(clamp(baseMask / spill, 0., 1.), 1.5);
float desat = clamp(rgba.r * 0.2126 + rgba.g * 0.7152 + rgba.b * 0.0722, 0., 1.); // 计算当前像素的灰度值
rgba.rgb = mix(vec3(desat, desat, desat), rgba.rgb, spillVal);
FragColor = rgba;
}
`, Ii = [-1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1], Ri = [0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1];
function Ei(i, t, e) {
const n = Ke(i, i.VERTEX_SHADER, t), a = Ke(i, i.FRAGMENT_SHADER, e), r = i.createProgram();
if (i.attachShader(r, n), i.attachShader(r, a), i.linkProgram(r), !i.getProgramParameter(r, i.LINK_STATUS))
throw Error(
i.getProgramInfoLog(r) ?? "Unable to initialize the shader program"
);
return r;
}
function Ke(i, t, e) {
const n = i.createShader(t);
if (i.shaderSource(n, e), i.compileShader(n), !i.getShaderParameter(n, i.COMPILE_STATUS)) {
const a = i.getShaderInfoLog(n);
throw i.deleteShader(n), Error(a ?? "An error occurred compiling the shaders");
}
return n;
}
function Pi(i, t, e) {
i.bindTexture(i.TEXTURE_2D, e), i.texImage2D(i.TEXTURE_2D, 0, i.RGBA, i.RGBA, i.UNSIGNED_BYTE, t), i.drawArrays(i.TRIANGLES, 0, 6);
}
function Di(i) {
const t = i.createTexture();
if (t == null) throw Error("Create WebGL texture error");
i.bindTexture(i.TEXTURE_2D, t);
const e = 0, n = i.RGBA, a = 1, r = 1, o = 0, c = i.RGBA, l = i.UNSIGNED_BYTE, f = new Uint8Array([0, 0, 255, 255]);
return i.texImage2D(
i.TEXTURE_2D,
e,
n,
a,
r,
o,
c,
l,
f
), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_MAG_FILTER, i.LINEAR), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_MIN_FILTER, i.LINEAR), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_WRAP_S, i.CLAMP_TO_EDGE), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_WRAP_T, i.CLAMP_TO_EDGE), t;
}
function Oi(i) {
const t = "document" in globalThis ? globalThis.document.createElement("canvas") : new OffscreenCanvas(i.width, i.height);
t.width = i.width, t.height = i.height;
const e = t.getContext("webgl2", {
premultipliedAlpha: !1,
alpha: !0
});
if (e == null) throw Error("Cant create gl context");
const n = Ei(e, ki, Fi);
e.useProgram(n), e.uniform3fv(
e.getUniformLocation(n, "keyColor"),
i.keyColor.map((l) => l / 255)
), e.uniform1f(
e.getUniformLocation(n, "similarity"),
i.similarity
), e.uniform1f(
e.getUniformLocation(n, "smoothness"),
i.smoothness
), e.uniform1f(e.getUniformLocation(n, "spill"), i.spill);
const a = e.createBuffer();
e.bindBuffer(e.ARRAY_BUFFER, a), e.bufferData(e.ARRAY_BUFFER, new Float32Array(Ii), e.STATIC_DRAW);
const r = e.getAttribLocation(n, "a_position");
e.vertexAttribPointer(
r,
2,
e.FLOAT,
!1,
Float32Array.BYTES_PER_ELEMENT * 2,
0
), e.enableVertexAttribArray(r);
const o = e.createBuffer();
e.bindBuffer(e.ARRAY_BUFFER, o), e.bufferData(
e.ARRAY_BUFFER,
new Float32Array(Ri),
e.STATIC_DRAW
);
const c = e.getAttribLocation(n, "a_texCoord");
return e.vertexAttribPointer(
c,
2,
e.FLOAT,
!1,
Float32Array.BYTES_PER_ELEMENT * 2,
0
), e.enableVertexAttribArray(c), e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL, 1), { cvs: t, gl: e };
}
function Bi(i) {
return i instanceof VideoFrame ? { width: i.codedWidth, height: i.codedHeight } : { width: i.width, height: i.height };
}
function Mi(i) {
const e = new OffscreenCanvas(1, 1).getContext("2d");
e.drawImage(i, 0, 0);
const {
data: [n, a, r]
} = e.getImageData(0, 0, 1, 1);
return [n, a, r];
}
const kn = (i) => {
let t = null, e = null, n = i.keyColor, a = null;
return async (r) => {
if ((t == null || e == null || a == null) && (n == null && (n = Mi(r)), { cvs: t, gl: e } = Oi({
...Bi(r),
keyColor: n,
...i
}), a = Di(e)), Pi(e, r, a), globalThis.VideoFrame != null && r instanceof globalThis.VideoFrame) {
const o = new VideoFrame(t, {
alpha: "keep",
timestamp: r.timestamp,
duration: r.duration ?? void 0
});
return r.close(), o;
}
return createImageBitmap(t, {
imageOrientation: r instanceof ImageBitmap ? "flipY" : "none"
});
};
};
function _i(i) {
return document.createElement(i);
}
function zi(i) {
var t = "", e = new Uint8Array(i), n = e.byteLength;
for (let a = 0; a < n; a++)
t += String.fromCharCode(e[a]);
return window.btoa(t);
}
async function Li(i, t, e = {}) {
var f;
const n = _i("pre");
n.style.cssText = `margin: 0; ${t}; position: fixed;`, n.textContent = i, document.body.appendChild(n), (f = e.onCreated) == null || f.call(e, n);
const { width: a, height: r } = n.getBoundingClientRect();
n.remove();
const o = new Image();
o.width = a, o.height = r;
const c = e.font == null ? "" : `
@font-face {
font-family: '${e.font.name}';
src: url('data:font/woff2;base64,${zi(await (await fetch(e.font.url)).arrayBuffer())}') format('woff2');
}
`, l = `
<svg xmlns="http://www.w3.org/2000/svg" width="${a}" height="${r}">
<style>
${c}
</style>
<foreignObject width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml">${n.outerHTML}</div>
</foreignObject>
</svg>
`.replace(/\t/g, "").replace(/#/g, "%23");
return o.src = `data:image/svg+xml;charset=utf-8,${l}`, await new Promise((u) => {
o.onload = u;
}), o;
}
async function Fn(i, t, e = {}) {
const n = await Li(i, t, e), a = new OffscreenCanvas(n.width, n.height), r = a.getContext("2d");
return r == null || r.drawImage(n, 0, 0, n.width, n.height), await createImageBitmap(a);
}
function Vi(i) {
const t = new Float32Array(
i.map((n) => n.length).reduce((n, a) => n + a)
);
let e = 0;
for (const n of i)
t.set(n, e), e += n.length;
return t;
}
function Ui(i) {
const t = [];
for (let e = 0; e < i.length; e += 1)
for (let n = 0; n < i[e].length; n += 1)
t[n] == null && (t[n] = []), t[n].push(i[e][n]);
return t.map(Vi);
}
function ci(i) {
if (i.format === "f32-planar") {
const t = [];
for (let e = 0; e < i.numberOfChannels; e += 1) {
const n = i.allocationSize({ planeIndex: e }), a = new ArrayBuffer(n);
i.copyTo(a, { planeIndex: e }), t.push(new Float32Array(a));
}
return t;
} else if (i.format === "f32") {
const t = new ArrayBuffer(i.allocationSize({ planeIndex: 0 }));
return i.copyTo(t, { planeIndex: 0 }), $i(new Float32Array(t), i.numberOfChannels);
} else if (i.format === "s16") {
const t = new ArrayBuffer(i.allocationSize({ planeIndex: 0 }));
return i.copyTo(t, { planeIndex: 0 }), Ni(new Int16Array(t), i.numberOfChannels);
}
throw Error("Unsupported audio data format");
}
function Ni(i, t) {
const e = i.length / t, n = Array.from(
{ length: t },
() => new Float32Array(e)
);
for (let a = 0; a < e; a++)
for (let r = 0; r < t; r++) {
const o = i[a * t + r];
n[r][a] = o / 32768;
}
return n;
}
function $i(i, t) {
const e = i.length / t, n = Array.from(
{ length: t },
() => new Float32Array(e)
);
for (let a = 0; a < e; a++)
for (let r = 0; r < t; r++)
n[r][a] = i[a * t + r];
return n;
}
function Ve(i) {
return Array(i.numberOfChannels).fill(0).map((t, e) => i.getChannelData(e));
}
async function Hi(i, t) {
var o;
const e = {
type: t,
data: i
}, n = new ImageDecoder(e);
await Promise.all([n.completed, n.tracks.ready]);
let a = ((o = n.tracks.selectedTrack) == null ? void 0 : o.frameCount) ?? 1;
const r = [];
for (let c = 0; c < a; c += 1)
r.push((await n.decode({ frameIndex: c })).image);
return r;
}
function Ye(i) {
var n, a;
const t = Math.max(...i.map((r) => {
var o;
return ((o = r[0]) == null ? void 0 : o.length) ?? 0;
})), e = new Float32Array(t * 2);
for (let r = 0; r < t; r++) {
let o = 0, c = 0;
for (let l = 0; l < i.length; l++) {
const f = ((n = i[l][0]) == null ? void 0 : n[r]) ?? 0, u = ((a = i[l][1]) == null ? void 0 : a[r]) ?? f;
o += f, c += u;
}
e[r] = o, e[r + t] = c;
}
return e;
}
async function Wi(i, t, e) {
const n = i.length, a = Array(e.chanCount).fill(0).map(() => new Float32Array(0));
if (n === 0) return a;
const r = Math.max(...i.map((f) => f.length));
if (r === 0) return a;
if (globalThis.OfflineAudioContext == null)
return i.map(
(f) => new Float32Array(
Ai.resample(f, t, e.rate, {
method: "sinc",
LPF: !1
})
)
);
const o = new globalThis.OfflineAudioContext(
e.chanCount,
r * e.rate / t,
e.rate
), c = o.createBufferSource(), l = o.createBuffer(n, r, t);
return i.forEach((f, u) => l.copyToChannel(f, u)), c.buffer = l, c.connect(o.destination), c.start(), Ve(await o.startRendering());
}
function Ue(i) {
return new Promise((t) => {
const e = Si(() => {
e(), t();
}, i);
});
}
function De(i, t, e) {
const n = e - t, a = new Float32Array(n);
let r = 0;
for (; r < n; )
a[r] = i[(t + r) % i.length], r += 1;
return a;
}
function li(i, t) {
const e = Math.floor(i.length / t), n = new Float32Array(e);
for (let a = 0; a < e; a++) {
const r = a * t, o = Math.floor(r), c = r - o;
o + 1 < i.length ? n[a] = i[o] * (1 - c) + i[o + 1] * c : n[a] = i[o];
}
return n;
}
const T = {
sampleRate: 48e3,
channelCount: 2,
codec: "mp4a.40.2"
};
function Ne(i, t) {
const e = t.videoTracks[0], n = {};
if (e != null) {
const r = Xi(i.getTrackById(e.id)).buffer, { descKey: o, type: c } = e.codec.startsWith("avc1") ? { descKey: "avcDecoderConfigRecord", type: "avc1" } : e.codec.startsWith("hvc1") ? { descKey: "hevcDecoderConfigRecord", type: "hvc1" } : { descKey: "", type: "" };
o !== "" && (n.videoTrackConf = {
timescale: e.timescale,
duration: e.duration,
width: e.video.width,
height: e.video.height,
brands: t.brands,
type: c,
[o]: r
}), n.videoDecoderConf = {
codec: e.codec,
codedHeight: e.video.height,
codedWidth: e.video.width,
description: r instanceof Uint8Array ? r : new Uint8Array(r)
};
}
const a = t.audioTracks[0];
if (a != null) {
const r = je(i);
n.audioTrackConf = {
timescale: a.timescale,
samplerate: a.audio.sample_rate,
channel_count: a.audio.channel_count,
hdlr: "soun",
type: a.codec.startsWith("mp4a") ? "mp4a" : a.codec,
description: je(i)
}, n.audioDecoderConf = {
codec: a.codec.startsWith("mp4a") ? T.codec : a.codec,
numberOfChannels: a.audio.channel_count,
sampleRate: a.audio.sample_rate,
...r == null ? {} : Gi(r)
};
}
return n;
}
function Xi(i) {
for (const t of i.mdia.minf.stbl.stsd.entries) {
const e = t.avcC ?? t.hvcC ?? t.av1C ?? t.vpcC;
if (e != null) {
const n = new mt.DataStream(
void 0,
0,
mt.DataStream.BIG_ENDIAN
);
return e.write(n), new Uint8Array(n.buffer.slice(8));
}
}
throw Error("avcC, hvcC, av1C or VPX not found");
}
function je(i, t = "mp4a") {
var n;
const e = (n = i.moov) == null ? void 0 : n.traks.map((a) => a.mdia.minf.stbl.stsd.entries).flat().find(({ type: a }) => a === t);
return e == null ? void 0 : e.esds;
}
function Gi(i) {
var c;
const t = (c = i.esd.descs[0]) == null ? void 0 : c.descs[0];
if (t == null) return {};
const [e, n] = t.data, a = ((e & 7) << 1) + (n >> 7), r = (n & 127) >> 3;
return {
sampleRate: [
96e3,
88200,
64e3,
48e3,
44100,
32e3,
24e3,
22050,
16e3,
12e3,
11025,
8e3,
7350
][a],
numberOfChannels: r
};
}
async function Ji(i, t, e) {
const n = mt.createFile(!1);
n.onReady = (r) => {
var l, f;
t({ mp4boxFile: n, info: r });
const o = (l = r.videoTracks[0]) == null ? void 0 : l.id;
o != null && n.setExtractionOptions(o, "video", { nbSamples: 100 });
const c = (f = r.audioTracks[0]) == null ? void 0 : f.id;
c != null && n.setExtractionOptions(c, "audio", { nbSamples: 100 }), n.start();
}, n.onSamples = e, await a();
async function a() {
let r = 0;
const o = 30 * 1024 * 1024;
for (; ; ) {
const c = await i.read(o, {
at: r
});
if (c.byteLength === 0) break;
c.fileStart = r;
const l = n.appendBuffer(c);
if (l == null) break;
r = l;
}
n.stop();
}
}
let $e = 0;
function Re(i) {
return i.kind === "file" && i.createReader instanceof Function;
}
var Ce, Jt, Kt, V, E, H, Yt, U, st, Ot, yt, W, O, Bt;
const pt = class pt {
constructor(t, e = {}) {
d(this, Ce, $e++);
d(this, Jt, k.create(`MP4Clip id:${s(this, Ce)},`));
v(this, "ready");
d(this, Kt, !1);
d(this, V, {
// 微秒
duration: 0,
width: 0,
height: 0,
audioSampleRate: 0,
audioChanCount: 0
});
d(this, E);
d(this, H, []);
d(this, Yt, 1);
d(this, U, []);
d(this, st, []);
d(this, Ot, null);
d(this, yt, null);
d(this, W, {
video: null,
audio: null
});
d(this, O, { audio: !0 });
/**
* 拦截 {@link MP4Clip.tick} 方法返回的数据,用于对图像、音频数据二次处理
* @param time 调用 tick 的时间
* @param tickRet tick 返回的数据
*
* @see [移除视频绿幕背景](https://webav-tech.github.io/WebAV/demo/3_2-chromakey-video)
*/
v(this, "tickInterceptor", async (t, e) => e);
d(this, Bt, new AbortController());
if (!(t instanceof ReadableStream) && !Re(t) && !Array.isArray(t.videoSamples))
throw Error("Illegal argument");
h(this, O, { audio: !0, ...e }), h(this, Yt, typeof e.audio == "object" && "volume" in e.audio ? e.audio.volume : 1);
const n = async (a) => (await Pe(s(this, E), a), s(this, E));
h(this, E, Re(t) ? t : "localFile" in t ? t.localFile : Ee()), this.ready = (t instanceof ReadableStream ? n(t).then(
(a) => qe(a, s(this, O))
) : Re(t) ? qe(t, s(this, O)) : Promise.resolve(t)).then(
async ({ videoSamples: a, audioSamples: r, decoderConf: o, headerBoxPos: c }) => {
h(this, U, a), h(this, st, r), h(this, W, o), h(this, H, c);
const { videoFrameFinder: l, audioFrameFinder: f } = Yi(
{
video: o.video == null ? null : {
...o.video,
hardwareAcceleration: s(this, O).__unsafe_hardwareAcceleration__
},
audio: o.audio
},
await s(this, E).createReader(),
a,
r,
s(this, O).audio !== !1 ? s(this, Yt) : 0
);
return h(this, Ot, l), h(this, yt, f), h(this, V, Ki(o, a, r)), s(this, Jt).info("MP4Clip meta:", s(this, V)), { ...s(this, V) };
}
);
}
get meta() {
return { ...s(this, V) };
}
/**
* 提供视频头(box: ftyp, moov)的二进制数据
* 使用任意 mp4 demxer 解析即可获得详细的视频信息
* 单元测试包含使用 mp4box.js 解析示例代码
*/
async getFileHeaderBinData() {
await this.ready;
const t = await s(this, E).getOriginFile();
if (t == null) throw Error("MP4Clip localFile is not origin file");
return await new Blob(
s(this, H).map(
({ start: e, size: n }) => t.slice(e, e + n)
)
).arrayBuffer();
}
/**
* 获取素材指定时刻的图像帧、音频数据
* @param time 微秒
*/
async tick(t) {
var a, r, o;
if (t >= s(this, V).duration)
return await this.tickInterceptor(t, {
audio: await ((a = s(this, yt)) == null ? void 0 : a.find(t)) ?? [],
state: "done"
});
const [e, n] = await Promise.all([
((r = s(this, yt)) == null ? void 0 : r.find(t)) ?? [],
(o = s(this, Ot)) == null ? void 0 : o.find(t)
]);
return n == null ? await this.tickInterceptor(t, {
audio: e,
state: "success"
}) : await this.tickInterceptor(t, {
video: n,
audio: e,
state: "success"
});
}
/**
* 生成缩略图,默认每个关键帧生成一个 100px 宽度的缩略图。
*
* @param imgWidth 缩略图宽度,默认 100
* @param opts Partial<ThumbnailOpts>
* @returns Promise<Array<{ ts: number; img: Blob }>>
*/
async thumbnails(t = 100, e) {
s(this, Bt).abort(), h(this, Bt, new AbortController());
const n = s(this, Bt).signal;
await this.ready;
const a = "generate thumbnails aborted";
if (n.aborted) throw Error(a);
const { width: r, height: o } = s(this, V), c = tn(
t,
Math.round(o * (t / r)),
{ quality: 0.1, type: "image/png" }
);
return new Promise(
async (l, f) => {
let u = [];
const w = s(this, W).video;
if (w == null || s(this, U).length === 0) {
m();
return;
}
n.addEventListener("abort", () => {
f(Error(a));
});
async function m() {
n.aborted || l(
await Promise.all(
u.map(async (b) => ({
ts: b.ts,
img: await b.img
}))
)
);
}
function x(b) {
u.push({
ts: b.timestamp,
img: c(b)
});
}
const { start: p = 0, end: g = s(this, V).duration, step: y } = e ?? {};
if (y) {
let b = p;
const C = new hi(
await s(this, E).createReader(),
s(this, U),
{
...w,
hardwareAcceleration: s(this, O).__unsafe_hardwareAcceleration__
}
);
for (; b <= g && !n.aborted; ) {
const S = await C.find(b);
S && x(S), b += y;
}
C.destroy(), m();
} else
await an(
s(this, U),
s(this, E),
w,
n,
{ start: p, end: g },
(b, C) => {
b != null && x(b), C && m();
}
);
}
);
}
async split(t) {
if (await this.ready, t <= 0 || t >= s(this, V).duration)
throw Error('"time" out of bounds');
const [e, n] = en(
s(this, U),
t
), [a, r] = nn(
s(this, st),
t
), o = new pt(
{
localFile: s(this, E),
videoSamples: e ?? [],
audioSamples: a ?? [],
decoderConf: s(this, W),
headerBoxPos: s(this, H)
},
s(this, O)
), c = new pt(
{
localFile: s(this, E),
videoSamples: n ?? [],
audioSamples: r ?? [],
decoderConf: s(this, W),
headerBoxPos: s(this, H)
},
s(this, O)
);
return await Promise.all([o.ready, c.ready]), [o, c];
}
async clone() {
await this.ready;
const t = new pt(
{
localFile: s(this, E),
videoSamples: [...s(this, U)],
audioSamples: [...s(this, st)],
decoderConf: s(this, W),
headerBoxPos: s(this, H)
},
s(this, O)
);
return await t.ready, t.tickInterceptor = this.tickInterceptor, t;
}
/**
* 拆分 MP4Clip 为仅包含视频轨道和音频轨道的 MP4Clip
* @returns Mp4CLip[]
*/
async splitTrack() {
await this.ready;
const t = [];
if (s(this, U).length > 0) {
const e = new pt(
{
localFile: s(this, E),
videoSamples: [...s(this, U)],
audioSamples: [],
decoderConf: {
video: s(this, W).video,
audio: null
},
headerBoxPos: s(this, H)
},
s(this, O)
);
await e.ready, e.tickInterceptor = this.tickInterceptor, t.push(e);
}
if (s(this, st).length > 0) {
const e = new pt(
{
localFile: s(this, E),
videoSamples: [],
audioSamples: [...s(this, st)],
decoderConf: {
audio: s(this, W).audio,
video: null
},
headerBoxPos: s(this, H)
},
s(this, O)
);
await e.ready, e.tickInterceptor = this.tickInterceptor, t.push(e);
}
return t;
}
destroy() {
var t, e;
s(this, Kt) || (s(this, Jt).info("MP4Clip destroy"), h(this, Kt, !0), (t = s(this, Ot)) == null || t.destroy(), (e = s(this, yt)) == null || e.destroy());
}
};
Ce = new WeakMap(), Jt = new WeakMap(), Kt = new WeakMap(), V = new WeakMap(), E = new WeakMap(), H = new WeakMap(), Yt = new WeakMap(), U = new WeakMap(), st = new WeakMap(), Ot = new WeakMap(), yt = new WeakMap(), W = new WeakMap(), O = new WeakMap(), Bt = new WeakMap();
let Qe = pt;
function Ki(i, t, e) {
const n = {
duration: 0,
width: 0,
height: 0,
audioSampleRate: 0,
audioChanCount: 0
};
i.video != null && t.length > 0 && (n.width = i.video.codedWidth ?? 0, n.height = i.video.codedHeight ?? 0), i.audio != null && e.length > 0 && (n.audioSampleRate = T.sampleRate, n.audioChanCount = T.channelCount);
let a = 0, r = 0;
if (t.length > 0)
for (let o = t.length - 1; o >= 0; o--) {
const c = t[o];
if (!c.deleted) {
a = c.cts + c.duration;
break;
}
}
if (e.length > 0) {
const o = e.at(-1);
r = o.cts + o.duration;
}
return n.duration = Math.max(a, r), n;
}
function Yi(i, t, e, n, a) {
return {
audioFrameFinder: a === 0 || i.audio == null || n.length === 0 ? null : new Qi(
t,
n,
i.audio,
{
volume: a,
targetSampleRate: T.sampleRate
}
),
videoFrameFinder: i.video == null || e.length === 0 ? null : new hi(
t,
e,
i.video
)
};
}
async function qe(i, t = {}) {
let e = null;
const n = { video: null, audio: null };
let a = [], r = [], o = [], c = -1, l = -1;
const f = await i.createReader();
await Ji(
f,
(m) => {
e = m.info;
const x = m.mp4boxFile.ftyp;
o.push({ start: x.start, size: x.size });
const p = m.mp4boxFile.moov;
o.push({ start: p.start, size: p.size });
let { videoDecoderConf: g, audioDecoderConf: y } = Ne(
m.mp4boxFile,
m.info
);
n.video = g ?? null, n.audio = y ?? null, g == null && y == null && k.error("MP4Clip no video and audio track"), k.info(
"mp4BoxFile moov ready",
{
...m.info,
tracks: null,
videoTracks: null,
audioTracks: null
},
n
);
},
(m, x, p) => {
if (x === "video") {
c === -1 && (c = p[0].dts);
for (const g of p)
a.push(w(g, c, "video"));
} else if (x === "audio" && t.audio) {
l === -1 && (l = p[0].dts);
for (const g of p)
r.push(w(g, l, "audio"));
}
}
), await f.close();
const u = a.at(-1) ?? r.at(-1);
if (e == null)
throw Error("MP4Clip stream is done, but not emit ready");
if (u == null)
throw Error("MP4Clip stream not contain any sample");
return Be(a), k.info("mp4 stream parsed"), {
videoSamples: a,
audioSamples: r,
decoderConf: n,
headerBoxPos: o
};
function w(m, x = 0, p) {
const g = p === "video" && m.is_sync ? sn(m.data, m.description.type) : -1;
let y = m.offset, b = m.size;
return g >= 0 && (y += g, b -= g), {
...m,
is_idr: g >= 0,
offset: y,
size: b,
cts: (m.cts - x) / m.timescale * 1e6,
dts: (m.dts - x) / m.timescale * 1e6,
duration: m.duration / m.timescale * 1e6,
timescale: 1e6,
// 音频数据量可控,直接保存在内存中
data: p === "video" ? null : m.data
};
}
}
var R, gt, bt, jt, xt, X, M, at, Ct, Mt, Qt, _t, rt, qt, vt, Zt;
class hi {
constructor(t, e, n) {
d(this, R, null);
d(this, gt, 0);
d(this, bt, { abort: !1, st: performance.now() });
v(this, "find", async (t) => {
(s(this, R) == null || s(this, R).state === "closed" || t <= s(this, gt) || t - s(this, gt) > 3e6) && s(this, vt).call(this, t), s(this, bt).abort = !0, h(this, gt, t), h(this, bt, { abort: !1, st: performance.now() });
const e = await s(this, _t).call(this, t, s(this, R), s(this, bt));
return h(this, Mt, 0), e;
});
// fix VideoFrame duration is null
d(this, jt, 0);
d(this, xt, !1);
d(this, X, 0);
d(this, M, []);
d(this, at, 0);
d(this, Ct, 0);
d(this, Mt, 0);
d(this, Qt, !1);
d(this, _t, async (t, e, n) => {
if (e == null || e.state === "closed" || n.abort) return null;
if (s(this, M).length > 0) {
const a = s(this, M)[0];
return t < a.timestamp ? null : (s(this, M).shift(), t > a.timestamp + (a.duration ?? 0) ? (a.close(), await s(this, _t).call(this, t, e, n)) : (!s(this, Qt) && s(this, M).length < 10 && s(this, qt).call(this, e).catch((r) => {
throw h(this, Qt, !0), s(this, vt).call(this, t), r;
}), a));
}
if (s(this, rt) || s(this, at) < s(this, Ct) && e.decodeQueueSize > 0) {
if (performance.now() - n.st > 6e3)
throw Error(
`MP4Clip.tick video timeout, ${JSON.stringify(s(this, Zt).call(this))}`
);
h(this, Mt, s(this, Mt) + 1), await Ue(15);
} else {
if (s(this, X) >= this.samples.length)
return null;
try {
await s(this, qt).call(this, e);
} catch (a) {
throw s(this, vt).call(this, t), a;
}
}
return await s(this, _t).call(this, t, e, n);
});
d(this, rt, !1);
d(this, qt, async (t) => {
var a, r;
if (s(this, rt) || t.decodeQueueSize > 600) return;
let e = s(this, X) + 1;
if (e > this.samples.length) return;
h(this, rt, !0);
let n = !1;
for (; e < this.samples.length; e++) {
const o = this.samples[e];
if (!n && !o.deleted && (n = !0), o.is_idr) break;
}
if (n) {
const o = this.samples.slice(s(this, X), e);
if (((a = o[0]) == null ? void 0 : a.is_idr) !== !0)
k.warn("First sample not idr frame");
else {
const c = performance.now(), l = await ui(o, this.localFileReader), f = performance.now() - c;
if (f > 1e3) {
const u = o[0], w = o.at(-1), m = w.offset + w.size - u.offset;
k.warn(
`Read video samples time cost: ${Math.round(f)}ms, file chunk size: ${m}`
);
}
if (t.state === "closed") return;
h(this, jt, ((r = l[0]) == null ? void 0 : r.duration) ?? 0), Oe(t, l, {
onDecodingError: (u) => {
if (s(this, xt))
throw u;
s(this, at) === 0 && (h(this, xt, !0), k.warn("Downgrade to software decode"), s(this, vt).call(this));
}
}), h(this, Ct, s(this, Ct) + l.length);
}
}
h(this, X, e), h(this, rt, !1);
});
d(this, vt, (t) => {
var n, a;
if (h(this, rt, !1), s(this, M).forEach((r) => r.close()), h(this, M, []), t == null || t === 0)
h(this, X, 0);
else {
let r = 0;
for (let o = 0; o < this.samples.length; o++) {
const c = this.samples[o];
if (c.is_idr && (r = o), !(c.cts < t)) {
h(this, X, r);
break;
}
}
}
h(this, Ct, 0), h(this, at, 0), ((n = s(this, R)) == null ? void 0 : n.state) !== "closed" && ((a = s(this, R)) == null || a.close());
const e = {
...this.conf,
...s(this, xt) ? { hardwareAcceleration: "prefer-software" } : {}
};
h(this, R, new VideoDecoder({
output: (r) => {
if (h(this, at, s(this, at) + 1), r.timestamp === -1) {
r.close();
return;
}
let o = r;
r.duration == null && (o = new VideoFrame(r, {
duration: s(this, jt)
}), r.close()), s(this, M).push(o);
},
error: (r) => {
if (r.message.includes("Codec reclaimed due to inactivity")) {
h(this, R, null), k.warn(r.message);
return;
}
const o = `VideoFinder VideoDecoder err: ${r.message}, config: ${JSON.stringify(e)}, state: ${JSON.stringify(s(this, Zt).call(this))}`;
throw k.error(o), Error(o);
}
})), s(this, R).configure(e);
});
d(this, Zt, () => {
var t, e;
return {
time: s(this, gt),
decState: (t = s(this, R)) == null ? void 0 : t.state,
decQSize: (e = s(this, R)) == null ? void 0 : e.decodeQueueSize,
decCusorIdx: s(this, X),
sampleLen: this.samples.length,
inputCnt: s(this, Ct),
outputCnt: s(this, at),
cacheFrameLen: s(this, M).length,
softDeocde: s(this, xt),
clipIdCnt: $e,
sleepCnt: s(this, Mt),
memInfo: di()
};
});
v(this, "destroy", () => {
var t, e;
((t = s(this, R)) == null ? void 0 : t.state) !== "closed" && ((e = s(this, R)) == null || e.close()), h(this, R, null), s(this, bt).abort = !0, s(this, M).forEach((n) => n.close()), h(this, M, []), this.localFileReader.close();
});
this.localFileReader = t, this.samples = e, this.conf = n;
}
}
R = new WeakMap(), gt = new WeakMap(), bt = new WeakMap(), jt = new WeakMap(), xt = new WeakMap(), X = new WeakMap(), M = new WeakMap(), at = new WeakMap(), Ct = new WeakMap(), Mt = new WeakMap(), Qt = new WeakMap(), _t = new WeakMap(), rt = new WeakMap(), qt = new WeakMap(), vt = new WeakMap(), Zt = new WeakMap();
function ji(i, t) {
for (let e = 0; e < t.length; e++) {
const n = t[e];
if (i >= n.cts && i < n.cts + n.duration)
return e;
if (n.cts > i) break;
}
return 0;
}
var te, ee, N, St, G, tt, z, zt, ie, ne, ve, Se;
class Qi {
constructor(t, e, n, a) {
d(this, te, 1);
d(this, ee);
d(this, N, null);
d(this, St, { abort: !1, st: performance.now() });
v(this, "find", async (t) => {
const e = t <= s(this, G) || t - s(this, G) > 1e5;
(s(this, N) == null || s(this, N).state === "closed" || e) && s(this, ve).call(this), e && (h(this, G, t), h(this, tt, ji(t, this.samples))), s(this, St).abort = !0;
const n = t - s(this, G);
h(this, G, t), h(this, St, { abort: !1, st: performance.now() });
const a = await s(this, ie).call(this, Math.ceil(n * (s(this, ee) / 1e6)), s(this, N), s(this, St));
return h(this, zt, 0), a;
});
d(this, G, 0);
d(this, tt, 0);
d(this, z, {
frameCnt: 0,
data: []
});
d(this, zt, 0);
d(this, ie, async (t, e = null, n) => {
if (e == null || n.abort || e.state === "closed" || t === 0)
return [];
const a = s(this, z).frameCnt - t;
if (a > 0)
return a < T.sampleRate / 10 && s(this, ne).call(this, e), Ze(s(this, z), t);
if (e.decoding) {
if (performance.now() - n.st > 3e3)
throw n.abort = !0, Error(
`MP4Clip.tick audio timeout, ${JSON.stringify(s(this, Se).call(this))}`
);
h(this, zt, s(this, zt) + 1), await Ue(15);
} else {
if (s(this, tt) >= this.samples.length - 1)
return Ze(s(this, z), s(this, z).frameCnt);
s(this, ne).call(this, e);
}
return s(this, ie).call(this, t, e, n);
});
d(this, ne, (t) => {
if (t.decodeQueueSize > 10) return;
const n = [];
let a = s(this, tt);
for (; a < this.samples.length; ) {
const r = this.samples[a];
if (a += 1, !r.deleted && (n.push(r), n.length >= 10))
break;
}
h(this, tt, a), t.decode(
n.map(
(r) => new EncodedAudioChunk({
type: "key",
timestamp: r.cts,
duration: r.duration,
data: r.data
})
)
);
});
d(this, ve, () => {
var t;
h(this, G, 0), h(this, tt, 0), h(this, z, {
frameCnt: 0,
data: []
}), (t = s(this, N)) == null || t.close(), h(this, N, qi(
this.conf,
{
resampleRate: T.sampleRate,
volume: s(this, te)
},
(e) => {
s(this, z).data.push(e), s(this, z).frameCnt += e[0].length;
}
));
});
d(this, Se, () => {
var t, e;
return {
time: s(this, G),
decState: (t = s(this, N)) == null ? void 0 : t.state,
decQSize: (e = s(this, N)) == null ? void 0 : e.decodeQueueSize,
decCusorIdx: s(this, tt),
sampleLen: this.samples.length,
pcmLen: s(this, z).frameCnt,
clipIdCnt: $e,
sleepCnt: s(this, zt),
memInfo: di()
};
});
v(this, "destroy", () => {
h(this, N, null), s(this, St).abort = !0, h(this, z, {
frameCnt: 0,
data: []
}), this.localFileReader.close();
});
this.localFileReader = t, this.samples = e, this.conf = n, h(this, te, a.volume), h(this, ee, a.targetSampleRate);
}
}
te = new WeakMap(), ee = new WeakMap(), N = new WeakMap(), St = new WeakMap(), G = new WeakMap(), tt = new WeakMap(), z = new WeakMap(), zt = new WeakMap(), ie = new WeakMap(), ne = new WeakMap(), ve = new WeakMap(), Se = new WeakMap();
function qi(i, t, e) {
let n = 0, a = 0;
const r = (u) => {
if (a += 1, u.length !== 0) {
if (t.volume !== 1)
for (const w of u)
for (let m = 0; m < w.length; m++) w[m] *= t.volume;
u.length === 1 && (u = [u[0], u[0]]), e(u);
}
}, o = Zi(r), c = t.resampleRate !== i.sampleRate;
let l = new AudioDecoder({
output: (u) => {
const w = ci(u);
c ? o(
() => Wi(w, u.sampleRate, {
rate: t.resampleRate,
chanCount: u.numberOfChannels
})
) : r(w), u.close();
},
error: (u) => {
u.message.includes("Codec reclaimed due to inactivity") || f("MP4Clip AudioDecoder err", u);
}
});
l.configure(i);
function f(u, w) {
const m = `${u}: ${w.message}, state: ${JSON.stringify(
{
qSize: l.decodeQueueSize,
state: l.state,
inputCnt: n,
outputCnt: a
}
)}`;
throw k.error(m), Error(m);
}
return {
decode(u) {
n += u.length;
try {
for (const w of u) l.decode(w);
} catch (w) {
f("decode audio chunk error", w);
}
},
close() {
l.state !== "closed" && l.close();
},
get decoding() {
return n > a && l.decodeQueueSize > 0;
},
get state() {
return l.state;
},
get decodeQueueSize() {
return l.decodeQueueSize;
}
};
}
function Zi(i) {
const t = [];
let e = 0;
function n(o, c) {
t[c] = o, a();
}
function a() {
const o = t[e];
o != null && (i(o), e += 1, a());
}
let r = 0;
return (o) => {
const c = r;
r += 1, o().then((l) => n(l, c)).catch((l) => n(l, c));
};
}
function Ze(i, t) {
const e = [new Float32Array(t), new Float32Array(t)];
let n = 0, a = 0;
for (; a < i.data.length; ) {
const [r, o] = i.data[a];
if (n + r.length > t) {
const c = t - n;
e[0].set(r.subarray(0, c), n), e[1].set(o.subarray(0, c), n), i.data[a][0] = r.subarray(c, r.length), i.data[a][1] = o.subarray(c, o.length);
break;
} else
e[0].set(r, n), e[1].set(o, n), n += r.length, a++;
}
return i.data = i.data.slice(a), i.frameCnt -= t, e;
}
async function ui(i, t) {
const e = i[0], n = i.at(-1);
if (n == null) return [];
const a = n.offset + n.size - e.offset;
if (a < 3e7) {
const r = new Uint8Array(
await t.read(a, { at: e.offset })
);
return i.map((o) => {
const c = o.offset - e.offset;
return new EncodedVideoChunk({
type: o.is_sync ? "key" : "delta",
timestamp: o.cts,
duration: o.duration,
data: r.subarray(c, c + o.size)
});
});
}
return await Promise.all(
i.map(async (r) => new EncodedVideoChunk({
type: r.is_sync ? "key" : "delta",
timestamp: r.cts,
duration: r.duration,
data: await t.read(r.size, {
at: r.offset
})
}))
);
}
function tn(i, t, e) {
const n = new OffscreenCanvas(i, t), a = n.getContext("2d");
return async (r) => (a.drawImage(r, 0, 0, i, t), r.close(), await n.convertToBlob(e));
}
function en(i, t) {
if (i.length === 0) return [];
let e = 0, n = 0, a = -1;
for (let l = 0; l < i.length; l++) {
const f = i[l];
if (a === -1 && t < f.cts && (a = l - 1), f.is_idr)
if (a === -1)
e = l;
else {
n = l;
break;
}
}
const r = i[a];
if (r == null) throw Error("Not found video sample by time");
const o = i.slice(0, n === 0 ? i.length : n).map((l) => ({ ...l }));
for (let l = e; l < o.length; l++) {
const f = o[l];
t < f.cts && (f.deleted = !0, f.cts = -1);
}
Be(o);
const c = i.slice(r.is_idr ? a : e).map((l) => ({ ...l, cts: l.cts - t }));
for (const l of c)
l.cts < 0 && (l.deleted = !0, l.cts = -1);
return Be(c), [o, c];
}
function nn(i, t) {
if (i.length === 0) return [];
let e = -1;
for (let r = 0; r < i.length; r++) {
const o = i[r];
if (!(t > o.cts)) {
e = r;
break;
}
}
if (e === -1) throw Error("Not found audio sample by time");
const n = i.slice(0, e).map((r) => ({ ...r })), a = i.slice(e).map((r) => ({ ...r, cts: r.cts - t }));
return [n, a];
}
function Oe(i, t, e) {
let n = 0;
if (i.state === "configured") {
for (; n < t.length; n++) i.decode(t[n]);
i.flush().catch((a) => {
if (!(a instanceof Error)) throw a;
if (a.message.includes("Decoding error") && e.onDecodingError != null) {
e.onDecodingError(a);
return;
}
if (!a.message.includes("Aborted due to close"))
throw a;
});
}
}
function sn(i, t) {
if (t !== "avc1" && t !== "hvc1") return 0;
const e = new DataView(i.buffer);
let n = 0;
for (; n < i.byteLength - 4; ) {
if (t === "avc1" && (e.getUint8(n + 4) & 31) === 5)
return n;
if (t === "hvc1") {
const a = e.getUint8(n + 4) >> 1 & 63;
if (a === 19 || a === 20) return n;
}
n += e.getUint32(n) + 4;
}
return -1;
}
async function an(i, t, e, n, a, r) {
const o = await t.createReader(), c = await ui(
i.filter(
(u) => !u.deleted && u.is_sync && u.cts >= a.start && u.cts <= a.end
),
o
);
if (c.length === 0 || n.aborted) return;
let l = 0;
Oe(f(), c, {
onDecodingError: (u) => {
k.warn("thumbnailsByKeyFrame", u), l === 0 ? Oe(f(!0), c, {
onDecodingError: (w) => {
o.close(), k.error("thumbnailsByKeyFrame retry soft deocde", w);
}
}) : (r(null, !0), o.close());
}
});
function f(u = !1) {
const w = {
...e,
...u ? { hardwareAcceleration: "prefer-software" } : {}
}, m = new VideoDecoder({
output: (x) => {
l += 1;
const p = l === c.length;
r(x, p), p && (o.close(), m.state !== "closed" && m.close());
},
error: (x) => {
const p = `thumbnails decoder error: ${x.message}, config: ${JSON.stringify(w)}, state: ${JSON.stringify(
{
qSize: m.decodeQueueSize,
state: m.state,
outputCnt: l,
inputCnt: c.length
}
)}`;
throw k.error(p), Error(p);
}
});
return n.addEventListener("abort", () => {
o.close(), m.state !== "closed" && m.close();
}), m.configure(w), m;
}
}
function Be(i) {
let t = 0, e = null;
for (const n of i)
if (!n.deleted) {
if (n.is_sync && (t += 1), t >= 2) break;
(e == null || n.cts < e.cts) && (e = n);
}
e != null && e.cts < 2e5 && (e.duration += e.cts, e.cts = 0);
}
function di() {
try {
const i = performance.memory;
return {
jsHeapSizeLimit: i.jsHeapSizeLimit,
totalJSHeapSize: i.totalJSHeapSize,
usedJSHeapSize: i.usedJSHeapSize,
percentUsed: (i.usedJSHeapSize / i.jsHeapSizeLimit).toFixed(3),
percentTotal: (i.totalJSHeapSize / i.jsHeapSizeLimit).toFixed(3)
};
} catch {
return {};
}
}
var P, L, F, Te, fi;
const wt = class wt {
/**
* 静态图片可使用流、ImageBitmap 初始化
*
* 动图需要使用 VideoFrame[] 或提供图片类型
*/
constructor(t) {
d(this, Te);
v(this, "ready");
d(this, P, {
// 微秒
duration: 0,
width: 0,
height: 0
});
d(this, L, null);
d(this, F, []);
v(this, "tickInterceptor", async (t, e) => e);
const e = (n) => (h(this, L, n), s(this, P).width = n.width, s(this, P).height = n.height, s(this, P).duration = 1 / 0, { ...s(this, P) });
if (t instanceof ReadableStream)
this.ready = new Response(t).blob().then((n) => createImageBitmap(n)).then(e);
else if (t instanceof ImageBitmap)
this.ready = Promise.resolve(e(t));
else if (Array.isArray(t) && t.every((n) => n instanceof VideoFrame)) {
h(this, F, t);
const n = s(this, F)[0];
if (n == null) throw Error("The frame count must be greater than 0");
h(this, P, {
width: n.displayWidth,
height: n.displayHeight,
duration: s(this, F).reduce(
(a, r) => a + (r.duration ?? 0),
0
)
}), this.ready = Promise.resolve({ ...s(this, P), duration: 1 / 0 });
} else if ("type" in t)
this.ready = B(this, Te, fi).call(this, t.stream, t.type).then(() => ({
width: s(this, P).width,
height: s(this, P).height,
duration: 1 / 0
}));
else
throw Error("Illegal arguments");
}
/**
* ⚠️ 静态图片的 duration 为 Infinity
*
* 使用 Sprite 包装时需要将它的 duration 设置为有限数
*
*/
get meta() {
return { ...s(this, P) };
}
async tick(t) {
if (s(this, L) != null)
return await this.tickInterceptor(t, {
video: await createImageBitmap(s(this, L)),
state: "success"
});
const e = t % s(this, P).duration;
return await this.tickInterceptor(t, {
video: (s(this, F).find(
(n) => e >= n.timestamp && e <= n.timestamp + (n.duration ?? 0)
) ?? s(this, F)[0]).clone(),
state: "success"
});
}
async split(t) {
if (await this.ready, s(this, L) != null)
return [
new wt(await createImageBitmap(s(this, L))),
new wt(await createImageBitmap(s(this, L)))
];
let e = -1;
for (let r = 0; r < s(this, F).length; r++) {
const o = s(this, F)[r];
if (!(t > o.timestamp)) {
e = r;
break;
}
}
if (e === -1) throw Error("Not found frame by time");
const n = s(this, F).slice(0, e).map((r) => new VideoFrame(r)), a = s(this, F).slice(e).map(
(r) => new VideoFrame(r, {
timestamp: r.timestamp - t
})
);
return [new wt(n), new wt(a)];
}
async clone() {
await this.ready;
const t = s(this, L) == null ? s(this, F).map((e) => e.clone()) : await createImageBitmap(s(this, L));
return new wt(t);
}
destroy() {
var t;
k.info("ImgClip destroy"), (t = s(this, L)) == null || t.close(), s(this, F).forEach((e) => e.close());
}
};
P = new WeakMap(), L = new WeakMap(), F = new WeakMap(), Te = new WeakSet(), fi = async function(t, e) {
h(this, F, await Hi(t, e));
const n = s(this, F)[0];
if (n == null) throw Error("No frame available in gif");
h(this, P, {
duration: s(this, F).reduce((a, r) => a + (r.duration ?? 0), 0),
width: n.codedWidth,
height: n.codedHeight
}), k.info("ImgClip ready:", s(this, P));
};
let ti = wt;
var Tt, et, ot, J, Ae, mi, ct, K;
const Z = class Z {
/**
*
* @param dataSource 音频文件流
* @param opts 音频配置,控制音量、是否循环
*/
constructor(t, e = {}) {
d(this, Ae);
v(this, "ready");
d(this, Tt, {
// 微秒
duration: 0,
width: 0,
height: 0
});
d(this, et, new Float32Array());
d(this, ot, new Float32Array());
d(this, J);
/**
* 拦截 {@link AudioClip.tick} 方法返回的数据,用于对音频数据二次处理
* @param time 调用 tick 的时间
* @param tickRet tick 返回的数据
*
* @see [移除视频绿幕背景](https://webav-tech.github.io/WebAV/demo/3_2-chromakey-video)
*/
v(this, "tickInterceptor", async (t, e) => e);
// 微秒
d(this, ct, 0);
d(this, K, 0);
h(this, J, {
loop: !1,
volume: 1,
...e
}), this.ready = B(this, Ae, mi).call(this, t).then(() => ({
// audio 没有宽高,无需绘制
width: 0,
height: 0,
duration: e.loop ? 1 / 0 : s(this, Tt).duration
}));
}
/**
* 音频元信息
*
* ⚠️ 注意,这里是转换后(标准化)的元信息,非原始音频元信息
*/
get meta() {
return {
...s(this, Tt),
sampleRate: T.sampleRate,
chanCount: 2
};
}
/**
* 获取音频素材完整的 PCM 数据
*/
getPCMData() {
return [s(this, et), s(this, ot)];
}
/**
* 返回上次与当前时刻差对应的音频 PCM 数据;
*
* 若差值超过 3s 或当前时间小于上次时间,则重置状态
* @example
* tick(0) // => []
* tick(1e6) // => [leftChanPCM(1s), rightChanPCM(1s)]
*
*/
async tick(t) {
if (!s(this, J).loop && t >= s(this, Tt).duration)
return await this.tickInterceptor(t, { audio: [], state: "done" });
const e = t - s(this, ct);
if (t < s(this, ct) || e > 3e6)
return h(this, ct, t), h(this, K, Math.ceil(
s(this, ct) / 1e6 * T.sampleRate
)), await this.tickInterceptor(t, {
audio: [new Float32Array(0), new Float32Array(0)],
state: "success"
});
h(this, ct, t);
const n = Math.ceil(
e / 1e6 * T.sampleRate
), a = s(this, K) + n, r = s(this, J).loop ? [
De(s(this, et), s(this, K), a),
De(s(this, ot), s(this, K), a)
] : [
s(this, et).slice(s(this, K), a),
s(this, ot).slice(s(this, K), a)
];
return h(this, K, a), await this.tickInterceptor(t, { audio: r, state: "success" });
}
/**
* 按指定时间切割,返回前后两个音频素材
* @param time 时间,单位微秒
*/
async split(t) {
await this.ready;
const e = Math.ceil(t / 1e6 * T.sampleRate), n = new Z(
this.getPCMData().map((r) => r.slice(0, e)),
s(this, J)
), a = new Z(
this.getPCMData().map((r) => r.slice(e)),
s(this, J)
);
return [n, a];
}
async clone() {
await this.ready;
const t = new Z(this.getPCMData(), s(this, J));
return await t.ready, t;
}
/**
* 销毁实例,释放资源
*/
destroy() {
h(this, et, new Float32Array(0)), h(this, ot, new Float32Array(0)), k.info("---- audioclip destroy ----");
}
};
Tt = new WeakMap(), et = new WeakMap(), ot = new WeakMap(), J = new WeakMap(), Ae = new WeakSet(), mi = async function(t) {
Z.ctx == null && (Z.ctx = new AudioContext({
sampleRate: T.sampleRate
}));
const e = performance.now(), n = t instanceof ReadableStream ? await rn(t, Z.ctx) : t;
k.info("Audio clip decoded complete:", performance.now() - e);
const a = s(this, J).volume;
if (a !== 1)
for (const r of n)
for (let o = 0; o < r.length; o += 1) r[o] *= a;
s(this, Tt).duration = n[0].length / T.sampleRate * 1e6, h(this, et, n[0]), h(this, ot, n[1] ?? s(this, et)), k.info(
"Audio clip convert to AudioData, time:",
performance.now() - e
);
}, ct = new WeakMap(), K = new WeakMap(), v(Z, "ctx", null);
let ei = Z;
async function rn(i, t) {
const e = await new Response(i).arrayBuffer();
return Ve(await t.decodeAudioData(e));
}
var At, se, Lt, Vt;
const ke = class ke {
constructor(t) {
v(this, "ready");
d(this, At, {
// 微秒
duration: 0,
width: 0,
height: 0
});
d(this, se, () => {
});
/**
* 实时流的音轨
*/
v(this, "audioTrack");
d(this, Lt, null);
d(this, Vt);
h(this, Vt, t), this.audioTrack = t.getAudioTracks()[0] ?? null, s(this, At).duration = 1 / 0;
const e = t.getVideoTracks()[0];
e != null ? (e.contentHint = "motion", this.ready = new Promise((n) => {
h(this, se, on(e, (a) => {
s(this, At).width = a.width, s(this, At).height = a.height, h(this, Lt, a), n(this.meta);
}));
})) : this.ready = Promise.resolve(this.meta);
}
get meta() {
return {
...s(this, At)
};
}
async tick() {
return {
video: s(this, Lt) == null ? null : await createImageBitmap(s(this, Lt)),
audio: [],
state: "success"
};
}
async split() {
return [await this.clone(