@xmaxai/web-sdk 1.0.10 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,211 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
-
3
- var _chunk4DE2IREAcjs = require('./chunk-4DE2IREA.cjs');
4
-
5
- // src/Media/Recording/RecordingProcessor.ts
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
- var _mediabunny = require('mediabunny');
19
-
20
- // src/Media/Recording/RecordingTimeline.ts
21
- var _RecordingTimeline = class _RecordingTimeline {
22
- /**
23
- * 从显示时间戳估计正常帧间隔,只裁掉显著超长的等待部分。
24
- */
25
- constructor(timestamps, duration, audioPackets) {
26
- _chunk4DE2IREAcjs.__publicField.call(void 0, this, "duration", duration);
27
- /**
28
- * 原始时长、正常帧时长与待删除区间
29
- */
30
- _chunk4DE2IREAcjs.__publicField.call(void 0, this, "frameDuration");
31
- _chunk4DE2IREAcjs.__publicField.call(void 0, this, "cuts");
32
- if (!Number.isFinite(duration) || duration <= 0 || timestamps.length === 0 || timestamps.some((value) => !Number.isFinite(value) || value < 0 || value > duration)) {
33
- throw new Error("Invalid recording timeline");
34
- }
35
- const sorted = [...timestamps].sort((a, b) => a - b);
36
- const intervals = sorted.slice(1).map((time, index) => time - sorted[index]).filter((value) => value > 0).sort((a, b) => a - b);
37
- this.frameDuration = _nullishCoalesce(intervals[Math.floor(intervals.length / 2)], () => ( _RecordingTimeline.fallbackFrameDuration));
38
- const threshold = Math.max(_RecordingTimeline.minimumGap, this.frameDuration * _RecordingTimeline.gapMultiplier);
39
- const cuts = sorted.flatMap((start, index) => {
40
- const end = _nullishCoalesce(sorted[index + 1], () => ( duration));
41
- return end - start > threshold + 1e-6 ? [{ start: start + this.frameDuration, end }] : [];
42
- });
43
- this.cuts = audioPackets ? cuts.flatMap((cut) => {
44
- const contained = audioPackets.filter((packet) => packet.end > packet.start && packet.start >= cut.start && packet.end <= cut.end);
45
- if (contained.length === 0) {
46
- return [];
47
- }
48
- return [{
49
- start: Math.min(...contained.map((packet) => packet.start)),
50
- end: Math.max(...contained.map((packet) => packet.end))
51
- }];
52
- }) : cuts;
53
- }
54
- /**
55
- * 将原始时间映射到裁剪后的时间;删除区间内的时刻映射到同一接缝。
56
- */
57
- map(time) {
58
- let removed = 0;
59
- for (const cut of this.cuts) {
60
- if (time <= cut.start) {
61
- break;
62
- }
63
- removed += Math.min(time, cut.end) - cut.start;
64
- }
65
- return time - removed;
66
- }
67
- /**
68
- * 判断一个音频包是否位于删除区间;区间已对齐包边界,不会切断压缩数据。
69
- */
70
- removes(start, end) {
71
- return this.cuts.some((cut) => start >= cut.start && end <= cut.end);
72
- }
73
- };
74
- /**
75
- * 长间隔判定与默认帧时长
76
- */
77
- _chunk4DE2IREAcjs.__publicField.call(void 0, _RecordingTimeline, "minimumGap", 0.12);
78
- _chunk4DE2IREAcjs.__publicField.call(void 0, _RecordingTimeline, "gapMultiplier", 3);
79
- _chunk4DE2IREAcjs.__publicField.call(void 0, _RecordingTimeline, "fallbackFrameDuration", 1 / 30);
80
- var RecordingTimeline = _RecordingTimeline;
81
-
82
- // src/Media/Recording/RecordingProcessor.ts
83
- var _RecordingProcessor = class _RecordingProcessor {
84
- /**
85
- * 裁剪成功返回新文件,无需裁剪时返回原文件;失败交由调用方保留原始录像。
86
- */
87
- async process(blob) {
88
- if (blob.size > _RecordingProcessor.maximumBytes) {
89
- throw new Error("Recording is too large to process");
90
- }
91
- const input = new (0, _mediabunny.Input)({ source: new (0, _mediabunny.BlobSource)(blob), formats: [_mediabunny.MP4, _mediabunny.WEBM] });
92
- const controller = new AbortController();
93
- let output;
94
- let timer;
95
- const timeout = new Promise((_, reject) => {
96
- timer = setTimeout(() => {
97
- controller.abort();
98
- reject(new Error("Recording processing timed out"));
99
- }, _RecordingProcessor.timeoutMs);
100
- });
101
- try {
102
- return await Promise.race([
103
- this.rewrite(input, blob, controller.signal, (value) => {
104
- output = value;
105
- }),
106
- timeout
107
- ]);
108
- } finally {
109
- controller.abort();
110
- clearTimeout(timer);
111
- input.dispose();
112
- if (output && output.state !== "finalized" && output.state !== "canceled") {
113
- void output.cancel().catch(() => {
114
- });
115
- }
116
- }
117
- }
118
- /**
119
- * 读取有限数量的编码包,维持原始解码顺序。
120
- */
121
- async readPackets(track, signal) {
122
- const packets = [];
123
- for await (const packet of new (0, _mediabunny.EncodedPacketSink)(track).packets()) {
124
- signal.throwIfAborted();
125
- packets.push(packet);
126
- if (packets.length > _RecordingProcessor.maximumPackets) {
127
- throw new Error("Recording has too many media packets");
128
- }
129
- }
130
- return packets;
131
- }
132
- /**
133
- * 保留所有视频帧,删除等待区间内的完整音频包,并重新封装两条时间线。
134
- */
135
- async rewrite(input, original, signal, registerOutput) {
136
- const video = await input.getPrimaryVideoTrack();
137
- if (!video) {
138
- throw new Error("Recording has no video track");
139
- }
140
- const videoPackets = await this.readPackets(video, signal);
141
- const audio = await input.getPrimaryAudioTrack();
142
- const audioPackets = audio ? await this.readPackets(audio, signal) : [];
143
- const duration = await input.computeDuration();
144
- const audioRanges = audioPackets.map((packet, index) => ({
145
- start: packet.timestamp,
146
- end: packet.duration > 0 ? packet.timestamp + packet.duration : _nullishCoalesce(_optionalChain([audioPackets, 'access', _2 => _2[index + 1], 'optionalAccess', _3 => _3.timestamp]), () => ( duration))
147
- }));
148
- const timeline = new RecordingTimeline(videoPackets.map((packet) => packet.timestamp), duration, audio ? audioRanges : void 0);
149
- if (timeline.cuts.length === 0) {
150
- return original;
151
- }
152
- const videoCodec = await video.getCodec();
153
- const videoConfig = await video.getDecoderConfig();
154
- const audioCodec = await _optionalChain([audio, 'optionalAccess', _4 => _4.getCodec, 'call', _5 => _5()]);
155
- const audioConfig = await _optionalChain([audio, 'optionalAccess', _6 => _6.getDecoderConfig, 'call', _7 => _7()]);
156
- if (!videoCodec || !videoConfig || audio && (!audioCodec || !audioConfig)) {
157
- throw new Error("Unsupported recording codec");
158
- }
159
- const isWebM = (await input.getFormat()).name === _mediabunny.WEBM.name;
160
- const rotation = await video.getRotation();
161
- const flip = await video.getFlip();
162
- signal.throwIfAborted();
163
- const output = new (0, _mediabunny.Output)({
164
- format: isWebM ? new (0, _mediabunny.WebMOutputFormat)() : new (0, _mediabunny.Mp4OutputFormat)(),
165
- target: new (0, _mediabunny.BufferTarget)()
166
- });
167
- registerOutput(output);
168
- const videoSource = new (0, _mediabunny.EncodedVideoPacketSource)(videoCodec);
169
- output.addVideoTrack(videoSource, { rotation, flip });
170
- const audioSource = audioCodec ? new (0, _mediabunny.EncodedAudioPacketSource)(audioCodec) : void 0;
171
- if (audioSource) {
172
- output.addAudioTrack(audioSource);
173
- }
174
- await output.start();
175
- for (const packet of videoPackets) {
176
- signal.throwIfAborted();
177
- const timestamp = timeline.map(packet.timestamp);
178
- const duration2 = timeline.map(packet.timestamp + packet.duration) - timestamp;
179
- await videoSource.add(packet.clone({ timestamp, duration: duration2 }), { decoderConfig: videoConfig });
180
- }
181
- videoSource.close();
182
- if (audioSource && audioConfig) {
183
- for (const [index, packet] of audioPackets.entries()) {
184
- signal.throwIfAborted();
185
- const range = audioRanges[index];
186
- if (timeline.removes(range.start, range.end)) {
187
- continue;
188
- }
189
- await audioSource.add(packet.clone({ timestamp: timeline.map(packet.timestamp) }), { decoderConfig: audioConfig });
190
- }
191
- audioSource.close();
192
- }
193
- signal.throwIfAborted();
194
- await output.finalize();
195
- if (!output.target.buffer || output.target.buffer.byteLength > _RecordingProcessor.maximumBytes) {
196
- throw new Error("Processed recording exceeds the memory limit");
197
- }
198
- return new Blob([output.target.buffer], { type: output.format.mimeType });
199
- }
200
- };
201
- /**
202
- * 后处理资源上限,与实时连接的清理超时分离
203
- */
204
- _chunk4DE2IREAcjs.__publicField.call(void 0, _RecordingProcessor, "timeoutMs", 1e4);
205
- _chunk4DE2IREAcjs.__publicField.call(void 0, _RecordingProcessor, "maximumBytes", 64 * 1024 * 1024);
206
- _chunk4DE2IREAcjs.__publicField.call(void 0, _RecordingProcessor, "maximumPackets", 1e4);
207
- var RecordingProcessor = _RecordingProcessor;
208
-
209
-
210
- exports.RecordingProcessor = RecordingProcessor;
211
- //# sourceMappingURL=RecordingProcessor-4TP5NICO.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/private/var/folders/5j/p3g3j18x2w3byrl5wtnk94tc0000gn/T/xmax-web-release-ox16QE/source/packages/xmax-sdk/dist/RecordingProcessor-4TP5NICO.cjs","../src/Media/Recording/RecordingProcessor.ts","../src/Media/Recording/RecordingTimeline.ts"],"names":["duration"],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACA;ACJA;AACE;AAAY;AAAc;AAA0B;AACpD;AAA0B;AAAO;AAAK;AAAM;AAAiB;AAAQ;AAAA,wCAEhE;ADcP;AACA;AERO,IAAM,mBAAA,EAAN,MAAM,mBAAkB;AAAA;AAAA;AAAA;AAAA,EAiB7B,WAAA,CAAY,UAAA,EAAwC,QAAA,EAAkB,YAAA,EAA8C;AAAhE,IAAA,6CAAA,IAAA,EAAA,UAAA,EAAA,QAAA,CAAA;AANpD;AAAA;AAAA;AAAA,IAAA,6CAAA,IAAA,EAAS,eAAA,CAAA;AACT,IAAA,6CAAA,IAAA,EAAS,MAAA,CAAA;AAMP,IAAA,GAAA,CAAI,CAAC,MAAA,CAAO,QAAA,CAAS,QAAQ,EAAA,GAAK,SAAA,GAAY,EAAA,GAAK,UAAA,CAAW,OAAA,IAAW,EAAA,GACrE,UAAA,CAAW,IAAA,CAAK,CAAA,KAAA,EAAA,GAAS,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,EAAA,GAAK,MAAA,EAAQ,EAAA,GAAK,MAAA,EAAQ,QAAQ,CAAA,EAAG;AACtF,MAAA,MAAM,IAAI,KAAA,CAAM,4BAA4B,CAAA;AAAA,IAC9C;AAEA,IAAA,MAAM,OAAA,EAAS,CAAC,GAAG,UAAU,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,EAAA,GAAM,EAAA,EAAI,CAAC,CAAA;AACnD,IAAA,MAAM,UAAA,EAAY,MAAA,CAAO,KAAA,CAAM,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,IAAA,EAAM,KAAA,EAAA,GAAU,KAAA,EAAO,MAAA,CAAO,KAAK,CAAE,CAAA,CACzE,MAAA,CAAO,CAAA,KAAA,EAAA,GAAS,MAAA,EAAQ,CAAC,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,EAAA,GAAM,EAAA,EAAI,CAAC,CAAA;AAClD,IAAA,IAAA,CAAK,cAAA,mBAAgB,SAAA,CAAU,IAAA,CAAK,KAAA,CAAM,SAAA,CAAU,OAAA,EAAS,CAAC,CAAC,CAAA,UAAK,kBAAA,CAAkB,uBAAA;AACtF,IAAA,MAAM,UAAA,EAAY,IAAA,CAAK,GAAA,CAAI,kBAAA,CAAkB,UAAA,EAAY,IAAA,CAAK,cAAA,EAAgB,kBAAA,CAAkB,aAAa,CAAA;AAE7G,IAAA,MAAM,KAAA,EAAO,MAAA,CAAO,OAAA,CAAQ,CAAC,KAAA,EAAO,KAAA,EAAA,GAAU;AAC5C,MAAA,MAAM,IAAA,mBAAM,MAAA,CAAO,MAAA,EAAQ,CAAC,CAAA,UAAK,UAAA;AACjC,MAAA,OAAO,IAAA,EAAM,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,CAAC,EAAE,KAAA,EAAO,MAAA,EAAQ,IAAA,CAAK,aAAA,EAAe,IAAI,CAAC,EAAA,EAAI,CAAC,CAAA;AAAA,IAC1F,CAAC,CAAA;AAGD,IAAA,IAAA,CAAK,KAAA,EAAO,aAAA,EAAe,IAAA,CAAK,OAAA,CAAQ,CAAA,GAAA,EAAA,GAAO;AAC7C,MAAA,MAAM,UAAA,EAAY,YAAA,CAAa,MAAA,CAAO,CAAA,MAAA,EAAA,GAAU,MAAA,CAAO,IAAA,EAAM,MAAA,CAAO,MAAA,GAClE,MAAA,CAAO,MAAA,GAAS,GAAA,CAAI,MAAA,GAAS,MAAA,CAAO,IAAA,GAAO,GAAA,CAAI,GAAG,CAAA;AACpD,MAAA,GAAA,CAAI,SAAA,CAAU,OAAA,IAAW,CAAA,EAAG;AAC1B,QAAA,OAAO,CAAC,CAAA;AAAA,MACV;AACA,MAAA,OAAO,CAAC;AAAA,QACN,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,GAAG,SAAA,CAAU,GAAA,CAAI,CAAA,MAAA,EAAA,GAAU,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,QACxD,GAAA,EAAK,IAAA,CAAK,GAAA,CAAI,GAAG,SAAA,CAAU,GAAA,CAAI,CAAA,MAAA,EAAA,GAAU,MAAA,CAAO,GAAG,CAAC;AAAA,MACtD,CAAC,CAAA;AAAA,IACH,CAAC,EAAA,EAAI,IAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,GAAA,CAAI,IAAA,EAAsB;AACxB,IAAA,IAAI,QAAA,EAAU,CAAA;AACd,IAAA,IAAA,CAAA,MAAW,IAAA,GAAO,IAAA,CAAK,IAAA,EAAM;AAC3B,MAAA,GAAA,CAAI,KAAA,GAAQ,GAAA,CAAI,KAAA,EAAO;AACrB,QAAA,KAAA;AAAA,MACF;AACA,MAAA,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,IAAA,EAAM,GAAA,CAAI,GAAG,EAAA,EAAI,GAAA,CAAI,KAAA;AAAA,IAC3C;AACA,IAAA,OAAO,KAAA,EAAO,OAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAA,CAAQ,KAAA,EAAe,GAAA,EAAsB;AAC3C,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,CAAA,GAAA,EAAA,GAAO,MAAA,GAAS,GAAA,CAAI,MAAA,GAAS,IAAA,GAAO,GAAA,CAAI,GAAG,CAAA;AAAA,EACnE;AACF,CAAA;AAAA;AAAA;AAAA;AAhEE,6CAAA,kBAJW,EAIa,YAAA,EAAa,IAAA,CAAA;AACrC,6CAAA,kBALW,EAKa,eAAA,EAAgB,CAAA,CAAA;AACxC,6CAAA,kBANW,EAMa,uBAAA,EAAwB,EAAA,EAAI,EAAA,CAAA;AAN/C,IAAM,kBAAA,EAAN,kBAAA;AFqEP;AACA;ACvEO,IAAM,oBAAA,EAAN,MAAM,oBAAmB;AAAA;AAAA;AAAA;AAAA,EAW9B,MAAM,OAAA,CAAQ,IAAA,EAA2B;AACvC,IAAA,GAAA,CAAI,IAAA,CAAK,KAAA,EAAO,mBAAA,CAAmB,YAAA,EAAc;AAC/C,MAAA,MAAM,IAAI,KAAA,CAAM,mCAAmC,CAAA;AAAA,IACrD;AAEA,IAAA,MAAM,MAAA,EAAQ,IAAI,sBAAA,CAAM,EAAE,MAAA,EAAQ,IAAI,2BAAA,CAAW,IAAI,CAAA,EAAG,OAAA,EAAS,CAAC,eAAA,EAAK,gBAAI,EAAE,CAAC,CAAA;AAC9E,IAAA,MAAM,WAAA,EAAa,IAAI,eAAA,CAAgB,CAAA;AACvC,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI,KAAA;AACJ,IAAA,MAAM,QAAA,EAAU,IAAI,OAAA,CAAe,CAAC,CAAA,EAAG,MAAA,EAAA,GAAW;AAChD,MAAA,MAAA,EAAQ,UAAA,CAAW,CAAA,EAAA,GAAM;AACvB,QAAA,UAAA,CAAW,KAAA,CAAM,CAAA;AACjB,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,gCAAgC,CAAC,CAAA;AAAA,MACpD,CAAA,EAAG,mBAAA,CAAmB,SAAS,CAAA;AAAA,IACjC,CAAC,CAAA;AAED,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK;AAAA,QACxB,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,IAAA,EAAM,UAAA,CAAW,MAAA,EAAQ,CAAA,KAAA,EAAA,GAAS;AAAE,UAAA,OAAA,EAAS,KAAA;AAAA,QAAO,CAAC,CAAA;AAAA,QACzE;AAAA,MACF,CAAC,CAAA;AAAA,IACH,EAAA,QAAE;AACA,MAAA,UAAA,CAAW,KAAA,CAAM,CAAA;AACjB,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,KAAA,CAAM,OAAA,CAAQ,CAAA;AACd,MAAA,GAAA,CAAI,OAAA,GAAU,MAAA,CAAO,MAAA,IAAU,YAAA,GAAe,MAAA,CAAO,MAAA,IAAU,UAAA,EAAY;AACzE,QAAA,KAAK,MAAA,CAAO,MAAA,CAAO,CAAA,CAAE,KAAA,CAAM,CAAA,EAAA,GAAM;AAAA,QAAC,CAAC,CAAA;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,WAAA,CAAY,KAAA,EAAmB,MAAA,EAA+C;AAC1F,IAAA,MAAM,QAAA,EAA2B,CAAC,CAAA;AAClC,IAAA,IAAA,MAAA,CAAA,MAAiB,OAAA,GAAU,IAAI,kCAAA,CAAkB,KAAK,CAAA,CAAE,OAAA,CAAQ,CAAA,EAAG;AACjE,MAAA,MAAA,CAAO,cAAA,CAAe,CAAA;AACtB,MAAA,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA;AACnB,MAAA,GAAA,CAAI,OAAA,CAAQ,OAAA,EAAS,mBAAA,CAAmB,cAAA,EAAgB;AACtD,QAAA,MAAM,IAAI,KAAA,CAAM,sCAAsC,CAAA;AAAA,MACxD;AAAA,IACF;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,OAAA,CACZ,KAAA,EACA,QAAA,EACA,MAAA,EACA,cAAA,EACe;AACf,IAAA,MAAM,MAAA,EAAQ,MAAM,KAAA,CAAM,oBAAA,CAAqB,CAAA;AAC/C,IAAA,GAAA,CAAI,CAAC,KAAA,EAAO;AACV,MAAA,MAAM,IAAI,KAAA,CAAM,8BAA8B,CAAA;AAAA,IAChD;AACA,IAAA,MAAM,aAAA,EAAe,MAAM,IAAA,CAAK,WAAA,CAAY,KAAA,EAAO,MAAM,CAAA;AACzD,IAAA,MAAM,MAAA,EAAQ,MAAM,KAAA,CAAM,oBAAA,CAAqB,CAAA;AAC/C,IAAA,MAAM,aAAA,EAAe,MAAA,EAAQ,MAAM,IAAA,CAAK,WAAA,CAAY,KAAA,EAAO,MAAM,EAAA,EAAI,CAAC,CAAA;AACtE,IAAA,MAAM,SAAA,EAAW,MAAM,KAAA,CAAM,eAAA,CAAgB,CAAA;AAC7C,IAAA,MAAM,YAAA,EAAc,YAAA,CAAa,GAAA,CAAI,CAAC,MAAA,EAAQ,KAAA,EAAA,GAAA,CAAW;AAAA,MACvD,KAAA,EAAO,MAAA,CAAO,SAAA;AAAA,MACd,GAAA,EAAK,MAAA,CAAO,SAAA,EAAW,EAAA,EAAI,MAAA,CAAO,UAAA,EAAY,MAAA,CAAO,SAAA,mCAAW,YAAA,qBAAa,MAAA,EAAQ,CAAC,CAAA,6BAAG,WAAA,UAAa;AAAA,IACxG,CAAA,CAAE,CAAA;AACF,IAAA,MAAM,SAAA,EAAW,IAAI,iBAAA,CAAkB,YAAA,CAAa,GAAA,CAAI,CAAA,MAAA,EAAA,GAAU,MAAA,CAAO,SAAS,CAAA,EAAG,QAAA,EAAU,MAAA,EAAQ,YAAA,EAAc,KAAA,CAAS,CAAA;AAC9H,IAAA,GAAA,CAAI,QAAA,CAAS,IAAA,CAAK,OAAA,IAAW,CAAA,EAAG;AAC9B,MAAA,OAAO,QAAA;AAAA,IACT;AAEA,IAAA,MAAM,WAAA,EAAa,MAAM,KAAA,CAAM,QAAA,CAAS,CAAA;AACxC,IAAA,MAAM,YAAA,EAAc,MAAM,KAAA,CAAM,gBAAA,CAAiB,CAAA;AACjD,IAAA,MAAM,WAAA,EAAa,sBAAM,KAAA,6BAAO,QAAA,mBAAS,GAAA;AACzC,IAAA,MAAM,YAAA,EAAc,sBAAM,KAAA,6BAAO,gBAAA,mBAAiB,GAAA;AAClD,IAAA,GAAA,CAAI,CAAC,WAAA,GAAc,CAAC,YAAA,GAAgB,MAAA,GAAA,CAAU,CAAC,WAAA,GAAc,CAAC,WAAA,CAAA,EAAe;AAC3E,MAAA,MAAM,IAAI,KAAA,CAAM,6BAA6B,CAAA;AAAA,IAC/C;AACA,IAAA,MAAM,OAAA,EAAA,CAAU,MAAM,KAAA,CAAM,SAAA,CAAU,CAAA,CAAA,CAAG,KAAA,IAAS,gBAAA,CAAK,IAAA;AACvD,IAAA,MAAM,SAAA,EAAW,MAAM,KAAA,CAAM,WAAA,CAAY,CAAA;AACzC,IAAA,MAAM,KAAA,EAAO,MAAM,KAAA,CAAM,OAAA,CAAQ,CAAA;AACjC,IAAA,MAAA,CAAO,cAAA,CAAe,CAAA;AAEtB,IAAA,MAAM,OAAA,EAAS,IAAI,uBAAA,CAAO;AAAA,MACxB,MAAA,EAAQ,OAAA,EAAS,IAAI,iCAAA,CAAiB,EAAA,EAAI,IAAI,gCAAA,CAAgB,CAAA;AAAA,MAC9D,MAAA,EAAQ,IAAI,6BAAA,CAAa;AAAA,IAC3B,CAAC,CAAA;AACD,IAAA,cAAA,CAAe,MAAM,CAAA;AACrB,IAAA,MAAM,YAAA,EAAc,IAAI,yCAAA,CAAyB,UAAU,CAAA;AAC3D,IAAA,MAAA,CAAO,aAAA,CAAc,WAAA,EAAa,EAAE,QAAA,EAAU,KAAK,CAAC,CAAA;AACpD,IAAA,MAAM,YAAA,EAAc,WAAA,EAAa,IAAI,yCAAA,CAAyB,UAAU,EAAA,EAAI,KAAA,CAAA;AAC5E,IAAA,GAAA,CAAI,WAAA,EAAa;AACf,MAAA,MAAA,CAAO,aAAA,CAAc,WAAW,CAAA;AAAA,IAClC;AACA,IAAA,MAAM,MAAA,CAAO,KAAA,CAAM,CAAA;AAGnB,IAAA,IAAA,CAAA,MAAW,OAAA,GAAU,YAAA,EAAc;AACjC,MAAA,MAAA,CAAO,cAAA,CAAe,CAAA;AACtB,MAAA,MAAM,UAAA,EAAY,QAAA,CAAS,GAAA,CAAI,MAAA,CAAO,SAAS,CAAA;AAC/C,MAAA,MAAMA,UAAAA,EAAW,QAAA,CAAS,GAAA,CAAI,MAAA,CAAO,UAAA,EAAY,MAAA,CAAO,QAAQ,EAAA,EAAI,SAAA;AACpE,MAAA,MAAM,WAAA,CAAY,GAAA,CAAI,MAAA,CAAO,KAAA,CAAM,EAAE,SAAA,EAAW,QAAA,EAAAA,UAAS,CAAC,CAAA,EAAG,EAAE,aAAA,EAAe,YAAY,CAAC,CAAA;AAAA,IAC7F;AACA,IAAA,WAAA,CAAY,KAAA,CAAM,CAAA;AAElB,IAAA,GAAA,CAAI,YAAA,GAAe,WAAA,EAAa;AAC9B,MAAA,IAAA,CAAA,MAAW,CAAC,KAAA,EAAO,MAAM,EAAA,GAAK,YAAA,CAAa,OAAA,CAAQ,CAAA,EAAG;AACpD,QAAA,MAAA,CAAO,cAAA,CAAe,CAAA;AACtB,QAAA,MAAM,MAAA,EAAQ,WAAA,CAAY,KAAK,CAAA;AAC/B,QAAA,GAAA,CAAI,QAAA,CAAS,OAAA,CAAQ,KAAA,CAAM,KAAA,EAAO,KAAA,CAAM,GAAG,CAAA,EAAG;AAC5C,UAAA,QAAA;AAAA,QACF;AACA,QAAA,MAAM,WAAA,CAAY,GAAA,CAAI,MAAA,CAAO,KAAA,CAAM,EAAE,SAAA,EAAW,QAAA,CAAS,GAAA,CAAI,MAAA,CAAO,SAAS,EAAE,CAAC,CAAA,EAAG,EAAE,aAAA,EAAe,YAAY,CAAC,CAAA;AAAA,MACnH;AACA,MAAA,WAAA,CAAY,KAAA,CAAM,CAAA;AAAA,IACpB;AACA,IAAA,MAAA,CAAO,cAAA,CAAe,CAAA;AACtB,IAAA,MAAM,MAAA,CAAO,QAAA,CAAS,CAAA;AACtB,IAAA,GAAA,CAAI,CAAC,MAAA,CAAO,MAAA,CAAO,OAAA,GAAU,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,WAAA,EAAa,mBAAA,CAAmB,YAAA,EAAc;AAC9F,MAAA,MAAM,IAAI,KAAA,CAAM,8CAA8C,CAAA;AAAA,IAChE;AACA,IAAA,OAAO,IAAI,IAAA,CAAK,CAAC,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA,EAAG,EAAE,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,SAAS,CAAC,CAAA;AAAA,EAC1E;AACF,CAAA;AAAA;AAAA;AAAA;AAnIE,6CAAA,mBAJW,EAIa,WAAA,EAAY,GAAA,CAAA;AACpC,6CAAA,mBALW,EAKa,cAAA,EAAe,GAAA,EAAK,KAAA,EAAO,IAAA,CAAA;AACnD,6CAAA,mBANW,EAMa,gBAAA,EAAiB,GAAA,CAAA;AANpC,IAAM,mBAAA,EAAN,mBAAA;ADqMP;AACE;AACF,gDAAC","file":"/private/var/folders/5j/p3g3j18x2w3byrl5wtnk94tc0000gn/T/xmax-web-release-ox16QE/source/packages/xmax-sdk/dist/RecordingProcessor-4TP5NICO.cjs","sourcesContent":[null,"import {\n BlobSource, BufferTarget, EncodedAudioPacketSource, EncodedPacketSink,\n EncodedVideoPacketSource, Input, MP4, WEBM, Mp4OutputFormat, Output, WebMOutputFormat,\n type EncodedPacket, type InputTrack,\n} from \"mediabunny\";\nimport { RecordingTimeline } from \"./RecordingTimeline\";\n\n/**\n * 录制完成后裁掉长帧等待;音视频共用按音频包边界对齐的时间映射,不重新编码。\n */\nexport class RecordingProcessor {\n /**\n * 后处理资源上限,与实时连接的清理超时分离\n */\n private static readonly timeoutMs = 10_000;\n private static readonly maximumBytes = 64 * 1024 * 1024;\n private static readonly maximumPackets = 10_000;\n\n /**\n * 裁剪成功返回新文件,无需裁剪时返回原文件;失败交由调用方保留原始录像。\n */\n async process(blob: Blob): Promise<Blob> {\n if (blob.size > RecordingProcessor.maximumBytes) {\n throw new Error(\"Recording is too large to process\");\n }\n\n const input = new Input({ source: new BlobSource(blob), formats: [MP4, WEBM] });\n const controller = new AbortController();\n let output: Output | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n reject(new Error(\"Recording processing timed out\"));\n }, RecordingProcessor.timeoutMs);\n });\n\n try {\n return await Promise.race([\n this.rewrite(input, blob, controller.signal, value => { output = value; }),\n timeout,\n ]);\n } finally {\n controller.abort();\n clearTimeout(timer);\n input.dispose();\n if (output && output.state !== \"finalized\" && output.state !== \"canceled\") {\n void output.cancel().catch(() => {});\n }\n }\n }\n\n /**\n * 读取有限数量的编码包,维持原始解码顺序。\n */\n private async readPackets(track: InputTrack, signal: AbortSignal): Promise<EncodedPacket[]> {\n const packets: EncodedPacket[] = [];\n for await (const packet of new EncodedPacketSink(track).packets()) {\n signal.throwIfAborted();\n packets.push(packet);\n if (packets.length > RecordingProcessor.maximumPackets) {\n throw new Error(\"Recording has too many media packets\");\n }\n }\n return packets;\n }\n\n /**\n * 保留所有视频帧,删除等待区间内的完整音频包,并重新封装两条时间线。\n */\n private async rewrite(\n input: Input,\n original: Blob,\n signal: AbortSignal,\n registerOutput: (output: Output) => void,\n ): Promise<Blob> {\n const video = await input.getPrimaryVideoTrack();\n if (!video) {\n throw new Error(\"Recording has no video track\");\n }\n const videoPackets = await this.readPackets(video, signal);\n const audio = await input.getPrimaryAudioTrack();\n const audioPackets = audio ? await this.readPackets(audio, signal) : [];\n const duration = await input.computeDuration();\n const audioRanges = audioPackets.map((packet, index) => ({\n start: packet.timestamp,\n end: packet.duration > 0 ? packet.timestamp + packet.duration : audioPackets[index + 1]?.timestamp ?? duration,\n }));\n const timeline = new RecordingTimeline(videoPackets.map(packet => packet.timestamp), duration, audio ? audioRanges : undefined);\n if (timeline.cuts.length === 0) {\n return original;\n }\n\n const videoCodec = await video.getCodec();\n const videoConfig = await video.getDecoderConfig();\n const audioCodec = await audio?.getCodec();\n const audioConfig = await audio?.getDecoderConfig();\n if (!videoCodec || !videoConfig || (audio && (!audioCodec || !audioConfig))) {\n throw new Error(\"Unsupported recording codec\");\n }\n const isWebM = (await input.getFormat()).name === WEBM.name;\n const rotation = await video.getRotation();\n const flip = await video.getFlip();\n signal.throwIfAborted();\n\n const output = new Output({\n format: isWebM ? new WebMOutputFormat() : new Mp4OutputFormat(),\n target: new BufferTarget(),\n });\n registerOutput(output);\n const videoSource = new EncodedVideoPacketSource(videoCodec);\n output.addVideoTrack(videoSource, { rotation, flip });\n const audioSource = audioCodec ? new EncodedAudioPacketSource(audioCodec) : undefined;\n if (audioSource) {\n output.addAudioTrack(audioSource);\n }\n await output.start();\n\n // 不指定固定帧率,避免容器把原本不同的显示时间戳量化到同一帧。\n for (const packet of videoPackets) {\n signal.throwIfAborted();\n const timestamp = timeline.map(packet.timestamp);\n const duration = timeline.map(packet.timestamp + packet.duration) - timestamp;\n await videoSource.add(packet.clone({ timestamp, duration }), { decoderConfig: videoConfig });\n }\n videoSource.close();\n\n if (audioSource && audioConfig) {\n for (const [index, packet] of audioPackets.entries()) {\n signal.throwIfAborted();\n const range = audioRanges[index]!;\n if (timeline.removes(range.start, range.end)) {\n continue;\n }\n await audioSource.add(packet.clone({ timestamp: timeline.map(packet.timestamp) }), { decoderConfig: audioConfig });\n }\n audioSource.close();\n }\n signal.throwIfAborted();\n await output.finalize();\n if (!output.target.buffer || output.target.buffer.byteLength > RecordingProcessor.maximumBytes) {\n throw new Error(\"Processed recording exceeds the memory limit\");\n }\n return new Blob([output.target.buffer], { type: output.format.mimeType });\n }\n}\n","/**\n * 原始录制时间线上的区间,单位为秒。\n */\nexport interface RecordingTimeRange {\n start: number;\n end: number;\n}\n\n/**\n * 视频长帧裁剪计划。音视频共用同一映射,避免分别累计时间造成漂移。\n */\nexport class RecordingTimeline {\n /**\n * 长间隔判定与默认帧时长\n */\n private static readonly minimumGap = 0.12;\n private static readonly gapMultiplier = 3;\n private static readonly fallbackFrameDuration = 1 / 30;\n\n /**\n * 原始时长、正常帧时长与待删除区间\n */\n readonly frameDuration: number;\n readonly cuts: readonly RecordingTimeRange[];\n\n /**\n * 从显示时间戳估计正常帧间隔,只裁掉显著超长的等待部分。\n */\n constructor(timestamps: readonly number[], readonly duration: number, audioPackets?: readonly RecordingTimeRange[]) {\n if (!Number.isFinite(duration) || duration <= 0 || timestamps.length === 0 ||\n timestamps.some(value => !Number.isFinite(value) || value < 0 || value > duration)) {\n throw new Error(\"Invalid recording timeline\");\n }\n\n const sorted = [...timestamps].sort((a, b) => a - b);\n const intervals = sorted.slice(1).map((time, index) => time - sorted[index]!)\n .filter(value => value > 0).sort((a, b) => a - b);\n this.frameDuration = intervals[Math.floor(intervals.length / 2)] ?? RecordingTimeline.fallbackFrameDuration;\n const threshold = Math.max(RecordingTimeline.minimumGap, this.frameDuration * RecordingTimeline.gapMultiplier);\n\n const cuts = sorted.flatMap((start, index) => {\n const end = sorted[index + 1] ?? duration;\n return end - start > threshold + 1e-6 ? [{ start: start + this.frameDuration, end }] : [];\n });\n\n // 只删除完全落在等待区间内的音频包;同一裁剪边界同时用于视频,避免重编码延迟。\n this.cuts = audioPackets ? cuts.flatMap(cut => {\n const contained = audioPackets.filter(packet => packet.end > packet.start &&\n packet.start >= cut.start && packet.end <= cut.end);\n if (contained.length === 0) {\n return [];\n }\n return [{\n start: Math.min(...contained.map(packet => packet.start)),\n end: Math.max(...contained.map(packet => packet.end)),\n }];\n }) : cuts;\n }\n\n /**\n * 将原始时间映射到裁剪后的时间;删除区间内的时刻映射到同一接缝。\n */\n map(time: number): number {\n let removed = 0;\n for (const cut of this.cuts) {\n if (time <= cut.start) {\n break;\n }\n removed += Math.min(time, cut.end) - cut.start;\n }\n return time - removed;\n }\n\n /**\n * 判断一个音频包是否位于删除区间;区间已对齐包边界,不会切断压缩数据。\n */\n removes(start: number, end: number): boolean {\n return this.cuts.some(cut => start >= cut.start && end <= cut.end);\n }\n}\n"]}
@@ -1,211 +0,0 @@
1
- import {
2
- __publicField
3
- } from "./chunk-V6TY7KAL.js";
4
-
5
- // src/Media/Recording/RecordingProcessor.ts
6
- import {
7
- BlobSource,
8
- BufferTarget,
9
- EncodedAudioPacketSource,
10
- EncodedPacketSink,
11
- EncodedVideoPacketSource,
12
- Input,
13
- MP4,
14
- WEBM,
15
- Mp4OutputFormat,
16
- Output,
17
- WebMOutputFormat
18
- } from "mediabunny";
19
-
20
- // src/Media/Recording/RecordingTimeline.ts
21
- var _RecordingTimeline = class _RecordingTimeline {
22
- /**
23
- * 从显示时间戳估计正常帧间隔,只裁掉显著超长的等待部分。
24
- */
25
- constructor(timestamps, duration, audioPackets) {
26
- __publicField(this, "duration", duration);
27
- /**
28
- * 原始时长、正常帧时长与待删除区间
29
- */
30
- __publicField(this, "frameDuration");
31
- __publicField(this, "cuts");
32
- if (!Number.isFinite(duration) || duration <= 0 || timestamps.length === 0 || timestamps.some((value) => !Number.isFinite(value) || value < 0 || value > duration)) {
33
- throw new Error("Invalid recording timeline");
34
- }
35
- const sorted = [...timestamps].sort((a, b) => a - b);
36
- const intervals = sorted.slice(1).map((time, index) => time - sorted[index]).filter((value) => value > 0).sort((a, b) => a - b);
37
- this.frameDuration = intervals[Math.floor(intervals.length / 2)] ?? _RecordingTimeline.fallbackFrameDuration;
38
- const threshold = Math.max(_RecordingTimeline.minimumGap, this.frameDuration * _RecordingTimeline.gapMultiplier);
39
- const cuts = sorted.flatMap((start, index) => {
40
- const end = sorted[index + 1] ?? duration;
41
- return end - start > threshold + 1e-6 ? [{ start: start + this.frameDuration, end }] : [];
42
- });
43
- this.cuts = audioPackets ? cuts.flatMap((cut) => {
44
- const contained = audioPackets.filter((packet) => packet.end > packet.start && packet.start >= cut.start && packet.end <= cut.end);
45
- if (contained.length === 0) {
46
- return [];
47
- }
48
- return [{
49
- start: Math.min(...contained.map((packet) => packet.start)),
50
- end: Math.max(...contained.map((packet) => packet.end))
51
- }];
52
- }) : cuts;
53
- }
54
- /**
55
- * 将原始时间映射到裁剪后的时间;删除区间内的时刻映射到同一接缝。
56
- */
57
- map(time) {
58
- let removed = 0;
59
- for (const cut of this.cuts) {
60
- if (time <= cut.start) {
61
- break;
62
- }
63
- removed += Math.min(time, cut.end) - cut.start;
64
- }
65
- return time - removed;
66
- }
67
- /**
68
- * 判断一个音频包是否位于删除区间;区间已对齐包边界,不会切断压缩数据。
69
- */
70
- removes(start, end) {
71
- return this.cuts.some((cut) => start >= cut.start && end <= cut.end);
72
- }
73
- };
74
- /**
75
- * 长间隔判定与默认帧时长
76
- */
77
- __publicField(_RecordingTimeline, "minimumGap", 0.12);
78
- __publicField(_RecordingTimeline, "gapMultiplier", 3);
79
- __publicField(_RecordingTimeline, "fallbackFrameDuration", 1 / 30);
80
- var RecordingTimeline = _RecordingTimeline;
81
-
82
- // src/Media/Recording/RecordingProcessor.ts
83
- var _RecordingProcessor = class _RecordingProcessor {
84
- /**
85
- * 裁剪成功返回新文件,无需裁剪时返回原文件;失败交由调用方保留原始录像。
86
- */
87
- async process(blob) {
88
- if (blob.size > _RecordingProcessor.maximumBytes) {
89
- throw new Error("Recording is too large to process");
90
- }
91
- const input = new Input({ source: new BlobSource(blob), formats: [MP4, WEBM] });
92
- const controller = new AbortController();
93
- let output;
94
- let timer;
95
- const timeout = new Promise((_, reject) => {
96
- timer = setTimeout(() => {
97
- controller.abort();
98
- reject(new Error("Recording processing timed out"));
99
- }, _RecordingProcessor.timeoutMs);
100
- });
101
- try {
102
- return await Promise.race([
103
- this.rewrite(input, blob, controller.signal, (value) => {
104
- output = value;
105
- }),
106
- timeout
107
- ]);
108
- } finally {
109
- controller.abort();
110
- clearTimeout(timer);
111
- input.dispose();
112
- if (output && output.state !== "finalized" && output.state !== "canceled") {
113
- void output.cancel().catch(() => {
114
- });
115
- }
116
- }
117
- }
118
- /**
119
- * 读取有限数量的编码包,维持原始解码顺序。
120
- */
121
- async readPackets(track, signal) {
122
- const packets = [];
123
- for await (const packet of new EncodedPacketSink(track).packets()) {
124
- signal.throwIfAborted();
125
- packets.push(packet);
126
- if (packets.length > _RecordingProcessor.maximumPackets) {
127
- throw new Error("Recording has too many media packets");
128
- }
129
- }
130
- return packets;
131
- }
132
- /**
133
- * 保留所有视频帧,删除等待区间内的完整音频包,并重新封装两条时间线。
134
- */
135
- async rewrite(input, original, signal, registerOutput) {
136
- const video = await input.getPrimaryVideoTrack();
137
- if (!video) {
138
- throw new Error("Recording has no video track");
139
- }
140
- const videoPackets = await this.readPackets(video, signal);
141
- const audio = await input.getPrimaryAudioTrack();
142
- const audioPackets = audio ? await this.readPackets(audio, signal) : [];
143
- const duration = await input.computeDuration();
144
- const audioRanges = audioPackets.map((packet, index) => ({
145
- start: packet.timestamp,
146
- end: packet.duration > 0 ? packet.timestamp + packet.duration : audioPackets[index + 1]?.timestamp ?? duration
147
- }));
148
- const timeline = new RecordingTimeline(videoPackets.map((packet) => packet.timestamp), duration, audio ? audioRanges : void 0);
149
- if (timeline.cuts.length === 0) {
150
- return original;
151
- }
152
- const videoCodec = await video.getCodec();
153
- const videoConfig = await video.getDecoderConfig();
154
- const audioCodec = await audio?.getCodec();
155
- const audioConfig = await audio?.getDecoderConfig();
156
- if (!videoCodec || !videoConfig || audio && (!audioCodec || !audioConfig)) {
157
- throw new Error("Unsupported recording codec");
158
- }
159
- const isWebM = (await input.getFormat()).name === WEBM.name;
160
- const rotation = await video.getRotation();
161
- const flip = await video.getFlip();
162
- signal.throwIfAborted();
163
- const output = new Output({
164
- format: isWebM ? new WebMOutputFormat() : new Mp4OutputFormat(),
165
- target: new BufferTarget()
166
- });
167
- registerOutput(output);
168
- const videoSource = new EncodedVideoPacketSource(videoCodec);
169
- output.addVideoTrack(videoSource, { rotation, flip });
170
- const audioSource = audioCodec ? new EncodedAudioPacketSource(audioCodec) : void 0;
171
- if (audioSource) {
172
- output.addAudioTrack(audioSource);
173
- }
174
- await output.start();
175
- for (const packet of videoPackets) {
176
- signal.throwIfAborted();
177
- const timestamp = timeline.map(packet.timestamp);
178
- const duration2 = timeline.map(packet.timestamp + packet.duration) - timestamp;
179
- await videoSource.add(packet.clone({ timestamp, duration: duration2 }), { decoderConfig: videoConfig });
180
- }
181
- videoSource.close();
182
- if (audioSource && audioConfig) {
183
- for (const [index, packet] of audioPackets.entries()) {
184
- signal.throwIfAborted();
185
- const range = audioRanges[index];
186
- if (timeline.removes(range.start, range.end)) {
187
- continue;
188
- }
189
- await audioSource.add(packet.clone({ timestamp: timeline.map(packet.timestamp) }), { decoderConfig: audioConfig });
190
- }
191
- audioSource.close();
192
- }
193
- signal.throwIfAborted();
194
- await output.finalize();
195
- if (!output.target.buffer || output.target.buffer.byteLength > _RecordingProcessor.maximumBytes) {
196
- throw new Error("Processed recording exceeds the memory limit");
197
- }
198
- return new Blob([output.target.buffer], { type: output.format.mimeType });
199
- }
200
- };
201
- /**
202
- * 后处理资源上限,与实时连接的清理超时分离
203
- */
204
- __publicField(_RecordingProcessor, "timeoutMs", 1e4);
205
- __publicField(_RecordingProcessor, "maximumBytes", 64 * 1024 * 1024);
206
- __publicField(_RecordingProcessor, "maximumPackets", 1e4);
207
- var RecordingProcessor = _RecordingProcessor;
208
- export {
209
- RecordingProcessor
210
- };
211
- //# sourceMappingURL=RecordingProcessor-ZSBJFF4J.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/Media/Recording/RecordingProcessor.ts","../src/Media/Recording/RecordingTimeline.ts"],"sourcesContent":["import {\n BlobSource, BufferTarget, EncodedAudioPacketSource, EncodedPacketSink,\n EncodedVideoPacketSource, Input, MP4, WEBM, Mp4OutputFormat, Output, WebMOutputFormat,\n type EncodedPacket, type InputTrack,\n} from \"mediabunny\";\nimport { RecordingTimeline } from \"./RecordingTimeline\";\n\n/**\n * 录制完成后裁掉长帧等待;音视频共用按音频包边界对齐的时间映射,不重新编码。\n */\nexport class RecordingProcessor {\n /**\n * 后处理资源上限,与实时连接的清理超时分离\n */\n private static readonly timeoutMs = 10_000;\n private static readonly maximumBytes = 64 * 1024 * 1024;\n private static readonly maximumPackets = 10_000;\n\n /**\n * 裁剪成功返回新文件,无需裁剪时返回原文件;失败交由调用方保留原始录像。\n */\n async process(blob: Blob): Promise<Blob> {\n if (blob.size > RecordingProcessor.maximumBytes) {\n throw new Error(\"Recording is too large to process\");\n }\n\n const input = new Input({ source: new BlobSource(blob), formats: [MP4, WEBM] });\n const controller = new AbortController();\n let output: Output | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n reject(new Error(\"Recording processing timed out\"));\n }, RecordingProcessor.timeoutMs);\n });\n\n try {\n return await Promise.race([\n this.rewrite(input, blob, controller.signal, value => { output = value; }),\n timeout,\n ]);\n } finally {\n controller.abort();\n clearTimeout(timer);\n input.dispose();\n if (output && output.state !== \"finalized\" && output.state !== \"canceled\") {\n void output.cancel().catch(() => {});\n }\n }\n }\n\n /**\n * 读取有限数量的编码包,维持原始解码顺序。\n */\n private async readPackets(track: InputTrack, signal: AbortSignal): Promise<EncodedPacket[]> {\n const packets: EncodedPacket[] = [];\n for await (const packet of new EncodedPacketSink(track).packets()) {\n signal.throwIfAborted();\n packets.push(packet);\n if (packets.length > RecordingProcessor.maximumPackets) {\n throw new Error(\"Recording has too many media packets\");\n }\n }\n return packets;\n }\n\n /**\n * 保留所有视频帧,删除等待区间内的完整音频包,并重新封装两条时间线。\n */\n private async rewrite(\n input: Input,\n original: Blob,\n signal: AbortSignal,\n registerOutput: (output: Output) => void,\n ): Promise<Blob> {\n const video = await input.getPrimaryVideoTrack();\n if (!video) {\n throw new Error(\"Recording has no video track\");\n }\n const videoPackets = await this.readPackets(video, signal);\n const audio = await input.getPrimaryAudioTrack();\n const audioPackets = audio ? await this.readPackets(audio, signal) : [];\n const duration = await input.computeDuration();\n const audioRanges = audioPackets.map((packet, index) => ({\n start: packet.timestamp,\n end: packet.duration > 0 ? packet.timestamp + packet.duration : audioPackets[index + 1]?.timestamp ?? duration,\n }));\n const timeline = new RecordingTimeline(videoPackets.map(packet => packet.timestamp), duration, audio ? audioRanges : undefined);\n if (timeline.cuts.length === 0) {\n return original;\n }\n\n const videoCodec = await video.getCodec();\n const videoConfig = await video.getDecoderConfig();\n const audioCodec = await audio?.getCodec();\n const audioConfig = await audio?.getDecoderConfig();\n if (!videoCodec || !videoConfig || (audio && (!audioCodec || !audioConfig))) {\n throw new Error(\"Unsupported recording codec\");\n }\n const isWebM = (await input.getFormat()).name === WEBM.name;\n const rotation = await video.getRotation();\n const flip = await video.getFlip();\n signal.throwIfAborted();\n\n const output = new Output({\n format: isWebM ? new WebMOutputFormat() : new Mp4OutputFormat(),\n target: new BufferTarget(),\n });\n registerOutput(output);\n const videoSource = new EncodedVideoPacketSource(videoCodec);\n output.addVideoTrack(videoSource, { rotation, flip });\n const audioSource = audioCodec ? new EncodedAudioPacketSource(audioCodec) : undefined;\n if (audioSource) {\n output.addAudioTrack(audioSource);\n }\n await output.start();\n\n // 不指定固定帧率,避免容器把原本不同的显示时间戳量化到同一帧。\n for (const packet of videoPackets) {\n signal.throwIfAborted();\n const timestamp = timeline.map(packet.timestamp);\n const duration = timeline.map(packet.timestamp + packet.duration) - timestamp;\n await videoSource.add(packet.clone({ timestamp, duration }), { decoderConfig: videoConfig });\n }\n videoSource.close();\n\n if (audioSource && audioConfig) {\n for (const [index, packet] of audioPackets.entries()) {\n signal.throwIfAborted();\n const range = audioRanges[index]!;\n if (timeline.removes(range.start, range.end)) {\n continue;\n }\n await audioSource.add(packet.clone({ timestamp: timeline.map(packet.timestamp) }), { decoderConfig: audioConfig });\n }\n audioSource.close();\n }\n signal.throwIfAborted();\n await output.finalize();\n if (!output.target.buffer || output.target.buffer.byteLength > RecordingProcessor.maximumBytes) {\n throw new Error(\"Processed recording exceeds the memory limit\");\n }\n return new Blob([output.target.buffer], { type: output.format.mimeType });\n }\n}\n","/**\n * 原始录制时间线上的区间,单位为秒。\n */\nexport interface RecordingTimeRange {\n start: number;\n end: number;\n}\n\n/**\n * 视频长帧裁剪计划。音视频共用同一映射,避免分别累计时间造成漂移。\n */\nexport class RecordingTimeline {\n /**\n * 长间隔判定与默认帧时长\n */\n private static readonly minimumGap = 0.12;\n private static readonly gapMultiplier = 3;\n private static readonly fallbackFrameDuration = 1 / 30;\n\n /**\n * 原始时长、正常帧时长与待删除区间\n */\n readonly frameDuration: number;\n readonly cuts: readonly RecordingTimeRange[];\n\n /**\n * 从显示时间戳估计正常帧间隔,只裁掉显著超长的等待部分。\n */\n constructor(timestamps: readonly number[], readonly duration: number, audioPackets?: readonly RecordingTimeRange[]) {\n if (!Number.isFinite(duration) || duration <= 0 || timestamps.length === 0 ||\n timestamps.some(value => !Number.isFinite(value) || value < 0 || value > duration)) {\n throw new Error(\"Invalid recording timeline\");\n }\n\n const sorted = [...timestamps].sort((a, b) => a - b);\n const intervals = sorted.slice(1).map((time, index) => time - sorted[index]!)\n .filter(value => value > 0).sort((a, b) => a - b);\n this.frameDuration = intervals[Math.floor(intervals.length / 2)] ?? RecordingTimeline.fallbackFrameDuration;\n const threshold = Math.max(RecordingTimeline.minimumGap, this.frameDuration * RecordingTimeline.gapMultiplier);\n\n const cuts = sorted.flatMap((start, index) => {\n const end = sorted[index + 1] ?? duration;\n return end - start > threshold + 1e-6 ? [{ start: start + this.frameDuration, end }] : [];\n });\n\n // 只删除完全落在等待区间内的音频包;同一裁剪边界同时用于视频,避免重编码延迟。\n this.cuts = audioPackets ? cuts.flatMap(cut => {\n const contained = audioPackets.filter(packet => packet.end > packet.start &&\n packet.start >= cut.start && packet.end <= cut.end);\n if (contained.length === 0) {\n return [];\n }\n return [{\n start: Math.min(...contained.map(packet => packet.start)),\n end: Math.max(...contained.map(packet => packet.end)),\n }];\n }) : cuts;\n }\n\n /**\n * 将原始时间映射到裁剪后的时间;删除区间内的时刻映射到同一接缝。\n */\n map(time: number): number {\n let removed = 0;\n for (const cut of this.cuts) {\n if (time <= cut.start) {\n break;\n }\n removed += Math.min(time, cut.end) - cut.start;\n }\n return time - removed;\n }\n\n /**\n * 判断一个音频包是否位于删除区间;区间已对齐包边界,不会切断压缩数据。\n */\n removes(start: number, end: number): boolean {\n return this.cuts.some(cut => start >= cut.start && end <= cut.end);\n }\n}\n"],"mappings":"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","names":["duration"]}
@@ -1,8 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
-
5
-
6
-
7
- exports.__publicField = __publicField;
8
- //# sourceMappingURL=chunk-4DE2IREA.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/private/var/folders/5j/p3g3j18x2w3byrl5wtnk94tc0000gn/T/xmax-web-release-ox16QE/source/packages/xmax-sdk/dist/chunk-4DE2IREA.cjs"],"names":[],"mappings":"AAAA,6EAAI,UAAU,EAAE,MAAM,CAAC,cAAc;AACrC,IAAI,gBAAgB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK;AAC/J,IAAI,cAAc,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,eAAe,CAAC,GAAG,EAAE,OAAO,IAAI,IAAI,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC;AAC9G;AACA;AACE;AACF,sCAAC","file":"/private/var/folders/5j/p3g3j18x2w3byrl5wtnk94tc0000gn/T/xmax-web-release-ox16QE/source/packages/xmax-sdk/dist/chunk-4DE2IREA.cjs"}