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