@memberjunction/ai-realtime-client 6.2.0-edge.3 → 6.2.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -29
- package/dist/audio/audioMeter.d.ts +71 -0
- package/dist/audio/audioMeter.d.ts.map +1 -1
- package/dist/audio/audioMeter.js +83 -0
- package/dist/audio/audioMeter.js.map +1 -1
- package/dist/audio/mediaElementAudioRouter.d.ts +41 -0
- package/dist/audio/mediaElementAudioRouter.d.ts.map +1 -0
- package/dist/audio/mediaElementAudioRouter.js +82 -0
- package/dist/audio/mediaElementAudioRouter.js.map +1 -0
- package/dist/audio/micCapture.d.ts +12 -3
- package/dist/audio/micCapture.d.ts.map +1 -1
- package/dist/audio/micCapture.js +15 -1
- package/dist/audio/micCapture.js.map +1 -1
- package/dist/audio/pcmPlayback.d.ts +72 -4
- package/dist/audio/pcmPlayback.d.ts.map +1 -1
- package/dist/audio/pcmPlayback.js +101 -1
- package/dist/audio/pcmPlayback.js.map +1 -1
- package/dist/drivers/assemblyAIRealtimeClient.d.ts +2 -0
- package/dist/drivers/assemblyAIRealtimeClient.d.ts.map +1 -1
- package/dist/drivers/assemblyAIRealtimeClient.js +14 -0
- package/dist/drivers/assemblyAIRealtimeClient.js.map +1 -1
- package/dist/drivers/elevenLabsRealtimeClient.d.ts +2 -0
- package/dist/drivers/elevenLabsRealtimeClient.d.ts.map +1 -1
- package/dist/drivers/elevenLabsRealtimeClient.js +14 -0
- package/dist/drivers/elevenLabsRealtimeClient.js.map +1 -1
- package/dist/drivers/geminiAvatarOutput.d.ts +132 -0
- package/dist/drivers/geminiAvatarOutput.d.ts.map +1 -0
- package/dist/drivers/geminiAvatarOutput.js +310 -0
- package/dist/drivers/geminiAvatarOutput.js.map +1 -0
- package/dist/drivers/geminiEnterpriseRealtimeClient.d.ts +38 -0
- package/dist/drivers/geminiEnterpriseRealtimeClient.d.ts.map +1 -0
- package/dist/drivers/geminiEnterpriseRealtimeClient.js +107 -0
- package/dist/drivers/geminiEnterpriseRealtimeClient.js.map +1 -0
- package/dist/drivers/geminiRealtimeClient.d.ts +314 -48
- package/dist/drivers/geminiRealtimeClient.d.ts.map +1 -1
- package/dist/drivers/geminiRealtimeClient.js +759 -176
- package/dist/drivers/geminiRealtimeClient.js.map +1 -1
- package/dist/drivers/openAILiveClient.d.ts +5 -8
- package/dist/drivers/openAILiveClient.d.ts.map +1 -1
- package/dist/drivers/openAILiveClient.js +19 -32
- package/dist/drivers/openAILiveClient.js.map +1 -1
- package/dist/drivers/openAIRealtimeClient.d.ts +13 -26
- package/dist/drivers/openAIRealtimeClient.d.ts.map +1 -1
- package/dist/drivers/openAIRealtimeClient.js +28 -55
- package/dist/drivers/openAIRealtimeClient.js.map +1 -1
- package/dist/generic/baseRealtimeClient.d.ts +173 -29
- package/dist/generic/baseRealtimeClient.d.ts.map +1 -1
- package/dist/generic/baseRealtimeClient.js +233 -14
- package/dist/generic/baseRealtimeClient.js.map +1 -1
- package/dist/generic/openAIProtocolClient.d.ts +2 -0
- package/dist/generic/openAIProtocolClient.d.ts.map +1 -1
- package/dist/generic/openAIProtocolClient.js +14 -0
- package/dist/generic/openAIProtocolClient.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/media/attachVideoSource.d.ts +23 -0
- package/dist/media/attachVideoSource.d.ts.map +1 -0
- package/dist/media/attachVideoSource.js +49 -0
- package/dist/media/attachVideoSource.js.map +1 -0
- package/dist/media/channelVideoSource.d.ts +110 -14
- package/dist/media/channelVideoSource.d.ts.map +1 -1
- package/dist/media/channelVideoSource.js +183 -18
- package/dist/media/channelVideoSource.js.map +1 -1
- package/dist/media/decoders/frameScheduler.d.ts +119 -0
- package/dist/media/decoders/frameScheduler.d.ts.map +1 -0
- package/dist/media/decoders/frameScheduler.js +238 -0
- package/dist/media/decoders/frameScheduler.js.map +1 -0
- package/dist/media/decoders/imageFrameDecoder.d.ts +58 -0
- package/dist/media/decoders/imageFrameDecoder.d.ts.map +1 -0
- package/dist/media/decoders/imageFrameDecoder.js +110 -0
- package/dist/media/decoders/imageFrameDecoder.js.map +1 -0
- package/dist/media/decoders/mseFmp4Decoder.d.ts +130 -0
- package/dist/media/decoders/mseFmp4Decoder.d.ts.map +1 -0
- package/dist/media/decoders/mseFmp4Decoder.js +402 -0
- package/dist/media/decoders/mseFmp4Decoder.js.map +1 -0
- package/dist/media/decoders/streamCanvas.d.ts +46 -0
- package/dist/media/decoders/streamCanvas.d.ts.map +1 -0
- package/dist/media/decoders/streamCanvas.js +163 -0
- package/dist/media/decoders/streamCanvas.js.map +1 -0
- package/dist/media/decoders/videoMimeTypes.d.ts +29 -0
- package/dist/media/decoders/videoMimeTypes.d.ts.map +1 -0
- package/dist/media/decoders/videoMimeTypes.js +50 -0
- package/dist/media/decoders/videoMimeTypes.js.map +1 -0
- package/dist/media/decoders/webCodecsChunkDecoder.d.ts +99 -0
- package/dist/media/decoders/webCodecsChunkDecoder.d.ts.map +1 -0
- package/dist/media/decoders/webCodecsChunkDecoder.js +265 -0
- package/dist/media/decoders/webCodecsChunkDecoder.js.map +1 -0
- package/dist/media/displayCapture.d.ts +128 -0
- package/dist/media/displayCapture.d.ts.map +1 -0
- package/dist/media/displayCapture.js +245 -0
- package/dist/media/displayCapture.js.map +1 -0
- package/dist/media/frameCapture.d.ts +25 -8
- package/dist/media/frameCapture.d.ts.map +1 -1
- package/dist/media/frameCapture.js +42 -72
- package/dist/media/frameCapture.js.map +1 -1
- package/dist/media/frameSampler.d.ts +94 -0
- package/dist/media/frameSampler.d.ts.map +1 -0
- package/dist/media/frameSampler.js +134 -0
- package/dist/media/frameSampler.js.map +1 -0
- package/dist/media/index.d.ts +32 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +32 -0
- package/dist/media/index.js.map +1 -0
- package/dist/media/localMediaController.d.ts +116 -0
- package/dist/media/localMediaController.d.ts.map +1 -0
- package/dist/media/localMediaController.js +281 -0
- package/dist/media/localMediaController.js.map +1 -0
- package/dist/media/mediaLayout.d.ts +93 -0
- package/dist/media/mediaLayout.d.ts.map +1 -0
- package/dist/media/mediaLayout.js +187 -0
- package/dist/media/mediaLayout.js.map +1 -0
- package/dist/media/mediaPreview.d.ts +94 -0
- package/dist/media/mediaPreview.d.ts.map +1 -0
- package/dist/media/mediaPreview.js +154 -0
- package/dist/media/mediaPreview.js.map +1 -0
- package/dist/media/mediaStage.d.ts +99 -0
- package/dist/media/mediaStage.d.ts.map +1 -0
- package/dist/media/mediaStage.js +171 -0
- package/dist/media/mediaStage.js.map +1 -0
- package/dist/media/model.d.ts +154 -0
- package/dist/media/model.d.ts.map +1 -0
- package/dist/media/model.js +9 -0
- package/dist/media/model.js.map +1 -0
- package/dist/media/playbackClock.d.ts +20 -0
- package/dist/media/playbackClock.d.ts.map +1 -0
- package/dist/media/playbackClock.js +13 -0
- package/dist/media/playbackClock.js.map +1 -0
- package/dist/media/videoFrameDecoder.d.ts +86 -0
- package/dist/media/videoFrameDecoder.d.ts.map +1 -0
- package/dist/media/videoFrameDecoder.js +2 -0
- package/dist/media/videoFrameDecoder.js.map +1 -0
- package/dist/media/videoFrameDecoderRegistry.d.ts +40 -0
- package/dist/media/videoFrameDecoderRegistry.d.ts.map +1 -0
- package/dist/media/videoFrameDecoderRegistry.js +69 -0
- package/dist/media/videoFrameDecoderRegistry.js.map +1 -0
- package/dist/media/videoPacing.d.ts +32 -0
- package/dist/media/videoPacing.d.ts.map +1 -0
- package/dist/media/videoPacing.js +37 -0
- package/dist/media/videoPacing.js.map +1 -0
- package/dist/media/videoPlayout.d.ts +168 -0
- package/dist/media/videoPlayout.d.ts.map +1 -0
- package/dist/media/videoPlayout.js +217 -0
- package/dist/media/videoPlayout.js.map +1 -0
- package/dist/media/videoSourceArbiter.d.ts +250 -0
- package/dist/media/videoSourceArbiter.d.ts.map +1 -0
- package/dist/media/videoSourceArbiter.js +423 -0
- package/dist/media/videoSourceArbiter.js.map +1 -0
- package/dist/testing/conformanceAssertions.d.ts +25 -0
- package/dist/testing/conformanceAssertions.d.ts.map +1 -0
- package/dist/testing/conformanceAssertions.js +64 -0
- package/dist/testing/conformanceAssertions.js.map +1 -0
- package/dist/testing/conformanceFixtures.d.ts +76 -0
- package/dist/testing/conformanceFixtures.d.ts.map +1 -0
- package/dist/testing/conformanceFixtures.js +169 -0
- package/dist/testing/conformanceFixtures.js.map +1 -0
- package/dist/testing/conformanceGates.d.ts +32 -0
- package/dist/testing/conformanceGates.d.ts.map +1 -0
- package/dist/testing/conformanceGates.js +35 -0
- package/dist/testing/conformanceGates.js.map +1 -0
- package/dist/testing/conformanceMicrophone.d.ts +10 -0
- package/dist/testing/conformanceMicrophone.d.ts.map +1 -0
- package/dist/testing/conformanceMicrophone.js +71 -0
- package/dist/testing/conformanceMicrophone.js.map +1 -0
- package/dist/testing/conformanceTimeline.d.ts +128 -0
- package/dist/testing/conformanceTimeline.d.ts.map +1 -0
- package/dist/testing/conformanceTimeline.js +32 -0
- package/dist/testing/conformanceTimeline.js.map +1 -0
- package/dist/testing/conformanceVideoStream.d.ts +29 -0
- package/dist/testing/conformanceVideoStream.d.ts.map +1 -0
- package/dist/testing/conformanceVideoStream.js +39 -0
- package/dist/testing/conformanceVideoStream.js.map +1 -0
- package/dist/testing/index.d.ts +29 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +28 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/realtimeVideoConformance.d.ts +23 -0
- package/dist/testing/realtimeVideoConformance.d.ts.map +1 -0
- package/dist/testing/realtimeVideoConformance.js +139 -0
- package/dist/testing/realtimeVideoConformance.js.map +1 -0
- package/dist/testing/realtimeVideoConformanceChecks.d.ts +4 -0
- package/dist/testing/realtimeVideoConformanceChecks.d.ts.map +1 -0
- package/dist/testing/realtimeVideoConformanceChecks.js +164 -0
- package/dist/testing/realtimeVideoConformanceChecks.js.map +1 -0
- package/dist/testing/realtimeVideoConformanceFrameChecks.d.ts +4 -0
- package/dist/testing/realtimeVideoConformanceFrameChecks.d.ts.map +1 -0
- package/dist/testing/realtimeVideoConformanceFrameChecks.js +63 -0
- package/dist/testing/realtimeVideoConformanceFrameChecks.js.map +1 -0
- package/dist/testing/realtimeVideoConformanceSession.d.ts +108 -0
- package/dist/testing/realtimeVideoConformanceSession.d.ts.map +1 -0
- package/dist/testing/realtimeVideoConformanceSession.js +270 -0
- package/dist/testing/realtimeVideoConformanceSession.js.map +1 -0
- package/dist/testing/realtimeVideoConformanceTurnChecks.d.ts +4 -0
- package/dist/testing/realtimeVideoConformanceTurnChecks.d.ts.map +1 -0
- package/dist/testing/realtimeVideoConformanceTurnChecks.js +248 -0
- package/dist/testing/realtimeVideoConformanceTurnChecks.js.map +1 -0
- package/dist/testing/realtimeVideoConformanceTypes.d.ts +155 -0
- package/dist/testing/realtimeVideoConformanceTypes.d.ts.map +1 -0
- package/dist/testing/realtimeVideoConformanceTypes.js +2 -0
- package/dist/testing/realtimeVideoConformanceTypes.js.map +1 -0
- package/dist/testing/recordingPlayout.d.ts +84 -0
- package/dist/testing/recordingPlayout.d.ts.map +1 -0
- package/dist/testing/recordingPlayout.js +120 -0
- package/dist/testing/recordingPlayout.js.map +1 -0
- package/package.json +29 -4
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MSE FMP4 DECODER: plays fragmented MP4 frames (`Kind: 'fmp4'`), such as a Gemini Live avatar, through
|
|
3
|
+
* Media Source Extensions into the host's `<video>` element. The `'mse-fmp4'` decoder of `VideoPlayout`.
|
|
4
|
+
*
|
|
5
|
+
* - **Format.** The first piece of a turn is an init segment (`Piece: 'init'`, `ftyp` + `moov`); each later piece is a
|
|
6
|
+
* media fragment (`moof` + `mdat`). A turn may send a fresh init segment and restart its timestamps; the source buffer
|
|
7
|
+
* runs in `sequence` mode, so playback stays continuous either way.
|
|
8
|
+
* - **Codecs come from the init segment.** The source buffer takes the codecs the init segment's tracks name
|
|
9
|
+
* (`avc1.42c01f` alone for video-only MP4), read by `ReadFmp4Init` from `@memberjunction/ai`; until an init arrives, or
|
|
10
|
+
* when it names a codec the reader doesn't know, it takes the player's configured type.
|
|
11
|
+
* - **The video carries the voice.** An avatar's audio is muxed into the MP4, so the element is unmuted and lips and voice
|
|
12
|
+
* stay in sync on the media's own timestamps. While media is buffered ahead of the playhead, {@link MseFmp4Decoder.IsPlaying}
|
|
13
|
+
* is true: the agent is audibly speaking. When the voice plays elsewhere (separate PCM), the element is muted. The
|
|
14
|
+
* element-attached hook lets the realtime client route the element's audio into its own Web Audio graph.
|
|
15
|
+
* - **End of turn** ends the stream once the turn's pieces are in, so playback runs to the true end and holds the last
|
|
16
|
+
* frame. **Barge-in** drops everything not yet played and pauses. **Gaps** (between turns, or while a connection
|
|
17
|
+
* resumes) hold the last frame.
|
|
18
|
+
* - **Browsers.** `MediaSource` (Chrome, Edge, Firefox, desktop Safari), or `ManagedMediaSource` (iOS Safari 17.1+).
|
|
19
|
+
*
|
|
20
|
+
* @module @memberjunction/ai-realtime-client/media
|
|
21
|
+
*/
|
|
22
|
+
import { type RealtimeVideoFrame } from '@memberjunction/ai';
|
|
23
|
+
import type { IVideoFrameDecoder, VideoFrameDecoderContext, VideoFrameDecoderRegistration } from '../videoFrameDecoder.js';
|
|
24
|
+
/** Plays fragmented MP4 frames through MSE into one `<video>` element at a time. */
|
|
25
|
+
export declare class MseFmp4Decoder implements IVideoFrameDecoder {
|
|
26
|
+
private readonly context;
|
|
27
|
+
private readonly mimeType;
|
|
28
|
+
private readonly backBufferSeconds;
|
|
29
|
+
private readonly pending;
|
|
30
|
+
private element;
|
|
31
|
+
private mediaSource;
|
|
32
|
+
private objectUrl;
|
|
33
|
+
private sourceBuffer;
|
|
34
|
+
/** The type the source buffer was opened with, or last changed to. */
|
|
35
|
+
private bufferType;
|
|
36
|
+
/** The latest init segment, replayed into a new source buffer when the decoder moves to another element. */
|
|
37
|
+
private lastInit;
|
|
38
|
+
/** Set by Flush: the next append starts at the playhead instead of after what was dropped. */
|
|
39
|
+
private restartAtPlayhead;
|
|
40
|
+
private retriedAfterQuota;
|
|
41
|
+
/** Where the last back-buffer trim ended, in media time. */
|
|
42
|
+
private trimmedTo;
|
|
43
|
+
/** Set by EndOfTurn: end the stream once every pending piece is in. */
|
|
44
|
+
private endPending;
|
|
45
|
+
/** Where each appended piece ends on the element's timeline (seconds), oldest first, until the playhead passes it. */
|
|
46
|
+
private readonly appendedEnds;
|
|
47
|
+
/** Whether the source buffer is appending a piece: its `updateend` records where the piece ends. */
|
|
48
|
+
private appending;
|
|
49
|
+
private disposed;
|
|
50
|
+
/** Whether this browser can play `mimeType` through MSE. */
|
|
51
|
+
static CanPlay(mimeType: string): boolean;
|
|
52
|
+
/** @param context The player this decoder plays for. */
|
|
53
|
+
constructor(context: VideoFrameDecoderContext);
|
|
54
|
+
/**
|
|
55
|
+
* Whether the element is playing forward with media buffered ahead of the playhead: the agent is audibly
|
|
56
|
+
* speaking. False while it waits for data, which it does just short of the end of a turn that hasn't
|
|
57
|
+
* been ended with {@link EndOfTurn}.
|
|
58
|
+
*/
|
|
59
|
+
get IsPlaying(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* How many pieces are still to play: pending (waiting for the source buffer, or for an element), being appended, or
|
|
62
|
+
* appended and ending past the playhead. The source buffer lays pieces end to end (`sequence` mode), so a piece ends
|
|
63
|
+
* where the buffered media ended once it was in. A piece a {@link Flush} removed, or the browser rejected, is not
|
|
64
|
+
* counted; nor is media of an element the decoder let go.
|
|
65
|
+
*/
|
|
66
|
+
get FramesAhead(): number;
|
|
67
|
+
/** Hands over one frame, in the order they arrived: an init segment or a media fragment. */
|
|
68
|
+
Append(frame: RealtimeVideoFrame): void;
|
|
69
|
+
/**
|
|
70
|
+
* The turn's last piece has arrived. Ends the stream once it is all in, so playback runs to the true end of
|
|
71
|
+
* the turn; the next {@link Append} reopens it and playback continues from there.
|
|
72
|
+
*/
|
|
73
|
+
EndOfTurn(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Barge-in: drops everything not yet played and pauses, so the voice stops now instead of after the audio the
|
|
76
|
+
* browser has already decoded. The last frame stays on screen; the next turn's media resumes playback.
|
|
77
|
+
*/
|
|
78
|
+
Flush(): void;
|
|
79
|
+
/** Stops playout and releases the element and the media source. */
|
|
80
|
+
Dispose(): void;
|
|
81
|
+
/** Takes the element over: a new media source, the latest init segment replayed into it, then what is pending. */
|
|
82
|
+
Attach(element: HTMLVideoElement): void;
|
|
83
|
+
/** Lets the element go: its source is removed and the media source released. */
|
|
84
|
+
Detach(): void;
|
|
85
|
+
/** Readies an element before it plays: its audio on or off, the attach hook, and what iOS's managed MSE requires. */
|
|
86
|
+
private prepareElement;
|
|
87
|
+
private openSourceBuffer;
|
|
88
|
+
/** Appends the next pending frame when the buffer is free. */
|
|
89
|
+
private pump;
|
|
90
|
+
/** The type for a source buffer that takes `init`: the init's own codecs when this browser plays them, else the player's type. */
|
|
91
|
+
private typeFor;
|
|
92
|
+
/**
|
|
93
|
+
* Switches the source buffer to an init segment's codecs before the init is appended, when they differ from the
|
|
94
|
+
* buffer's: it opened with the player's type before the first init arrived, or a later init changed tracks. A browser
|
|
95
|
+
* without `changeType`, or an init whose codecs can't be read or played, leaves the buffer as it is.
|
|
96
|
+
*/
|
|
97
|
+
private matchBufferType;
|
|
98
|
+
/** After each append or removal: trims played media, appends the next piece, and ends a finished turn. */
|
|
99
|
+
private afterUpdate;
|
|
100
|
+
/**
|
|
101
|
+
* An append finished: the piece ends where the buffered media now ends, since the buffer lays pieces end to end.
|
|
102
|
+
* Pieces the playhead has passed are forgotten here too, so the list holds only what is ahead.
|
|
103
|
+
*/
|
|
104
|
+
private recordAppendedEnd;
|
|
105
|
+
/** Forgets the appended pieces the playhead has reached the end of (to within {@link PLAYING_EPSILON_SECONDS}). */
|
|
106
|
+
private forgetPlayedPieces;
|
|
107
|
+
/** Ends the stream for a finished turn once nothing is pending or in flight. */
|
|
108
|
+
private endStreamWhenDrained;
|
|
109
|
+
/**
|
|
110
|
+
* A turn that ran to its end, or a Flush, leaves the element paused. Once new media reaches past the playhead,
|
|
111
|
+
* play on from there. An ended element is first sought to where it stopped: play() on an ended element starts
|
|
112
|
+
* over from the beginning (Chrome does), which would replay the previous turn.
|
|
113
|
+
*/
|
|
114
|
+
private resumeWhenMediaArrives;
|
|
115
|
+
/**
|
|
116
|
+
* Removes played media more than the back buffer's seconds behind the playhead, so a long session stays within the
|
|
117
|
+
* browser's buffer quota. A trim waits until {@link TRIM_STEP_SECONDS} more have played since the last one, so a
|
|
118
|
+
* removal that doesn't shrink the buffer can't repeat on every `updateend`. When `bufferFull`, keeps only
|
|
119
|
+
* {@link QUOTA_BACK_BUFFER_SECONDS} and skips the wait.
|
|
120
|
+
*
|
|
121
|
+
* @returns Whether a removal started (the next append waits for its `updateend`).
|
|
122
|
+
*/
|
|
123
|
+
private trimBackBuffer;
|
|
124
|
+
/** Out of buffer space: trim harder and retry once. Anything else drops the piece. */
|
|
125
|
+
private handleAppendError;
|
|
126
|
+
private detach;
|
|
127
|
+
}
|
|
128
|
+
/** The `'mse-fmp4'` decoder: fragmented MP4 frames whose type this browser's Media Source Extensions play. */
|
|
129
|
+
export declare const MSE_FMP4_DECODER: VideoFrameDecoderRegistration;
|
|
130
|
+
//# sourceMappingURL=mseFmp4Decoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mseFmp4Decoder.d.ts","sourceRoot":"","sources":["../../../src/media/decoders/mseFmp4Decoder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAA6C,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxG,OAAO,KAAK,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAC;AA2CxH,oFAAoF;AACpF,qBAAa,cAAe,YAAW,kBAAkB;IA+BzC,OAAO,CAAC,QAAQ,CAAC,OAAO;IA9BpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAA6B;IACjD,sEAAsE;IACtE,OAAO,CAAC,UAAU,CAAuB;IACzC,4GAA4G;IAC5G,OAAO,CAAC,QAAQ,CAAuC;IACvD,8FAA8F;IAC9F,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,iBAAiB,CAAS;IAClC,4DAA4D;IAC5D,OAAO,CAAC,SAAS,CAAK;IACtB,uEAAuE;IACvE,OAAO,CAAC,UAAU,CAAS;IAC3B,sHAAsH;IACtH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAC7C,oGAAoG;IACpG,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;IAEzB,4DAA4D;WAC9C,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIhD,wDAAwD;gBAC3B,OAAO,EAAE,wBAAwB;IAK9D;;;;OAIG;IACH,IAAW,SAAS,IAAI,OAAO,CAO9B;IAED;;;;;OAKG;IACH,IAAW,WAAW,IAAI,MAAM,CAG/B;IAED,4FAA4F;IACrF,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI;IAoB9C;;;OAGG;IACI,SAAS,IAAI,IAAI;IAKxB;;;OAGG;IACI,KAAK,IAAI,IAAI;IAsBpB,mEAAmE;IAC5D,OAAO,IAAI,IAAI;IAMtB,kHAAkH;IAC3G,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAsB9C,gFAAgF;IACzE,MAAM,IAAI,IAAI;IAIrB,qHAAqH;IACrH,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,gBAAgB;IAmBxB,8DAA8D;IAC9D,OAAO,CAAC,IAAI;IAuBZ,kIAAkI;IAClI,OAAO,CAAC,OAAO;IAKf;;;;OAIG;IACH,OAAO,CAAC,eAAe;IASvB,0GAA0G;IAC1G,OAAO,CAAC,WAAW;IAUnB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAUzB,mHAAmH;IACnH,OAAO,CAAC,kBAAkB;IAU1B,gFAAgF;IAChF,OAAO,CAAC,oBAAoB;IAa5B;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAiB9B;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAgBtB,sFAAsF;IACtF,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,MAAM;CAoBjB;AAED,8GAA8G;AAC9G,eAAO,MAAM,gBAAgB,EAAE,6BAM9B,CAAC"}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MSE FMP4 DECODER: plays fragmented MP4 frames (`Kind: 'fmp4'`), such as a Gemini Live avatar, through
|
|
3
|
+
* Media Source Extensions into the host's `<video>` element. The `'mse-fmp4'` decoder of `VideoPlayout`.
|
|
4
|
+
*
|
|
5
|
+
* - **Format.** The first piece of a turn is an init segment (`Piece: 'init'`, `ftyp` + `moov`); each later piece is a
|
|
6
|
+
* media fragment (`moof` + `mdat`). A turn may send a fresh init segment and restart its timestamps; the source buffer
|
|
7
|
+
* runs in `sequence` mode, so playback stays continuous either way.
|
|
8
|
+
* - **Codecs come from the init segment.** The source buffer takes the codecs the init segment's tracks name
|
|
9
|
+
* (`avc1.42c01f` alone for video-only MP4), read by `ReadFmp4Init` from `@memberjunction/ai`; until an init arrives, or
|
|
10
|
+
* when it names a codec the reader doesn't know, it takes the player's configured type.
|
|
11
|
+
* - **The video carries the voice.** An avatar's audio is muxed into the MP4, so the element is unmuted and lips and voice
|
|
12
|
+
* stay in sync on the media's own timestamps. While media is buffered ahead of the playhead, {@link MseFmp4Decoder.IsPlaying}
|
|
13
|
+
* is true: the agent is audibly speaking. When the voice plays elsewhere (separate PCM), the element is muted. The
|
|
14
|
+
* element-attached hook lets the realtime client route the element's audio into its own Web Audio graph.
|
|
15
|
+
* - **End of turn** ends the stream once the turn's pieces are in, so playback runs to the true end and holds the last
|
|
16
|
+
* frame. **Barge-in** drops everything not yet played and pauses. **Gaps** (between turns, or while a connection
|
|
17
|
+
* resumes) hold the last frame.
|
|
18
|
+
* - **Browsers.** `MediaSource` (Chrome, Edge, Firefox, desktop Safari), or `ManagedMediaSource` (iOS Safari 17.1+).
|
|
19
|
+
*
|
|
20
|
+
* @module @memberjunction/ai-realtime-client/media
|
|
21
|
+
*/
|
|
22
|
+
import { ReadFmp4Init } from '@memberjunction/ai';
|
|
23
|
+
/** Back-buffer trims wait until this many more seconds have played, so a removal can never repeat in a loop. */
|
|
24
|
+
const TRIM_STEP_SECONDS = 5;
|
|
25
|
+
/** Seconds of played media kept when the browser's buffer is full. */
|
|
26
|
+
const QUOTA_BACK_BUFFER_SECONDS = 2;
|
|
27
|
+
/** Pieces held while no element is attached; older ones are dropped beyond this. */
|
|
28
|
+
const MAX_PENDING_PIECES = 600;
|
|
29
|
+
/** How far ahead of the playhead counts as "still playing", in seconds. */
|
|
30
|
+
const PLAYING_EPSILON_SECONDS = 0.05;
|
|
31
|
+
/** `HTMLMediaElement.HAVE_FUTURE_DATA`: the element has data to play beyond the current position. */
|
|
32
|
+
const HAVE_FUTURE_DATA = 3;
|
|
33
|
+
/** The MSE implementation this browser offers: the classic one, else the managed one. */
|
|
34
|
+
function mediaSourceClass() {
|
|
35
|
+
const scope = globalThis;
|
|
36
|
+
if (scope.MediaSource) {
|
|
37
|
+
return { Class: scope.MediaSource, Managed: false };
|
|
38
|
+
}
|
|
39
|
+
return scope.ManagedMediaSource ? { Class: scope.ManagedMediaSource, Managed: true } : null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The MSE type an init segment's video and audio tracks need, from their codecs: `video/mp4; codecs="avc1.42c01f,
|
|
43
|
+
* mp4a.40.2"` for a muxed avatar, `video/mp4; codecs="avc1.42c01f"` for video alone. `null` when the init can't be read,
|
|
44
|
+
* declares neither, or names a codec the reader doesn't know.
|
|
45
|
+
*/
|
|
46
|
+
function mseTypeOf(init) {
|
|
47
|
+
const tracks = ReadFmp4Init(init)?.Tracks.filter((track) => track.Handler === 'vide' || track.Handler === 'soun') ?? [];
|
|
48
|
+
if (tracks.length === 0 || tracks.some((track) => !track.Codec)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return `video/mp4; codecs="${tracks.map((track) => track.Codec).join(', ')}"`;
|
|
52
|
+
}
|
|
53
|
+
/** Plays fragmented MP4 frames through MSE into one `<video>` element at a time. */
|
|
54
|
+
export class MseFmp4Decoder {
|
|
55
|
+
/** Whether this browser can play `mimeType` through MSE. */
|
|
56
|
+
static CanPlay(mimeType) {
|
|
57
|
+
return mediaSourceClass()?.Class.isTypeSupported(mimeType) ?? false;
|
|
58
|
+
}
|
|
59
|
+
/** @param context The player this decoder plays for. */
|
|
60
|
+
constructor(context) {
|
|
61
|
+
this.context = context;
|
|
62
|
+
this.pending = [];
|
|
63
|
+
this.element = null;
|
|
64
|
+
this.mediaSource = null;
|
|
65
|
+
this.objectUrl = null;
|
|
66
|
+
this.sourceBuffer = null;
|
|
67
|
+
/** The type the source buffer was opened with, or last changed to. */
|
|
68
|
+
this.bufferType = null;
|
|
69
|
+
/** The latest init segment, replayed into a new source buffer when the decoder moves to another element. */
|
|
70
|
+
this.lastInit = null;
|
|
71
|
+
/** Set by Flush: the next append starts at the playhead instead of after what was dropped. */
|
|
72
|
+
this.restartAtPlayhead = false;
|
|
73
|
+
this.retriedAfterQuota = false;
|
|
74
|
+
/** Where the last back-buffer trim ended, in media time. */
|
|
75
|
+
this.trimmedTo = 0;
|
|
76
|
+
/** Set by EndOfTurn: end the stream once every pending piece is in. */
|
|
77
|
+
this.endPending = false;
|
|
78
|
+
/** Where each appended piece ends on the element's timeline (seconds), oldest first, until the playhead passes it. */
|
|
79
|
+
this.appendedEnds = [];
|
|
80
|
+
/** Whether the source buffer is appending a piece: its `updateend` records where the piece ends. */
|
|
81
|
+
this.appending = false;
|
|
82
|
+
this.disposed = false;
|
|
83
|
+
this.mimeType = context.MimeType;
|
|
84
|
+
this.backBufferSeconds = context.BackBufferSeconds;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Whether the element is playing forward with media buffered ahead of the playhead: the agent is audibly
|
|
88
|
+
* speaking. False while it waits for data, which it does just short of the end of a turn that hasn't
|
|
89
|
+
* been ended with {@link EndOfTurn}.
|
|
90
|
+
*/
|
|
91
|
+
get IsPlaying() {
|
|
92
|
+
const element = this.element;
|
|
93
|
+
const buffered = this.sourceBuffer?.buffered;
|
|
94
|
+
if (!element || element.paused || element.readyState < HAVE_FUTURE_DATA || !buffered || buffered.length === 0) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return buffered.end(buffered.length - 1) - element.currentTime > PLAYING_EPSILON_SECONDS;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* How many pieces are still to play: pending (waiting for the source buffer, or for an element), being appended, or
|
|
101
|
+
* appended and ending past the playhead. The source buffer lays pieces end to end (`sequence` mode), so a piece ends
|
|
102
|
+
* where the buffered media ended once it was in. A piece a {@link Flush} removed, or the browser rejected, is not
|
|
103
|
+
* counted; nor is media of an element the decoder let go.
|
|
104
|
+
*/
|
|
105
|
+
get FramesAhead() {
|
|
106
|
+
this.forgetPlayedPieces();
|
|
107
|
+
return this.pending.length + (this.appending ? 1 : 0) + this.appendedEnds.length;
|
|
108
|
+
}
|
|
109
|
+
/** Hands over one frame, in the order they arrived: an init segment or a media fragment. */
|
|
110
|
+
Append(frame) {
|
|
111
|
+
if (this.disposed || frame.Kind !== 'fmp4') {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// More media is coming, so a turn that was about to end continues instead.
|
|
115
|
+
this.endPending = false;
|
|
116
|
+
if (frame.Piece === 'init') {
|
|
117
|
+
this.lastInit = frame;
|
|
118
|
+
}
|
|
119
|
+
else if (!this.lastInit) {
|
|
120
|
+
this.context.Report('fragment-before-init', 'A video fragment arrived before its init segment and was dropped.');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.pending.push(frame);
|
|
124
|
+
if (this.pending.length > MAX_PENDING_PIECES) {
|
|
125
|
+
this.pending.splice(0, this.pending.length - MAX_PENDING_PIECES);
|
|
126
|
+
this.context.Report('pending-overflow', 'Video arrived with no element attached; the oldest pieces were dropped.');
|
|
127
|
+
}
|
|
128
|
+
this.pump();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The turn's last piece has arrived. Ends the stream once it is all in, so playback runs to the true end of
|
|
132
|
+
* the turn; the next {@link Append} reopens it and playback continues from there.
|
|
133
|
+
*/
|
|
134
|
+
EndOfTurn() {
|
|
135
|
+
this.endPending = true;
|
|
136
|
+
this.endStreamWhenDrained();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Barge-in: drops everything not yet played and pauses, so the voice stops now instead of after the audio the
|
|
140
|
+
* browser has already decoded. The last frame stays on screen; the next turn's media resumes playback.
|
|
141
|
+
*/
|
|
142
|
+
Flush() {
|
|
143
|
+
this.pending.length = 0;
|
|
144
|
+
this.endPending = false;
|
|
145
|
+
// What is buffered ahead of the playhead is removed, and an append under way is aborted: none of it plays.
|
|
146
|
+
this.appendedEnds.length = 0;
|
|
147
|
+
this.appending = false;
|
|
148
|
+
const buffer = this.sourceBuffer;
|
|
149
|
+
const element = this.element;
|
|
150
|
+
if (!buffer || !element) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
element.pause();
|
|
154
|
+
if (buffer.updating) {
|
|
155
|
+
buffer.abort();
|
|
156
|
+
}
|
|
157
|
+
const buffered = buffer.buffered;
|
|
158
|
+
if (buffered.length > 0 && buffered.end(buffered.length - 1) > element.currentTime) {
|
|
159
|
+
buffer.remove(element.currentTime, Infinity);
|
|
160
|
+
}
|
|
161
|
+
this.restartAtPlayhead = true;
|
|
162
|
+
}
|
|
163
|
+
/** Stops playout and releases the element and the media source. */
|
|
164
|
+
Dispose() {
|
|
165
|
+
this.disposed = true;
|
|
166
|
+
this.pending.length = 0;
|
|
167
|
+
this.detach();
|
|
168
|
+
}
|
|
169
|
+
/** Takes the element over: a new media source, the latest init segment replayed into it, then what is pending. */
|
|
170
|
+
Attach(element) {
|
|
171
|
+
if (this.disposed) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.detach();
|
|
175
|
+
const mse = mediaSourceClass();
|
|
176
|
+
if (!mse || !mse.Class.isTypeSupported(this.mimeType)) {
|
|
177
|
+
this.context.Report('unsupported', `This browser cannot play ${this.mimeType} through Media Source Extensions.`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const mediaSource = new mse.Class();
|
|
181
|
+
this.mediaSource = mediaSource;
|
|
182
|
+
this.element = element;
|
|
183
|
+
this.prepareElement(element, mse.Managed);
|
|
184
|
+
mediaSource.addEventListener('sourceopen', () => this.openSourceBuffer(mediaSource), { once: true });
|
|
185
|
+
this.objectUrl = URL.createObjectURL(mediaSource);
|
|
186
|
+
element.src = this.objectUrl;
|
|
187
|
+
void element.play().catch(() => {
|
|
188
|
+
// Playback starts when the first media arrives, or on the next user gesture if the browser waits.
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/** Lets the element go: its source is removed and the media source released. */
|
|
192
|
+
Detach() {
|
|
193
|
+
this.detach();
|
|
194
|
+
}
|
|
195
|
+
/** Readies an element before it plays: its audio on or off, the attach hook, and what iOS's managed MSE requires. */
|
|
196
|
+
prepareElement(element, managed) {
|
|
197
|
+
if (managed) {
|
|
198
|
+
// ManagedMediaSource plays only when remote playback (AirPlay) is off or has an alternative source.
|
|
199
|
+
element.disableRemotePlayback = true;
|
|
200
|
+
}
|
|
201
|
+
// Set either way: a host may hand over an element it muted (a media tile never plays audio itself).
|
|
202
|
+
element.muted = !this.context.CarriesVoice;
|
|
203
|
+
try {
|
|
204
|
+
this.context.OnElementAttached?.(element);
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
console.warn(`[VideoPlayout] The element-attached hook failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
openSourceBuffer(mediaSource) {
|
|
211
|
+
if (this.mediaSource !== mediaSource) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const type = this.typeFor(this.lastInit?.Data ?? null);
|
|
215
|
+
const buffer = mediaSource.addSourceBuffer(type);
|
|
216
|
+
this.bufferType = type;
|
|
217
|
+
// Sequence mode lays pieces end to end, so a turn that restarts its timestamps still plays on.
|
|
218
|
+
buffer.mode = 'sequence';
|
|
219
|
+
buffer.addEventListener('updateend', () => this.afterUpdate());
|
|
220
|
+
buffer.addEventListener('error', () => this.context.Report('append-failed', 'The browser could not decode a video piece.'));
|
|
221
|
+
this.sourceBuffer = buffer;
|
|
222
|
+
// A new source buffer knows no tracks yet: lead with the latest init segment unless one is already next.
|
|
223
|
+
if (this.lastInit && !(this.pending.length > 0 && this.pending[0].Piece === 'init')) {
|
|
224
|
+
this.pending.unshift(this.lastInit);
|
|
225
|
+
}
|
|
226
|
+
this.pump();
|
|
227
|
+
}
|
|
228
|
+
/** Appends the next pending frame when the buffer is free. */
|
|
229
|
+
pump() {
|
|
230
|
+
const buffer = this.sourceBuffer;
|
|
231
|
+
if (!buffer || buffer.updating || this.pending.length === 0) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (this.restartAtPlayhead && this.element) {
|
|
235
|
+
buffer.timestampOffset = this.element.currentTime;
|
|
236
|
+
this.restartAtPlayhead = false;
|
|
237
|
+
}
|
|
238
|
+
const frame = this.pending[0];
|
|
239
|
+
try {
|
|
240
|
+
if (frame.Piece === 'init') {
|
|
241
|
+
this.matchBufferType(buffer, frame.Data);
|
|
242
|
+
}
|
|
243
|
+
buffer.appendBuffer(frame.Data);
|
|
244
|
+
this.pending.shift();
|
|
245
|
+
this.appending = true;
|
|
246
|
+
this.retriedAfterQuota = false;
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
this.handleAppendError(err);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/** The type for a source buffer that takes `init`: the init's own codecs when this browser plays them, else the player's type. */
|
|
253
|
+
typeFor(init) {
|
|
254
|
+
const fromInit = init ? mseTypeOf(init) : null;
|
|
255
|
+
return fromInit && mediaSourceClass()?.Class.isTypeSupported(fromInit) ? fromInit : this.mimeType;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Switches the source buffer to an init segment's codecs before the init is appended, when they differ from the
|
|
259
|
+
* buffer's: it opened with the player's type before the first init arrived, or a later init changed tracks. A browser
|
|
260
|
+
* without `changeType`, or an init whose codecs can't be read or played, leaves the buffer as it is.
|
|
261
|
+
*/
|
|
262
|
+
matchBufferType(buffer, init) {
|
|
263
|
+
const type = mseTypeOf(init);
|
|
264
|
+
if (!type || type === this.bufferType || typeof buffer.changeType !== 'function' || !mediaSourceClass()?.Class.isTypeSupported(type)) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
buffer.changeType(type);
|
|
268
|
+
this.bufferType = type;
|
|
269
|
+
}
|
|
270
|
+
/** After each append or removal: trims played media, appends the next piece, and ends a finished turn. */
|
|
271
|
+
afterUpdate() {
|
|
272
|
+
this.recordAppendedEnd();
|
|
273
|
+
this.resumeWhenMediaArrives();
|
|
274
|
+
if (this.trimBackBuffer()) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
this.pump();
|
|
278
|
+
this.endStreamWhenDrained();
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* An append finished: the piece ends where the buffered media now ends, since the buffer lays pieces end to end.
|
|
282
|
+
* Pieces the playhead has passed are forgotten here too, so the list holds only what is ahead.
|
|
283
|
+
*/
|
|
284
|
+
recordAppendedEnd() {
|
|
285
|
+
if (!this.appending) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.appending = false;
|
|
289
|
+
const buffered = this.sourceBuffer?.buffered;
|
|
290
|
+
this.appendedEnds.push(buffered && buffered.length > 0 ? buffered.end(buffered.length - 1) : 0);
|
|
291
|
+
this.forgetPlayedPieces();
|
|
292
|
+
}
|
|
293
|
+
/** Forgets the appended pieces the playhead has reached the end of (to within {@link PLAYING_EPSILON_SECONDS}). */
|
|
294
|
+
forgetPlayedPieces() {
|
|
295
|
+
const element = this.element;
|
|
296
|
+
if (!element) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
while (this.appendedEnds.length > 0 && this.appendedEnds[0] - element.currentTime <= PLAYING_EPSILON_SECONDS) {
|
|
300
|
+
this.appendedEnds.shift();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/** Ends the stream for a finished turn once nothing is pending or in flight. */
|
|
304
|
+
endStreamWhenDrained() {
|
|
305
|
+
const mediaSource = this.mediaSource;
|
|
306
|
+
if (!this.endPending || !mediaSource || mediaSource.readyState !== 'open' || this.sourceBuffer?.updating || this.pending.length > 0) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
this.endPending = false;
|
|
310
|
+
try {
|
|
311
|
+
mediaSource.endOfStream();
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
this.context.Report('append-failed', `Could not end the turn's stream: ${err instanceof Error ? err.message : String(err)}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* A turn that ran to its end, or a Flush, leaves the element paused. Once new media reaches past the playhead,
|
|
319
|
+
* play on from there. An ended element is first sought to where it stopped: play() on an ended element starts
|
|
320
|
+
* over from the beginning (Chrome does), which would replay the previous turn.
|
|
321
|
+
*/
|
|
322
|
+
resumeWhenMediaArrives() {
|
|
323
|
+
const element = this.element;
|
|
324
|
+
const buffered = this.sourceBuffer?.buffered;
|
|
325
|
+
// After a Flush, wait for media appended after it: the abort's own `updateend` arrives before the removal
|
|
326
|
+
// has shrunk the buffer, and resuming then would play the voice the Flush just cut off.
|
|
327
|
+
if (!element || !element.paused || this.restartAtPlayhead || !buffered || buffered.length === 0) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const position = element.currentTime;
|
|
331
|
+
if (buffered.end(buffered.length - 1) - position > PLAYING_EPSILON_SECONDS) {
|
|
332
|
+
if (element.ended) {
|
|
333
|
+
element.currentTime = position;
|
|
334
|
+
}
|
|
335
|
+
void element.play().catch(() => undefined);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Removes played media more than the back buffer's seconds behind the playhead, so a long session stays within the
|
|
340
|
+
* browser's buffer quota. A trim waits until {@link TRIM_STEP_SECONDS} more have played since the last one, so a
|
|
341
|
+
* removal that doesn't shrink the buffer can't repeat on every `updateend`. When `bufferFull`, keeps only
|
|
342
|
+
* {@link QUOTA_BACK_BUFFER_SECONDS} and skips the wait.
|
|
343
|
+
*
|
|
344
|
+
* @returns Whether a removal started (the next append waits for its `updateend`).
|
|
345
|
+
*/
|
|
346
|
+
trimBackBuffer(bufferFull = false) {
|
|
347
|
+
const buffer = this.sourceBuffer;
|
|
348
|
+
const element = this.element;
|
|
349
|
+
if (!buffer || !element || buffer.updating || buffer.buffered.length === 0) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
const keepFrom = element.currentTime - (bufferFull ? QUOTA_BACK_BUFFER_SECONDS : this.backBufferSeconds);
|
|
353
|
+
const removableFrom = Math.max(buffer.buffered.start(0), bufferFull ? 0 : this.trimmedTo);
|
|
354
|
+
if (keepFrom <= 0 || keepFrom - removableFrom < (bufferFull ? 0.001 : TRIM_STEP_SECONDS)) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
buffer.remove(0, keepFrom);
|
|
358
|
+
this.trimmedTo = keepFrom;
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
/** Out of buffer space: trim harder and retry once. Anything else drops the piece. */
|
|
362
|
+
handleAppendError(err) {
|
|
363
|
+
const quota = err instanceof Error && err.name === 'QuotaExceededError';
|
|
364
|
+
if (quota && !this.retriedAfterQuota && this.trimBackBuffer(true)) {
|
|
365
|
+
this.retriedAfterQuota = true;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
this.pending.shift();
|
|
369
|
+
this.retriedAfterQuota = false;
|
|
370
|
+
this.context.Report('append-failed', err instanceof Error ? err.message : String(err));
|
|
371
|
+
this.pump();
|
|
372
|
+
}
|
|
373
|
+
detach() {
|
|
374
|
+
if (this.element) {
|
|
375
|
+
this.element.removeAttribute('src');
|
|
376
|
+
this.element.load();
|
|
377
|
+
}
|
|
378
|
+
if (this.objectUrl) {
|
|
379
|
+
URL.revokeObjectURL(this.objectUrl);
|
|
380
|
+
}
|
|
381
|
+
this.element = null;
|
|
382
|
+
this.mediaSource = null;
|
|
383
|
+
this.sourceBuffer = null;
|
|
384
|
+
this.bufferType = null;
|
|
385
|
+
this.objectUrl = null;
|
|
386
|
+
this.restartAtPlayhead = false;
|
|
387
|
+
this.trimmedTo = 0;
|
|
388
|
+
this.endPending = false;
|
|
389
|
+
// The media source and what it buffered are gone; the pending pieces stay for the next element.
|
|
390
|
+
this.appendedEnds.length = 0;
|
|
391
|
+
this.appending = false;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/** The `'mse-fmp4'` decoder: fragmented MP4 frames whose type this browser's Media Source Extensions play. */
|
|
395
|
+
export const MSE_FMP4_DECODER = {
|
|
396
|
+
Name: 'mse-fmp4',
|
|
397
|
+
Kind: 'fmp4',
|
|
398
|
+
Priority: 0,
|
|
399
|
+
CanPlay: (mimeType) => MseFmp4Decoder.CanPlay(mimeType),
|
|
400
|
+
Create: (context) => new MseFmp4Decoder(context),
|
|
401
|
+
};
|
|
402
|
+
//# sourceMappingURL=mseFmp4Decoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mseFmp4Decoder.js","sourceRoot":"","sources":["../../../src/media/decoders/mseFmp4Decoder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview STREAM CANVAS: turns decoded pictures (WebCodecs `VideoFrame`s, `ImageBitmap`s) into a live
|
|
3
|
+
* `MediaStream` that the host's `<video>` element shows, so a decoder that isn't MSE still plays into the element: the
|
|
4
|
+
* tile, its stall watch (`requestVideoFrameCallback` fires once per picture) and its placements work as for any video.
|
|
5
|
+
*
|
|
6
|
+
* - **Chromium:** a `MediaStreamTrackGenerator` takes each picture as a `VideoFrame`, with no copy.
|
|
7
|
+
* - **Elsewhere:** a canvas captured at 0 fps (`captureStream(0)`) is drawn on, and a frame is requested after each draw.
|
|
8
|
+
* - **The last picture holds.** Between pictures the element keeps showing the last one, and an element attached later
|
|
9
|
+
* is shown it: the generator is written a copy again, or the canvas is captured again. A picture that reaches an element
|
|
10
|
+
* before its media pipeline is connected is lost (in Chromium that takes a task or two), so the copy is offered every
|
|
11
|
+
* {@link REPLAY_INTERVAL_MS} until the element has a frame, at most {@link MAX_REPLAYS} times.
|
|
12
|
+
* - **Time moves forward.** Each picture written to the generator is stamped with the wall clock, after the one before, so a
|
|
13
|
+
* stream that restarts its media time doesn't feed the track frames that go back in time.
|
|
14
|
+
*
|
|
15
|
+
* @module @memberjunction/ai-realtime-client/media
|
|
16
|
+
*/
|
|
17
|
+
/** A decoded picture. The stream canvas takes it over and closes it. */
|
|
18
|
+
export type StreamPicture = VideoFrame | ImageBitmap;
|
|
19
|
+
/** How often the last picture is offered again to an element just attached, until it has a frame, in milliseconds. */
|
|
20
|
+
export declare const REPLAY_INTERVAL_MS = 50;
|
|
21
|
+
/** How many times the last picture is offered to an element just attached. */
|
|
22
|
+
export declare const MAX_REPLAYS = 20;
|
|
23
|
+
/** Shows decoded pictures in `<video>` elements through one live stream. */
|
|
24
|
+
export declare class StreamCanvas {
|
|
25
|
+
private readonly sink;
|
|
26
|
+
private element;
|
|
27
|
+
private replayTimer;
|
|
28
|
+
/** Whether this browser can turn pictures into a stream: a track generator, or a canvas it can capture. */
|
|
29
|
+
static IsAvailable(): boolean;
|
|
30
|
+
constructor();
|
|
31
|
+
/** Shows a picture in the attached element (and any attached later), and closes it. */
|
|
32
|
+
Show(picture: StreamPicture): void;
|
|
33
|
+
/** Shows the stream in an element, with the last picture. The element plays muted: the stream has no audio. */
|
|
34
|
+
Attach(element: HTMLVideoElement): void;
|
|
35
|
+
/** Takes the stream out of the element it was attached to, unless another source replaced it there. */
|
|
36
|
+
Detach(): void;
|
|
37
|
+
/** Detaches, stops the stream's track and closes the last picture. */
|
|
38
|
+
Dispose(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Offers the last picture to a just-attached element until it has a frame, every {@link REPLAY_INTERVAL_MS}. Letting
|
|
41
|
+
* the element go cancels the offers.
|
|
42
|
+
*/
|
|
43
|
+
private replayUntilShown;
|
|
44
|
+
private cancelReplay;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=streamCanvas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamCanvas.d.ts","sourceRoot":"","sources":["../../../src/media/decoders/streamCanvas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,WAAW,CAAC;AAErD,sHAAsH;AACtH,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,8EAA8E;AAC9E,eAAO,MAAM,WAAW,KAAK,CAAC;AA0H9B,4EAA4E;AAC5E,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,WAAW,CAA8C;IAEjE,2GAA2G;WAC7F,WAAW,IAAI,OAAO;;IASpC,uFAAuF;IAChF,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAIzC,+GAA+G;IACxG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAY9C,uGAAuG;IAChG,MAAM,IAAI,IAAI;IAUrB,sEAAsE;IAC/D,OAAO,IAAI,IAAI;IAKtB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,YAAY;CAMvB"}
|