diff --git a/packages/p2p-media-loader-core/src/http-loader.ts b/packages/p2p-media-loader-core/src/http-loader.ts index e6522539..d6592f28 100644 --- a/packages/p2p-media-loader-core/src/http-loader.ts +++ b/packages/p2p-media-loader-core/src/http-loader.ts @@ -1,12 +1,15 @@ import { RequestAbortError, FetchError } from "./errors"; import { Segment } from "./types"; -import { HttpRequest } from "./request"; +import { HttpRequest, LoadProgress } from "./request"; export function getHttpSegmentRequest(segment: Segment): Readonly { - const { promise, abortController } = fetchSegmentData(segment); + const { promise, abortController, progress, startTimestamp } = + fetchSegmentData(segment); return { type: "http", promise, + progress, + startTimestamp, abort: () => abortController.abort(), }; } @@ -22,6 +25,7 @@ function fetchSegmentData(segment: Segment) { } const abortController = new AbortController(); + let progress: LoadProgress | undefined; const loadSegmentData = async () => { try { const response = await window.fetch(url, { @@ -29,7 +33,10 @@ function fetchSegmentData(segment: Segment) { signal: abortController.signal, }); - if (response.ok) return response.arrayBuffer(); + if (response.ok) { + progress = monitorFetchProgress(response); + return response.arrayBuffer(); + } throw new FetchError( response.statusText ?? `Network response was not for ${segmentId}`, response.status, @@ -43,5 +50,44 @@ function fetchSegmentData(segment: Segment) { } }; - return { promise: loadSegmentData(), abortController }; + return { + promise: loadSegmentData(), + abortController, + progress, + startTimestamp: performance.now(), + }; +} + +function monitorFetchProgress( + response: Response +): Readonly | undefined { + const totalLengthString = response.headers.get("Content-Length"); + if (totalLengthString === null || !response.body) return; + + const totalLength = +totalLengthString; + const progress: LoadProgress = { + percent: 0, + loadedBytes: 0, + totalLength, + }; + const reader = response.body.getReader(); + + const monitor = async () => { + for await (const chunk of readStream(reader)) { + progress.loadedBytes += chunk.length; + progress.percent = (progress.loadedBytes / totalLength) * 100; + } + }; + void monitor(); + return progress; +} + +async function* readStream( + reader: ReadableStreamDefaultReader +): AsyncGenerator { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + yield value; + } } diff --git a/packages/p2p-media-loader-core/src/request.ts b/packages/p2p-media-loader-core/src/request.ts index d2ca3492..49929c27 100644 --- a/packages/p2p-media-loader-core/src/request.ts +++ b/packages/p2p-media-loader-core/src/request.ts @@ -6,9 +6,17 @@ export type EngineCallbacks = { onError: (reason?: unknown) => void; }; +export type LoadProgress = { + percent: number; + loadedBytes: number; + totalLength: number; +}; + type RequestBase = { promise: Promise; abort: () => void; + progress?: Readonly; + startTimestamp: number; }; export type HttpRequest = RequestBase & {