@openmoq/playback 0.0.0-stage → 0.6.1
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/LICENSE.txt +202 -0
- package/README.md +18 -2
- package/dist/adaptive-tolerance.d.ts +128 -0
- package/dist/adaptive-tolerance.d.ts.map +1 -0
- package/dist/adaptive-tolerance.js +339 -0
- package/dist/adaptive-tolerance.js.map +1 -0
- package/dist/bandwidth-estimator.d.ts +49 -0
- package/dist/bandwidth-estimator.d.ts.map +1 -0
- package/dist/bandwidth-estimator.js +77 -0
- package/dist/bandwidth-estimator.js.map +1 -0
- package/dist/browser/index.global.js +2813 -0
- package/dist/browser/index.global.js.map +1 -0
- package/dist/browser/index.js +2788 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/buffer-based-controller.d.ts +87 -0
- package/dist/buffer-based-controller.d.ts.map +1 -0
- package/dist/buffer-based-controller.js +85 -0
- package/dist/buffer-based-controller.js.map +1 -0
- package/dist/decoder-state.d.ts +103 -0
- package/dist/decoder-state.d.ts.map +1 -0
- package/dist/decoder-state.js +145 -0
- package/dist/decoder-state.js.map +1 -0
- package/dist/gap-detector.d.ts +83 -0
- package/dist/gap-detector.d.ts.map +1 -0
- package/dist/gap-detector.js +129 -0
- package/dist/gap-detector.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/jitter-buffer.d.ts +73 -0
- package/dist/jitter-buffer.d.ts.map +1 -0
- package/dist/jitter-buffer.js +216 -0
- package/dist/jitter-buffer.js.map +1 -0
- package/dist/keyframe-validator.d.ts +26 -0
- package/dist/keyframe-validator.d.ts.map +1 -0
- package/dist/keyframe-validator.js +170 -0
- package/dist/keyframe-validator.js.map +1 -0
- package/dist/pipeline.d.ts +215 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +852 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/recovery.d.ts +166 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +176 -0
- package/dist/recovery.js.map +1 -0
- package/dist/render-scheduler.d.ts +50 -0
- package/dist/render-scheduler.d.ts.map +1 -0
- package/dist/render-scheduler.js +84 -0
- package/dist/render-scheduler.js.map +1 -0
- package/dist/startup-buffer.d.ts +68 -0
- package/dist/startup-buffer.d.ts.map +1 -0
- package/dist/startup-buffer.js +107 -0
- package/dist/startup-buffer.js.map +1 -0
- package/dist/sync.d.ts +234 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +343 -0
- package/dist/sync.js.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -3
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive jitter tolerance controller.
|
|
3
|
+
*
|
|
4
|
+
* Auto-calibrates gap timeout and drift threshold from observed network
|
|
5
|
+
* jitter. Implements graduated recovery (monitoring → extended wait → skip)
|
|
6
|
+
* with stall-count escalation and exponential decay.
|
|
7
|
+
*
|
|
8
|
+
* Sans-I/O: time is an explicit parameter, no browser APIs.
|
|
9
|
+
* All state is flat and mutable in place — zero allocations in the hot path.
|
|
10
|
+
*
|
|
11
|
+
* @see draft-ietf-moq-loc-01 §2.3.1.1 (CaptureTimestamp)
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
// ─── Recovery phases ────────────────────────────────────────────────
|
|
15
|
+
export const RecoveryPhase = {
|
|
16
|
+
/** Baseline — no active jitter issues. */
|
|
17
|
+
Normal: 'normal',
|
|
18
|
+
/** Gap detected, observing whether transient or persistent. */
|
|
19
|
+
Monitoring: 'monitoring',
|
|
20
|
+
/** Persistent gap — waiting with extended timeout before skipping. */
|
|
21
|
+
ExtendedWait: 'extended_wait',
|
|
22
|
+
};
|
|
23
|
+
export const DEFAULT_TOLERANCE_CONFIG = {
|
|
24
|
+
emaAlpha: 0.125, // 1/8 — RFC 6298 gain
|
|
25
|
+
varianceAlpha: 0.25, // 1/4 — RFC 6298 gain
|
|
26
|
+
slidingWindowSize: 128, // ~4s at 30fps
|
|
27
|
+
jitterMultiplier: 4.0, // μ + 4σ ≈ 99.99% of Gaussian
|
|
28
|
+
minGapTimeoutMs: 50, // floor: ~1.5 video frames
|
|
29
|
+
maxGapTimeoutMs: 2000, // ceiling
|
|
30
|
+
minDriftThresholdMs: 80,
|
|
31
|
+
driftThresholdMultiplier: 3.0,
|
|
32
|
+
stallIncrementMs: 33, // 1 video frame interval
|
|
33
|
+
maxStallIncrements: 3, // max 3 × 33ms = ~100ms added
|
|
34
|
+
monitorWindowMs: 500, // observe before escalating
|
|
35
|
+
monitorCleanFrames: 60, // 1s at 60fps tick rate
|
|
36
|
+
monitorCleanDurationMs: 1000,
|
|
37
|
+
maxExtendedWaitMs: 2000,
|
|
38
|
+
skipFrequencyThreshold: 3, // 3 skips in window → quality reduce
|
|
39
|
+
skipFrequencyWindowMs: 30000,
|
|
40
|
+
decayMinCleanFrames: 120, // 2s at 60fps
|
|
41
|
+
decayMinCleanDurationMs: 2000,
|
|
42
|
+
decayFactor: 0.9, // 10% reduction per decay step
|
|
43
|
+
};
|
|
44
|
+
const TICK_NO_SKIP = { shouldSkip: false };
|
|
45
|
+
const TICK_SKIP = { shouldSkip: true };
|
|
46
|
+
// ─── Sliding window max ─────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* O(1) amortized sliding window maximum using a monotonic deque.
|
|
49
|
+
* Pre-allocated, zero-alloc after construction.
|
|
50
|
+
*/
|
|
51
|
+
class SlidingMax {
|
|
52
|
+
buf;
|
|
53
|
+
deq;
|
|
54
|
+
capacity;
|
|
55
|
+
writePos = 0;
|
|
56
|
+
count = 0;
|
|
57
|
+
dHead = 0;
|
|
58
|
+
dTail = 0;
|
|
59
|
+
constructor(capacity) {
|
|
60
|
+
this.capacity = capacity;
|
|
61
|
+
this.buf = new Float64Array(capacity);
|
|
62
|
+
this.deq = new Uint32Array(capacity);
|
|
63
|
+
}
|
|
64
|
+
push(value) {
|
|
65
|
+
const cap = this.capacity;
|
|
66
|
+
const wIdx = this.writePos % cap;
|
|
67
|
+
// Evict expired front
|
|
68
|
+
if (this.count === cap && this.dHead !== this.dTail
|
|
69
|
+
&& this.deq[this.dHead % cap] === wIdx) {
|
|
70
|
+
this.dHead++;
|
|
71
|
+
}
|
|
72
|
+
// Maintain monotonicity from back
|
|
73
|
+
while (this.dHead !== this.dTail
|
|
74
|
+
&& this.buf[this.deq[(this.dTail - 1) % cap] % cap] <= value) {
|
|
75
|
+
this.dTail--;
|
|
76
|
+
}
|
|
77
|
+
this.deq[this.dTail % cap] = wIdx;
|
|
78
|
+
this.dTail++;
|
|
79
|
+
this.buf[wIdx] = value;
|
|
80
|
+
this.writePos++;
|
|
81
|
+
if (this.count < cap)
|
|
82
|
+
this.count++;
|
|
83
|
+
}
|
|
84
|
+
get max() {
|
|
85
|
+
if (this.dHead === this.dTail)
|
|
86
|
+
return 0;
|
|
87
|
+
return this.buf[this.deq[this.dHead % this.capacity] % this.capacity];
|
|
88
|
+
}
|
|
89
|
+
reset() {
|
|
90
|
+
this.writePos = 0;
|
|
91
|
+
this.count = 0;
|
|
92
|
+
this.dHead = 0;
|
|
93
|
+
this.dTail = 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ─── Controller ─────────────────────────────────────────────────────
|
|
97
|
+
/**
|
|
98
|
+
* Adaptive jitter tolerance controller.
|
|
99
|
+
*
|
|
100
|
+
* Consumes frame arrival and gap events, produces auto-calibrated
|
|
101
|
+
* thresholds for gap timeout and drift detection.
|
|
102
|
+
*/
|
|
103
|
+
export class AdaptiveToleranceController {
|
|
104
|
+
cfg;
|
|
105
|
+
// ── Jitter estimation ──
|
|
106
|
+
_jitterEma = 0;
|
|
107
|
+
_jitterVar = 0;
|
|
108
|
+
_lastTransitMs = 0;
|
|
109
|
+
_hasLastTransit = false;
|
|
110
|
+
_slidingMax;
|
|
111
|
+
// ── Phase state ──
|
|
112
|
+
_phase = RecoveryPhase.Normal;
|
|
113
|
+
_phaseEnteredMs = 0;
|
|
114
|
+
_monitorGapCount = 0;
|
|
115
|
+
_stallCount = 0;
|
|
116
|
+
_stallEscalationMs = 0;
|
|
117
|
+
// ── Skip frequency tracking ──
|
|
118
|
+
_skipTimestamps = [];
|
|
119
|
+
// ── Clean streak tracking (for monitoring exit + decay) ──
|
|
120
|
+
_cleanFrameCount = 0;
|
|
121
|
+
_cleanStreakStartMs = 0;
|
|
122
|
+
// ── Quality reduction ──
|
|
123
|
+
_shouldReduceQuality = false;
|
|
124
|
+
// ── Protocol signals ──
|
|
125
|
+
_deliveryTimeoutMs = 0;
|
|
126
|
+
// ── Capture timestamp jitter (for drift threshold) ──
|
|
127
|
+
_lastCaptureUs = 0;
|
|
128
|
+
_hasCaptureRef = false;
|
|
129
|
+
_captureJitterEma = 0;
|
|
130
|
+
constructor(config) {
|
|
131
|
+
this.cfg = config;
|
|
132
|
+
this._slidingMax = new SlidingMax(config.slidingWindowSize);
|
|
133
|
+
}
|
|
134
|
+
// ─── Read-only state ──────────────────────────────────────────
|
|
135
|
+
get phase() { return this._phase; }
|
|
136
|
+
get jitterEmaMs() { return this._jitterEma; }
|
|
137
|
+
get jitterVarMs() { return this._jitterVar; }
|
|
138
|
+
get slidingMaxMs() { return this._slidingMax.max; }
|
|
139
|
+
get stallCount() { return this._stallCount; }
|
|
140
|
+
get shouldReduceQuality() { return this._shouldReduceQuality; }
|
|
141
|
+
/**
|
|
142
|
+
* Auto-calibrated gap timeout in milliseconds.
|
|
143
|
+
*
|
|
144
|
+
* baseline = max(minGap, EMA + K × variance, slidingMax)
|
|
145
|
+
* effective = min(baseline + stallEscalation, maxGap, deliveryTimeout)
|
|
146
|
+
*/
|
|
147
|
+
get effectiveGapTimeoutMs() {
|
|
148
|
+
const adaptive = Math.max(this.cfg.minGapTimeoutMs, this._jitterEma + this.cfg.jitterMultiplier * this._jitterVar, this._slidingMax.max);
|
|
149
|
+
const withStall = adaptive + this._stallEscalationMs;
|
|
150
|
+
let capped = Math.min(withStall, this.cfg.maxGapTimeoutMs);
|
|
151
|
+
if (this._deliveryTimeoutMs > 0) {
|
|
152
|
+
capped = Math.min(capped, this._deliveryTimeoutMs);
|
|
153
|
+
}
|
|
154
|
+
return capped;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Auto-calibrated drift threshold in milliseconds.
|
|
158
|
+
*
|
|
159
|
+
* Based on observed CaptureTimestamp interarrival jitter.
|
|
160
|
+
*/
|
|
161
|
+
get effectiveDriftThresholdMs() {
|
|
162
|
+
if (!this._hasCaptureRef || this._captureJitterEma === 0) {
|
|
163
|
+
return this.cfg.minDriftThresholdMs;
|
|
164
|
+
}
|
|
165
|
+
return Math.max(this.cfg.minDriftThresholdMs, this._captureJitterEma * this.cfg.driftThresholdMultiplier);
|
|
166
|
+
}
|
|
167
|
+
// ─── Events IN ────────────────────────────────────────────────
|
|
168
|
+
/**
|
|
169
|
+
* Notify that a frame arrived.
|
|
170
|
+
*
|
|
171
|
+
* @param nowMs Current time in milliseconds
|
|
172
|
+
* @param captureUs CaptureTimestamp in microseconds
|
|
173
|
+
* @param arrivalMs Arrival time in milliseconds
|
|
174
|
+
*/
|
|
175
|
+
onFrameArrived(nowMs, captureUs, arrivalMs) {
|
|
176
|
+
// Update interarrival jitter EMA (EWMA over transit deltas)
|
|
177
|
+
const transitMs = arrivalMs - captureUs * 0.001;
|
|
178
|
+
if (this._hasLastTransit) {
|
|
179
|
+
const d = transitMs - this._lastTransitMs;
|
|
180
|
+
const absD = d < 0 ? -d : d;
|
|
181
|
+
// EWMA update: α = 1/8, β = 1/4 (RFC 6298)
|
|
182
|
+
const emaErr = absD - this._jitterEma;
|
|
183
|
+
this._jitterEma += this.cfg.emaAlpha * emaErr;
|
|
184
|
+
this._jitterVar += this.cfg.varianceAlpha * (absD - this._jitterVar);
|
|
185
|
+
// Sliding window max
|
|
186
|
+
this._slidingMax.push(absD);
|
|
187
|
+
}
|
|
188
|
+
this._lastTransitMs = transitMs;
|
|
189
|
+
this._hasLastTransit = true;
|
|
190
|
+
// Capture timestamp jitter (for drift threshold)
|
|
191
|
+
if (this._hasCaptureRef) {
|
|
192
|
+
const captureIntervalUs = captureUs - this._lastCaptureUs;
|
|
193
|
+
// Expected ~33333 for 30fps, ~20000 for audio — we don't know, just track variance
|
|
194
|
+
const captureJitterMs = Math.abs(captureIntervalUs) * 0.001;
|
|
195
|
+
// Track the variance of capture intervals, not absolute value
|
|
196
|
+
if (this._captureJitterEma === 0) {
|
|
197
|
+
this._captureJitterEma = captureJitterMs;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
this._captureJitterEma += this.cfg.emaAlpha * (captureJitterMs - this._captureJitterEma);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
this._lastCaptureUs = captureUs;
|
|
204
|
+
this._hasCaptureRef = true;
|
|
205
|
+
// Clean streak tracking
|
|
206
|
+
this._cleanFrameCount++;
|
|
207
|
+
if (this._cleanFrameCount === 1) {
|
|
208
|
+
this._cleanStreakStartMs = nowMs;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Notify that a gap was detected.
|
|
213
|
+
*
|
|
214
|
+
* @param nowMs Current time in milliseconds
|
|
215
|
+
* @param groupId The group ID where the gap was detected
|
|
216
|
+
*/
|
|
217
|
+
onGapDetected(nowMs, _groupId) {
|
|
218
|
+
this._cleanFrameCount = 0;
|
|
219
|
+
switch (this._phase) {
|
|
220
|
+
case RecoveryPhase.Normal:
|
|
221
|
+
this._phase = RecoveryPhase.Monitoring;
|
|
222
|
+
this._phaseEnteredMs = nowMs;
|
|
223
|
+
this._monitorGapCount = 1;
|
|
224
|
+
break;
|
|
225
|
+
case RecoveryPhase.Monitoring:
|
|
226
|
+
this._monitorGapCount++;
|
|
227
|
+
if (this._monitorGapCount >= 2
|
|
228
|
+
&& nowMs - this._phaseEnteredMs <= this.cfg.monitorWindowMs) {
|
|
229
|
+
// Persistent gap — escalate
|
|
230
|
+
this._phase = RecoveryPhase.ExtendedWait;
|
|
231
|
+
this._phaseEnteredMs = nowMs;
|
|
232
|
+
}
|
|
233
|
+
break;
|
|
234
|
+
case RecoveryPhase.ExtendedWait:
|
|
235
|
+
// Already waiting — no further escalation
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Periodic tick — evaluate phase transitions and decay.
|
|
241
|
+
*
|
|
242
|
+
* @param nowMs Current time in milliseconds
|
|
243
|
+
* @returns TickResult indicating whether caller should skip forward
|
|
244
|
+
*/
|
|
245
|
+
tick(nowMs) {
|
|
246
|
+
switch (this._phase) {
|
|
247
|
+
case RecoveryPhase.Monitoring: {
|
|
248
|
+
// Check if monitoring window expired without 2nd gap → clean exit
|
|
249
|
+
if (nowMs - this._phaseEnteredMs > this.cfg.monitorWindowMs) {
|
|
250
|
+
// Check clean period
|
|
251
|
+
if (this._cleanFrameCount >= this.cfg.monitorCleanFrames
|
|
252
|
+
&& nowMs - this._cleanStreakStartMs >= this.cfg.monitorCleanDurationMs) {
|
|
253
|
+
this._phase = RecoveryPhase.Normal;
|
|
254
|
+
return TICK_NO_SKIP;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
case RecoveryPhase.ExtendedWait: {
|
|
260
|
+
if (nowMs - this._phaseEnteredMs >= this.cfg.maxExtendedWaitMs) {
|
|
261
|
+
// Timeout — skip forward
|
|
262
|
+
this._stallCount++;
|
|
263
|
+
this._stallEscalationMs = Math.min(this._stallCount * this.cfg.stallIncrementMs, this.cfg.maxStallIncrements * this.cfg.stallIncrementMs);
|
|
264
|
+
// Track skip frequency for quality reduction
|
|
265
|
+
this._skipTimestamps.push(nowMs);
|
|
266
|
+
this._pruneSkipTimestamps(nowMs);
|
|
267
|
+
if (this._skipTimestamps.length >= this.cfg.skipFrequencyThreshold) {
|
|
268
|
+
this._shouldReduceQuality = true;
|
|
269
|
+
}
|
|
270
|
+
this._phase = RecoveryPhase.Normal;
|
|
271
|
+
this._cleanFrameCount = 0;
|
|
272
|
+
return TICK_SKIP;
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
case RecoveryPhase.Normal: {
|
|
277
|
+
// Decay stall escalation during clean periods
|
|
278
|
+
if (this._stallEscalationMs > 0
|
|
279
|
+
&& this._cleanFrameCount >= this.cfg.decayMinCleanFrames
|
|
280
|
+
&& this._cleanStreakStartMs > 0
|
|
281
|
+
&& nowMs - this._cleanStreakStartMs >= this.cfg.decayMinCleanDurationMs) {
|
|
282
|
+
this._stallEscalationMs *= this.cfg.decayFactor;
|
|
283
|
+
if (this._stallEscalationMs < 1) {
|
|
284
|
+
this._stallEscalationMs = 0;
|
|
285
|
+
this._stallCount = 0;
|
|
286
|
+
}
|
|
287
|
+
// Reset clean counters for next decay step
|
|
288
|
+
this._cleanFrameCount = 0;
|
|
289
|
+
this._cleanStreakStartMs = nowMs;
|
|
290
|
+
}
|
|
291
|
+
// Decay quality reduction flag
|
|
292
|
+
if (this._shouldReduceQuality) {
|
|
293
|
+
this._pruneSkipTimestamps(nowMs);
|
|
294
|
+
if (this._skipTimestamps.length < this.cfg.skipFrequencyThreshold) {
|
|
295
|
+
this._shouldReduceQuality = false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return TICK_NO_SKIP;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Set DELIVERY_TIMEOUT from protocol negotiation.
|
|
305
|
+
* Caps the effective gap timeout.
|
|
306
|
+
*
|
|
307
|
+
* @see draft-ietf-moq-transport-16 §9.2.2.2
|
|
308
|
+
*/
|
|
309
|
+
setDeliveryTimeout(timeoutMs) {
|
|
310
|
+
this._deliveryTimeoutMs = timeoutMs;
|
|
311
|
+
}
|
|
312
|
+
/** Reset all state (seek, pause→resume). */
|
|
313
|
+
reset() {
|
|
314
|
+
this._jitterEma = 0;
|
|
315
|
+
this._jitterVar = 0;
|
|
316
|
+
this._lastTransitMs = 0;
|
|
317
|
+
this._hasLastTransit = false;
|
|
318
|
+
this._slidingMax.reset();
|
|
319
|
+
this._phase = RecoveryPhase.Normal;
|
|
320
|
+
this._phaseEnteredMs = 0;
|
|
321
|
+
this._monitorGapCount = 0;
|
|
322
|
+
this._stallCount = 0;
|
|
323
|
+
this._stallEscalationMs = 0;
|
|
324
|
+
this._skipTimestamps = [];
|
|
325
|
+
this._cleanFrameCount = 0;
|
|
326
|
+
this._cleanStreakStartMs = 0;
|
|
327
|
+
this._shouldReduceQuality = false;
|
|
328
|
+
this._deliveryTimeoutMs = 0;
|
|
329
|
+
this._lastCaptureUs = 0;
|
|
330
|
+
this._hasCaptureRef = false;
|
|
331
|
+
this._captureJitterEma = 0;
|
|
332
|
+
}
|
|
333
|
+
// ─── Internal ─────────────────────────────────────────────────
|
|
334
|
+
_pruneSkipTimestamps(nowMs) {
|
|
335
|
+
const windowStart = nowMs - this.cfg.skipFrequencyWindowMs;
|
|
336
|
+
this._skipTimestamps = this._skipTimestamps.filter(t => t >= windowStart);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=adaptive-tolerance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-tolerance.js","sourceRoot":"","sources":["../src/adaptive-tolerance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BandwidthEstimator — EWMA-smoothed bandwidth estimation from
|
|
3
|
+
* group arrival rate.
|
|
4
|
+
*
|
|
5
|
+
* MoQ/QUIC has no HTTP request/response cycle to time, so traditional
|
|
6
|
+
* ABR segment-download bandwidth estimation doesn't apply. Instead we
|
|
7
|
+
* measure the effective throughput from object arrival timing:
|
|
8
|
+
*
|
|
9
|
+
* instantaneous_bps = group_size_bytes * 8 / inter_arrival_seconds
|
|
10
|
+
*
|
|
11
|
+
* Smoothed with EWMA (Exponentially Weighted Moving Average) to filter
|
|
12
|
+
* single-sample spikes and bursty delivery patterns (conference WiFi).
|
|
13
|
+
*
|
|
14
|
+
* The estimate drives proactive quality decisions — downshift BEFORE
|
|
15
|
+
* stalls occur, based on arrival rate vs current track bitrate.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
export declare class BandwidthEstimator {
|
|
20
|
+
private readonly alpha;
|
|
21
|
+
private readonly minSamples;
|
|
22
|
+
private estimateBps;
|
|
23
|
+
private lastTimestampUs;
|
|
24
|
+
private lastGroupBytes;
|
|
25
|
+
private sampleCount;
|
|
26
|
+
/**
|
|
27
|
+
* @param alpha EWMA smoothing factor (0-1). Higher = more responsive
|
|
28
|
+
* to recent samples, lower = smoother. Default 0.3.
|
|
29
|
+
* @param minSamples Minimum group arrivals before reporting a non-zero
|
|
30
|
+
* estimate. Early samples include connection latency
|
|
31
|
+
* and aren't representative. Default 5 (~10s at 2s GOPs).
|
|
32
|
+
*/
|
|
33
|
+
constructor(alpha?: number, minSamples?: number);
|
|
34
|
+
/**
|
|
35
|
+
* Record a group arrival. Call once per complete group (GOP).
|
|
36
|
+
*
|
|
37
|
+
* @param bytes Total bytes in the group.
|
|
38
|
+
* @param timestampUs Arrival timestamp in microseconds.
|
|
39
|
+
*/
|
|
40
|
+
recordGroup(bytes: number, timestampUs: number): void;
|
|
41
|
+
/**
|
|
42
|
+
* Current smoothed bandwidth estimate in kbps.
|
|
43
|
+
* Returns 0 if fewer than 2 groups have been recorded.
|
|
44
|
+
*/
|
|
45
|
+
getEstimateKbps(): number;
|
|
46
|
+
/** Reset all state. */
|
|
47
|
+
reset(): void;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=bandwidth-estimator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bandwidth-estimator.d.ts","sourceRoot":"","sources":["../src/bandwidth-estimator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,qBAAa,kBAAkB;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,WAAW,CAAK;IAExB;;;;;;OAMG;gBACS,KAAK,SAAM,EAAE,UAAU,SAAI;IAKvC;;;;;OAKG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAiBrD;;;OAGG;IACH,eAAe,IAAI,MAAM;IAKzB,uBAAuB;IACvB,KAAK,IAAI,IAAI;CAMhB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BandwidthEstimator — EWMA-smoothed bandwidth estimation from
|
|
3
|
+
* group arrival rate.
|
|
4
|
+
*
|
|
5
|
+
* MoQ/QUIC has no HTTP request/response cycle to time, so traditional
|
|
6
|
+
* ABR segment-download bandwidth estimation doesn't apply. Instead we
|
|
7
|
+
* measure the effective throughput from object arrival timing:
|
|
8
|
+
*
|
|
9
|
+
* instantaneous_bps = group_size_bytes * 8 / inter_arrival_seconds
|
|
10
|
+
*
|
|
11
|
+
* Smoothed with EWMA (Exponentially Weighted Moving Average) to filter
|
|
12
|
+
* single-sample spikes and bursty delivery patterns (conference WiFi).
|
|
13
|
+
*
|
|
14
|
+
* The estimate drives proactive quality decisions — downshift BEFORE
|
|
15
|
+
* stalls occur, based on arrival rate vs current track bitrate.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
export class BandwidthEstimator {
|
|
20
|
+
alpha;
|
|
21
|
+
minSamples;
|
|
22
|
+
estimateBps = null;
|
|
23
|
+
lastTimestampUs = null;
|
|
24
|
+
lastGroupBytes = null;
|
|
25
|
+
sampleCount = 0;
|
|
26
|
+
/**
|
|
27
|
+
* @param alpha EWMA smoothing factor (0-1). Higher = more responsive
|
|
28
|
+
* to recent samples, lower = smoother. Default 0.3.
|
|
29
|
+
* @param minSamples Minimum group arrivals before reporting a non-zero
|
|
30
|
+
* estimate. Early samples include connection latency
|
|
31
|
+
* and aren't representative. Default 5 (~10s at 2s GOPs).
|
|
32
|
+
*/
|
|
33
|
+
constructor(alpha = 0.3, minSamples = 5) {
|
|
34
|
+
this.alpha = alpha;
|
|
35
|
+
this.minSamples = minSamples;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Record a group arrival. Call once per complete group (GOP).
|
|
39
|
+
*
|
|
40
|
+
* @param bytes Total bytes in the group.
|
|
41
|
+
* @param timestampUs Arrival timestamp in microseconds.
|
|
42
|
+
*/
|
|
43
|
+
recordGroup(bytes, timestampUs) {
|
|
44
|
+
if (this.lastTimestampUs !== null && this.lastGroupBytes !== null) {
|
|
45
|
+
const deltaUs = timestampUs - this.lastTimestampUs;
|
|
46
|
+
if (deltaUs > 0) {
|
|
47
|
+
this.sampleCount++;
|
|
48
|
+
const instantBps = (bytes * 8) / (deltaUs / 1_000_000);
|
|
49
|
+
if (this.estimateBps === null) {
|
|
50
|
+
this.estimateBps = instantBps;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this.estimateBps = this.alpha * instantBps + (1 - this.alpha) * this.estimateBps;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
this.lastTimestampUs = timestampUs;
|
|
58
|
+
this.lastGroupBytes = bytes;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Current smoothed bandwidth estimate in kbps.
|
|
62
|
+
* Returns 0 if fewer than 2 groups have been recorded.
|
|
63
|
+
*/
|
|
64
|
+
getEstimateKbps() {
|
|
65
|
+
if (this.estimateBps === null || this.sampleCount < this.minSamples)
|
|
66
|
+
return 0;
|
|
67
|
+
return this.estimateBps / 1000;
|
|
68
|
+
}
|
|
69
|
+
/** Reset all state. */
|
|
70
|
+
reset() {
|
|
71
|
+
this.estimateBps = null;
|
|
72
|
+
this.lastTimestampUs = null;
|
|
73
|
+
this.lastGroupBytes = null;
|
|
74
|
+
this.sampleCount = 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=bandwidth-estimator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bandwidth-estimator.js","sourceRoot":"","sources":["../src/bandwidth-estimator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,OAAO,kBAAkB;IACV,KAAK,CAAS;IACd,UAAU,CAAS;IAC5B,WAAW,GAAkB,IAAI,CAAC;IAClC,eAAe,GAAkB,IAAI,CAAC;IACtC,cAAc,GAAkB,IAAI,CAAC;IACrC,WAAW,GAAG,CAAC,CAAC;IAExB;;;;;;OAMG;IACH,YAAY,KAAK,GAAG,GAAG,EAAE,UAAU,GAAG,CAAC;QACnC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IACjC,CAAC;IAED;;;;;OAKG;IACH,WAAW,CAAC,KAAa,EAAE,WAAmB;QAC1C,IAAI,IAAI,CAAC,eAAe,KAAK,IAAI,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;YAChE,MAAM,OAAO,GAAG,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC;YACnD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;gBACd,IAAI,CAAC,WAAW,EAAE,CAAC;gBACnB,MAAM,UAAU,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;gBACvD,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;oBAC5B,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;gBAClC,CAAC;qBAAM,CAAC;oBACJ,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,KAAK,GAAG,UAAU,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC;gBACrF,CAAC;YACL,CAAC;QACL,CAAC;QACD,IAAI,CAAC,eAAe,GAAG,WAAW,CAAC;QACnC,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;IAChC,CAAC;IAED;;;OAGG;IACH,eAAe;QACX,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,IAAI,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,UAAU;YAAE,OAAO,CAAC,CAAC;QAC9E,OAAO,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IACnC,CAAC;IAED,uBAAuB;IACvB,KAAK;QACD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC5B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;IACzB,CAAC;CACJ"}
|