UNPKG

@webrock/av-cliper

Version:

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

1,646 lines (1,642 loc) 88.4 kB
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(