@elevenlabs/react-native 1.0.3 → 1.1.1
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-check-types.log +1 -1
- package/.turbo/turbo-generate-version.log +1 -1
- package/.turbo/turbo-lint$colon$es.log +1 -1
- package/.turbo/turbo-lint$colon$prettier.log +1 -1
- package/CHANGELOG.md +23 -0
- package/dist/index.react-native.d.ts.map +1 -1
- package/dist/index.react-native.js +4 -2
- package/dist/index.react-native.js.map +1 -1
- package/dist/nativeVolume.d.ts +10 -0
- package/dist/nativeVolume.d.ts.map +1 -0
- package/dist/nativeVolume.js +210 -0
- package/dist/nativeVolume.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/index.react-native.ts +4 -2
- package/src/nativeVolume.ts +273 -0
- package/src/version.ts +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
> @elevenlabs/react-native@1.
|
|
2
|
+
> @elevenlabs/react-native@1.1.1 generate-version /home/runner/work/packages/packages/packages/react-native
|
|
3
3
|
> printf "// This file is auto-generated during build\nexport const PACKAGE_VERSION = \"%s\";\n" "$npm_package_version" > src/version.ts
|
|
4
4
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @elevenlabs/react-native@1.
|
|
2
|
+
> @elevenlabs/react-native@1.1.1 lint:prettier /home/runner/work/packages/packages/packages/react-native
|
|
3
3
|
> prettier 'src/**/*.ts' --check
|
|
4
4
|
|
|
5
5
|
Checking formatting...
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @elevenlabs/react-native
|
|
2
2
|
|
|
3
|
+
## 1.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [4237f72]
|
|
8
|
+
- @elevenlabs/client@1.2.1
|
|
9
|
+
- @elevenlabs/react@1.1.1
|
|
10
|
+
|
|
11
|
+
## 1.1.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 0d5c368: Fix getInputVolume/getOutputVolume returning 0 in React Native by adding native volume providers using LiveKit's RMS and multiband FFT processors.
|
|
16
|
+
|
|
17
|
+
**Breaking:** `getByteFrequencyData()` now returns data focused on the human voice range (100-8000 Hz) instead of the full spectrum (0 to sampleRate/2). On web, `getVolume()` is also computed from this range. The deprecated `getAnalyser()` method still provides direct access to the raw `AnalyserNode` for consumers needing full-spectrum data.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- Updated dependencies [806edd0]
|
|
22
|
+
- Updated dependencies [0d5c368]
|
|
23
|
+
- @elevenlabs/react@1.1.0
|
|
24
|
+
- @elevenlabs/client@1.2.0
|
|
25
|
+
|
|
3
26
|
## 1.0.3
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.react-native.d.ts","sourceRoot":"","sources":["../src/index.react-native.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.react-native.d.ts","sourceRoot":"","sources":["../src/index.react-native.ts"],"names":[],"mappings":"AAoDA,cAAc,YAAY,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { registerGlobals, AudioSession, AndroidAudioTypePresets, } from "@livekit/react-native";
|
|
2
2
|
import { setSetupStrategy, webSessionSetup, } from "@elevenlabs/client/internal";
|
|
3
|
+
import { attachNativeVolume } from "./nativeVolume.js";
|
|
3
4
|
// Polyfill WebRTC globals needed by livekit-client in React Native
|
|
4
5
|
registerGlobals();
|
|
5
6
|
/**
|
|
@@ -7,7 +8,8 @@ registerGlobals();
|
|
|
7
8
|
*
|
|
8
9
|
* 1. Configures and starts the native AudioSession
|
|
9
10
|
* 2. Delegates connection + input/output setup to the web strategy
|
|
10
|
-
* 3. Wraps
|
|
11
|
+
* 3. Wraps input/output controllers with native volume processors
|
|
12
|
+
* 4. Wraps detach to stop the native AudioSession on cleanup
|
|
11
13
|
*/
|
|
12
14
|
async function reactNativeSessionSetup(options) {
|
|
13
15
|
await AudioSession.configureAudio({
|
|
@@ -20,7 +22,7 @@ async function reactNativeSessionSetup(options) {
|
|
|
20
22
|
},
|
|
21
23
|
});
|
|
22
24
|
await AudioSession.startAudioSession();
|
|
23
|
-
const result = await webSessionSetup(options);
|
|
25
|
+
const result = attachNativeVolume(await webSessionSetup(options));
|
|
24
26
|
const originalDetach = result.detach;
|
|
25
27
|
return {
|
|
26
28
|
...result,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.react-native.js","sourceRoot":"","sources":["../src/index.react-native.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,YAAY,EACZ,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,gBAAgB,EAChB,eAAe,GAEhB,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.react-native.js","sourceRoot":"","sources":["../src/index.react-native.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,YAAY,EACZ,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,gBAAgB,EAChB,eAAe,GAEhB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,mEAAmE;AACnE,eAAe,EAAE,CAAC;AAElB;;;;;;;GAOG;AACH,KAAK,UAAU,uBAAuB,CACpC,OAAgB;IAEhB,MAAM,YAAY,CAAC,cAAc,CAAC;QAChC,OAAO,EAAE;YACP,mBAAmB,EAAE,CAAC,SAAS,CAAC;YAChC,gBAAgB,EAAE,uBAAuB,CAAC,aAAa;SACxD;QACD,GAAG,EAAE;YACH,aAAa,EAAE,SAAS;SACzB;KACF,CAAC,CAAC;IACH,MAAM,YAAY,CAAC,iBAAiB,EAAE,CAAC;IAEvC,MAAM,MAAM,GAAG,kBAAkB,CAAC,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;IAElE,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC;IACrC,OAAO;QACL,GAAG,MAAM;QACT,MAAM,EAAE,GAAG,EAAE;YACX,cAAc,EAAE,CAAC;YACjB,YAAY,CAAC,gBAAgB,EAAE,CAAC;QAClC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,gBAAgB,CAAC,uBAAuB,CAAC,CAAC;AAE1C,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type VoiceSessionSetupResult } from "@elevenlabs/client/internal";
|
|
2
|
+
/**
|
|
3
|
+
* Attaches native volume processors to the WebRTC connection so that
|
|
4
|
+
* `getInputVolume()` / `getOutputVolume()` return real values on
|
|
5
|
+
* React Native. Falls through unchanged for non-WebRTC connections;
|
|
6
|
+
* WebSocket volume on React Native is a known gap (MediaDeviceInput/
|
|
7
|
+
* MediaDeviceOutput depend on AudioContext which is unavailable in RN).
|
|
8
|
+
*/
|
|
9
|
+
export declare function attachNativeVolume(result: VoiceSessionSetupResult): VoiceSessionSetupResult;
|
|
10
|
+
//# sourceMappingURL=nativeVolume.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeVolume.d.ts","sourceRoot":"","sources":["../src/nativeVolume.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,6BAA6B,CAAC;AAkPrC;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,uBAAuB,GAC9B,uBAAuB,CAezB"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { NativeModules, NativeEventEmitter } from "react-native";
|
|
2
|
+
import { WebRTCConnection } from "@elevenlabs/client";
|
|
3
|
+
import { MIN_VOICE_FREQUENCY, MAX_VOICE_FREQUENCY, } from "@elevenlabs/client/internal";
|
|
4
|
+
const LiveKitModule = NativeModules.LivekitReactNativeModule;
|
|
5
|
+
let emitter = null;
|
|
6
|
+
function getEmitter() {
|
|
7
|
+
emitter ??= new NativeEventEmitter(LiveKitModule);
|
|
8
|
+
return emitter;
|
|
9
|
+
}
|
|
10
|
+
const MULTIBAND_FREQUENCY_OPTIONS = {
|
|
11
|
+
minFrequency: MIN_VOICE_FREQUENCY,
|
|
12
|
+
maxFrequency: MAX_VOICE_FREQUENCY,
|
|
13
|
+
updateInterval: 40,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A VolumeProvider backed by native LiveKit audio processors.
|
|
17
|
+
*
|
|
18
|
+
* Both processors are created lazily: the RMS volume processor on the first
|
|
19
|
+
* `getVolume()` call, and the FFT multiband processor on the first
|
|
20
|
+
* `getByteFrequencyData()` call (with its band count matching the buffer
|
|
21
|
+
* length). If the buffer size changes, the multiband processor is recreated.
|
|
22
|
+
*/
|
|
23
|
+
class NativeVolumeProvider {
|
|
24
|
+
pcId;
|
|
25
|
+
trackId;
|
|
26
|
+
volume = 0;
|
|
27
|
+
magnitudes = [];
|
|
28
|
+
// Volume processor state (lazy)
|
|
29
|
+
volumeTag = null;
|
|
30
|
+
volumeSub = null;
|
|
31
|
+
// Multiband processor state (lazy)
|
|
32
|
+
multibandTag = null;
|
|
33
|
+
multibandSub = null;
|
|
34
|
+
currentBands = 0;
|
|
35
|
+
constructor(pcId, trackId) {
|
|
36
|
+
this.pcId = pcId;
|
|
37
|
+
this.trackId = trackId;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Rebinds this provider to a new track, cleaning up any existing native
|
|
41
|
+
* processors so they are lazily recreated against the new track.
|
|
42
|
+
*/
|
|
43
|
+
updateTrack(pcId, trackId) {
|
|
44
|
+
this.cleanupProcessors();
|
|
45
|
+
this.pcId = pcId;
|
|
46
|
+
this.trackId = trackId;
|
|
47
|
+
this.volume = 0;
|
|
48
|
+
this.magnitudes = [];
|
|
49
|
+
}
|
|
50
|
+
ensureVolumeProcessor() {
|
|
51
|
+
if (this.volumeTag)
|
|
52
|
+
return;
|
|
53
|
+
this.volumeTag = LiveKitModule.createVolumeProcessor(this.pcId, this.trackId);
|
|
54
|
+
this.volumeSub = getEmitter().addListener("LK_VOLUME_PROCESSED", (event) => {
|
|
55
|
+
if (event.id === this.volumeTag) {
|
|
56
|
+
this.volume = event.volume;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
ensureMultibandProcessor(bands) {
|
|
61
|
+
if (bands === this.currentBands)
|
|
62
|
+
return;
|
|
63
|
+
// Cleanup previous processor
|
|
64
|
+
if (this.multibandTag) {
|
|
65
|
+
LiveKitModule.deleteMultibandVolumeProcessor(this.multibandTag, this.pcId, this.trackId);
|
|
66
|
+
this.multibandSub?.remove();
|
|
67
|
+
}
|
|
68
|
+
this.currentBands = bands;
|
|
69
|
+
this.magnitudes = [];
|
|
70
|
+
this.multibandTag = LiveKitModule.createMultibandVolumeProcessor({ ...MULTIBAND_FREQUENCY_OPTIONS, bands }, this.pcId, this.trackId);
|
|
71
|
+
this.multibandSub = getEmitter().addListener("LK_MULTIBAND_PROCESSED", (event) => {
|
|
72
|
+
if (event.id === this.multibandTag) {
|
|
73
|
+
this.magnitudes = event.magnitudes;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
getVolume() {
|
|
78
|
+
this.ensureVolumeProcessor();
|
|
79
|
+
return this.volume < 0 ? 0 : this.volume > 1 ? 1 : this.volume;
|
|
80
|
+
}
|
|
81
|
+
getByteFrequencyData(buffer) {
|
|
82
|
+
this.ensureMultibandProcessor(buffer.length);
|
|
83
|
+
if (this.magnitudes.length === 0) {
|
|
84
|
+
// No multiband data yet; fall back to uniform fill from RMS volume
|
|
85
|
+
this.ensureVolumeProcessor();
|
|
86
|
+
const clamped = this.volume < 0 ? 0 : this.volume > 1 ? 1 : this.volume;
|
|
87
|
+
buffer.fill(Math.round(clamped * 255));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
91
|
+
const m = this.magnitudes[i] ?? 0;
|
|
92
|
+
buffer[i] = Math.round((m < 0 ? 0 : m > 1 ? 1 : m) * 255);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
cleanupProcessors() {
|
|
96
|
+
if (this.volumeTag) {
|
|
97
|
+
LiveKitModule.deleteVolumeProcessor(this.volumeTag, this.pcId, this.trackId);
|
|
98
|
+
this.volumeSub?.remove();
|
|
99
|
+
this.volumeTag = null;
|
|
100
|
+
this.volumeSub = null;
|
|
101
|
+
}
|
|
102
|
+
if (this.multibandTag) {
|
|
103
|
+
LiveKitModule.deleteMultibandVolumeProcessor(this.multibandTag, this.pcId, this.trackId);
|
|
104
|
+
this.multibandSub?.remove();
|
|
105
|
+
this.multibandTag = null;
|
|
106
|
+
this.multibandSub = null;
|
|
107
|
+
this.currentBands = 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
cleanup() {
|
|
111
|
+
this.cleanupProcessors();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Sets up lazy native volume providers for the WebRTC connection. No native
|
|
116
|
+
* processors are created until `getVolume()` or `getByteFrequencyData()` is
|
|
117
|
+
* actually called. Returns a cleanup function.
|
|
118
|
+
*/
|
|
119
|
+
function setupNativeVolumeProcessors(connection) {
|
|
120
|
+
const room = connection.getRoom();
|
|
121
|
+
const providers = [];
|
|
122
|
+
// --- Input (local mic track) ---
|
|
123
|
+
let inputProvider = null;
|
|
124
|
+
function setupInputTrack(track) {
|
|
125
|
+
const mst = track.mediaStreamTrack;
|
|
126
|
+
const pcId = mst._peerConnectionId ?? -1;
|
|
127
|
+
if (inputProvider) {
|
|
128
|
+
inputProvider.updateTrack(pcId, mst.id);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
inputProvider = new NativeVolumeProvider(pcId, mst.id);
|
|
132
|
+
providers.push(inputProvider);
|
|
133
|
+
connection.setInputVolumeProvider(inputProvider);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const micPub = room.localParticipant.audioTrackPublications.values().next();
|
|
137
|
+
const micTrack = micPub.done ? undefined : micPub.value?.track;
|
|
138
|
+
if (micTrack) {
|
|
139
|
+
setupInputTrack(micTrack);
|
|
140
|
+
}
|
|
141
|
+
// Re-bind the input provider when the local mic track is republished
|
|
142
|
+
// (e.g. after setAudioInputDevice)
|
|
143
|
+
const localTrackHandler = (publication) => {
|
|
144
|
+
if (publication.track?.kind === "audio") {
|
|
145
|
+
setupInputTrack(publication.track);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
room.on("localTrackPublished", localTrackHandler);
|
|
149
|
+
// --- Output (remote agent track) ---
|
|
150
|
+
let outputProvider = null;
|
|
151
|
+
function setupOutputTrack(track) {
|
|
152
|
+
const mst = track.mediaStreamTrack;
|
|
153
|
+
const pcId = mst._peerConnectionId ?? -1;
|
|
154
|
+
if (outputProvider) {
|
|
155
|
+
outputProvider.updateTrack(pcId, mst.id);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
outputProvider = new NativeVolumeProvider(pcId, mst.id);
|
|
159
|
+
providers.push(outputProvider);
|
|
160
|
+
connection.setOutputVolumeProvider(outputProvider);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Check for an existing remote audio track from the agent
|
|
164
|
+
for (const participant of room.remoteParticipants.values()) {
|
|
165
|
+
if (participant.identity?.includes("agent")) {
|
|
166
|
+
for (const pub of participant.audioTrackPublications.values()) {
|
|
167
|
+
if (pub.track) {
|
|
168
|
+
setupOutputTrack(pub.track);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Listen for future track subscriptions
|
|
175
|
+
const trackHandler = (track, _publication, participant) => {
|
|
176
|
+
if (track.kind === "audio" && participant?.identity?.includes("agent")) {
|
|
177
|
+
setupOutputTrack(track);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
room.on("trackSubscribed", trackHandler);
|
|
181
|
+
return () => {
|
|
182
|
+
room.off("localTrackPublished", localTrackHandler);
|
|
183
|
+
room.off("trackSubscribed", trackHandler);
|
|
184
|
+
for (const provider of providers) {
|
|
185
|
+
provider.cleanup();
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Attaches native volume processors to the WebRTC connection so that
|
|
191
|
+
* `getInputVolume()` / `getOutputVolume()` return real values on
|
|
192
|
+
* React Native. Falls through unchanged for non-WebRTC connections;
|
|
193
|
+
* WebSocket volume on React Native is a known gap (MediaDeviceInput/
|
|
194
|
+
* MediaDeviceOutput depend on AudioContext which is unavailable in RN).
|
|
195
|
+
*/
|
|
196
|
+
export function attachNativeVolume(result) {
|
|
197
|
+
if (!(result.connection instanceof WebRTCConnection)) {
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
const cleanup = setupNativeVolumeProcessors(result.connection);
|
|
201
|
+
const originalDetach = result.detach;
|
|
202
|
+
return {
|
|
203
|
+
...result,
|
|
204
|
+
detach: () => {
|
|
205
|
+
cleanup();
|
|
206
|
+
originalDetach();
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=nativeVolume.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeVolume.js","sourceRoot":"","sources":["../src/nativeVolume.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "1.
|
|
1
|
+
export declare const PACKAGE_VERSION = "1.1.1";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elevenlabs/react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "ElevenLabs React Native SDK for the Agents Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"react-native": ">=0.70.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@elevenlabs/client": "1.1
|
|
31
|
-
"@elevenlabs/react": "1.
|
|
30
|
+
"@elevenlabs/client": "1.2.1",
|
|
31
|
+
"@elevenlabs/react": "1.1.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/react": "^19.1.1",
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
webSessionSetup,
|
|
10
10
|
type VoiceSessionSetupResult,
|
|
11
11
|
} from "@elevenlabs/client/internal";
|
|
12
|
+
import { attachNativeVolume } from "./nativeVolume.js";
|
|
12
13
|
|
|
13
14
|
// Polyfill WebRTC globals needed by livekit-client in React Native
|
|
14
15
|
registerGlobals();
|
|
@@ -18,7 +19,8 @@ registerGlobals();
|
|
|
18
19
|
*
|
|
19
20
|
* 1. Configures and starts the native AudioSession
|
|
20
21
|
* 2. Delegates connection + input/output setup to the web strategy
|
|
21
|
-
* 3. Wraps
|
|
22
|
+
* 3. Wraps input/output controllers with native volume processors
|
|
23
|
+
* 4. Wraps detach to stop the native AudioSession on cleanup
|
|
22
24
|
*/
|
|
23
25
|
async function reactNativeSessionSetup(
|
|
24
26
|
options: Options
|
|
@@ -34,7 +36,7 @@ async function reactNativeSessionSetup(
|
|
|
34
36
|
});
|
|
35
37
|
await AudioSession.startAudioSession();
|
|
36
38
|
|
|
37
|
-
const result = await webSessionSetup(options);
|
|
39
|
+
const result = attachNativeVolume(await webSessionSetup(options));
|
|
38
40
|
|
|
39
41
|
const originalDetach = result.detach;
|
|
40
42
|
return {
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { NativeModules, NativeEventEmitter } from "react-native";
|
|
2
|
+
import { WebRTCConnection, type VolumeProvider } from "@elevenlabs/client";
|
|
3
|
+
import {
|
|
4
|
+
MIN_VOICE_FREQUENCY,
|
|
5
|
+
MAX_VOICE_FREQUENCY,
|
|
6
|
+
type VoiceSessionSetupResult,
|
|
7
|
+
} from "@elevenlabs/client/internal";
|
|
8
|
+
|
|
9
|
+
const LiveKitModule = NativeModules.LivekitReactNativeModule;
|
|
10
|
+
|
|
11
|
+
let emitter: NativeEventEmitter | null = null;
|
|
12
|
+
function getEmitter(): NativeEventEmitter {
|
|
13
|
+
emitter ??= new NativeEventEmitter(LiveKitModule);
|
|
14
|
+
return emitter;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface VolumeEvent {
|
|
18
|
+
id: string;
|
|
19
|
+
volume: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface MultibandEvent {
|
|
23
|
+
id: string;
|
|
24
|
+
magnitudes: number[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const MULTIBAND_FREQUENCY_OPTIONS = {
|
|
28
|
+
minFrequency: MIN_VOICE_FREQUENCY,
|
|
29
|
+
maxFrequency: MAX_VOICE_FREQUENCY,
|
|
30
|
+
updateInterval: 40,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A VolumeProvider backed by native LiveKit audio processors.
|
|
35
|
+
*
|
|
36
|
+
* Both processors are created lazily: the RMS volume processor on the first
|
|
37
|
+
* `getVolume()` call, and the FFT multiband processor on the first
|
|
38
|
+
* `getByteFrequencyData()` call (with its band count matching the buffer
|
|
39
|
+
* length). If the buffer size changes, the multiband processor is recreated.
|
|
40
|
+
*/
|
|
41
|
+
class NativeVolumeProvider implements VolumeProvider {
|
|
42
|
+
private volume = 0;
|
|
43
|
+
private magnitudes: number[] = [];
|
|
44
|
+
|
|
45
|
+
// Volume processor state (lazy)
|
|
46
|
+
private volumeTag: string | null = null;
|
|
47
|
+
private volumeSub: { remove: () => void } | null = null;
|
|
48
|
+
|
|
49
|
+
// Multiband processor state (lazy)
|
|
50
|
+
private multibandTag: string | null = null;
|
|
51
|
+
private multibandSub: { remove: () => void } | null = null;
|
|
52
|
+
private currentBands = 0;
|
|
53
|
+
|
|
54
|
+
constructor(
|
|
55
|
+
private pcId: number,
|
|
56
|
+
private trackId: string
|
|
57
|
+
) {}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Rebinds this provider to a new track, cleaning up any existing native
|
|
61
|
+
* processors so they are lazily recreated against the new track.
|
|
62
|
+
*/
|
|
63
|
+
updateTrack(pcId: number, trackId: string) {
|
|
64
|
+
this.cleanupProcessors();
|
|
65
|
+
this.pcId = pcId;
|
|
66
|
+
this.trackId = trackId;
|
|
67
|
+
this.volume = 0;
|
|
68
|
+
this.magnitudes = [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private ensureVolumeProcessor() {
|
|
72
|
+
if (this.volumeTag) return;
|
|
73
|
+
this.volumeTag = LiveKitModule.createVolumeProcessor(
|
|
74
|
+
this.pcId,
|
|
75
|
+
this.trackId
|
|
76
|
+
);
|
|
77
|
+
this.volumeSub = getEmitter().addListener(
|
|
78
|
+
"LK_VOLUME_PROCESSED",
|
|
79
|
+
(event: VolumeEvent) => {
|
|
80
|
+
if (event.id === this.volumeTag) {
|
|
81
|
+
this.volume = event.volume;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private ensureMultibandProcessor(bands: number) {
|
|
88
|
+
if (bands === this.currentBands) return;
|
|
89
|
+
|
|
90
|
+
// Cleanup previous processor
|
|
91
|
+
if (this.multibandTag) {
|
|
92
|
+
LiveKitModule.deleteMultibandVolumeProcessor(
|
|
93
|
+
this.multibandTag,
|
|
94
|
+
this.pcId,
|
|
95
|
+
this.trackId
|
|
96
|
+
);
|
|
97
|
+
this.multibandSub?.remove();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.currentBands = bands;
|
|
101
|
+
this.magnitudes = [];
|
|
102
|
+
this.multibandTag = LiveKitModule.createMultibandVolumeProcessor(
|
|
103
|
+
{ ...MULTIBAND_FREQUENCY_OPTIONS, bands },
|
|
104
|
+
this.pcId,
|
|
105
|
+
this.trackId
|
|
106
|
+
);
|
|
107
|
+
this.multibandSub = getEmitter().addListener(
|
|
108
|
+
"LK_MULTIBAND_PROCESSED",
|
|
109
|
+
(event: MultibandEvent) => {
|
|
110
|
+
if (event.id === this.multibandTag) {
|
|
111
|
+
this.magnitudes = event.magnitudes;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getVolume(): number {
|
|
118
|
+
this.ensureVolumeProcessor();
|
|
119
|
+
return this.volume < 0 ? 0 : this.volume > 1 ? 1 : this.volume;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getByteFrequencyData(buffer: Uint8Array<ArrayBuffer>): void {
|
|
123
|
+
this.ensureMultibandProcessor(buffer.length);
|
|
124
|
+
if (this.magnitudes.length === 0) {
|
|
125
|
+
// No multiband data yet; fall back to uniform fill from RMS volume
|
|
126
|
+
this.ensureVolumeProcessor();
|
|
127
|
+
const clamped = this.volume < 0 ? 0 : this.volume > 1 ? 1 : this.volume;
|
|
128
|
+
buffer.fill(Math.round(clamped * 255));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
132
|
+
const m = this.magnitudes[i] ?? 0;
|
|
133
|
+
buffer[i] = Math.round((m < 0 ? 0 : m > 1 ? 1 : m) * 255);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private cleanupProcessors() {
|
|
138
|
+
if (this.volumeTag) {
|
|
139
|
+
LiveKitModule.deleteVolumeProcessor(
|
|
140
|
+
this.volumeTag,
|
|
141
|
+
this.pcId,
|
|
142
|
+
this.trackId
|
|
143
|
+
);
|
|
144
|
+
this.volumeSub?.remove();
|
|
145
|
+
this.volumeTag = null;
|
|
146
|
+
this.volumeSub = null;
|
|
147
|
+
}
|
|
148
|
+
if (this.multibandTag) {
|
|
149
|
+
LiveKitModule.deleteMultibandVolumeProcessor(
|
|
150
|
+
this.multibandTag,
|
|
151
|
+
this.pcId,
|
|
152
|
+
this.trackId
|
|
153
|
+
);
|
|
154
|
+
this.multibandSub?.remove();
|
|
155
|
+
this.multibandTag = null;
|
|
156
|
+
this.multibandSub = null;
|
|
157
|
+
this.currentBands = 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
cleanup() {
|
|
162
|
+
this.cleanupProcessors();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Sets up lazy native volume providers for the WebRTC connection. No native
|
|
168
|
+
* processors are created until `getVolume()` or `getByteFrequencyData()` is
|
|
169
|
+
* actually called. Returns a cleanup function.
|
|
170
|
+
*/
|
|
171
|
+
function setupNativeVolumeProcessors(connection: WebRTCConnection): () => void {
|
|
172
|
+
const room = connection.getRoom();
|
|
173
|
+
const providers: NativeVolumeProvider[] = [];
|
|
174
|
+
|
|
175
|
+
// --- Input (local mic track) ---
|
|
176
|
+
let inputProvider: NativeVolumeProvider | null = null;
|
|
177
|
+
|
|
178
|
+
function setupInputTrack(track: any) {
|
|
179
|
+
const mst = track.mediaStreamTrack as any;
|
|
180
|
+
const pcId: number = mst._peerConnectionId ?? -1;
|
|
181
|
+
if (inputProvider) {
|
|
182
|
+
inputProvider.updateTrack(pcId, mst.id);
|
|
183
|
+
} else {
|
|
184
|
+
inputProvider = new NativeVolumeProvider(pcId, mst.id);
|
|
185
|
+
providers.push(inputProvider);
|
|
186
|
+
connection.setInputVolumeProvider(inputProvider);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const micPub = room.localParticipant.audioTrackPublications.values().next();
|
|
191
|
+
const micTrack = micPub.done ? undefined : micPub.value?.track;
|
|
192
|
+
if (micTrack) {
|
|
193
|
+
setupInputTrack(micTrack);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Re-bind the input provider when the local mic track is republished
|
|
197
|
+
// (e.g. after setAudioInputDevice)
|
|
198
|
+
const localTrackHandler = (publication: any) => {
|
|
199
|
+
if (publication.track?.kind === "audio") {
|
|
200
|
+
setupInputTrack(publication.track);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
room.on("localTrackPublished", localTrackHandler);
|
|
204
|
+
|
|
205
|
+
// --- Output (remote agent track) ---
|
|
206
|
+
let outputProvider: NativeVolumeProvider | null = null;
|
|
207
|
+
|
|
208
|
+
function setupOutputTrack(track: any) {
|
|
209
|
+
const mst = track.mediaStreamTrack as any;
|
|
210
|
+
const pcId: number = mst._peerConnectionId ?? -1;
|
|
211
|
+
if (outputProvider) {
|
|
212
|
+
outputProvider.updateTrack(pcId, mst.id);
|
|
213
|
+
} else {
|
|
214
|
+
outputProvider = new NativeVolumeProvider(pcId, mst.id);
|
|
215
|
+
providers.push(outputProvider);
|
|
216
|
+
connection.setOutputVolumeProvider(outputProvider);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check for an existing remote audio track from the agent
|
|
221
|
+
for (const participant of room.remoteParticipants.values()) {
|
|
222
|
+
if (participant.identity?.includes("agent")) {
|
|
223
|
+
for (const pub of participant.audioTrackPublications.values()) {
|
|
224
|
+
if (pub.track) {
|
|
225
|
+
setupOutputTrack(pub.track);
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Listen for future track subscriptions
|
|
233
|
+
const trackHandler = (track: any, _publication: any, participant: any) => {
|
|
234
|
+
if (track.kind === "audio" && participant?.identity?.includes("agent")) {
|
|
235
|
+
setupOutputTrack(track);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
room.on("trackSubscribed", trackHandler);
|
|
239
|
+
|
|
240
|
+
return () => {
|
|
241
|
+
room.off("localTrackPublished", localTrackHandler);
|
|
242
|
+
room.off("trackSubscribed", trackHandler);
|
|
243
|
+
for (const provider of providers) {
|
|
244
|
+
provider.cleanup();
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Attaches native volume processors to the WebRTC connection so that
|
|
251
|
+
* `getInputVolume()` / `getOutputVolume()` return real values on
|
|
252
|
+
* React Native. Falls through unchanged for non-WebRTC connections;
|
|
253
|
+
* WebSocket volume on React Native is a known gap (MediaDeviceInput/
|
|
254
|
+
* MediaDeviceOutput depend on AudioContext which is unavailable in RN).
|
|
255
|
+
*/
|
|
256
|
+
export function attachNativeVolume(
|
|
257
|
+
result: VoiceSessionSetupResult
|
|
258
|
+
): VoiceSessionSetupResult {
|
|
259
|
+
if (!(result.connection instanceof WebRTCConnection)) {
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const cleanup = setupNativeVolumeProcessors(result.connection);
|
|
264
|
+
|
|
265
|
+
const originalDetach = result.detach;
|
|
266
|
+
return {
|
|
267
|
+
...result,
|
|
268
|
+
detach: () => {
|
|
269
|
+
cleanup();
|
|
270
|
+
originalDetach();
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|