@blockcast/mmt-render 0.1.0-main.c71423b8a4e4 → 0.1.0-main.ce3c8aeb7f68
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/dist/audio/audio-render-pipeline.js +192 -247
- package/dist/audio/audio-render-pipeline.js.map +1 -1
- package/dist/audio/audio-ring-buffer.js +93 -127
- package/dist/audio/audio-ring-buffer.js.map +1 -1
- package/dist/audio/index.js +288 -360
- package/dist/audio/index.js.map +2 -2
- package/dist/pacing/frame-pacer.js +18 -38
- package/dist/pacing/frame-pacer.js.map +1 -1
- package/dist/pacing/index.js +18 -38
- package/dist/pacing/index.js.map +1 -1
- package/dist/video/index.js +103 -137
- package/dist/video/index.js.map +1 -1
- package/dist/video/keyframe-gate.js +5 -17
- package/dist/video/keyframe-gate.js.map +1 -1
- package/dist/video/video-render-pipeline.js +103 -137
- package/dist/video/video-render-pipeline.js.map +1 -1
- package/dist/worklet/render-worklet.js +171 -219
- package/dist/worklet/render-worklet.js.map +2 -2
- package/package.json +3 -3
|
@@ -1,39 +1,20 @@
|
|
|
1
|
-
var __typeError = (msg) => {
|
|
2
|
-
throw TypeError(msg);
|
|
3
|
-
};
|
|
4
|
-
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
-
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
6
|
-
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);
|
|
7
|
-
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
8
|
-
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
9
|
-
var __privateWrapper = (obj, member, setter, getter) => ({
|
|
10
|
-
set _(value) {
|
|
11
|
-
__privateSet(obj, member, value, setter);
|
|
12
|
-
},
|
|
13
|
-
get _() {
|
|
14
|
-
return __privateGet(obj, member, getter);
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
|
|
18
1
|
// src/audio/audio-ring-buffer.ts
|
|
19
|
-
var _buffer, _writeIndex, _readIndex, _stalled, _startupFrames, _resumeFrames, _resumeFollowsStartup, _hasStarted, _capacityHeadroomFrames, _timestampAnchors, _diagEnabled, _diagReads, _diagFillSum, _diagOverflowSamples, _AudioRingBuffer_instances, pruneTimestampAnchors_fn, refillFrames_fn;
|
|
20
2
|
var AudioRingBuffer = class {
|
|
3
|
+
#buffer;
|
|
4
|
+
#writeIndex = 0;
|
|
5
|
+
#readIndex = 0;
|
|
6
|
+
#stalled;
|
|
7
|
+
#startupFrames;
|
|
8
|
+
#resumeFrames;
|
|
9
|
+
#resumeFollowsStartup;
|
|
10
|
+
#hasStarted;
|
|
11
|
+
#capacityHeadroomFrames;
|
|
12
|
+
#timestampAnchors = [];
|
|
13
|
+
#diagEnabled;
|
|
14
|
+
#diagReads = 0;
|
|
15
|
+
#diagFillSum = 0;
|
|
16
|
+
#diagOverflowSamples = 0;
|
|
21
17
|
constructor(config) {
|
|
22
|
-
__privateAdd(this, _AudioRingBuffer_instances);
|
|
23
|
-
__privateAdd(this, _buffer);
|
|
24
|
-
__privateAdd(this, _writeIndex, 0);
|
|
25
|
-
__privateAdd(this, _readIndex, 0);
|
|
26
|
-
__privateAdd(this, _stalled);
|
|
27
|
-
__privateAdd(this, _startupFrames);
|
|
28
|
-
__privateAdd(this, _resumeFrames);
|
|
29
|
-
__privateAdd(this, _resumeFollowsStartup);
|
|
30
|
-
__privateAdd(this, _hasStarted);
|
|
31
|
-
__privateAdd(this, _capacityHeadroomFrames);
|
|
32
|
-
__privateAdd(this, _timestampAnchors, []);
|
|
33
|
-
__privateAdd(this, _diagEnabled);
|
|
34
|
-
__privateAdd(this, _diagReads, 0);
|
|
35
|
-
__privateAdd(this, _diagFillSum, 0);
|
|
36
|
-
__privateAdd(this, _diagOverflowSamples, 0);
|
|
37
18
|
if (!Number.isInteger(config.channels) || config.channels <= 0) {
|
|
38
19
|
throw new Error("invalid channels");
|
|
39
20
|
}
|
|
@@ -59,35 +40,35 @@ var AudioRingBuffer = class {
|
|
|
59
40
|
}
|
|
60
41
|
this.channels = config.channels;
|
|
61
42
|
this.rate = config.rate;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
43
|
+
this.#startupFrames = startupFrames;
|
|
44
|
+
this.#resumeFrames = resumeFrames;
|
|
45
|
+
this.#resumeFollowsStartup = config.resumeFrames === void 0;
|
|
46
|
+
this.#hasStarted = config.startUnstalled ?? false;
|
|
47
|
+
this.#capacityHeadroomFrames = capacityFrames - thresholdFrames;
|
|
48
|
+
this.#stalled = !(config.startUnstalled ?? false);
|
|
49
|
+
this.#diagEnabled = config.diag ?? false;
|
|
50
|
+
this.#buffer = Array.from(
|
|
70
51
|
{ length: config.channels },
|
|
71
52
|
() => new Float32Array(capacityFrames)
|
|
72
|
-
)
|
|
53
|
+
);
|
|
73
54
|
}
|
|
74
55
|
get stalled() {
|
|
75
|
-
return
|
|
56
|
+
return this.#stalled;
|
|
76
57
|
}
|
|
77
58
|
get startupFrames() {
|
|
78
|
-
return
|
|
59
|
+
return this.#startupFrames;
|
|
79
60
|
}
|
|
80
61
|
get capacity() {
|
|
81
|
-
return
|
|
62
|
+
return this.#buffer[0].length;
|
|
82
63
|
}
|
|
83
64
|
get length() {
|
|
84
|
-
return
|
|
65
|
+
return this.#writeIndex - this.#readIndex;
|
|
85
66
|
}
|
|
86
67
|
/** Timestamp of the next media sample after the PCM already emitted. */
|
|
87
68
|
get timestampUs() {
|
|
88
|
-
const anchor =
|
|
89
|
-
if (!anchor || anchor.sampleIndex >
|
|
90
|
-
return anchor.timestampUs + (
|
|
69
|
+
const anchor = this.#timestampAnchors[0];
|
|
70
|
+
if (!anchor || anchor.sampleIndex > this.#readIndex) return void 0;
|
|
71
|
+
return anchor.timestampUs + (this.#readIndex - anchor.sampleIndex) / this.rate * 1e6;
|
|
91
72
|
}
|
|
92
73
|
write(timestampUs, data) {
|
|
93
74
|
if (data.length !== this.channels) throw new Error("wrong channel count");
|
|
@@ -97,68 +78,68 @@ var AudioRingBuffer = class {
|
|
|
97
78
|
if (data.some((channel) => channel.length !== samples)) {
|
|
98
79
|
throw new Error("mismatched channel lengths");
|
|
99
80
|
}
|
|
100
|
-
const start =
|
|
81
|
+
const start = this.#writeIndex;
|
|
101
82
|
const end = start + samples;
|
|
102
|
-
|
|
103
|
-
const overflow = end -
|
|
83
|
+
this.#timestampAnchors.push({ sampleIndex: start, timestampUs });
|
|
84
|
+
const overflow = end - this.#readIndex - this.capacity;
|
|
104
85
|
if (overflow > 0) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
|
|
86
|
+
this.#stalled = false;
|
|
87
|
+
this.#hasStarted = true;
|
|
88
|
+
this.#readIndex += overflow;
|
|
89
|
+
if (this.#diagEnabled) this.#diagOverflowSamples += overflow;
|
|
90
|
+
this.#pruneTimestampAnchors();
|
|
110
91
|
}
|
|
111
|
-
const refillFrames =
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
92
|
+
const refillFrames = this.#hasStarted ? this.#resumeFrames : this.#startupFrames;
|
|
93
|
+
if (this.#stalled && end - this.#readIndex >= refillFrames) {
|
|
94
|
+
this.#stalled = false;
|
|
95
|
+
this.#hasStarted = true;
|
|
115
96
|
}
|
|
116
97
|
for (let channel = 0; channel < this.channels; channel++) {
|
|
117
98
|
const src = data[channel];
|
|
118
|
-
const dst =
|
|
99
|
+
const dst = this.#buffer[channel];
|
|
119
100
|
for (let i = 0; i < samples; i++) {
|
|
120
101
|
dst[(start + i) % dst.length] = src[i];
|
|
121
102
|
}
|
|
122
103
|
}
|
|
123
|
-
|
|
104
|
+
this.#writeIndex = end;
|
|
124
105
|
}
|
|
125
106
|
read(output) {
|
|
126
107
|
if (output.length !== this.channels) throw new Error("wrong channel count");
|
|
127
|
-
if (
|
|
108
|
+
if (this.#stalled) return 0;
|
|
128
109
|
const available = this.length;
|
|
129
110
|
if (available === 0) {
|
|
130
|
-
|
|
111
|
+
this.#stalled = true;
|
|
131
112
|
return 0;
|
|
132
113
|
}
|
|
133
114
|
const requested = output[0].length;
|
|
134
115
|
const readSamples = Math.min(available, requested);
|
|
135
116
|
for (let channel = 0; channel < this.channels; channel++) {
|
|
136
117
|
const dst = output[channel];
|
|
137
|
-
const src =
|
|
118
|
+
const src = this.#buffer[channel];
|
|
138
119
|
for (let i = 0; i < readSamples; i++) {
|
|
139
|
-
dst[i] = src[(
|
|
120
|
+
dst[i] = src[(this.#readIndex + i) % src.length];
|
|
140
121
|
}
|
|
141
122
|
for (let i = readSamples; i < requested; i++) dst[i] = 0;
|
|
142
123
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (readSamples < requested)
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
124
|
+
this.#readIndex += readSamples;
|
|
125
|
+
this.#pruneTimestampAnchors();
|
|
126
|
+
if (readSamples < requested) this.#stalled = true;
|
|
127
|
+
if (this.#diagEnabled) {
|
|
128
|
+
this.#diagReads++;
|
|
129
|
+
this.#diagFillSum += available;
|
|
149
130
|
}
|
|
150
131
|
return readSamples;
|
|
151
132
|
}
|
|
152
133
|
/** Clear queued PCM; optionally treat the next fill as post-start recovery. */
|
|
153
134
|
reset(startUnstalled = false, resumeAfterReset = false) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
135
|
+
this.#writeIndex = 0;
|
|
136
|
+
this.#readIndex = 0;
|
|
137
|
+
this.#stalled = !startUnstalled;
|
|
138
|
+
this.#hasStarted = startUnstalled || resumeAfterReset;
|
|
139
|
+
this.#timestampAnchors = [];
|
|
140
|
+
this.#diagReads = 0;
|
|
141
|
+
this.#diagFillSum = 0;
|
|
142
|
+
this.#diagOverflowSamples = 0;
|
|
162
143
|
}
|
|
163
144
|
/**
|
|
164
145
|
* Change the startup latency while preserving the configured storage
|
|
@@ -173,18 +154,18 @@ var AudioRingBuffer = class {
|
|
|
173
154
|
if (!Number.isSafeInteger(newStartupFrames) || newStartupFrames <= 0) {
|
|
174
155
|
throw new Error("invalid latency");
|
|
175
156
|
}
|
|
176
|
-
const newResumeFrames =
|
|
177
|
-
const newCapacity = Math.max(newStartupFrames, newResumeFrames) +
|
|
157
|
+
const newResumeFrames = this.#resumeFollowsStartup ? newStartupFrames : this.#resumeFrames;
|
|
158
|
+
const newCapacity = Math.max(newStartupFrames, newResumeFrames) + this.#capacityHeadroomFrames;
|
|
178
159
|
if (!Number.isSafeInteger(newCapacity) || newCapacity < Math.max(newStartupFrames, newResumeFrames)) {
|
|
179
160
|
throw new Error("invalid capacity frames");
|
|
180
161
|
}
|
|
181
|
-
const wasStalled =
|
|
182
|
-
|
|
183
|
-
|
|
162
|
+
const wasStalled = this.#stalled;
|
|
163
|
+
this.#startupFrames = newStartupFrames;
|
|
164
|
+
this.#resumeFrames = newResumeFrames;
|
|
184
165
|
if (newCapacity === this.capacity) {
|
|
185
|
-
if (wasStalled && this.length >=
|
|
186
|
-
|
|
187
|
-
|
|
166
|
+
if (wasStalled && this.length >= this.#refillFrames()) {
|
|
167
|
+
this.#stalled = false;
|
|
168
|
+
this.#hasStarted = true;
|
|
188
169
|
}
|
|
189
170
|
return;
|
|
190
171
|
}
|
|
@@ -193,61 +174,46 @@ var AudioRingBuffer = class {
|
|
|
193
174
|
() => new Float32Array(newCapacity)
|
|
194
175
|
);
|
|
195
176
|
const samplesToKeep = Math.min(this.length, newCapacity);
|
|
196
|
-
const copyStart =
|
|
177
|
+
const copyStart = this.#writeIndex - samplesToKeep;
|
|
197
178
|
for (let channel = 0; channel < this.channels; channel++) {
|
|
198
|
-
const src =
|
|
179
|
+
const src = this.#buffer[channel];
|
|
199
180
|
const dst = next[channel];
|
|
200
181
|
for (let i = 0; i < samplesToKeep; i++) {
|
|
201
182
|
const sampleIndex = copyStart + i;
|
|
202
183
|
dst[sampleIndex % dst.length] = src[sampleIndex % src.length];
|
|
203
184
|
}
|
|
204
185
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (!
|
|
186
|
+
this.#buffer = next;
|
|
187
|
+
this.#readIndex = copyStart;
|
|
188
|
+
this.#pruneTimestampAnchors();
|
|
189
|
+
this.#stalled = samplesToKeep === 0 || wasStalled && samplesToKeep < this.#refillFrames();
|
|
190
|
+
if (!this.#stalled) this.#hasStarted = true;
|
|
210
191
|
}
|
|
211
192
|
getDiagAndReset() {
|
|
212
|
-
if (!
|
|
193
|
+
if (!this.#diagEnabled || this.#diagReads === 0) return null;
|
|
213
194
|
const result = {
|
|
214
|
-
reads:
|
|
215
|
-
avgFill: Math.round(
|
|
195
|
+
reads: this.#diagReads,
|
|
196
|
+
avgFill: Math.round(this.#diagFillSum / this.#diagReads),
|
|
216
197
|
capacity: this.capacity,
|
|
217
198
|
skips: 0,
|
|
218
199
|
dups: 0,
|
|
219
|
-
overflowSamples:
|
|
200
|
+
overflowSamples: this.#diagOverflowSamples
|
|
220
201
|
};
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
202
|
+
this.#diagReads = 0;
|
|
203
|
+
this.#diagFillSum = 0;
|
|
204
|
+
this.#diagOverflowSamples = 0;
|
|
224
205
|
return result;
|
|
225
206
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
_capacityHeadroomFrames = new WeakMap();
|
|
236
|
-
_timestampAnchors = new WeakMap();
|
|
237
|
-
_diagEnabled = new WeakMap();
|
|
238
|
-
_diagReads = new WeakMap();
|
|
239
|
-
_diagFillSum = new WeakMap();
|
|
240
|
-
_diagOverflowSamples = new WeakMap();
|
|
241
|
-
_AudioRingBuffer_instances = new WeakSet();
|
|
242
|
-
pruneTimestampAnchors_fn = function() {
|
|
243
|
-
let keep = 0;
|
|
244
|
-
while (keep + 1 < __privateGet(this, _timestampAnchors).length && __privateGet(this, _timestampAnchors)[keep + 1].sampleIndex <= __privateGet(this, _readIndex)) {
|
|
245
|
-
keep++;
|
|
207
|
+
#pruneTimestampAnchors() {
|
|
208
|
+
let keep = 0;
|
|
209
|
+
while (keep + 1 < this.#timestampAnchors.length && this.#timestampAnchors[keep + 1].sampleIndex <= this.#readIndex) {
|
|
210
|
+
keep++;
|
|
211
|
+
}
|
|
212
|
+
if (keep > 0) this.#timestampAnchors.splice(0, keep);
|
|
213
|
+
}
|
|
214
|
+
#refillFrames() {
|
|
215
|
+
return this.#hasStarted ? this.#resumeFrames : this.#startupFrames;
|
|
246
216
|
}
|
|
247
|
-
if (keep > 0) __privateGet(this, _timestampAnchors).splice(0, keep);
|
|
248
|
-
};
|
|
249
|
-
refillFrames_fn = function() {
|
|
250
|
-
return __privateGet(this, _hasStarted) ? __privateGet(this, _resumeFrames) : __privateGet(this, _startupFrames);
|
|
251
217
|
};
|
|
252
218
|
export {
|
|
253
219
|
AudioRingBuffer
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/audio/audio-ring-buffer.ts"],
|
|
4
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"],
|
|
5
|
-
"mappings": "
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|