From 5f362024be4436144ff3fa12cb080871d067f649 Mon Sep 17 00:00:00 2001 From: Steven Vancoillie Date: Wed, 15 Jan 2025 16:40:58 +0100 Subject: [PATCH] fix: export more data from the HTTP+MP4 pipeline Expose details about response headers and stream end so that the player can use that data. --- CHANGELOG.md | 3 ++- package.json | 2 +- src/player/HttpMp4Video.tsx | 16 ++++++++-------- src/streams/http-mp4-pipeline.ts | 30 +++++++++++++++++++----------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69e5c98ec..3a340e293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ -## 14.0.0-beta.4 +## 14.0.0-beta.5 +- Added some missing info to the HTTP MP4 pipeline. - Updated documentation regarding release flow, which now requires manual update of version and changelog. - Fixed player non-relative imports from within the package. diff --git a/package.json b/package.json index b5e11ed4c..d69523d23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "media-stream-library", - "version": "14.0.0-beta.4", + "version": "14.0.0-beta.5", "description": "Media libraries for Node and the Web.", "repository": { "type": "git", diff --git a/src/player/HttpMp4Video.tsx b/src/player/HttpMp4Video.tsx index a6b0fbf02..9f6b9b3a4 100644 --- a/src/player/HttpMp4Video.tsx +++ b/src/player/HttpMp4Video.tsx @@ -136,9 +136,6 @@ export const HttpMp4Video: React.FC = ({ const videoEl = videoRef.current if (src !== undefined && src.length > 0 && videoEl !== null) { - const endedCallback = () => { - __onEndedRef.current?.() - } logDebug('create pipeline', src) const newPipeline = new HttpMp4Pipeline({ uri: src, @@ -146,8 +143,6 @@ export const HttpMp4Video: React.FC = ({ }) setPipeline(newPipeline) - newPipeline.onServerClose = endedCallback - return () => { logDebug('close pipeline and clear video') newPipeline.close() @@ -162,13 +157,18 @@ export const HttpMp4Video: React.FC = ({ useEffect(() => { if (play && pipeline && !fetching) { - pipeline.onHeaders = (headers) => { + const endedCallback = () => { + __onEndedRef.current?.() + } + pipeline.start().then(({ headers, finished }) => { __sensorTmRef.current = parseTransformHeader( headers.get('video-sensor-transform') ?? headers.get('video-metadata-transform') ) - } - pipeline.start() + finished.finally(() => { + endedCallback() + }) + }) logDebug('initiated data fetching') setFetching(true) } diff --git a/src/streams/http-mp4-pipeline.ts b/src/streams/http-mp4-pipeline.ts index 100cb7d09..0c42912b6 100644 --- a/src/streams/http-mp4-pipeline.ts +++ b/src/streams/http-mp4-pipeline.ts @@ -13,19 +13,14 @@ export interface HttpMp4Config { * * A pipeline that connects to an HTTP server and can process an MP4 data stream * that is then sent to a HTML video element - * - * Handlers that can be set on the pipeline: - * - `onServerClose`: called when the server closes the connection */ export class HttpMp4Pipeline { - public onHeaders?: (headers: Headers) => void - public onServerClose?: () => void - private abortController?: AbortController private downloadedBytes: number = 0 private options?: RequestInit - private readonly mediaElement: HTMLVideoElement private uri: string + public readonly mediaElement: HTMLVideoElement + public streamStart?: number constructor(config: HttpMp4Config) { const { uri, options, mediaElement } = config @@ -36,13 +31,16 @@ export class HttpMp4Pipeline { this.mediaElement = mediaElement } - /** Initiates the stream and resolves when the media stream has completed. */ - public async start(msgHandler?: (msg: IsomMessage) => void) { + /** Initiates the stream and resolves when the media stream has completed. + * Returns the original response headers and a result promise. */ + public async start( + msgHandler?: (msg: IsomMessage) => void + ): Promise<{ headers: Headers; finished: Promise }> { this.abortController?.abort('stream restarted') this.abortController = new AbortController() - const { ok, status, statusText, headers, body } = await fetch(this.uri, { + const { ok, headers, status, statusText, body } = await fetch(this.uri, { signal: this.abortController.signal, ...this.options, }) @@ -70,7 +68,8 @@ export class HttpMp4Pipeline { mseSink.onMessage = msgHandler } - body + this.streamStart = performance.now() + const finished = body .pipeThrough(adapter) .pipeTo(mseSink.writable) .then(() => { @@ -79,6 +78,8 @@ export class HttpMp4Pipeline { .catch((err) => { logDebug(`http-mp4 pipeline ended: ${err}`) }) + + return { headers, finished } } public close() { @@ -101,6 +102,13 @@ export class HttpMp4Pipeline { return this.downloadedBytes } + public get bitrate() { + return this.streamStart !== undefined + ? (8 * this.downloadedBytes) / + ((performance.now() - this.streamStart) / 1000) + : 0 + } + /** Refresh the stream and passes the captured MP4 data to the provided * callback. Capture can be ended by calling the returned trigger, or * if the buffer reaches max size. */