@elevenlabs/react-native 1.0.3 → 1.1.0

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @elevenlabs/react-native@1.0.3 build /home/runner/work/packages/packages/packages/react-native
2
+ > @elevenlabs/react-native@1.1.0 build /home/runner/work/packages/packages/packages/react-native
3
3
  > tsc --build tsconfig.build.json
4
4
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @elevenlabs/react-native@1.0.3 check-types /home/runner/work/packages/packages/packages/react-native
2
+ > @elevenlabs/react-native@1.1.0 check-types /home/runner/work/packages/packages/packages/react-native
3
3
  > tsc --noEmit --skipLibCheck
4
4
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @elevenlabs/react-native@1.0.3 generate-version /home/runner/work/packages/packages/packages/react-native
2
+ > @elevenlabs/react-native@1.1.0 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,4 +1,4 @@
1
1
 
2
- > @elevenlabs/react-native@1.0.3 lint:es /home/runner/work/packages/packages/packages/react-native
2
+ > @elevenlabs/react-native@1.1.0 lint:es /home/runner/work/packages/packages/packages/react-native
3
3
  > pnpm exec eslint .
4
4
 
@@ -1,5 +1,5 @@
1
1
 
2
- > @elevenlabs/react-native@1.0.3 lint:prettier /home/runner/work/packages/packages/packages/react-native
2
+ > @elevenlabs/react-native@1.1.0 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,20 @@
1
1
  # @elevenlabs/react-native
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0d5c368: Fix getInputVolume/getOutputVolume returning 0 in React Native by adding native volume providers using LiveKit's RMS and multiband FFT processors.
8
+
9
+ **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.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [806edd0]
14
+ - Updated dependencies [0d5c368]
15
+ - @elevenlabs/react@1.1.0
16
+ - @elevenlabs/client@1.2.0
17
+
3
18
  ## 1.0.3
4
19
 
5
20
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"index.react-native.d.ts","sourceRoot":"","sources":["../src/index.react-native.ts"],"names":[],"mappings":"AAkDA,cAAc,YAAY,CAAC"}
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 detach to stop the native AudioSession on cleanup
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;AAErC,mEAAmE;AACnE,eAAe,EAAE,CAAC;AAElB;;;;;;GAMG;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,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;IAE9C,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"}
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.0.3";
1
+ export declare const PACKAGE_VERSION = "1.1.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // This file is auto-generated during build
2
- export const PACKAGE_VERSION = "1.0.3";
2
+ export const PACKAGE_VERSION = "1.1.0";
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevenlabs/react-native",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
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.2",
31
- "@elevenlabs/react": "1.0.3"
30
+ "@elevenlabs/client": "1.2.0",
31
+ "@elevenlabs/react": "1.1.0"
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 detach to stop the native AudioSession on cleanup
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
+ }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // This file is auto-generated during build
2
- export const PACKAGE_VERSION = "1.0.3";
2
+ export const PACKAGE_VERSION = "1.1.0";