mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
build(spf): build26 from 45504a2b
This commit is contained in:
+50
@@ -0,0 +1,50 @@
|
||||
//#region src/network/bandwidth-estimator.d.ts
|
||||
/**
|
||||
* Dual EWMA Bandwidth Estimator
|
||||
*
|
||||
* Estimates available bandwidth using two EWMA calculations with different
|
||||
* half-lives, taking the minimum of both. This approach (from Shaka Player):
|
||||
*
|
||||
* - **Fast EWMA** (2s half-life): Reacts quickly to bandwidth drops
|
||||
* - **Slow EWMA** (5s half-life): Provides stability during fluctuations
|
||||
* - **min(fast, slow)**: Adapts down quickly, up slowly
|
||||
*
|
||||
* This naturally provides asymmetric behavior needed for good QoE:
|
||||
* avoiding stalls (quick downgrade) while preventing oscillation (slow upgrade).
|
||||
*/
|
||||
/**
|
||||
* Bandwidth estimator state.
|
||||
*
|
||||
* This state structure will be managed by O1 (State Container).
|
||||
* Functions in this module operate on this state immutably.
|
||||
*/
|
||||
interface BandwidthState {
|
||||
/** Fast-moving EWMA estimate (raw, uncorrected). */
|
||||
fastEstimate: number;
|
||||
/** Total weight accumulated in fast EWMA. */
|
||||
fastTotalWeight: number;
|
||||
/** Slow-moving EWMA estimate (raw, uncorrected). */
|
||||
slowEstimate: number;
|
||||
/** Total weight accumulated in slow EWMA. */
|
||||
slowTotalWeight: number;
|
||||
/** Total bytes sampled across all valid samples. */
|
||||
bytesSampled: number;
|
||||
}
|
||||
/**
|
||||
* Configuration for bandwidth estimation.
|
||||
*/
|
||||
interface BandwidthConfig {
|
||||
/** Half-life for fast EWMA in seconds. */
|
||||
fastHalfLife: number;
|
||||
/** Half-life for slow EWMA in seconds. */
|
||||
slowHalfLife: number;
|
||||
/** Minimum total bytes before trusting the estimate. */
|
||||
minTotalBytes: number;
|
||||
/** Minimum bytes per sample to count (filters TTFB-dominated samples). */
|
||||
minBytes: number;
|
||||
/** Minimum sample duration in ms (filters cached responses). */
|
||||
minDuration: number;
|
||||
}
|
||||
//#endregion
|
||||
export { BandwidthConfig, BandwidthState };
|
||||
//# sourceMappingURL=bandwidth-estimator.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"bandwidth-estimator.d.ts","names":[],"sources":["../../../src/network/bandwidth-estimator.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;UAsBiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;;;UAMe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA"}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import { applyZeroFactor, calculateEwma } from "./ewma.js";
|
||||
//#region src/network/bandwidth-estimator.ts
|
||||
/**
|
||||
* Dual EWMA Bandwidth Estimator
|
||||
*
|
||||
* Estimates available bandwidth using two EWMA calculations with different
|
||||
* half-lives, taking the minimum of both. This approach (from Shaka Player):
|
||||
*
|
||||
* - **Fast EWMA** (2s half-life): Reacts quickly to bandwidth drops
|
||||
* - **Slow EWMA** (5s half-life): Provides stability during fluctuations
|
||||
* - **min(fast, slow)**: Adapts down quickly, up slowly
|
||||
*
|
||||
* This naturally provides asymmetric behavior needed for good QoE:
|
||||
* avoiding stalls (quick downgrade) while preventing oscillation (slow upgrade).
|
||||
*/
|
||||
/**
|
||||
* Default bandwidth estimator configuration.
|
||||
*
|
||||
* Values match Shaka Player defaults based on experimentation.
|
||||
*/
|
||||
const DEFAULT_BANDWIDTH_CONFIG = {
|
||||
fastHalfLife: 2,
|
||||
slowHalfLife: 5,
|
||||
minTotalBytes: 128e3,
|
||||
minBytes: 16e3,
|
||||
minDuration: 5
|
||||
};
|
||||
/**
|
||||
* Add a bandwidth sample from a segment download.
|
||||
*
|
||||
* Samples are filtered based on:
|
||||
* - Minimum bytes (filters TTFB-dominated small segments)
|
||||
* - Minimum duration (filters cached responses)
|
||||
*
|
||||
* Valid samples update both fast and slow EWMA estimates.
|
||||
*
|
||||
* @param state - Current estimator state
|
||||
* @param durationMs - Download duration in milliseconds
|
||||
* @param numBytes - Number of bytes downloaded
|
||||
* @param config - Optional estimator configuration (uses defaults if not provided)
|
||||
* @returns New estimator state with sample incorporated (or unchanged if filtered)
|
||||
*
|
||||
* @example
|
||||
* let state = { fastEstimate: 0, fastTotalWeight: 0, ... };
|
||||
* // Sample: 1MB in 1 second
|
||||
* state = sampleBandwidth(state, 1000, 1_000_000);
|
||||
*/
|
||||
function sampleBandwidth(state, durationMs, numBytes, config = DEFAULT_BANDWIDTH_CONFIG) {
|
||||
const updatedBytesSampled = state.bytesSampled + numBytes;
|
||||
if (numBytes < config.minBytes) return {
|
||||
...state,
|
||||
bytesSampled: updatedBytesSampled
|
||||
};
|
||||
if (durationMs < config.minDuration) return {
|
||||
...state,
|
||||
bytesSampled: updatedBytesSampled
|
||||
};
|
||||
const bandwidth = 8e3 * numBytes / durationMs;
|
||||
const weight = durationMs / 1e3;
|
||||
return {
|
||||
fastEstimate: calculateEwma(state.fastEstimate, bandwidth, weight, config.fastHalfLife),
|
||||
fastTotalWeight: state.fastTotalWeight + weight,
|
||||
slowEstimate: calculateEwma(state.slowEstimate, bandwidth, weight, config.slowHalfLife),
|
||||
slowTotalWeight: state.slowTotalWeight + weight,
|
||||
bytesSampled: updatedBytesSampled
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get the current bandwidth estimate.
|
||||
*
|
||||
* Returns the **minimum** of the fast and slow EWMA estimates.
|
||||
* This provides the key asymmetric behavior:
|
||||
* - When bandwidth drops, fast EWMA reacts first and dominates (quick adaptation)
|
||||
* - When bandwidth rises, slow EWMA lags behind and dominates (slow adaptation)
|
||||
*
|
||||
* Uses default estimate until enough data has been sampled — and when no
|
||||
* estimator state exists at all (`state === undefined`).
|
||||
*
|
||||
* @param state - Current estimator state, or `undefined` before any samples have been collected
|
||||
* @param defaultEstimate - Fallback estimate before sufficient samples (bps)
|
||||
* @param config - Optional estimator configuration (uses defaults if not provided)
|
||||
* @returns Bandwidth estimate in bits per second
|
||||
*
|
||||
* @example
|
||||
* const estimate = getBandwidthEstimate(state, 5_000_000); // 5 Mbps default
|
||||
*/
|
||||
function getBandwidthEstimate(state, defaultEstimate, config = DEFAULT_BANDWIDTH_CONFIG) {
|
||||
if (!state || state.bytesSampled < config.minTotalBytes) return defaultEstimate;
|
||||
const fastEstimate = applyZeroFactor(state.fastEstimate, state.fastTotalWeight, config.fastHalfLife);
|
||||
const slowEstimate = applyZeroFactor(state.slowEstimate, state.slowTotalWeight, config.slowHalfLife);
|
||||
return Math.min(fastEstimate, slowEstimate);
|
||||
}
|
||||
//#endregion
|
||||
export { DEFAULT_BANDWIDTH_CONFIG, getBandwidthEstimate, sampleBandwidth };
|
||||
|
||||
//# sourceMappingURL=bandwidth-estimator.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+49
@@ -0,0 +1,49 @@
|
||||
//#region src/network/chunked-stream-iterable.ts
|
||||
const DEFAULT_MIN_CHUNK_SIZE = 2 ** 17;
|
||||
/**
|
||||
* Adapts a `ReadableStream<Uint8Array>` (e.g. `response.body`) into an
|
||||
* `AsyncIterable<Uint8Array>` that yields chunks no smaller than
|
||||
* `minChunkSize` bytes. Smaller network chunks are accumulated and yielded
|
||||
* together once the threshold is met. Any remainder is flushed on stream end.
|
||||
*
|
||||
* Errors from the underlying stream propagate naturally — the reader lock is
|
||||
* always released via `finally`.
|
||||
*/
|
||||
var ChunkedStreamIterable = class {
|
||||
minChunkSize;
|
||||
#readableStream;
|
||||
constructor(readableStream, { minChunkSize = DEFAULT_MIN_CHUNK_SIZE } = {}) {
|
||||
this.#readableStream = readableStream;
|
||||
this.minChunkSize = minChunkSize;
|
||||
}
|
||||
async *[Symbol.asyncIterator]() {
|
||||
let pending;
|
||||
const reader = this.#readableStream.getReader();
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
if (pending) yield pending;
|
||||
break;
|
||||
}
|
||||
pending = pending ? concat(pending, value) : value;
|
||||
if (pending.length >= this.minChunkSize) {
|
||||
yield pending;
|
||||
pending = void 0;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
};
|
||||
function concat(a, b) {
|
||||
const result = new Uint8Array(a.length + b.length);
|
||||
result.set(a);
|
||||
result.set(b, a.length);
|
||||
return result;
|
||||
}
|
||||
//#endregion
|
||||
export { ChunkedStreamIterable };
|
||||
|
||||
//# sourceMappingURL=chunked-stream-iterable.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunked-stream-iterable.js","names":["#readableStream"],"sources":["../../../src/network/chunked-stream-iterable.ts"],"sourcesContent":["const DEFAULT_MIN_CHUNK_SIZE = 2 ** 17; // 128 KB\n\nexport interface ChunkedStreamIterableOptions {\n minChunkSize?: number;\n}\n\n/**\n * Adapts a `ReadableStream<Uint8Array>` (e.g. `response.body`) into an\n * `AsyncIterable<Uint8Array>` that yields chunks no smaller than\n * `minChunkSize` bytes. Smaller network chunks are accumulated and yielded\n * together once the threshold is met. Any remainder is flushed on stream end.\n *\n * Errors from the underlying stream propagate naturally — the reader lock is\n * always released via `finally`.\n */\nexport class ChunkedStreamIterable implements AsyncIterable<Uint8Array> {\n readonly minChunkSize: number;\n #readableStream: ReadableStream<Uint8Array>;\n\n constructor(\n readableStream: ReadableStream<Uint8Array>,\n { minChunkSize = DEFAULT_MIN_CHUNK_SIZE }: ChunkedStreamIterableOptions = {}\n ) {\n this.#readableStream = readableStream;\n this.minChunkSize = minChunkSize;\n }\n\n async *[Symbol.asyncIterator](): AsyncGenerator<Uint8Array> {\n let pending: Uint8Array | undefined;\n const reader = this.#readableStream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n if (pending) yield pending;\n break;\n }\n\n pending = pending ? concat(pending, value) : value;\n\n if (pending.length >= this.minChunkSize) {\n yield pending;\n pending = undefined;\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n}\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n const result = new Uint8Array(a.length + b.length);\n result.set(a);\n result.set(b, a.length);\n return result;\n}\n"],"mappings":";AAAA,MAAM,yBAAyB,KAAK;;;;;;;;;;AAepC,IAAa,wBAAb,MAAwE;CACtE;CACA;CAEA,YACE,gBACA,EAAE,eAAe,2BAAyD,CAAC,GAC3E;EACA,KAAKA,kBAAkB;EACvB,KAAK,eAAe;CACtB;CAEA,QAAQ,OAAO,iBAA6C;EAC1D,IAAI;EACJ,MAAM,SAAS,KAAKA,gBAAgB,UAAU;EAC9C,IAAI;GACF,OAAO,MAAM;IACX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;IAC1C,IAAI,MAAM;KACR,IAAI,SAAS,MAAM;KACnB;IACF;IAEA,UAAU,UAAU,OAAO,SAAS,KAAK,IAAI;IAE7C,IAAI,QAAQ,UAAU,KAAK,cAAc;KACvC,MAAM;KACN,UAAU,KAAA;IACZ;GACF;EACF,UAAU;GACR,OAAO,YAAY;EACrB;CACF;AACF;AAEA,SAAS,OAAO,GAAe,GAA2B;CACxD,MAAM,SAAS,IAAI,WAAW,EAAE,SAAS,EAAE,MAAM;CACjD,OAAO,IAAI,CAAC;CACZ,OAAO,IAAI,GAAG,EAAE,MAAM;CACtB,OAAO;AACT"}
|
||||
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
//#region src/network/ewma.ts
|
||||
/**
|
||||
* Exponentially Weighted Moving Average (EWMA)
|
||||
*
|
||||
* Pure functional implementation of EWMA calculations.
|
||||
* Based on Shaka Player's EWMA algorithm.
|
||||
*/
|
||||
/**
|
||||
* Calculate alpha (decay factor) from half-life.
|
||||
*
|
||||
* Alpha determines how quickly old data "expires":
|
||||
* - alpha close to 1 = slow decay (long memory)
|
||||
* - alpha close to 0 = fast decay (short memory)
|
||||
*
|
||||
* @param halfLife - The quantity of prior samples (by weight) that make up
|
||||
* half of the new estimate. Must be positive.
|
||||
* @returns Alpha value between 0 and 1
|
||||
*
|
||||
* @example
|
||||
* const alpha = calculateAlpha(2); // ≈ 0.7071 for 2-second half-life
|
||||
*/
|
||||
function calculateAlpha(halfLife) {
|
||||
return Math.exp(Math.log(.5) / halfLife);
|
||||
}
|
||||
/**
|
||||
* Calculate exponentially weighted moving average.
|
||||
*
|
||||
* Updates an estimate by blending a new value with the previous estimate,
|
||||
* weighted by the sample duration. Longer samples have more influence.
|
||||
*
|
||||
* @param prevEstimate - Previous EWMA estimate
|
||||
* @param value - New sample value to incorporate
|
||||
* @param weight - Sample weight (typically duration in seconds)
|
||||
* @param halfLife - Half-life for decay (typically 2-5 seconds)
|
||||
* @returns Updated EWMA estimate
|
||||
*
|
||||
* @example
|
||||
* let estimate = 0;
|
||||
* estimate = calculateEwma(estimate, 1_000_000, 1, 2); // First sample
|
||||
* estimate = calculateEwma(estimate, 2_000_000, 1, 2); // Second sample
|
||||
*/
|
||||
function calculateEwma(prevEstimate, value, weight, halfLife) {
|
||||
const adjAlpha = calculateAlpha(halfLife) ** weight;
|
||||
return value * (1 - adjAlpha) + adjAlpha * prevEstimate;
|
||||
}
|
||||
/**
|
||||
* Apply zero-factor correction to EWMA estimate.
|
||||
*
|
||||
* The zero-factor correction compensates for bias when starting from zero.
|
||||
* Without this correction, early estimates would be artificially low.
|
||||
*
|
||||
* As totalWeight increases, the correction factor approaches 1, meaning
|
||||
* the estimate becomes more reliable and needs less correction.
|
||||
*
|
||||
* @param estimate - Raw EWMA estimate (uncorrected)
|
||||
* @param totalWeight - Accumulated weight from all samples
|
||||
* @param halfLife - Half-life used in EWMA calculation
|
||||
* @returns Corrected estimate, or 0 if totalWeight is 0
|
||||
*
|
||||
* @example
|
||||
* const raw = calculateEwma(0, 1_000_000, 1, 2);
|
||||
* const corrected = applyZeroFactor(raw, 1, 2); // ≈ 1_000_000
|
||||
*/
|
||||
function applyZeroFactor(estimate, totalWeight, halfLife) {
|
||||
if (totalWeight === 0) return 0;
|
||||
return estimate / (1 - calculateAlpha(halfLife) ** totalWeight);
|
||||
}
|
||||
//#endregion
|
||||
export { applyZeroFactor, calculateAlpha, calculateEwma };
|
||||
|
||||
//# sourceMappingURL=ewma.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ewma.js","names":[],"sources":["../../../src/network/ewma.ts"],"sourcesContent":["/**\n * Exponentially Weighted Moving Average (EWMA)\n *\n * Pure functional implementation of EWMA calculations.\n * Based on Shaka Player's EWMA algorithm.\n */\n\n/**\n * Calculate alpha (decay factor) from half-life.\n *\n * Alpha determines how quickly old data \"expires\":\n * - alpha close to 1 = slow decay (long memory)\n * - alpha close to 0 = fast decay (short memory)\n *\n * @param halfLife - The quantity of prior samples (by weight) that make up\n * half of the new estimate. Must be positive.\n * @returns Alpha value between 0 and 1\n *\n * @example\n * const alpha = calculateAlpha(2); // ≈ 0.7071 for 2-second half-life\n */\nexport function calculateAlpha(halfLife: number): number {\n return Math.exp(Math.log(0.5) / halfLife);\n}\n\n/**\n * Calculate exponentially weighted moving average.\n *\n * Updates an estimate by blending a new value with the previous estimate,\n * weighted by the sample duration. Longer samples have more influence.\n *\n * @param prevEstimate - Previous EWMA estimate\n * @param value - New sample value to incorporate\n * @param weight - Sample weight (typically duration in seconds)\n * @param halfLife - Half-life for decay (typically 2-5 seconds)\n * @returns Updated EWMA estimate\n *\n * @example\n * let estimate = 0;\n * estimate = calculateEwma(estimate, 1_000_000, 1, 2); // First sample\n * estimate = calculateEwma(estimate, 2_000_000, 1, 2); // Second sample\n */\nexport function calculateEwma(prevEstimate: number, value: number, weight: number, halfLife: number): number {\n const alpha = calculateAlpha(halfLife);\n const adjAlpha = alpha ** weight;\n return value * (1 - adjAlpha) + adjAlpha * prevEstimate;\n}\n\n/**\n * Apply zero-factor correction to EWMA estimate.\n *\n * The zero-factor correction compensates for bias when starting from zero.\n * Without this correction, early estimates would be artificially low.\n *\n * As totalWeight increases, the correction factor approaches 1, meaning\n * the estimate becomes more reliable and needs less correction.\n *\n * @param estimate - Raw EWMA estimate (uncorrected)\n * @param totalWeight - Accumulated weight from all samples\n * @param halfLife - Half-life used in EWMA calculation\n * @returns Corrected estimate, or 0 if totalWeight is 0\n *\n * @example\n * const raw = calculateEwma(0, 1_000_000, 1, 2);\n * const corrected = applyZeroFactor(raw, 1, 2); // ≈ 1_000_000\n */\nexport function applyZeroFactor(estimate: number, totalWeight: number, halfLife: number): number {\n if (totalWeight === 0) {\n return 0;\n }\n\n const alpha = calculateAlpha(halfLife);\n const zeroFactor = 1 - alpha ** totalWeight;\n return estimate / zeroFactor;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,eAAe,UAA0B;CACvD,OAAO,KAAK,IAAI,KAAK,IAAI,EAAG,IAAI,QAAQ;AAC1C;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,cAAc,cAAsB,OAAe,QAAgB,UAA0B;CAE3G,MAAM,WADQ,eAAe,QACR,KAAK;CAC1B,OAAO,SAAS,IAAI,YAAY,WAAW;AAC7C;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,gBAAgB,UAAkB,aAAqB,UAA0B;CAC/F,IAAI,gBAAgB,GAClB,OAAO;CAKT,OAAO,YADY,IADL,eAAe,QACF,KAAK;AAElC"}
|
||||
Vendored
+115
@@ -0,0 +1,115 @@
|
||||
import { sampleBandwidth } from "./bandwidth-estimator.js";
|
||||
import { ChunkedStreamIterable } from "./chunked-stream-iterable.js";
|
||||
//#region src/network/fetch.ts
|
||||
/**
|
||||
* HTTP Fetch Wrapper
|
||||
*
|
||||
* Composable building blocks:
|
||||
* - fetchResolvable() — fetch a Resource (handles byte ranges); returns Response
|
||||
* - getResponseText() — extract text from Response
|
||||
* - fetchResolvableStream() — single-stage async generator over body chunks
|
||||
* - fetchStream() — two-stage: await connection establishment, then lazily
|
||||
* iterate body chunks. Use when timing the connection start independently
|
||||
* of body consumption matters (e.g., observable fetch timing for ABR).
|
||||
* - createTrackedFetch() — factory for a fetchStream-shape function that
|
||||
* samples bandwidth (via EWMA) per chunk and notifies via callback.
|
||||
*/
|
||||
/**
|
||||
* Fetch resolvable from a Resource.
|
||||
*
|
||||
* Handles byte range requests if byteRange is present.
|
||||
* Returns native fetch Response for composability (can extract text, stream, etc.).
|
||||
*
|
||||
* @param addressable - Resource to fetch (url + optional byteRange)
|
||||
* @returns Promise resolving to Response
|
||||
*
|
||||
* @example
|
||||
* const response = await fetchResolvable({ url: 'https://example.com/segment.m4s' });
|
||||
* const text = await getResponseText(response);
|
||||
*
|
||||
* @example
|
||||
* // With byte range
|
||||
* const response = await fetchResolvable({
|
||||
* url: 'https://example.com/file.mp4',
|
||||
* byteRange: { start: 1000, end: 1999 }
|
||||
* });
|
||||
*/
|
||||
async function fetchResolvable(addressable, options) {
|
||||
const headers = new Headers(options?.headers);
|
||||
if (addressable.byteRange) {
|
||||
const { start, end } = addressable.byteRange;
|
||||
headers.set("Range", `bytes=${start}-${end}`);
|
||||
}
|
||||
const request = new Request(addressable.url, {
|
||||
method: "GET",
|
||||
headers,
|
||||
...options
|
||||
});
|
||||
return fetch(request);
|
||||
}
|
||||
/**
|
||||
* Extract text from Response.
|
||||
*
|
||||
* Accepts minimal Response-like object (just needs text() method).
|
||||
* Returns promise from response.text().
|
||||
*
|
||||
* @param response - Response-like object with text() method
|
||||
* @returns Promise resolving to text content
|
||||
*
|
||||
* @example
|
||||
* const response = await fetchResolvable(addressable);
|
||||
* const text = await getResponseText(response);
|
||||
*/
|
||||
function getResponseText(response) {
|
||||
return response.text();
|
||||
}
|
||||
/** Default {@link FetchText}: fetch the resource, reject on non-OK, return text. */
|
||||
const fetchResolvableText = async (addressable, options) => {
|
||||
const response = await fetchResolvable(addressable, options);
|
||||
if (!response.ok) throw new Error(`fetchResolvableText: ${response.status} ${response.statusText} for ${addressable.url}`);
|
||||
return getResponseText(response);
|
||||
};
|
||||
async function fetchStream(addressable, options) {
|
||||
const { minChunkSize, ...fetchOptions } = options ?? {};
|
||||
const response = await fetchResolvable(addressable, fetchOptions);
|
||||
if (!response.body) throw new Error("Response has no body");
|
||||
return new ChunkedStreamIterable(response.body, ...minChunkSize !== void 0 ? [{ minChunkSize }] : []);
|
||||
}
|
||||
/**
|
||||
* Returns a {@link FetchBytes} function that samples bandwidth via EWMA
|
||||
* per body chunk. The factory captures the running bandwidth state
|
||||
* internally; per chunk it computes the next state and notifies the
|
||||
* supplied `onSample` callback.
|
||||
*
|
||||
* The factory's internal accumulator is seeded from `initial` and updated
|
||||
* on every chunk; callers don't need to thread it back in. `onSample`
|
||||
* receives the *new* state after each chunk — typical use is to bridge
|
||||
* samples back into engine state for ABR consumers.
|
||||
*
|
||||
* @param initial - Starting `BandwidthState` (commonly zeros or the
|
||||
* engine's current accumulator).
|
||||
* @param onSample - Called with the new `BandwidthState` after each chunk.
|
||||
*/
|
||||
function createTrackedFetch(initial, onSample) {
|
||||
let state = initial;
|
||||
return async (addressable, options) => {
|
||||
const { minChunkSize, ...fetchOptions } = options ?? {};
|
||||
const response = await fetchResolvable(addressable, fetchOptions);
|
||||
if (!response.body) throw new Error("Response has no body");
|
||||
const body = response.body;
|
||||
return { [Symbol.asyncIterator]: async function* () {
|
||||
let chunkStart = performance.now();
|
||||
for await (const chunk of new ChunkedStreamIterable(body, ...minChunkSize !== void 0 ? [{ minChunkSize }] : [])) {
|
||||
const elapsed = performance.now() - chunkStart;
|
||||
state = sampleBandwidth(state, elapsed, chunk.byteLength);
|
||||
onSample(state);
|
||||
yield chunk;
|
||||
chunkStart = performance.now();
|
||||
}
|
||||
} };
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
export { createTrackedFetch, fetchResolvable, fetchResolvableText, fetchStream, getResponseText };
|
||||
|
||||
//# sourceMappingURL=fetch.js.map
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user