@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,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MEDIA ELEMENT AUDIO ROUTER: takes a media element's audio into Web Audio once per page and hands it out
|
|
3
|
+
* as a `MediaStream`, so every realtime call that shows the element can play, meter and record its voice.
|
|
4
|
+
*
|
|
5
|
+
* A browser connects an element to Web Audio only once, ever (`createMediaElementSource`). Connected inside one call's
|
|
6
|
+
* audio context, an element would be silent in every later call, and silent for good once that context closed. So the
|
|
7
|
+
* router owns one page-level `AudioContext`, created on the first route and resumed whenever it is found suspended. It
|
|
8
|
+
* routes each element into a `MediaStreamAudioDestinationNode`, never into its own speakers, so the element is heard
|
|
9
|
+
* only through the calls that take its stream, and it returns that same stream for the element every time.
|
|
10
|
+
*
|
|
11
|
+
* An element stays routed for the life of the page: its source node keeps it referenced. A player detaches its media
|
|
12
|
+
* when it lets an element go, so what stays behind is small.
|
|
13
|
+
*
|
|
14
|
+
* @module @memberjunction/ai-realtime-client
|
|
15
|
+
*/
|
|
16
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
17
|
+
/** Routes media elements' audio into one page-level audio context and hands each one out as a stream. */
|
|
18
|
+
export declare class MediaElementAudioRouter extends BaseSingleton<MediaElementAudioRouter> {
|
|
19
|
+
/** The page's routing context: `undefined` until the first route, `null` when this browser could not create one. */
|
|
20
|
+
private context;
|
|
21
|
+
/** Each element routed, with the stream that carries its audio; `null` for an element the browser refused. */
|
|
22
|
+
private readonly streams;
|
|
23
|
+
protected constructor();
|
|
24
|
+
/** The page's router. */
|
|
25
|
+
static get Instance(): MediaElementAudioRouter;
|
|
26
|
+
/**
|
|
27
|
+
* The stream that carries `element`'s audio. The first call for an element routes it; every later call returns the
|
|
28
|
+
* same stream. Resumes the routing context when it is suspended. `null` when this browser can't route the element
|
|
29
|
+
* (no Web Audio, or the element already feeds an audio context the router doesn't own), logged once per element;
|
|
30
|
+
* never throws.
|
|
31
|
+
*
|
|
32
|
+
* @param element The element whose audio a call wants.
|
|
33
|
+
*/
|
|
34
|
+
StreamFor(element: HTMLMediaElement): MediaStream | null;
|
|
35
|
+
/** The routing context, created on first use and resumed when suspended; `null` when it can't be created. */
|
|
36
|
+
private ensureContext;
|
|
37
|
+
private static createContext;
|
|
38
|
+
/** Connects the element's audio to a new stream destination, and to nothing else. */
|
|
39
|
+
private route;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=mediaElementAudioRouter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mediaElementAudioRouter.d.ts","sourceRoot":"","sources":["../../src/audio/mediaElementAudioRouter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,yGAAyG;AACzG,qBAAa,uBAAwB,SAAQ,aAAa,CAAC,uBAAuB,CAAC;IAC/E,oHAAoH;IACpH,OAAO,CAAC,OAAO,CAA8C;IAC7D,8GAA8G;IAC9G,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuD;IAE/E,SAAS;IAIT,yBAAyB;IACzB,WAAkB,QAAQ,IAAI,uBAAuB,CAEpD;IAED;;;;;;;OAOG;IACI,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,WAAW,GAAG,IAAI;IAU/D,6GAA6G;IAC7G,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,MAAM,CAAC,aAAa;IAS5B,qFAAqF;IACrF,OAAO,CAAC,KAAK;CAUhB"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MEDIA ELEMENT AUDIO ROUTER: takes a media element's audio into Web Audio once per page and hands it out
|
|
3
|
+
* as a `MediaStream`, so every realtime call that shows the element can play, meter and record its voice.
|
|
4
|
+
*
|
|
5
|
+
* A browser connects an element to Web Audio only once, ever (`createMediaElementSource`). Connected inside one call's
|
|
6
|
+
* audio context, an element would be silent in every later call, and silent for good once that context closed. So the
|
|
7
|
+
* router owns one page-level `AudioContext`, created on the first route and resumed whenever it is found suspended. It
|
|
8
|
+
* routes each element into a `MediaStreamAudioDestinationNode`, never into its own speakers, so the element is heard
|
|
9
|
+
* only through the calls that take its stream, and it returns that same stream for the element every time.
|
|
10
|
+
*
|
|
11
|
+
* An element stays routed for the life of the page: its source node keeps it referenced. A player detaches its media
|
|
12
|
+
* when it lets an element go, so what stays behind is small.
|
|
13
|
+
*
|
|
14
|
+
* @module @memberjunction/ai-realtime-client
|
|
15
|
+
*/
|
|
16
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
17
|
+
/** Routes media elements' audio into one page-level audio context and hands each one out as a stream. */
|
|
18
|
+
export class MediaElementAudioRouter extends BaseSingleton {
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
/** The page's routing context: `undefined` until the first route, `null` when this browser could not create one. */
|
|
22
|
+
this.context = undefined;
|
|
23
|
+
/** Each element routed, with the stream that carries its audio; `null` for an element the browser refused. */
|
|
24
|
+
this.streams = new WeakMap();
|
|
25
|
+
}
|
|
26
|
+
/** The page's router. */
|
|
27
|
+
static get Instance() {
|
|
28
|
+
return super.getInstance();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The stream that carries `element`'s audio. The first call for an element routes it; every later call returns the
|
|
32
|
+
* same stream. Resumes the routing context when it is suspended. `null` when this browser can't route the element
|
|
33
|
+
* (no Web Audio, or the element already feeds an audio context the router doesn't own), logged once per element;
|
|
34
|
+
* never throws.
|
|
35
|
+
*
|
|
36
|
+
* @param element The element whose audio a call wants.
|
|
37
|
+
*/
|
|
38
|
+
StreamFor(element) {
|
|
39
|
+
const context = this.ensureContext();
|
|
40
|
+
if (this.streams.has(element)) {
|
|
41
|
+
return this.streams.get(element) ?? null;
|
|
42
|
+
}
|
|
43
|
+
const stream = context ? this.route(context, element) : null;
|
|
44
|
+
this.streams.set(element, stream);
|
|
45
|
+
return stream;
|
|
46
|
+
}
|
|
47
|
+
/** The routing context, created on first use and resumed when suspended; `null` when it can't be created. */
|
|
48
|
+
ensureContext() {
|
|
49
|
+
if (this.context === undefined) {
|
|
50
|
+
this.context = MediaElementAudioRouter.createContext();
|
|
51
|
+
}
|
|
52
|
+
const context = this.context;
|
|
53
|
+
if (context?.state === 'suspended') {
|
|
54
|
+
context.resume().catch((error) => {
|
|
55
|
+
console.warn('[MediaElementAudioRouter] Could not resume the audio context that carries media elements\' audio:', error);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return context;
|
|
59
|
+
}
|
|
60
|
+
static createContext() {
|
|
61
|
+
try {
|
|
62
|
+
return new AudioContext();
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.warn('[MediaElementAudioRouter] Web Audio is not available: media elements\' audio stays on the elements.', error);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Connects the element's audio to a new stream destination, and to nothing else. */
|
|
70
|
+
route(context, element) {
|
|
71
|
+
try {
|
|
72
|
+
const destination = context.createMediaStreamDestination();
|
|
73
|
+
context.createMediaElementSource(element).connect(destination);
|
|
74
|
+
return destination.stream;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.warn('[MediaElementAudioRouter] Could not route the media element\'s audio, so calls that show it will not carry its voice:', error);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=mediaElementAudioRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mediaElementAudioRouter.js","sourceRoot":"","sources":["../../src/audio/mediaElementAudioRouter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,yGAAyG;AACzG,MAAM,OAAO,uBAAwB,SAAQ,aAAsC;IAM/E;QACI,KAAK,EAAE,CAAC;QANZ,oHAAoH;QAC5G,YAAO,GAAoC,SAAS,CAAC;QAC7D,8GAA8G;QAC7F,YAAO,GAAG,IAAI,OAAO,EAAwC,CAAC;IAI/E,CAAC;IAED,yBAAyB;IAClB,MAAM,KAAK,QAAQ;QACtB,OAAO,KAAK,CAAC,WAAW,EAA2B,CAAC;IACxD,CAAC;IAED;;;;;;;OAOG;IACI,SAAS,CAAC,OAAyB;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;QAC7C,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC7D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAClC,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,6GAA6G;IACrG,aAAa;QACjB,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,OAAO,GAAG,uBAAuB,CAAC,aAAa,EAAE,CAAC;QAC3D,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,OAAO,EAAE,KAAK,KAAK,WAAW,EAAE,CAAC;YACjC,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBAC7B,OAAO,CAAC,IAAI,CAAC,mGAAmG,EAAE,KAAK,CAAC,CAAC;YAC7H,CAAC,CAAC,CAAC;QACP,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;IAEO,MAAM,CAAC,aAAa;QACxB,IAAI,CAAC;YACD,OAAO,IAAI,YAAY,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,qGAAqG,EAAE,KAAK,CAAC,CAAC;YAC3H,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED,qFAAqF;IAC7E,KAAK,CAAC,OAAqB,EAAE,OAAyB;QAC1D,IAAI,CAAC;YACD,MAAM,WAAW,GAAG,OAAO,CAAC,4BAA4B,EAAE,CAAC;YAC3D,OAAO,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YAC/D,OAAO,WAAW,CAAC,MAAM,CAAC;QAC9B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,uHAAuH,EAAE,KAAK,CAAC,CAAC;YAC7I,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;CACJ"}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Handle returned by {@link
|
|
3
|
-
* teardown. Production wraps an `AudioContext` + `AudioWorkletNode`
|
|
4
|
-
*
|
|
2
|
+
* Handle returned by {@link CreatePcmMicCapture}: a driver rebinds it when the microphone's track
|
|
3
|
+
* changes and stops it on teardown. Production wraps an `AudioContext` + `AudioWorkletNode`
|
|
4
|
+
* pipeline; tests return a fake.
|
|
5
5
|
*/
|
|
6
6
|
export interface IPcmMicCapture {
|
|
7
7
|
/** Stops capture and releases the audio context / worklet resources. */
|
|
8
8
|
Stop(): void;
|
|
9
|
+
/**
|
|
10
|
+
* Captures from the stream's current audio track from now on, keeping the same context and
|
|
11
|
+
* worklet, so the chunks keep flowing to the same callback. A source node reads the track it was
|
|
12
|
+
* created with, so a stream whose track was replaced (a device switch) needs this. Does nothing
|
|
13
|
+
* after {@link Stop}.
|
|
14
|
+
*
|
|
15
|
+
* @throws When the stream has no audio track; capture stays on the previous one.
|
|
16
|
+
*/
|
|
17
|
+
Rebind(micStream: MediaStream): void;
|
|
9
18
|
}
|
|
10
19
|
/**
|
|
11
20
|
* Builds the shared PCM16 mic-capture pipeline for client-owned realtime audio planes:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"micCapture.d.ts","sourceRoot":"","sources":["../../src/audio/micCapture.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC3B,wEAAwE;IACxE,IAAI,IAAI,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"micCapture.d.ts","sourceRoot":"","sources":["../../src/audio/micCapture.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC3B,wEAAwE;IACxE,IAAI,IAAI,IAAI,CAAC;IACb;;;;;;;OAOG;IACH,MAAM,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;CACxC;AAkCD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,mBAAmB,CACrC,SAAS,EAAE,WAAW,EACtB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,GAC1C,OAAO,CAAC,cAAc,CAAC,CAmCzB;AAED,mDAAmD;AACnD,wBAAsB,mBAAmB,CACrC,SAAS,EAAE,WAAW,EACtB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,GAC1C,OAAO,CAAC,cAAc,CAAC,CAEzB"}
|
package/dist/audio/micCapture.js
CHANGED
|
@@ -47,7 +47,7 @@ async function loadCaptureWorklet(context) {
|
|
|
47
47
|
export async function CreatePcmMicCapture(micStream, sampleRate, onPcmChunk) {
|
|
48
48
|
const context = new AudioContext({ sampleRate });
|
|
49
49
|
await loadCaptureWorklet(context);
|
|
50
|
-
|
|
50
|
+
let source = context.createMediaStreamSource(micStream);
|
|
51
51
|
const worklet = new AudioWorkletNode(context, CAPTURE_WORKLET_NAME);
|
|
52
52
|
worklet.port.onmessage = (event) => {
|
|
53
53
|
onPcmChunk(EncodeFloat32ToPcm16Base64(event.data));
|
|
@@ -56,8 +56,22 @@ export async function CreatePcmMicCapture(micStream, sampleRate, onPcmChunk) {
|
|
|
56
56
|
const muteTail = context.createGain();
|
|
57
57
|
muteTail.gain.value = 0;
|
|
58
58
|
worklet.connect(muteTail).connect(context.destination);
|
|
59
|
+
let stopped = false;
|
|
59
60
|
return {
|
|
61
|
+
Rebind: (next) => {
|
|
62
|
+
if (stopped) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (next.getAudioTracks().length === 0) {
|
|
66
|
+
throw new Error('The microphone stream has no audio track.');
|
|
67
|
+
}
|
|
68
|
+
const replacement = context.createMediaStreamSource(next);
|
|
69
|
+
source.disconnect();
|
|
70
|
+
source = replacement;
|
|
71
|
+
source.connect(worklet);
|
|
72
|
+
},
|
|
60
73
|
Stop: () => {
|
|
74
|
+
stopped = true;
|
|
61
75
|
worklet.port.onmessage = null;
|
|
62
76
|
source.disconnect();
|
|
63
77
|
worklet.disconnect();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"micCapture.js","sourceRoot":"","sources":["../../src/audio/micCapture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"micCapture.js","sourceRoot":"","sources":["../../src/audio/micCapture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAqBxD,sEAAsE;AACtE,MAAM,oBAAoB,GAAG,2BAA2B,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,sBAAsB,GAAG;;;;;;;;;;qBAUV,oBAAoB;CACxC,CAAC;AAEF,wFAAwF;AACxF,KAAK,UAAU,kBAAkB,CAAC,OAAqB;IACnD,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC,CAAC,sBAAsB,CAAC,EAAE,EAAE,IAAI,EAAE,wBAAwB,EAAE,CAAC,CAAC,CAAC;IAC5G,IAAI,CAAC;QACD,MAAM,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;YAAS,CAAC;QACP,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACrC,SAAsB,EACtB,UAAkB,EAClB,UAAyC;IAEzC,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;IACjD,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;IACpE,OAAO,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,KAAiC,EAAE,EAAE;QAC3D,UAAU,CAAC,0BAA0B,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,CAAC,CAAC;IACF,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACxB,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;IACxB,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACvD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,OAAO;QACH,MAAM,EAAE,CAAC,IAAiB,EAAE,EAAE;YAC1B,IAAI,OAAO,EAAE,CAAC;gBACV,OAAO;YACX,CAAC;YACD,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACrC,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;YACjE,CAAC;YACD,MAAM,WAAW,GAAG,OAAO,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC;YAC1D,MAAM,CAAC,UAAU,EAAE,CAAC;YACpB,MAAM,GAAG,WAAW,CAAC;YACrB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,EAAE,GAAG,EAAE;YACP,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YAC9B,MAAM,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO,CAAC,UAAU,EAAE,CAAC;YACrB,QAAQ,CAAC,UAAU,EAAE,CAAC;YACtB,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC;QACzB,CAAC;KACJ,CAAC;AACN,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACrC,SAAsB,EACtB,UAAkB,EAClB,UAAyC;IAEzC,OAAO,mBAAmB,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AAClE,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IRealtimeAudioMeter } from './audioMeter.js';
|
|
2
|
+
import type { IPlaybackClock } from '../media/playbackClock.js';
|
|
2
3
|
/**
|
|
3
4
|
* The playback contract for a client-owned realtime audio plane: schedules raw PCM16 chunks
|
|
4
5
|
* for gapless playout and reports whether audio is AUDIBLY playing. Production is
|
|
@@ -10,8 +11,15 @@ import { IRealtimeAudioMeter } from './audioMeter.js';
|
|
|
10
11
|
* use this.
|
|
11
12
|
*/
|
|
12
13
|
export interface IRealtimePcmPlayback {
|
|
13
|
-
/**
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Schedules a raw PCM16 mono chunk back-to-back after any already-queued audio.
|
|
16
|
+
*
|
|
17
|
+
* @param pcm16 The chunk.
|
|
18
|
+
* @param mediaTimeMs Where the chunk starts on the stream's media timeline (ms), when the driver knows it: a playback
|
|
19
|
+
* that is also an `IPlaybackClock` then reads out the media time of the audio heard. A chunk without one continues
|
|
20
|
+
* the chunk before it.
|
|
21
|
+
*/
|
|
22
|
+
Enqueue(pcm16: ArrayBuffer, mediaTimeMs?: number): void;
|
|
15
23
|
/** Stops + clears every scheduled source (barge-in / interruption). */
|
|
16
24
|
Flush(): void;
|
|
17
25
|
/** `true` while scheduled audio is audibly playing (playhead ahead of the context clock). */
|
|
@@ -25,6 +33,21 @@ export interface IRealtimePcmPlayback {
|
|
|
25
33
|
* implementations) stay valid; callers use `playback.CreateMeter?.() ?? null`.
|
|
26
34
|
*/
|
|
27
35
|
CreateMeter?(): IRealtimeAudioMeter | null;
|
|
36
|
+
/**
|
|
37
|
+
* OPTIONAL: the agent's output audio as a `MediaStream`, so a host can mix the agent voice
|
|
38
|
+
* into a recording (issue #5153). Mirrors what WebRTC drivers get from the peer connection.
|
|
39
|
+
* Optional so test fakes and environments without `MediaStreamAudioDestinationNode` stay
|
|
40
|
+
* valid; callers use `playback.GetOutputStream?.() ?? null`.
|
|
41
|
+
*/
|
|
42
|
+
GetOutputStream?(): MediaStream | null;
|
|
43
|
+
/**
|
|
44
|
+
* OPTIONAL: plays a media element's audio through this engine's output, so the agent meter
|
|
45
|
+
* ({@link CreateMeter}) and the recording stream ({@link GetOutputStream}) carry it as they
|
|
46
|
+
* carry PCM: an avatar's voice is in its video. Once per element for each engine; later calls
|
|
47
|
+
* for the same element do nothing. An element shown in an earlier call works again in the
|
|
48
|
+
* next. Optional so test fakes stay valid; callers use `playback.ConnectMediaElement?.(element)`.
|
|
49
|
+
*/
|
|
50
|
+
ConnectMediaElement?(element: HTMLMediaElement): void;
|
|
28
51
|
}
|
|
29
52
|
/**
|
|
30
53
|
* Web Audio playout scheduler for raw PCM16 model audio at a driver-supplied sample rate.
|
|
@@ -36,12 +59,17 @@ export interface IRealtimePcmPlayback {
|
|
|
36
59
|
* sources) means audio is audibly coming out of the speaker. On interruption, {@link Flush}
|
|
37
60
|
* stops every scheduled source and rewinds the playhead.
|
|
38
61
|
*
|
|
62
|
+
* It is also the voice's **playback clock** ({@link IPlaybackClock}): each chunk enqueued with a media time keeps its
|
|
63
|
+
* place on the context's clock, and {@link CurrentTimeMs} reads out the media time of the audio heard now (the context's
|
|
64
|
+
* time less the output latency the browser reports). A video player given this clock shows each frame when the voice
|
|
65
|
+
* reaches it.
|
|
66
|
+
*
|
|
39
67
|
* Generalized from the Gemini driver's original 24 kHz-fixed engine: the sample rate is now a
|
|
40
68
|
* constructor parameter so providers that negotiate their output format at session start
|
|
41
69
|
* (e.g. ElevenLabs' `agent_output_audio_format`) can construct the playout engine with the
|
|
42
70
|
* negotiated rate.
|
|
43
71
|
*/
|
|
44
|
-
export declare class RealtimePcmPlayback implements IRealtimePcmPlayback {
|
|
72
|
+
export declare class RealtimePcmPlayback implements IRealtimePcmPlayback, IPlaybackClock {
|
|
45
73
|
private context;
|
|
46
74
|
private sampleRate;
|
|
47
75
|
/**
|
|
@@ -49,10 +77,19 @@ export declare class RealtimePcmPlayback implements IRealtimePcmPlayback {
|
|
|
49
77
|
* the single tap point {@link CreateMeter} analyses without altering the audio path.
|
|
50
78
|
*/
|
|
51
79
|
private masterGain;
|
|
80
|
+
/**
|
|
81
|
+
* Second sink fed by {@link masterGain} (alongside the speakers) that exposes the agent
|
|
82
|
+
* audio as a `MediaStream`. Null where the context lacks `createMediaStreamDestination`.
|
|
83
|
+
*/
|
|
84
|
+
private outputDestination;
|
|
52
85
|
/** The absolute context time up to which audio has been scheduled. */
|
|
53
86
|
private playheadTime;
|
|
54
87
|
/** Sources scheduled and not yet ended (so Flush can stop them). */
|
|
55
88
|
private activeSources;
|
|
89
|
+
/** Elements {@link ConnectMediaElement} has taken in (or tried to), so each is taken in once. */
|
|
90
|
+
private connectedElements;
|
|
91
|
+
/** Where each chunk scheduled and not long played sits on the context's clock and the media timeline, in order. */
|
|
92
|
+
private anchors;
|
|
56
93
|
/**
|
|
57
94
|
* @param sampleRate The PCM16 sample rate (Hz) of the chunks this engine will play
|
|
58
95
|
* (e.g. 24000 for Gemini Live, the negotiated `agent_output_audio_format` rate for
|
|
@@ -60,14 +97,45 @@ export declare class RealtimePcmPlayback implements IRealtimePcmPlayback {
|
|
|
60
97
|
*/
|
|
61
98
|
constructor(sampleRate: number);
|
|
62
99
|
/** @inheritdoc */
|
|
63
|
-
Enqueue(pcm16: ArrayBuffer): void;
|
|
100
|
+
Enqueue(pcm16: ArrayBuffer, mediaTimeMs?: number): void;
|
|
64
101
|
/** @inheritdoc */
|
|
65
102
|
Flush(): void;
|
|
103
|
+
/**
|
|
104
|
+
* The media time (ms) of the audio heard now: the context's time, less the output latency the browser reports
|
|
105
|
+
* (`outputLatency` and `baseLatency`, where it has them), placed in the chunk playing then. `null` while nothing plays,
|
|
106
|
+
* between chunks, or while the chunk playing carries no media time (untimed audio with no timed audio before it).
|
|
107
|
+
*/
|
|
108
|
+
get CurrentTimeMs(): number | null;
|
|
66
109
|
/** @inheritdoc */
|
|
67
110
|
get IsPlaying(): boolean;
|
|
68
111
|
/** @inheritdoc */
|
|
69
112
|
CreateMeter(): IRealtimeAudioMeter | null;
|
|
113
|
+
/**
|
|
114
|
+
* The agent's output audio as a `MediaStream`, or `null` when unsupported. Hosts mix this
|
|
115
|
+
* into a recording: WebRTC drivers get the agent voice from the peer connection, PCM
|
|
116
|
+
* drivers (which play through Web Audio) must expose it from here. The stream lives on
|
|
117
|
+
* this engine's own (e.g. 24 kHz) context, so consumers wrap it in a source node on THEIR
|
|
118
|
+
* context rather than reusing this one.
|
|
119
|
+
*/
|
|
120
|
+
GetOutputStream(): MediaStream | null;
|
|
121
|
+
/**
|
|
122
|
+
* Takes a media element's audio into the master gain, beside the PCM sources: the speakers,
|
|
123
|
+
* the meter and the recording tap then carry it. The audio arrives as the element's stream
|
|
124
|
+
* from the page's {@link MediaElementAudioRouter}, which connects the element to Web Audio
|
|
125
|
+
* once per page (a browser allows that once per element, ever), so the next call that shows
|
|
126
|
+
* the same element takes its voice too. Once per element for this engine. When the router
|
|
127
|
+
* can't route the element, it logs why; a failure here is logged too, never thrown: the call
|
|
128
|
+
* goes on without that element's voice.
|
|
129
|
+
*/
|
|
130
|
+
ConnectMediaElement(element: HTMLMediaElement): void;
|
|
70
131
|
/** @inheritdoc */
|
|
71
132
|
Close(): void;
|
|
133
|
+
/**
|
|
134
|
+
* Records where a chunk plays: its media time is the one given, else the end of the chunk before it, else `null`.
|
|
135
|
+
* Chunks played more than {@link ANCHOR_RETENTION_SECONDS} ago are forgotten.
|
|
136
|
+
*/
|
|
137
|
+
private anchor;
|
|
138
|
+
/** How long audio takes from the context's clock to the speakers, in seconds, as far as the browser reports it. */
|
|
139
|
+
private outputDelaySeconds;
|
|
72
140
|
}
|
|
73
141
|
//# sourceMappingURL=pcmPlayback.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pcmPlayback.d.ts","sourceRoot":"","sources":["../../src/audio/pcmPlayback.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAsB,MAAM,cAAc,CAAC;AAEvE;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACjC
|
|
1
|
+
{"version":3,"file":"pcmPlayback.d.ts","sourceRoot":"","sources":["../../src/audio/pcmPlayback.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAsB,MAAM,cAAc,CAAC;AAEvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAe7D;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACjC;;;;;;;OAOG;IACH,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxD,uEAAuE;IACvE,KAAK,IAAI,IAAI,CAAC;IACd,6FAA6F;IAC7F,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,yDAAyD;IACzD,KAAK,IAAI,IAAI,CAAC;IACd;;;;;OAKG;IACH,WAAW,CAAC,IAAI,mBAAmB,GAAG,IAAI,CAAC;IAC3C;;;;;OAKG;IACH,eAAe,CAAC,IAAI,WAAW,GAAG,IAAI,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,mBAAoB,YAAW,oBAAoB,EAAE,cAAc;IAC5E,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,UAAU,CAAS;IAC3B;;;OAGG;IACH,OAAO,CAAC,UAAU,CAAW;IAC7B;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAAgD;IACzE,sEAAsE;IACtE,OAAO,CAAC,YAAY,CAAK;IACzB,oEAAoE;IACpE,OAAO,CAAC,aAAa,CAAoC;IACzD,iGAAiG;IACjG,OAAO,CAAC,iBAAiB,CAAmC;IAC5D,mHAAmH;IACnH,OAAO,CAAC,OAAO,CAAqB;IAEpC;;;;OAIG;gBACS,UAAU,EAAE,MAAM;IAuB9B,kBAAkB;IACX,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAkB9D,kBAAkB;IACX,KAAK,IAAI,IAAI;IAapB;;;;OAIG;IACH,IAAW,aAAa,IAAI,MAAM,GAAG,IAAI,CAOxC;IAED,kBAAkB;IAClB,IAAW,SAAS,IAAI,OAAO,CAE9B;IAED,kBAAkB;IACX,WAAW,IAAI,mBAAmB,GAAG,IAAI;IAIhD;;;;;;OAMG;IACI,eAAe,IAAI,WAAW,GAAG,IAAI;IAI5C;;;;;;;;OAQG;IACI,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAgB3D,kBAAkB;IACX,KAAK,IAAI,IAAI;IAKpB;;;OAGG;IACH,OAAO,CAAC,MAAM;IAWd,mHAAmH;IACnH,OAAO,CAAC,kBAAkB;CAI7B"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Pcm16ToFloat32 } from './pcmUtils.js';
|
|
2
2
|
import { RealtimeAudioMeter } from './audioMeter.js';
|
|
3
|
+
import { MediaElementAudioRouter } from './mediaElementAudioRouter.js';
|
|
4
|
+
/** Played chunks are forgotten this long after they end, in seconds: longer than any output latency a clock subtracts. */
|
|
5
|
+
const ANCHOR_RETENTION_SECONDS = 2;
|
|
3
6
|
/**
|
|
4
7
|
* Web Audio playout scheduler for raw PCM16 model audio at a driver-supplied sample rate.
|
|
5
8
|
*
|
|
@@ -10,6 +13,11 @@ import { RealtimeAudioMeter } from './audioMeter.js';
|
|
|
10
13
|
* sources) means audio is audibly coming out of the speaker. On interruption, {@link Flush}
|
|
11
14
|
* stops every scheduled source and rewinds the playhead.
|
|
12
15
|
*
|
|
16
|
+
* It is also the voice's **playback clock** ({@link IPlaybackClock}): each chunk enqueued with a media time keeps its
|
|
17
|
+
* place on the context's clock, and {@link CurrentTimeMs} reads out the media time of the audio heard now (the context's
|
|
18
|
+
* time less the output latency the browser reports). A video player given this clock shows each frame when the voice
|
|
19
|
+
* reaches it.
|
|
20
|
+
*
|
|
13
21
|
* Generalized from the Gemini driver's original 24 kHz-fixed engine: the sample rate is now a
|
|
14
22
|
* constructor parameter so providers that negotiate their output format at session start
|
|
15
23
|
* (e.g. ElevenLabs' `agent_output_audio_format`) can construct the playout engine with the
|
|
@@ -22,17 +30,40 @@ export class RealtimePcmPlayback {
|
|
|
22
30
|
* ElevenLabs).
|
|
23
31
|
*/
|
|
24
32
|
constructor(sampleRate) {
|
|
33
|
+
/**
|
|
34
|
+
* Second sink fed by {@link masterGain} (alongside the speakers) that exposes the agent
|
|
35
|
+
* audio as a `MediaStream`. Null where the context lacks `createMediaStreamDestination`.
|
|
36
|
+
*/
|
|
37
|
+
this.outputDestination = null;
|
|
25
38
|
/** The absolute context time up to which audio has been scheduled. */
|
|
26
39
|
this.playheadTime = 0;
|
|
27
40
|
/** Sources scheduled and not yet ended (so Flush can stop them). */
|
|
28
41
|
this.activeSources = new Set();
|
|
42
|
+
/** Elements {@link ConnectMediaElement} has taken in (or tried to), so each is taken in once. */
|
|
43
|
+
this.connectedElements = new WeakSet();
|
|
44
|
+
/** Where each chunk scheduled and not long played sits on the context's clock and the media timeline, in order. */
|
|
45
|
+
this.anchors = [];
|
|
29
46
|
this.sampleRate = sampleRate;
|
|
30
47
|
this.context = new AudioContext({ sampleRate });
|
|
31
48
|
this.masterGain = this.context.createGain();
|
|
32
49
|
this.masterGain.connect(this.context.destination);
|
|
50
|
+
// Created eagerly so GetOutputStream stays a pure read. The feature-check covers older
|
|
51
|
+
// environments where the node does not exist (recording then stays mic-only).
|
|
52
|
+
if (typeof this.context.createMediaStreamDestination === 'function') {
|
|
53
|
+
// Recording is best-effort and must never fail the call: a throw here would
|
|
54
|
+
// propagate out of the driver's Connect and kill the voice session over a tap.
|
|
55
|
+
try {
|
|
56
|
+
const destination = this.context.createMediaStreamDestination();
|
|
57
|
+
this.masterGain.connect(destination);
|
|
58
|
+
this.outputDestination = destination;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.warn('[RealtimePcmPlayback] Could not create the recording tap — agent audio will not be recordable this session:', error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
33
64
|
}
|
|
34
65
|
/** @inheritdoc */
|
|
35
|
-
Enqueue(pcm16) {
|
|
66
|
+
Enqueue(pcm16, mediaTimeMs) {
|
|
36
67
|
const samples = Pcm16ToFloat32(pcm16);
|
|
37
68
|
if (samples.length === 0) {
|
|
38
69
|
return;
|
|
@@ -47,6 +78,7 @@ export class RealtimePcmPlayback {
|
|
|
47
78
|
const startAt = Math.max(this.playheadTime, this.context.currentTime);
|
|
48
79
|
source.start(startAt);
|
|
49
80
|
this.playheadTime = startAt + buffer.duration;
|
|
81
|
+
this.anchor(startAt, buffer.duration, mediaTimeMs);
|
|
50
82
|
}
|
|
51
83
|
/** @inheritdoc */
|
|
52
84
|
Flush() {
|
|
@@ -60,6 +92,20 @@ export class RealtimePcmPlayback {
|
|
|
60
92
|
}
|
|
61
93
|
this.activeSources.clear();
|
|
62
94
|
this.playheadTime = 0;
|
|
95
|
+
this.anchors = [];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The media time (ms) of the audio heard now: the context's time, less the output latency the browser reports
|
|
99
|
+
* (`outputLatency` and `baseLatency`, where it has them), placed in the chunk playing then. `null` while nothing plays,
|
|
100
|
+
* between chunks, or while the chunk playing carries no media time (untimed audio with no timed audio before it).
|
|
101
|
+
*/
|
|
102
|
+
get CurrentTimeMs() {
|
|
103
|
+
const heard = this.context.currentTime - this.outputDelaySeconds();
|
|
104
|
+
const playing = this.anchors.find((anchor) => anchor.StartAt <= heard && heard < anchor.StartAt + anchor.DurationS);
|
|
105
|
+
if (!playing || playing.MediaMs === null) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return playing.MediaMs + (heard - playing.StartAt) * 1000;
|
|
63
109
|
}
|
|
64
110
|
/** @inheritdoc */
|
|
65
111
|
get IsPlaying() {
|
|
@@ -69,10 +115,64 @@ export class RealtimePcmPlayback {
|
|
|
69
115
|
CreateMeter() {
|
|
70
116
|
return RealtimeAudioMeter.ForContextNode(this.context, this.masterGain);
|
|
71
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* The agent's output audio as a `MediaStream`, or `null` when unsupported. Hosts mix this
|
|
120
|
+
* into a recording: WebRTC drivers get the agent voice from the peer connection, PCM
|
|
121
|
+
* drivers (which play through Web Audio) must expose it from here. The stream lives on
|
|
122
|
+
* this engine's own (e.g. 24 kHz) context, so consumers wrap it in a source node on THEIR
|
|
123
|
+
* context rather than reusing this one.
|
|
124
|
+
*/
|
|
125
|
+
GetOutputStream() {
|
|
126
|
+
return this.outputDestination?.stream ?? null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Takes a media element's audio into the master gain, beside the PCM sources: the speakers,
|
|
130
|
+
* the meter and the recording tap then carry it. The audio arrives as the element's stream
|
|
131
|
+
* from the page's {@link MediaElementAudioRouter}, which connects the element to Web Audio
|
|
132
|
+
* once per page (a browser allows that once per element, ever), so the next call that shows
|
|
133
|
+
* the same element takes its voice too. Once per element for this engine. When the router
|
|
134
|
+
* can't route the element, it logs why; a failure here is logged too, never thrown: the call
|
|
135
|
+
* goes on without that element's voice.
|
|
136
|
+
*/
|
|
137
|
+
ConnectMediaElement(element) {
|
|
138
|
+
if (this.connectedElements.has(element)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.connectedElements.add(element);
|
|
142
|
+
const stream = MediaElementAudioRouter.Instance.StreamFor(element);
|
|
143
|
+
if (!stream) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
this.context.createMediaStreamSource(stream).connect(this.masterGain);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.warn('[RealtimePcmPlayback] Could not take the media element\'s audio into the agent audio; the speakers, the meter and the recording miss its voice:', error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
72
153
|
/** @inheritdoc */
|
|
73
154
|
Close() {
|
|
74
155
|
this.Flush();
|
|
75
156
|
void this.context.close();
|
|
76
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Records where a chunk plays: its media time is the one given, else the end of the chunk before it, else `null`.
|
|
160
|
+
* Chunks played more than {@link ANCHOR_RETENTION_SECONDS} ago are forgotten.
|
|
161
|
+
*/
|
|
162
|
+
anchor(startAt, durationS, mediaTimeMs) {
|
|
163
|
+
const previous = this.anchors[this.anchors.length - 1];
|
|
164
|
+
const continued = previous && previous.MediaMs !== null ? previous.MediaMs + previous.DurationS * 1000 : null;
|
|
165
|
+
const timed = typeof mediaTimeMs === 'number' && Number.isFinite(mediaTimeMs);
|
|
166
|
+
this.anchors.push({ StartAt: startAt, DurationS: durationS, MediaMs: timed ? mediaTimeMs : continued });
|
|
167
|
+
const forgetBefore = this.context.currentTime - ANCHOR_RETENTION_SECONDS;
|
|
168
|
+
while (this.anchors.length > 1 && this.anchors[0].StartAt + this.anchors[0].DurationS < forgetBefore) {
|
|
169
|
+
this.anchors.shift();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** How long audio takes from the context's clock to the speakers, in seconds, as far as the browser reports it. */
|
|
173
|
+
outputDelaySeconds() {
|
|
174
|
+
const latency = (value) => (typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0);
|
|
175
|
+
return latency(this.context.outputLatency) + latency(this.context.baseLatency);
|
|
176
|
+
}
|
|
77
177
|
}
|
|
78
178
|
//# sourceMappingURL=pcmPlayback.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pcmPlayback.js","sourceRoot":"","sources":["../../src/audio/pcmPlayback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAuB,kBAAkB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"pcmPlayback.js","sourceRoot":"","sources":["../../src/audio/pcmPlayback.ts"],"names":[],"mappings":"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"}
|
|
@@ -225,6 +225,8 @@ export declare class AssemblyAIRealtimeClient extends BaseRealtimeClient {
|
|
|
225
225
|
* un-mute is glitch-free — same policy as the other client drivers).
|
|
226
226
|
*/
|
|
227
227
|
SetMuted(muted: boolean): void;
|
|
228
|
+
/** Rebinds the PCM capture and the input meter to the stream's current track (obligation #10); the socket stays open. */
|
|
229
|
+
ReplaceMicrophone(micStream: MediaStream): Promise<void>;
|
|
228
230
|
/** @inheritdoc */
|
|
229
231
|
get IsBusy(): boolean;
|
|
230
232
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assemblyAIRealtimeClient.d.ts","sourceRoot":"","sources":["../../src/drivers/assemblyAIRealtimeClient.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"assemblyAIRealtimeClient.d.ts","sourceRoot":"","sources":["../../src/drivers/assemblyAIRealtimeClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -133,6 +133,7 @@ let AssemblyAIRealtimeClient = class AssemblyAIRealtimeClient extends BaseRealti
|
|
|
133
133
|
* format. Reports `'listening'` only after all of that (obligation #7).
|
|
134
134
|
*/
|
|
135
135
|
async Connect(config, micStream) {
|
|
136
|
+
this.AssertTransportSupported(config);
|
|
136
137
|
this.micStream = micStream;
|
|
137
138
|
this.closedByConsumer = false;
|
|
138
139
|
this.setState('connecting');
|
|
@@ -181,6 +182,9 @@ let AssemblyAIRealtimeClient = class AssemblyAIRealtimeClient extends BaseRealti
|
|
|
181
182
|
this.sendFrame({ type: 'session.update', session: sessionObject });
|
|
182
183
|
await ready;
|
|
183
184
|
this.playback = this.createPlayback(ASSEMBLYAI_PCM_SAMPLE_RATE);
|
|
185
|
+
// The agent voice plays through Web Audio only; publish it so a host recorder can mix
|
|
186
|
+
// it in (issue #5153). Null for playbacks with no output stream (fakes, no WebAudio).
|
|
187
|
+
this.publishRemoteMediaStream(this.playback.GetOutputStream?.() ?? null);
|
|
184
188
|
this.micCapture = await this.createMicCapture(micStream, ASSEMBLYAI_PCM_SAMPLE_RATE, (base64Pcm16) => this.sendMicChunk(base64Pcm16));
|
|
185
189
|
// Audio-activity capability (base obligation #9): agent side taps the playout
|
|
186
190
|
// engine's master gain; user side meters the mic stream. Null-safe — test fakes /
|
|
@@ -204,6 +208,7 @@ let AssemblyAIRealtimeClient = class AssemblyAIRealtimeClient extends BaseRealti
|
|
|
204
208
|
this.micCapture = null;
|
|
205
209
|
this.playback?.Close();
|
|
206
210
|
this.playback = null;
|
|
211
|
+
this.clearRemoteMediaStream();
|
|
207
212
|
if (this.socket) {
|
|
208
213
|
try {
|
|
209
214
|
this.socket.send(JSON.stringify({ type: 'session.end' }));
|
|
@@ -327,6 +332,15 @@ let AssemblyAIRealtimeClient = class AssemblyAIRealtimeClient extends BaseRealti
|
|
|
327
332
|
track.enabled = !muted;
|
|
328
333
|
}
|
|
329
334
|
}
|
|
335
|
+
/** Rebinds the PCM capture and the input meter to the stream's current track (obligation #10); the socket stays open. */
|
|
336
|
+
async ReplaceMicrophone(micStream) {
|
|
337
|
+
if (!this.micCapture) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
this.micCapture.Rebind(micStream);
|
|
341
|
+
this.micStream = micStream;
|
|
342
|
+
this.attachInputAudioMeter(RealtimeAudioMeter.ForMicStream(micStream));
|
|
343
|
+
}
|
|
330
344
|
/** @inheritdoc */
|
|
331
345
|
get IsBusy() {
|
|
332
346
|
return this.responseActive;
|