UNPKG

@chunpu/av-cliper

Version:

WebCodecs-based, combine video, audio, images, text, with animation support 基于 WebCodecs 合成 视频、音频、图片、文字,支持动画

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