Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: export more data from the HTTP+MP4 pipeline #1095

Merged
merged 1 commit into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
16 changes: 8 additions & 8 deletions src/player/HttpMp4Video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,13 @@ export const HttpMp4Video: React.FC<HttpMp4VideoProps> = ({
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,
mediaElement: videoEl,
})
setPipeline(newPipeline)

newPipeline.onServerClose = endedCallback

return () => {
logDebug('close pipeline and clear video')
newPipeline.close()
Expand All @@ -162,13 +157,18 @@ export const HttpMp4Video: React.FC<HttpMp4VideoProps> = ({

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)
}
Expand Down
30 changes: 19 additions & 11 deletions src/streams/http-mp4-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<void> }> {
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,
})
Expand Down Expand Up @@ -70,7 +68,8 @@ export class HttpMp4Pipeline {
mseSink.onMessage = msgHandler
}

body
this.streamStart = performance.now()
const finished = body
.pipeThrough(adapter)
.pipeTo(mseSink.writable)
.then(() => {
Expand All @@ -79,6 +78,8 @@ export class HttpMp4Pipeline {
.catch((err) => {
logDebug(`http-mp4 pipeline ended: ${err}`)
})

return { headers, finished }
}

public close() {
Expand All @@ -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. */
Expand Down
Loading