@blockcast/mmt-render 0.1.0-main.c0e9e40265a1
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.
- package/README.md +207 -0
- package/dist/.build-stamp +0 -0
- package/dist/audio/audio-buffer-plan.d.ts +27 -0
- package/dist/audio/audio-buffer-plan.d.ts.map +1 -0
- package/dist/audio/audio-render-pipeline.d.ts +83 -0
- package/dist/audio/audio-render-pipeline.d.ts.map +1 -0
- package/dist/audio/audio-render-pipeline.js +372 -0
- package/dist/audio/audio-render-pipeline.js.map +7 -0
- package/dist/audio/audio-ring-buffer.d.ts +55 -0
- package/dist/audio/audio-ring-buffer.d.ts.map +1 -0
- package/dist/audio/audio-ring-buffer.js +255 -0
- package/dist/audio/audio-ring-buffer.js.map +7 -0
- package/dist/audio/index.d.ts +9 -0
- package/dist/audio/index.d.ts.map +1 -0
- package/dist/audio/index.js +640 -0
- package/dist/audio/index.js.map +7 -0
- package/dist/audio/render-messages.d.ts +94 -0
- package/dist/audio/render-messages.d.ts.map +1 -0
- package/dist/audio/render-messages.js +1 -0
- package/dist/audio/render-messages.js.map +7 -0
- package/dist/audio/render-worklet.d.ts +13 -0
- package/dist/audio/render-worklet.d.ts.map +1 -0
- package/dist/audio/worklet-url.d.ts +25 -0
- package/dist/audio/worklet-url.d.ts.map +1 -0
- package/dist/audio/worklet-url.js +11 -0
- package/dist/audio/worklet-url.js.map +7 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +7 -0
- package/dist/pacing/au-duration.d.ts +18 -0
- package/dist/pacing/au-duration.d.ts.map +1 -0
- package/dist/pacing/au-duration.js +23 -0
- package/dist/pacing/au-duration.js.map +7 -0
- package/dist/pacing/frame-pacer.d.ts +71 -0
- package/dist/pacing/frame-pacer.d.ts.map +1 -0
- package/dist/pacing/frame-pacer.js +89 -0
- package/dist/pacing/frame-pacer.js.map +7 -0
- package/dist/pacing/index.d.ts +6 -0
- package/dist/pacing/index.d.ts.map +1 -0
- package/dist/pacing/index.js +166 -0
- package/dist/pacing/index.js.map +7 -0
- package/dist/pacing/interleave-timing.d.ts +64 -0
- package/dist/pacing/interleave-timing.d.ts.map +1 -0
- package/dist/pacing/interleave-timing.js +58 -0
- package/dist/pacing/interleave-timing.js.map +7 -0
- package/dist/video/index.d.ts +4 -0
- package/dist/video/index.d.ts.map +1 -0
- package/dist/video/index.js +247 -0
- package/dist/video/index.js.map +7 -0
- package/dist/video/keyframe-gate.d.ts +21 -0
- package/dist/video/keyframe-gate.d.ts.map +1 -0
- package/dist/video/keyframe-gate.js +39 -0
- package/dist/video/keyframe-gate.js.map +7 -0
- package/dist/video/video-render-pipeline.d.ts +60 -0
- package/dist/video/video-render-pipeline.d.ts.map +1 -0
- package/dist/video/video-render-pipeline.js +247 -0
- package/dist/video/video-render-pipeline.js.map +7 -0
- package/dist/worklet/render-worklet.js +403 -0
- package/dist/worklet/render-worklet.js.map +7 -0
- package/package.json +101 -0
- package/src/audio/audio-buffer-plan.ts +62 -0
- package/src/audio/audio-render-pipeline.ts +313 -0
- package/src/audio/audio-ring-buffer.ts +281 -0
- package/src/audio/index.ts +14 -0
- package/src/audio/render-messages.ts +110 -0
- package/src/audio/render-worklet.ts +161 -0
- package/src/audio/worklet-url.ts +30 -0
- package/src/index.ts +13 -0
- package/src/pacing/au-duration.ts +34 -0
- package/src/pacing/frame-pacer.ts +129 -0
- package/src/pacing/index.ts +5 -0
- package/src/pacing/interleave-timing.ts +95 -0
- package/src/video/index.ts +7 -0
- package/src/video/keyframe-gate.ts +35 -0
- package/src/video/video-render-pipeline.ts +179 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
var __typeError = (msg) => {
|
|
4
|
+
throw TypeError(msg);
|
|
5
|
+
};
|
|
6
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
7
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
8
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
9
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
10
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
11
|
+
var __privateWrapper = (obj, member, setter, getter) => ({
|
|
12
|
+
set _(value) {
|
|
13
|
+
__privateSet(obj, member, value, setter);
|
|
14
|
+
},
|
|
15
|
+
get _() {
|
|
16
|
+
return __privateGet(obj, member, getter);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// src/audio/audio-ring-buffer.ts
|
|
21
|
+
var _buffer, _writeIndex, _readIndex, _stalled, _startupFrames, _resumeFrames, _resumeFollowsStartup, _hasStarted, _capacityHeadroomFrames, _timestampAnchors, _diagEnabled, _diagReads, _diagFillSum, _diagOverflowSamples, _AudioRingBuffer_instances, pruneTimestampAnchors_fn, refillFrames_fn;
|
|
22
|
+
var AudioRingBuffer = class {
|
|
23
|
+
constructor(config) {
|
|
24
|
+
__privateAdd(this, _AudioRingBuffer_instances);
|
|
25
|
+
__privateAdd(this, _buffer);
|
|
26
|
+
__privateAdd(this, _writeIndex, 0);
|
|
27
|
+
__privateAdd(this, _readIndex, 0);
|
|
28
|
+
__privateAdd(this, _stalled);
|
|
29
|
+
__privateAdd(this, _startupFrames);
|
|
30
|
+
__privateAdd(this, _resumeFrames);
|
|
31
|
+
__privateAdd(this, _resumeFollowsStartup);
|
|
32
|
+
__privateAdd(this, _hasStarted);
|
|
33
|
+
__privateAdd(this, _capacityHeadroomFrames);
|
|
34
|
+
__privateAdd(this, _timestampAnchors, []);
|
|
35
|
+
__privateAdd(this, _diagEnabled);
|
|
36
|
+
__privateAdd(this, _diagReads, 0);
|
|
37
|
+
__privateAdd(this, _diagFillSum, 0);
|
|
38
|
+
__privateAdd(this, _diagOverflowSamples, 0);
|
|
39
|
+
if (!Number.isInteger(config.channels) || config.channels <= 0) {
|
|
40
|
+
throw new Error("invalid channels");
|
|
41
|
+
}
|
|
42
|
+
if (!Number.isFinite(config.rate) || config.rate <= 0) {
|
|
43
|
+
throw new Error("invalid sample rate");
|
|
44
|
+
}
|
|
45
|
+
if (!Number.isFinite(config.latencyMs) || config.latencyMs <= 0) {
|
|
46
|
+
throw new Error("invalid latency");
|
|
47
|
+
}
|
|
48
|
+
const latencyFrames = Math.ceil(config.rate * (config.latencyMs / 1e3));
|
|
49
|
+
const startupFrames = config.startupFrames ?? latencyFrames;
|
|
50
|
+
const resumeFrames = config.resumeFrames ?? startupFrames;
|
|
51
|
+
const thresholdFrames = Math.max(startupFrames, resumeFrames);
|
|
52
|
+
const capacityFrames = config.capacityFrames ?? Math.max(latencyFrames, thresholdFrames);
|
|
53
|
+
if (!Number.isSafeInteger(startupFrames) || startupFrames <= 0) {
|
|
54
|
+
throw new Error("invalid startup frames");
|
|
55
|
+
}
|
|
56
|
+
if (!Number.isSafeInteger(resumeFrames) || resumeFrames <= 0) {
|
|
57
|
+
throw new Error("invalid resume frames");
|
|
58
|
+
}
|
|
59
|
+
if (!Number.isSafeInteger(capacityFrames) || capacityFrames < thresholdFrames) {
|
|
60
|
+
throw new Error("invalid capacity frames");
|
|
61
|
+
}
|
|
62
|
+
this.channels = config.channels;
|
|
63
|
+
this.rate = config.rate;
|
|
64
|
+
__privateSet(this, _startupFrames, startupFrames);
|
|
65
|
+
__privateSet(this, _resumeFrames, resumeFrames);
|
|
66
|
+
__privateSet(this, _resumeFollowsStartup, config.resumeFrames === void 0);
|
|
67
|
+
__privateSet(this, _hasStarted, config.startUnstalled ?? false);
|
|
68
|
+
__privateSet(this, _capacityHeadroomFrames, capacityFrames - thresholdFrames);
|
|
69
|
+
__privateSet(this, _stalled, !(config.startUnstalled ?? false));
|
|
70
|
+
__privateSet(this, _diagEnabled, config.diag ?? false);
|
|
71
|
+
__privateSet(this, _buffer, Array.from(
|
|
72
|
+
{ length: config.channels },
|
|
73
|
+
() => new Float32Array(capacityFrames)
|
|
74
|
+
));
|
|
75
|
+
}
|
|
76
|
+
get stalled() {
|
|
77
|
+
return __privateGet(this, _stalled);
|
|
78
|
+
}
|
|
79
|
+
get startupFrames() {
|
|
80
|
+
return __privateGet(this, _startupFrames);
|
|
81
|
+
}
|
|
82
|
+
get capacity() {
|
|
83
|
+
return __privateGet(this, _buffer)[0].length;
|
|
84
|
+
}
|
|
85
|
+
get length() {
|
|
86
|
+
return __privateGet(this, _writeIndex) - __privateGet(this, _readIndex);
|
|
87
|
+
}
|
|
88
|
+
/** Timestamp of the next media sample after the PCM already emitted. */
|
|
89
|
+
get timestampUs() {
|
|
90
|
+
const anchor = __privateGet(this, _timestampAnchors)[0];
|
|
91
|
+
if (!anchor || anchor.sampleIndex > __privateGet(this, _readIndex)) return void 0;
|
|
92
|
+
return anchor.timestampUs + (__privateGet(this, _readIndex) - anchor.sampleIndex) / this.rate * 1e6;
|
|
93
|
+
}
|
|
94
|
+
write(timestampUs, data) {
|
|
95
|
+
if (data.length !== this.channels) throw new Error("wrong channel count");
|
|
96
|
+
if (!Number.isFinite(timestampUs)) throw new Error("invalid timestamp");
|
|
97
|
+
const samples = data[0].length;
|
|
98
|
+
if (samples === 0) return;
|
|
99
|
+
if (data.some((channel) => channel.length !== samples)) {
|
|
100
|
+
throw new Error("mismatched channel lengths");
|
|
101
|
+
}
|
|
102
|
+
const start = __privateGet(this, _writeIndex);
|
|
103
|
+
const end = start + samples;
|
|
104
|
+
__privateGet(this, _timestampAnchors).push({ sampleIndex: start, timestampUs });
|
|
105
|
+
const overflow = end - __privateGet(this, _readIndex) - this.capacity;
|
|
106
|
+
if (overflow > 0) {
|
|
107
|
+
__privateSet(this, _stalled, false);
|
|
108
|
+
__privateSet(this, _hasStarted, true);
|
|
109
|
+
__privateSet(this, _readIndex, __privateGet(this, _readIndex) + overflow);
|
|
110
|
+
if (__privateGet(this, _diagEnabled)) __privateSet(this, _diagOverflowSamples, __privateGet(this, _diagOverflowSamples) + overflow);
|
|
111
|
+
__privateMethod(this, _AudioRingBuffer_instances, pruneTimestampAnchors_fn).call(this);
|
|
112
|
+
}
|
|
113
|
+
const refillFrames = __privateGet(this, _hasStarted) ? __privateGet(this, _resumeFrames) : __privateGet(this, _startupFrames);
|
|
114
|
+
if (__privateGet(this, _stalled) && end - __privateGet(this, _readIndex) >= refillFrames) {
|
|
115
|
+
__privateSet(this, _stalled, false);
|
|
116
|
+
__privateSet(this, _hasStarted, true);
|
|
117
|
+
}
|
|
118
|
+
for (let channel = 0; channel < this.channels; channel++) {
|
|
119
|
+
const src = data[channel];
|
|
120
|
+
const dst = __privateGet(this, _buffer)[channel];
|
|
121
|
+
for (let i = 0; i < samples; i++) {
|
|
122
|
+
dst[(start + i) % dst.length] = src[i];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
__privateSet(this, _writeIndex, end);
|
|
126
|
+
}
|
|
127
|
+
read(output) {
|
|
128
|
+
if (output.length !== this.channels) throw new Error("wrong channel count");
|
|
129
|
+
if (__privateGet(this, _stalled)) return 0;
|
|
130
|
+
const available = this.length;
|
|
131
|
+
if (available === 0) {
|
|
132
|
+
__privateSet(this, _stalled, true);
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
const requested = output[0].length;
|
|
136
|
+
const readSamples = Math.min(available, requested);
|
|
137
|
+
for (let channel = 0; channel < this.channels; channel++) {
|
|
138
|
+
const dst = output[channel];
|
|
139
|
+
const src = __privateGet(this, _buffer)[channel];
|
|
140
|
+
for (let i = 0; i < readSamples; i++) {
|
|
141
|
+
dst[i] = src[(__privateGet(this, _readIndex) + i) % src.length];
|
|
142
|
+
}
|
|
143
|
+
for (let i = readSamples; i < requested; i++) dst[i] = 0;
|
|
144
|
+
}
|
|
145
|
+
__privateSet(this, _readIndex, __privateGet(this, _readIndex) + readSamples);
|
|
146
|
+
__privateMethod(this, _AudioRingBuffer_instances, pruneTimestampAnchors_fn).call(this);
|
|
147
|
+
if (readSamples < requested) __privateSet(this, _stalled, true);
|
|
148
|
+
if (__privateGet(this, _diagEnabled)) {
|
|
149
|
+
__privateWrapper(this, _diagReads)._++;
|
|
150
|
+
__privateSet(this, _diagFillSum, __privateGet(this, _diagFillSum) + available);
|
|
151
|
+
}
|
|
152
|
+
return readSamples;
|
|
153
|
+
}
|
|
154
|
+
/** Clear queued PCM; optionally treat the next fill as post-start recovery. */
|
|
155
|
+
reset(startUnstalled = false, resumeAfterReset = false) {
|
|
156
|
+
__privateSet(this, _writeIndex, 0);
|
|
157
|
+
__privateSet(this, _readIndex, 0);
|
|
158
|
+
__privateSet(this, _stalled, !startUnstalled);
|
|
159
|
+
__privateSet(this, _hasStarted, startUnstalled || resumeAfterReset);
|
|
160
|
+
__privateSet(this, _timestampAnchors, []);
|
|
161
|
+
__privateSet(this, _diagReads, 0);
|
|
162
|
+
__privateSet(this, _diagFillSum, 0);
|
|
163
|
+
__privateSet(this, _diagOverflowSamples, 0);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Change the startup latency while preserving the configured storage
|
|
167
|
+
* headroom above its buffering thresholds. Active playout keeps draining
|
|
168
|
+
* retained PCM; a stalled buffer waits for its startup or resume threshold.
|
|
169
|
+
*/
|
|
170
|
+
resize(latencyMs) {
|
|
171
|
+
if (!Number.isFinite(latencyMs) || latencyMs <= 0) {
|
|
172
|
+
throw new Error("invalid latency");
|
|
173
|
+
}
|
|
174
|
+
const newStartupFrames = Math.ceil(this.rate * (latencyMs / 1e3));
|
|
175
|
+
if (!Number.isSafeInteger(newStartupFrames) || newStartupFrames <= 0) {
|
|
176
|
+
throw new Error("invalid latency");
|
|
177
|
+
}
|
|
178
|
+
const newResumeFrames = __privateGet(this, _resumeFollowsStartup) ? newStartupFrames : __privateGet(this, _resumeFrames);
|
|
179
|
+
const newCapacity = Math.max(newStartupFrames, newResumeFrames) + __privateGet(this, _capacityHeadroomFrames);
|
|
180
|
+
if (!Number.isSafeInteger(newCapacity) || newCapacity < Math.max(newStartupFrames, newResumeFrames)) {
|
|
181
|
+
throw new Error("invalid capacity frames");
|
|
182
|
+
}
|
|
183
|
+
const wasStalled = __privateGet(this, _stalled);
|
|
184
|
+
__privateSet(this, _startupFrames, newStartupFrames);
|
|
185
|
+
__privateSet(this, _resumeFrames, newResumeFrames);
|
|
186
|
+
if (newCapacity === this.capacity) {
|
|
187
|
+
if (wasStalled && this.length >= __privateMethod(this, _AudioRingBuffer_instances, refillFrames_fn).call(this)) {
|
|
188
|
+
__privateSet(this, _stalled, false);
|
|
189
|
+
__privateSet(this, _hasStarted, true);
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const next = Array.from(
|
|
194
|
+
{ length: this.channels },
|
|
195
|
+
() => new Float32Array(newCapacity)
|
|
196
|
+
);
|
|
197
|
+
const samplesToKeep = Math.min(this.length, newCapacity);
|
|
198
|
+
const copyStart = __privateGet(this, _writeIndex) - samplesToKeep;
|
|
199
|
+
for (let channel = 0; channel < this.channels; channel++) {
|
|
200
|
+
const src = __privateGet(this, _buffer)[channel];
|
|
201
|
+
const dst = next[channel];
|
|
202
|
+
for (let i = 0; i < samplesToKeep; i++) {
|
|
203
|
+
const sampleIndex = copyStart + i;
|
|
204
|
+
dst[sampleIndex % dst.length] = src[sampleIndex % src.length];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
__privateSet(this, _buffer, next);
|
|
208
|
+
__privateSet(this, _readIndex, copyStart);
|
|
209
|
+
__privateMethod(this, _AudioRingBuffer_instances, pruneTimestampAnchors_fn).call(this);
|
|
210
|
+
__privateSet(this, _stalled, samplesToKeep === 0 || wasStalled && samplesToKeep < __privateMethod(this, _AudioRingBuffer_instances, refillFrames_fn).call(this));
|
|
211
|
+
if (!__privateGet(this, _stalled)) __privateSet(this, _hasStarted, true);
|
|
212
|
+
}
|
|
213
|
+
getDiagAndReset() {
|
|
214
|
+
if (!__privateGet(this, _diagEnabled) || __privateGet(this, _diagReads) === 0) return null;
|
|
215
|
+
const result = {
|
|
216
|
+
reads: __privateGet(this, _diagReads),
|
|
217
|
+
avgFill: Math.round(__privateGet(this, _diagFillSum) / __privateGet(this, _diagReads)),
|
|
218
|
+
capacity: this.capacity,
|
|
219
|
+
skips: 0,
|
|
220
|
+
dups: 0,
|
|
221
|
+
overflowSamples: __privateGet(this, _diagOverflowSamples)
|
|
222
|
+
};
|
|
223
|
+
__privateSet(this, _diagReads, 0);
|
|
224
|
+
__privateSet(this, _diagFillSum, 0);
|
|
225
|
+
__privateSet(this, _diagOverflowSamples, 0);
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
_buffer = new WeakMap();
|
|
230
|
+
_writeIndex = new WeakMap();
|
|
231
|
+
_readIndex = new WeakMap();
|
|
232
|
+
_stalled = new WeakMap();
|
|
233
|
+
_startupFrames = new WeakMap();
|
|
234
|
+
_resumeFrames = new WeakMap();
|
|
235
|
+
_resumeFollowsStartup = new WeakMap();
|
|
236
|
+
_hasStarted = new WeakMap();
|
|
237
|
+
_capacityHeadroomFrames = new WeakMap();
|
|
238
|
+
_timestampAnchors = new WeakMap();
|
|
239
|
+
_diagEnabled = new WeakMap();
|
|
240
|
+
_diagReads = new WeakMap();
|
|
241
|
+
_diagFillSum = new WeakMap();
|
|
242
|
+
_diagOverflowSamples = new WeakMap();
|
|
243
|
+
_AudioRingBuffer_instances = new WeakSet();
|
|
244
|
+
pruneTimestampAnchors_fn = function() {
|
|
245
|
+
let keep = 0;
|
|
246
|
+
while (keep + 1 < __privateGet(this, _timestampAnchors).length && __privateGet(this, _timestampAnchors)[keep + 1].sampleIndex <= __privateGet(this, _readIndex)) {
|
|
247
|
+
keep++;
|
|
248
|
+
}
|
|
249
|
+
if (keep > 0) __privateGet(this, _timestampAnchors).splice(0, keep);
|
|
250
|
+
};
|
|
251
|
+
refillFrames_fn = function() {
|
|
252
|
+
return __privateGet(this, _hasStarted) ? __privateGet(this, _resumeFrames) : __privateGet(this, _startupFrames);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// src/audio/render-worklet.ts
|
|
256
|
+
var _buffer2, _underflow, _diagUnderflow, _started, _stateCounter, _diagEnabled2, _diagCounter, _generation, _writtenFrames, _heldUntilStart, _resumeAfterReset, _Render_instances, flushDiag_fn;
|
|
257
|
+
var Render = class extends AudioWorkletProcessor {
|
|
258
|
+
constructor() {
|
|
259
|
+
super();
|
|
260
|
+
__privateAdd(this, _Render_instances);
|
|
261
|
+
__privateAdd(this, _buffer2);
|
|
262
|
+
__privateAdd(this, _underflow, 0);
|
|
263
|
+
__privateAdd(this, _diagUnderflow, 0);
|
|
264
|
+
__privateAdd(this, _started, false);
|
|
265
|
+
__privateAdd(this, _stateCounter, 0);
|
|
266
|
+
__privateAdd(this, _diagEnabled2, false);
|
|
267
|
+
__privateAdd(this, _diagCounter, 0);
|
|
268
|
+
__privateAdd(this, _generation, 0);
|
|
269
|
+
__privateAdd(this, _writtenFrames, 0);
|
|
270
|
+
__privateAdd(this, _heldUntilStart, false);
|
|
271
|
+
__privateAdd(this, _resumeAfterReset, false);
|
|
272
|
+
this.port.onmessage = (event) => {
|
|
273
|
+
const msg = event.data;
|
|
274
|
+
if (msg.type === "init") {
|
|
275
|
+
const heldUntilStart = msg.holdUntilStart ?? false;
|
|
276
|
+
const latencyFrames = Math.ceil(msg.rate * (msg.latencyMs / 1e3));
|
|
277
|
+
const startupFrames = msg.startupFrames ?? latencyFrames;
|
|
278
|
+
const capacityFrames = msg.capacityFrames ?? Math.max(latencyFrames, startupFrames) + latencyFrames;
|
|
279
|
+
if (!Number.isSafeInteger(startupFrames) || startupFrames <= 0) {
|
|
280
|
+
throw new Error("invalid startup frames");
|
|
281
|
+
}
|
|
282
|
+
if (!Number.isSafeInteger(capacityFrames) || capacityFrames < startupFrames) {
|
|
283
|
+
throw new Error("invalid capacity frames");
|
|
284
|
+
}
|
|
285
|
+
__privateSet(this, _generation, msg.generation);
|
|
286
|
+
__privateSet(this, _buffer2, new AudioRingBuffer({
|
|
287
|
+
channels: msg.channels,
|
|
288
|
+
rate: msg.rate,
|
|
289
|
+
latencyMs: msg.latencyMs,
|
|
290
|
+
// The explicit start gate owns the one-time startup prefill. Once
|
|
291
|
+
// released, resume from an underrun as soon as new PCM is accepted.
|
|
292
|
+
startupFrames,
|
|
293
|
+
resumeFrames: heldUntilStart ? 1 : void 0,
|
|
294
|
+
capacityFrames,
|
|
295
|
+
startUnstalled: msg.startUnstalled,
|
|
296
|
+
warmupReads: msg.warmupReads,
|
|
297
|
+
diag: msg.diag ?? false
|
|
298
|
+
}));
|
|
299
|
+
__privateSet(this, _diagEnabled2, msg.diag ?? false);
|
|
300
|
+
__privateSet(this, _underflow, 0);
|
|
301
|
+
__privateSet(this, _diagUnderflow, 0);
|
|
302
|
+
__privateSet(this, _started, false);
|
|
303
|
+
__privateSet(this, _writtenFrames, 0);
|
|
304
|
+
__privateSet(this, _heldUntilStart, heldUntilStart);
|
|
305
|
+
__privateSet(this, _resumeAfterReset, heldUntilStart);
|
|
306
|
+
} else if (msg.type === "data") {
|
|
307
|
+
if (!__privateGet(this, _buffer2)) return;
|
|
308
|
+
__privateGet(this, _buffer2).write(msg.timestamp, msg.data);
|
|
309
|
+
__privateSet(this, _writtenFrames, __privateGet(this, _writtenFrames) + (msg.data[0]?.length ?? 0));
|
|
310
|
+
if (__privateGet(this, _heldUntilStart) || __privateGet(this, _diagEnabled2)) {
|
|
311
|
+
this.port.postMessage({
|
|
312
|
+
type: "writeAck",
|
|
313
|
+
generation: __privateGet(this, _generation),
|
|
314
|
+
bufferedFrames: __privateGet(this, _buffer2).length,
|
|
315
|
+
writtenFrames: __privateGet(this, _writtenFrames)
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
} else if (msg.type === "start") {
|
|
319
|
+
if (msg.generation === __privateGet(this, _generation)) __privateSet(this, _heldUntilStart, false);
|
|
320
|
+
} else if (msg.type === "latency") {
|
|
321
|
+
if (!__privateGet(this, _buffer2)) return;
|
|
322
|
+
__privateGet(this, _buffer2).resize(msg.latencyMs);
|
|
323
|
+
} else if (msg.type === "reset") {
|
|
324
|
+
__privateSet(this, _generation, msg.generation);
|
|
325
|
+
__privateGet(this, _buffer2)?.reset(msg.startUnstalled ?? false, __privateGet(this, _resumeAfterReset));
|
|
326
|
+
__privateSet(this, _underflow, 0);
|
|
327
|
+
__privateSet(this, _diagUnderflow, 0);
|
|
328
|
+
__privateSet(this, _started, false);
|
|
329
|
+
__privateSet(this, _writtenFrames, 0);
|
|
330
|
+
__privateSet(this, _heldUntilStart, false);
|
|
331
|
+
} else if (msg.type === "requestDiag") {
|
|
332
|
+
__privateMethod(this, _Render_instances, flushDiag_fn).call(this);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
process(_inputs, outputs) {
|
|
337
|
+
const output = outputs[0];
|
|
338
|
+
const samplesRead = __privateGet(this, _heldUntilStart) ? 0 : __privateGet(this, _buffer2)?.read(output) ?? 0;
|
|
339
|
+
if (samplesRead > 0) __privateSet(this, _started, true);
|
|
340
|
+
if (samplesRead < output[0].length && __privateGet(this, _started)) {
|
|
341
|
+
const missing = output[0].length - samplesRead;
|
|
342
|
+
__privateSet(this, _underflow, __privateGet(this, _underflow) + missing);
|
|
343
|
+
__privateSet(this, _diagUnderflow, __privateGet(this, _diagUnderflow) + missing);
|
|
344
|
+
} else if (__privateGet(this, _underflow) > 0 && __privateGet(this, _buffer2)) {
|
|
345
|
+
console.warn(`audio underflow: ${Math.round(1e3 * __privateGet(this, _underflow) / __privateGet(this, _buffer2).rate)}ms`);
|
|
346
|
+
__privateSet(this, _underflow, 0);
|
|
347
|
+
}
|
|
348
|
+
__privateWrapper(this, _stateCounter)._++;
|
|
349
|
+
if (__privateGet(this, _buffer2) && __privateGet(this, _stateCounter) >= 5) {
|
|
350
|
+
__privateSet(this, _stateCounter, 0);
|
|
351
|
+
const msg = {
|
|
352
|
+
type: "state",
|
|
353
|
+
generation: __privateGet(this, _generation),
|
|
354
|
+
timestamp: __privateGet(this, _heldUntilStart) ? void 0 : __privateGet(this, _buffer2).timestampUs,
|
|
355
|
+
stalled: __privateGet(this, _heldUntilStart) || __privateGet(this, _buffer2).stalled,
|
|
356
|
+
bufferedFrames: __privateGet(this, _buffer2).length,
|
|
357
|
+
writtenFrames: __privateGet(this, _writtenFrames)
|
|
358
|
+
};
|
|
359
|
+
this.port.postMessage(msg);
|
|
360
|
+
}
|
|
361
|
+
if (__privateGet(this, _diagEnabled2) && __privateGet(this, _buffer2)) {
|
|
362
|
+
__privateWrapper(this, _diagCounter)._++;
|
|
363
|
+
if (__privateGet(this, _diagCounter) >= 375) {
|
|
364
|
+
__privateSet(this, _diagCounter, 0);
|
|
365
|
+
__privateMethod(this, _Render_instances, flushDiag_fn).call(this);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
_buffer2 = new WeakMap();
|
|
372
|
+
_underflow = new WeakMap();
|
|
373
|
+
_diagUnderflow = new WeakMap();
|
|
374
|
+
_started = new WeakMap();
|
|
375
|
+
_stateCounter = new WeakMap();
|
|
376
|
+
_diagEnabled2 = new WeakMap();
|
|
377
|
+
_diagCounter = new WeakMap();
|
|
378
|
+
_generation = new WeakMap();
|
|
379
|
+
_writtenFrames = new WeakMap();
|
|
380
|
+
_heldUntilStart = new WeakMap();
|
|
381
|
+
_resumeAfterReset = new WeakMap();
|
|
382
|
+
_Render_instances = new WeakSet();
|
|
383
|
+
flushDiag_fn = function() {
|
|
384
|
+
if (!__privateGet(this, _buffer2)) return;
|
|
385
|
+
const d = __privateGet(this, _buffer2).getDiagAndReset();
|
|
386
|
+
if (!d && __privateGet(this, _diagUnderflow) === 0) return;
|
|
387
|
+
const msg = {
|
|
388
|
+
type: "ringBufDiag",
|
|
389
|
+
generation: __privateGet(this, _generation),
|
|
390
|
+
reads: d?.reads ?? 0,
|
|
391
|
+
avgFill: d?.avgFill ?? 0,
|
|
392
|
+
capacity: d?.capacity ?? __privateGet(this, _buffer2).capacity,
|
|
393
|
+
skipsPerSec: d?.skips ?? 0,
|
|
394
|
+
dupsPerSec: d?.dups ?? 0,
|
|
395
|
+
overflowSamplesPerSec: d?.overflowSamples ?? 0,
|
|
396
|
+
underflowSamplesPerSec: __privateGet(this, _diagUnderflow)
|
|
397
|
+
};
|
|
398
|
+
__privateSet(this, _diagUnderflow, 0);
|
|
399
|
+
this.port.postMessage(msg);
|
|
400
|
+
};
|
|
401
|
+
registerProcessor("mmt-render", Render);
|
|
402
|
+
})();
|
|
403
|
+
//# sourceMappingURL=render-worklet.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/audio/audio-ring-buffer.ts", "../../src/audio/render-worklet.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Timestamp-anchored FIFO shared by MMT AudioWorklet consumers.\n *\n * PCM remains in decoder emission order. Media timestamps are exact anchors\n * for playout reporting only; they never reposition samples or trigger a\n * guessed discontinuity. The FIFO starts after its full catalog-derived\n * latency is buffered and performs no sample skip/dup clock recovery.\n */\n\nexport interface AudioRingBufferConfig {\n channels: number\n rate: number\n latencyMs: number\n /** Exact PCM startup threshold. Overrides latencyMs when provided. */\n startupFrames?: number\n /** PCM refill threshold after the first underrun. Defaults to startupFrames. */\n resumeFrames?: number\n /** Exact PCM storage ceiling. Must cover the startup and resume thresholds. */\n capacityFrames?: number\n startUnstalled?: boolean\n /** Deprecated compatibility input. Clock recovery is not performed. */\n warmupReads?: number\n diag?: boolean\n}\n\nexport interface AudioRingBufferDiag {\n reads: number\n avgFill: number\n capacity: number\n skips: number\n dups: number\n overflowSamples: number\n}\n\ntype TimestampAnchor = {\n sampleIndex: number\n timestampUs: number\n}\n\nexport class AudioRingBuffer {\n readonly channels: number\n readonly rate: number\n\n #buffer: Float32Array[]\n #writeIndex = 0\n #readIndex = 0\n #stalled: boolean\n #startupFrames: number\n #resumeFrames: number\n #resumeFollowsStartup: boolean\n #hasStarted: boolean\n #capacityHeadroomFrames: number\n #timestampAnchors: TimestampAnchor[] = []\n\n #diagEnabled: boolean\n #diagReads = 0\n #diagFillSum = 0\n #diagOverflowSamples = 0\n\n constructor(config: AudioRingBufferConfig) {\n if (!Number.isInteger(config.channels) || config.channels <= 0) {\n throw new Error('invalid channels')\n }\n if (!Number.isFinite(config.rate) || config.rate <= 0) {\n throw new Error('invalid sample rate')\n }\n if (!Number.isFinite(config.latencyMs) || config.latencyMs <= 0) {\n throw new Error('invalid latency')\n }\n\n const latencyFrames = Math.ceil(config.rate * (config.latencyMs / 1000))\n const startupFrames = config.startupFrames ?? latencyFrames\n const resumeFrames = config.resumeFrames ?? startupFrames\n const thresholdFrames = Math.max(startupFrames, resumeFrames)\n const capacityFrames = config.capacityFrames ?? Math.max(latencyFrames, thresholdFrames)\n if (!Number.isSafeInteger(startupFrames) || startupFrames <= 0) {\n throw new Error('invalid startup frames')\n }\n if (!Number.isSafeInteger(resumeFrames) || resumeFrames <= 0) {\n throw new Error('invalid resume frames')\n }\n if (!Number.isSafeInteger(capacityFrames) || capacityFrames < thresholdFrames) {\n throw new Error('invalid capacity frames')\n }\n\n this.channels = config.channels\n this.rate = config.rate\n this.#startupFrames = startupFrames\n this.#resumeFrames = resumeFrames\n this.#resumeFollowsStartup = config.resumeFrames === undefined\n this.#hasStarted = config.startUnstalled ?? false\n this.#capacityHeadroomFrames = capacityFrames - thresholdFrames\n this.#stalled = !(config.startUnstalled ?? false)\n this.#diagEnabled = config.diag ?? false\n this.#buffer = Array.from(\n { length: config.channels },\n () => new Float32Array(capacityFrames),\n )\n }\n\n get stalled(): boolean { return this.#stalled }\n get startupFrames(): number { return this.#startupFrames }\n get capacity(): number { return this.#buffer[0].length }\n get length(): number { return this.#writeIndex - this.#readIndex }\n\n /** Timestamp of the next media sample after the PCM already emitted. */\n get timestampUs(): number | undefined {\n const anchor = this.#timestampAnchors[0]\n if (!anchor || anchor.sampleIndex > this.#readIndex) return undefined\n return anchor.timestampUs + ((this.#readIndex - anchor.sampleIndex) / this.rate) * 1_000_000\n }\n\n write(timestampUs: number, data: Float32Array[]): void {\n if (data.length !== this.channels) throw new Error('wrong channel count')\n if (!Number.isFinite(timestampUs)) throw new Error('invalid timestamp')\n const samples = data[0].length\n if (samples === 0) return\n if (data.some((channel) => channel.length !== samples)) {\n throw new Error('mismatched channel lengths')\n }\n\n const start = this.#writeIndex\n const end = start + samples\n this.#timestampAnchors.push({ sampleIndex: start, timestampUs })\n\n const overflow = end - this.#readIndex - this.capacity\n if (overflow > 0) {\n this.#stalled = false\n this.#hasStarted = true\n this.#readIndex += overflow\n if (this.#diagEnabled) this.#diagOverflowSamples += overflow\n this.#pruneTimestampAnchors()\n }\n\n const refillFrames = this.#hasStarted ? this.#resumeFrames : this.#startupFrames\n if (this.#stalled && end - this.#readIndex >= refillFrames) {\n this.#stalled = false\n this.#hasStarted = true\n }\n\n for (let channel = 0; channel < this.channels; channel++) {\n const src = data[channel]\n const dst = this.#buffer[channel]\n for (let i = 0; i < samples; i++) {\n dst[(start + i) % dst.length] = src[i]\n }\n }\n this.#writeIndex = end\n }\n\n read(output: Float32Array[]): number {\n if (output.length !== this.channels) throw new Error('wrong channel count')\n if (this.#stalled) return 0\n\n const available = this.length\n if (available === 0) {\n this.#stalled = true\n return 0\n }\n\n const requested = output[0].length\n const readSamples = Math.min(available, requested)\n\n for (let channel = 0; channel < this.channels; channel++) {\n const dst = output[channel]\n const src = this.#buffer[channel]\n for (let i = 0; i < readSamples; i++) {\n dst[i] = src[(this.#readIndex + i) % src.length]\n }\n for (let i = readSamples; i < requested; i++) dst[i] = 0\n }\n\n this.#readIndex += readSamples\n this.#pruneTimestampAnchors()\n if (readSamples < requested) this.#stalled = true\n\n if (this.#diagEnabled) {\n this.#diagReads++\n this.#diagFillSum += available\n }\n return readSamples\n }\n\n /** Clear queued PCM; optionally treat the next fill as post-start recovery. */\n reset(startUnstalled = false, resumeAfterReset = false): void {\n this.#writeIndex = 0\n this.#readIndex = 0\n this.#stalled = !startUnstalled\n this.#hasStarted = startUnstalled || resumeAfterReset\n this.#timestampAnchors = []\n this.#diagReads = 0\n this.#diagFillSum = 0\n this.#diagOverflowSamples = 0\n }\n\n /**\n * Change the startup latency while preserving the configured storage\n * headroom above its buffering thresholds. Active playout keeps draining\n * retained PCM; a stalled buffer waits for its startup or resume threshold.\n */\n resize(latencyMs: number): void {\n if (!Number.isFinite(latencyMs) || latencyMs <= 0) {\n throw new Error('invalid latency')\n }\n const newStartupFrames = Math.ceil(this.rate * (latencyMs / 1000))\n if (!Number.isSafeInteger(newStartupFrames) || newStartupFrames <= 0) {\n throw new Error('invalid latency')\n }\n const newResumeFrames = this.#resumeFollowsStartup ? newStartupFrames : this.#resumeFrames\n const newCapacity =\n Math.max(newStartupFrames, newResumeFrames) + this.#capacityHeadroomFrames\n if (\n !Number.isSafeInteger(newCapacity) ||\n newCapacity < Math.max(newStartupFrames, newResumeFrames)\n ) {\n throw new Error('invalid capacity frames')\n }\n const wasStalled = this.#stalled\n this.#startupFrames = newStartupFrames\n this.#resumeFrames = newResumeFrames\n if (newCapacity === this.capacity) {\n if (wasStalled && this.length >= this.#refillFrames()) {\n this.#stalled = false\n this.#hasStarted = true\n }\n return\n }\n\n const next = Array.from(\n { length: this.channels },\n () => new Float32Array(newCapacity),\n )\n const samplesToKeep = Math.min(this.length, newCapacity)\n const copyStart = this.#writeIndex - samplesToKeep\n for (let channel = 0; channel < this.channels; channel++) {\n const src = this.#buffer[channel]\n const dst = next[channel]\n for (let i = 0; i < samplesToKeep; i++) {\n const sampleIndex = copyStart + i\n dst[sampleIndex % dst.length] = src[sampleIndex % src.length]\n }\n }\n\n this.#buffer = next\n this.#readIndex = copyStart\n this.#pruneTimestampAnchors()\n this.#stalled = samplesToKeep === 0 || (wasStalled && samplesToKeep < this.#refillFrames())\n if (!this.#stalled) this.#hasStarted = true\n }\n\n getDiagAndReset(): AudioRingBufferDiag | null {\n if (!this.#diagEnabled || this.#diagReads === 0) return null\n const result: AudioRingBufferDiag = {\n reads: this.#diagReads,\n avgFill: Math.round(this.#diagFillSum / this.#diagReads),\n capacity: this.capacity,\n skips: 0,\n dups: 0,\n overflowSamples: this.#diagOverflowSamples,\n }\n this.#diagReads = 0\n this.#diagFillSum = 0\n this.#diagOverflowSamples = 0\n return result\n }\n\n #pruneTimestampAnchors(): void {\n let keep = 0\n while (\n keep + 1 < this.#timestampAnchors.length &&\n this.#timestampAnchors[keep + 1].sampleIndex <= this.#readIndex\n ) {\n keep++\n }\n if (keep > 0) this.#timestampAnchors.splice(0, keep)\n }\n\n #refillFrames(): number {\n return this.#hasStarted ? this.#resumeFrames : this.#startupFrames\n }\n}\n", "/**\n * Shared AudioWorklet render processor.\n *\n * This file is bundled into a self-contained IIFE at build time\n * (dist/worklet/render-worklet.js) because AudioWorkletGlobalScope cannot\n * resolve bare specifiers. The AudioRingBuffer dependency is inlined at\n * bundle time; do not import from @blockcast packages at runtime.\n *\n * Consumers load the prebuilt bundle via audioWorklet.addModule(url) where\n * url is obtained from `@blockcast/mmt-render/audio/worklet-url`.\n */\n\nimport { AudioRingBuffer } from './audio-ring-buffer.js'\nimport type { FromMain, ToMain } from './render-messages.js'\n\nclass Render extends AudioWorkletProcessor {\n #buffer?: AudioRingBuffer\n #underflow = 0\n #diagUnderflow = 0\n #started = false\n #stateCounter = 0\n #diagEnabled = false\n #diagCounter = 0\n #generation = 0\n #writtenFrames = 0\n #heldUntilStart = false\n #resumeAfterReset = false\n\n constructor() {\n super()\n this.port.onmessage = (event: MessageEvent<FromMain>) => {\n const msg = event.data\n if (msg.type === 'init') {\n const heldUntilStart = msg.holdUntilStart ?? false\n const latencyFrames = Math.ceil(msg.rate * (msg.latencyMs / 1000))\n const startupFrames = msg.startupFrames ?? latencyFrames\n // Keep one latency window above the release threshold so PCM accepted\n // during the writeAck -> start round trip cannot evict the prefill head.\n const capacityFrames =\n msg.capacityFrames ?? Math.max(latencyFrames, startupFrames) + latencyFrames\n if (!Number.isSafeInteger(startupFrames) || startupFrames <= 0) {\n throw new Error('invalid startup frames')\n }\n if (!Number.isSafeInteger(capacityFrames) || capacityFrames < startupFrames) {\n throw new Error('invalid capacity frames')\n }\n this.#generation = msg.generation\n this.#buffer = new AudioRingBuffer({\n channels: msg.channels,\n rate: msg.rate,\n latencyMs: msg.latencyMs,\n // The explicit start gate owns the one-time startup prefill. Once\n // released, resume from an underrun as soon as new PCM is accepted.\n startupFrames,\n resumeFrames: heldUntilStart ? 1 : undefined,\n capacityFrames,\n startUnstalled: msg.startUnstalled,\n warmupReads: msg.warmupReads,\n diag: msg.diag ?? false,\n })\n this.#diagEnabled = msg.diag ?? false\n this.#underflow = 0\n this.#diagUnderflow = 0\n this.#started = false\n this.#writtenFrames = 0\n this.#heldUntilStart = heldUntilStart\n this.#resumeAfterReset = heldUntilStart\n } else if (msg.type === 'data') {\n if (!this.#buffer) return\n this.#buffer.write(msg.timestamp, msg.data)\n this.#writtenFrames += msg.data[0]?.length ?? 0\n if (this.#heldUntilStart || this.#diagEnabled) {\n this.port.postMessage({\n type: 'writeAck',\n generation: this.#generation,\n bufferedFrames: this.#buffer.length,\n writtenFrames: this.#writtenFrames,\n })\n }\n } else if (msg.type === 'start') {\n if (msg.generation === this.#generation) this.#heldUntilStart = false\n } else if (msg.type === 'latency') {\n if (!this.#buffer) return\n this.#buffer.resize(msg.latencyMs)\n } else if (msg.type === 'reset') {\n this.#generation = msg.generation\n this.#buffer?.reset(msg.startUnstalled ?? false, this.#resumeAfterReset)\n this.#underflow = 0\n this.#diagUnderflow = 0\n this.#started = false\n this.#writtenFrames = 0\n this.#heldUntilStart = false\n } else if (msg.type === 'requestDiag') {\n this.#flushDiag()\n }\n }\n }\n\n process(_inputs: Float32Array[][], outputs: Float32Array[][]): boolean {\n const output = outputs[0]\n const samplesRead = this.#heldUntilStart ? 0 : (this.#buffer?.read(output) ?? 0)\n\n if (samplesRead > 0) this.#started = true\n if (samplesRead < output[0].length && this.#started) {\n const missing = output[0].length - samplesRead\n this.#underflow += missing\n this.#diagUnderflow += missing\n } else if (this.#underflow > 0 && this.#buffer) {\n // eslint-disable-next-line no-console\n console.warn(`audio underflow: ${Math.round((1000 * this.#underflow) / this.#buffer.rate)}ms`)\n this.#underflow = 0\n }\n\n // State update ~60/sec (every 5 processes at 128-sample quantum @ 48kHz \u2248 2.67ms each)\n this.#stateCounter++\n if (this.#buffer && this.#stateCounter >= 5) {\n this.#stateCounter = 0\n const msg: ToMain = {\n type: 'state',\n generation: this.#generation,\n timestamp: this.#heldUntilStart ? undefined : this.#buffer.timestampUs,\n stalled: this.#heldUntilStart || this.#buffer.stalled,\n bufferedFrames: this.#buffer.length,\n writtenFrames: this.#writtenFrames,\n }\n this.port.postMessage(msg)\n }\n\n // Diag telemetry every ~1s if enabled.\n if (this.#diagEnabled && this.#buffer) {\n this.#diagCounter++\n if (this.#diagCounter >= 375) {\n this.#diagCounter = 0\n this.#flushDiag()\n }\n }\n\n return true\n }\n\n #flushDiag(): void {\n if (!this.#buffer) return\n const d = this.#buffer.getDiagAndReset()\n if (!d && this.#diagUnderflow === 0) return\n const msg: ToMain = {\n type: 'ringBufDiag',\n generation: this.#generation,\n reads: d?.reads ?? 0,\n avgFill: d?.avgFill ?? 0,\n capacity: d?.capacity ?? this.#buffer.capacity,\n skipsPerSec: d?.skips ?? 0,\n dupsPerSec: d?.dups ?? 0,\n overflowSamplesPerSec: d?.overflowSamples ?? 0,\n underflowSamplesPerSec: this.#diagUnderflow,\n }\n this.#diagUnderflow = 0\n this.port.postMessage(msg)\n }\n}\n\nregisterProcessor('mmt-render', Render)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["_buffer", "_diagEnabled"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blockcast/mmt-render",
|
|
3
|
+
"version": "0.1.0-main.c0e9e40265a1",
|
|
4
|
+
"description": "Shared MMT render pipeline (FEC-aware pacing, interleave timing, audio worklet) for MoQ players",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./pacing": {
|
|
14
|
+
"import": "./dist/pacing/index.js",
|
|
15
|
+
"types": "./dist/pacing/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./pacing/frame-pacer": {
|
|
18
|
+
"import": "./dist/pacing/frame-pacer.js",
|
|
19
|
+
"types": "./dist/pacing/frame-pacer.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./pacing/interleave-timing": {
|
|
22
|
+
"import": "./dist/pacing/interleave-timing.js",
|
|
23
|
+
"types": "./dist/pacing/interleave-timing.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./audio": {
|
|
26
|
+
"import": "./dist/audio/index.js",
|
|
27
|
+
"types": "./dist/audio/index.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./audio/ring-buffer": {
|
|
30
|
+
"import": "./dist/audio/audio-ring-buffer.js",
|
|
31
|
+
"types": "./dist/audio/audio-ring-buffer.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./audio/render-messages": {
|
|
34
|
+
"import": "./dist/audio/render-messages.js",
|
|
35
|
+
"types": "./dist/audio/render-messages.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./audio/render-pipeline": {
|
|
38
|
+
"import": "./dist/audio/audio-render-pipeline.js",
|
|
39
|
+
"types": "./dist/audio/audio-render-pipeline.d.ts"
|
|
40
|
+
},
|
|
41
|
+
"./audio/worklet-url": {
|
|
42
|
+
"import": "./dist/audio/worklet-url.js",
|
|
43
|
+
"types": "./dist/audio/worklet-url.d.ts"
|
|
44
|
+
},
|
|
45
|
+
"./audio/worklet.js": "./dist/worklet/render-worklet.js",
|
|
46
|
+
"./video": {
|
|
47
|
+
"import": "./dist/video/index.js",
|
|
48
|
+
"types": "./dist/video/index.d.ts"
|
|
49
|
+
},
|
|
50
|
+
"./video/keyframe-gate": {
|
|
51
|
+
"import": "./dist/video/keyframe-gate.js",
|
|
52
|
+
"types": "./dist/video/keyframe-gate.d.ts"
|
|
53
|
+
},
|
|
54
|
+
"./video/render-pipeline": {
|
|
55
|
+
"import": "./dist/video/video-render-pipeline.js",
|
|
56
|
+
"types": "./dist/video/video-render-pipeline.d.ts"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"dist",
|
|
61
|
+
"src",
|
|
62
|
+
"!src/**/*.test.ts",
|
|
63
|
+
"!src/**/*.spec.ts",
|
|
64
|
+
"!src/**/__tests__",
|
|
65
|
+
"!dist/**/*.test.*",
|
|
66
|
+
"!dist/**/*.spec.*"
|
|
67
|
+
],
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsc --emitDeclarationOnly && node build.mjs",
|
|
70
|
+
"dev": "tsc --watch",
|
|
71
|
+
"clean": "rm -rf dist",
|
|
72
|
+
"test": "vitest run",
|
|
73
|
+
"test:watch": "vitest"
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"@blockcast/mmt-container": "0.1.0-main.c0e9e40265a1",
|
|
77
|
+
"@blockcast/mmt-fec": "0.1.0-main.c0e9e40265a1"
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"@types/audioworklet": "^0.0.77",
|
|
81
|
+
"esbuild": "^0.28.0",
|
|
82
|
+
"typescript": "^5.3.0",
|
|
83
|
+
"vitest": "^1.0.0"
|
|
84
|
+
},
|
|
85
|
+
"keywords": [
|
|
86
|
+
"mmt",
|
|
87
|
+
"mmtp",
|
|
88
|
+
"render",
|
|
89
|
+
"audio-worklet",
|
|
90
|
+
"pacing",
|
|
91
|
+
"fec",
|
|
92
|
+
"interleave",
|
|
93
|
+
"moq"
|
|
94
|
+
],
|
|
95
|
+
"license": "Apache-2.0",
|
|
96
|
+
"repository": {
|
|
97
|
+
"type": "git",
|
|
98
|
+
"url": "https://github.com/Blockcast/libmmt.git",
|
|
99
|
+
"directory": "packages/mmt-render"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface AudioBurstBufferPlanInput {
|
|
2
|
+
sampleRate: number
|
|
3
|
+
chunkFrames: number
|
|
4
|
+
/** Catalog media-group duration. Integer milliseconds may be rounded. */
|
|
5
|
+
groupDurationMs: number
|
|
6
|
+
/** Optional catalog-derived datagram/FEC delivery span. */
|
|
7
|
+
deliveryWindowMs?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface AudioBurstBufferPlan {
|
|
11
|
+
/** PCM required before playout starts or resumes. */
|
|
12
|
+
startupFrames: number
|
|
13
|
+
/** Maximum retained PCM, including the next complete delivery burst. */
|
|
14
|
+
capacityFrames: number
|
|
15
|
+
/** Maximum PCM frames delivered as one catalog-defined burst. */
|
|
16
|
+
deliveryFrames: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function positive(value: number, label: string): number {
|
|
20
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error(`invalid ${label}`)
|
|
21
|
+
return value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Convert catalog delivery timing into exact codec-frame FIFO geometry.
|
|
26
|
+
*
|
|
27
|
+
* `groupDurationMs` is commonly rounded to an integer, so media-group units
|
|
28
|
+
* use nearest-unit rounding. FEC delivery windows are upper bounds and use
|
|
29
|
+
* ceiling. Storage holds the startup burst plus one complete successor burst;
|
|
30
|
+
* this prevents an atomic MoQ group from overwriting PCM while the worklet is
|
|
31
|
+
* draining the preceding group without adding a guessed time margin.
|
|
32
|
+
*/
|
|
33
|
+
export function deriveAudioBurstBufferPlan(
|
|
34
|
+
input: AudioBurstBufferPlanInput,
|
|
35
|
+
): AudioBurstBufferPlan {
|
|
36
|
+
const sampleRate = positive(input.sampleRate, 'sample rate')
|
|
37
|
+
const chunkFrames = positive(input.chunkFrames, 'audio chunk size')
|
|
38
|
+
const groupDurationMs = positive(input.groupDurationMs, 'audio group duration')
|
|
39
|
+
if (!Number.isInteger(chunkFrames)) throw new Error('invalid audio chunk size')
|
|
40
|
+
|
|
41
|
+
const chunkDurationMs = (1000 * chunkFrames) / sampleRate
|
|
42
|
+
const groupChunks = Math.max(1, Math.round(groupDurationMs / chunkDurationMs))
|
|
43
|
+
const deliveryWindowChunks =
|
|
44
|
+
input.deliveryWindowMs === undefined
|
|
45
|
+
? 0
|
|
46
|
+
: Math.max(
|
|
47
|
+
1,
|
|
48
|
+
Math.ceil(positive(input.deliveryWindowMs, 'audio delivery window') / chunkDurationMs),
|
|
49
|
+
)
|
|
50
|
+
const deliveryChunks = Math.max(groupChunks, deliveryWindowChunks)
|
|
51
|
+
const deliveryFrames = deliveryChunks * chunkFrames
|
|
52
|
+
const capacityFrames = deliveryFrames + deliveryFrames
|
|
53
|
+
if (!Number.isSafeInteger(deliveryFrames) || !Number.isSafeInteger(capacityFrames)) {
|
|
54
|
+
throw new Error('audio buffer frame count exceeds safe integer range')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
startupFrames: deliveryFrames,
|
|
59
|
+
capacityFrames,
|
|
60
|
+
deliveryFrames,
|
|
61
|
+
}
|
|
62
|
+
}
|