@livekit/react-native 2.5.1 → 2.6.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.
Files changed (95) hide show
  1. package/README.md +4 -3
  2. package/android/build.gradle +2 -1
  3. package/android/src/main/java/com/livekit/reactnative/LiveKitReactNative.kt +61 -5
  4. package/android/src/main/java/com/livekit/reactnative/LivekitReactNativeModule.kt +81 -4
  5. package/android/src/main/java/com/livekit/reactnative/audio/events/Events.kt +6 -0
  6. package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioFormat.kt +2 -0
  7. package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioProcessingController.kt +27 -0
  8. package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioProcessorInterface.kt +52 -0
  9. package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioRecordSamplesDispatcher.kt +72 -0
  10. package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioSinkManager.kt +75 -0
  11. package/android/src/main/java/com/livekit/reactnative/audio/processing/CustomAudioProcessingFactory.kt +78 -0
  12. package/android/src/main/java/com/livekit/reactnative/audio/processing/MultibandVolumeProcessor.kt +181 -0
  13. package/android/src/main/java/com/livekit/reactnative/audio/processing/VolumeProcessor.kt +67 -0
  14. package/android/src/main/java/com/livekit/reactnative/audio/processing/fft/FFTAudioAnalyzer.kt +224 -0
  15. package/ios/AudioUtils.swift +49 -0
  16. package/ios/LKAudioProcessingAdapter.h +26 -0
  17. package/ios/LKAudioProcessingAdapter.m +117 -0
  18. package/ios/LKAudioProcessingManager.h +34 -0
  19. package/ios/LKAudioProcessingManager.m +63 -0
  20. package/ios/LiveKitReactNativeModule.swift +234 -0
  21. package/ios/LivekitReactNative-Bridging-Header.h +5 -2
  22. package/ios/LivekitReactNative.h +2 -6
  23. package/ios/LivekitReactNative.m +3 -166
  24. package/ios/LivekitReactNativeModule.m +42 -0
  25. package/ios/Logging.swift +4 -0
  26. package/ios/audio/AVAudioPCMBuffer.swift +136 -0
  27. package/ios/audio/AudioProcessing.swift +163 -0
  28. package/ios/audio/AudioRendererManager.swift +71 -0
  29. package/ios/audio/FFTProcessor.swift +147 -0
  30. package/ios/audio/MultibandVolumeAudioRenderer.swift +67 -0
  31. package/ios/audio/RingBuffer.swift +51 -0
  32. package/ios/audio/VolumeAudioRenderer.swift +50 -0
  33. package/lib/commonjs/LKNativeModule.js +18 -0
  34. package/lib/commonjs/LKNativeModule.js.map +1 -0
  35. package/lib/commonjs/audio/AudioSession.js +9 -17
  36. package/lib/commonjs/audio/AudioSession.js.map +1 -1
  37. package/lib/commonjs/components/BarVisualizer.js +192 -0
  38. package/lib/commonjs/components/BarVisualizer.js.map +1 -0
  39. package/lib/commonjs/events/EventEmitter.js +45 -0
  40. package/lib/commonjs/events/EventEmitter.js.map +1 -0
  41. package/lib/commonjs/hooks/useMultibandTrackVolume.js +64 -0
  42. package/lib/commonjs/hooks/useMultibandTrackVolume.js.map +1 -0
  43. package/lib/commonjs/hooks/useTrackVolume.js +45 -0
  44. package/lib/commonjs/hooks/useTrackVolume.js.map +1 -0
  45. package/lib/commonjs/hooks.js +24 -0
  46. package/lib/commonjs/hooks.js.map +1 -1
  47. package/lib/commonjs/index.js +14 -0
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/module/LKNativeModule.js +12 -0
  50. package/lib/module/LKNativeModule.js.map +1 -0
  51. package/lib/module/audio/AudioSession.js +9 -17
  52. package/lib/module/audio/AudioSession.js.map +1 -1
  53. package/lib/module/components/BarVisualizer.js +182 -0
  54. package/lib/module/components/BarVisualizer.js.map +1 -0
  55. package/lib/module/events/EventEmitter.js +36 -0
  56. package/lib/module/events/EventEmitter.js.map +1 -0
  57. package/lib/module/hooks/useMultibandTrackVolume.js +58 -0
  58. package/lib/module/hooks/useMultibandTrackVolume.js.map +1 -0
  59. package/lib/module/hooks/useTrackVolume.js +39 -0
  60. package/lib/module/hooks/useTrackVolume.js.map +1 -0
  61. package/lib/module/hooks.js +2 -0
  62. package/lib/module/hooks.js.map +1 -1
  63. package/lib/module/index.js +3 -0
  64. package/lib/module/index.js.map +1 -1
  65. package/lib/typescript/lib/commonjs/LKNativeModule.d.ts +3 -0
  66. package/lib/typescript/lib/commonjs/components/BarVisualizer.d.ts +32 -0
  67. package/lib/typescript/lib/commonjs/events/EventEmitter.d.ts +4 -0
  68. package/lib/typescript/lib/commonjs/hooks/useMultibandTrackVolume.d.ts +8 -0
  69. package/lib/typescript/lib/commonjs/hooks/useTrackVolume.d.ts +8 -0
  70. package/lib/typescript/lib/module/LKNativeModule.d.ts +2 -0
  71. package/lib/typescript/lib/module/components/BarVisualizer.d.ts +10 -0
  72. package/lib/typescript/lib/module/events/EventEmitter.d.ts +3 -0
  73. package/lib/typescript/lib/module/hooks/useMultibandTrackVolume.d.ts +7 -0
  74. package/lib/typescript/lib/module/hooks/useTrackVolume.d.ts +7 -0
  75. package/lib/typescript/lib/module/hooks.d.ts +2 -0
  76. package/lib/typescript/lib/module/index.d.ts +1 -0
  77. package/lib/typescript/src/LKNativeModule.d.ts +2 -0
  78. package/lib/typescript/src/components/BarVisualizer.d.ts +49 -0
  79. package/lib/typescript/src/events/EventEmitter.d.ts +6 -0
  80. package/lib/typescript/src/hooks/useMultibandTrackVolume.d.ts +31 -0
  81. package/lib/typescript/src/hooks/useTrackVolume.d.ts +9 -0
  82. package/lib/typescript/src/hooks.d.ts +2 -0
  83. package/lib/typescript/src/index.d.ts +1 -0
  84. package/livekit-react-native.podspec +26 -6
  85. package/package.json +5 -5
  86. package/src/LKNativeModule.ts +19 -0
  87. package/src/audio/AudioSession.ts +9 -24
  88. package/src/components/BarVisualizer.tsx +252 -0
  89. package/src/events/EventEmitter.ts +51 -0
  90. package/src/hooks/useMultibandTrackVolume.ts +97 -0
  91. package/src/hooks/useTrackVolume.ts +62 -0
  92. package/src/hooks.ts +2 -0
  93. package/src/index.tsx +3 -0
  94. package/ios/AudioUtils.h +0 -9
  95. package/ios/AudioUtils.m +0 -48
@@ -0,0 +1,32 @@
1
+ export const __esModule: boolean;
2
+ /**
3
+ * @beta
4
+ */
5
+ /**
6
+ * Visualizes audio signals from a TrackReference as bars.
7
+ * If the `state` prop is set, it automatically transitions between VoiceAssistant states.
8
+ * @beta
9
+ *
10
+ * @remarks For VoiceAssistant state transitions this component requires a voice assistant agent running with livekit-agents \>= 0.9.0
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * function SimpleVoiceAssistant() {
15
+ * const { state, audioTrack } = useVoiceAssistant();
16
+ * return (
17
+ * <BarVisualizer
18
+ * state={state}
19
+ * trackRef={audioTrack}
20
+ * />
21
+ * );
22
+ * }
23
+ * ```
24
+ */
25
+ export function BarVisualizer({ style, state, barCount, trackRef, options }: {
26
+ style?: {} | undefined;
27
+ state: any;
28
+ barCount?: number | undefined;
29
+ trackRef: any;
30
+ options: any;
31
+ }): any;
32
+ export function useBarAnimator(state: any, columns: any, interval: any): any;
@@ -0,0 +1,4 @@
1
+ export const __esModule: boolean;
2
+ export function addListener(listener: any, eventName: any, eventHandler: any): void;
3
+ export function removeListener(listener: any): void;
4
+ export function setupNativeEvents(): void;
@@ -0,0 +1,8 @@
1
+ export const __esModule: boolean;
2
+ /**
3
+ * A hook for tracking the volume of an audio track across multiple frequency bands.
4
+ *
5
+ * @param trackOrTrackReference
6
+ * @returns A number array containing the volume for each frequency band.
7
+ */
8
+ export function useMultibandTrackVolume(trackOrTrackReference: any, options?: {}): any;
@@ -0,0 +1,8 @@
1
+ export const __esModule: boolean;
2
+ /**
3
+ * A hook for tracking the volume of an audio track.
4
+ *
5
+ * @param trackOrTrackReference
6
+ * @returns A number between 0-1 representing the volume.
7
+ */
8
+ export function useTrackVolume(trackOrTrackReference: any): any;
@@ -0,0 +1,2 @@
1
+ export default LiveKitModule;
2
+ declare const LiveKitModule: any;
@@ -0,0 +1,10 @@
1
+ export function BarVisualizer({ style, state, barCount, trackRef, options }: {
2
+ style?: {} | undefined;
3
+ state: any;
4
+ barCount?: number | undefined;
5
+ trackRef: any;
6
+ options: any;
7
+ }): React.CElement<import("react-native").ViewProps, View>;
8
+ export function useBarAnimator(state: any, columns: any, interval: any): never[];
9
+ import { View } from 'react-native';
10
+ import React from 'react';
@@ -0,0 +1,3 @@
1
+ export function setupNativeEvents(): void;
2
+ export function addListener(listener: any, eventName: any, eventHandler: any): void;
3
+ export function removeListener(listener: any): void;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A hook for tracking the volume of an audio track across multiple frequency bands.
3
+ *
4
+ * @param trackOrTrackReference
5
+ * @returns A number array containing the volume for each frequency band.
6
+ */
7
+ export function useMultibandTrackVolume(trackOrTrackReference: any, options?: {}): never[];
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A hook for tracking the volume of an audio track.
3
+ *
4
+ * @param trackOrTrackReference
5
+ * @returns A number between 0-1 representing the volume.
6
+ */
7
+ export function useTrackVolume(trackOrTrackReference: any): number;
@@ -1,2 +1,4 @@
1
1
  export * from "./hooks/useE2EEManager";
2
+ export * from "./hooks/useTrackVolume";
3
+ export * from "./hooks/useMultibandTrackVolume";
2
4
  export { useConnectionState, useDataChannel, useIsSpeaking, useLocalParticipant, useLocalParticipantPermissions, useParticipantInfo, useParticipants, useRemoteParticipants, useRemoteParticipant, useSpeakingParticipants, useSortedParticipants, useChat, useIsEncrypted, useRoomInfo, useIsMuted, useParticipantTracks, useLiveKitRoom, RoomContext, useRoomContext, ParticipantContext, useParticipantContext, TrackRefContext, useTrackRefContext, useTracks, isTrackReference, useEnsureTrackRef, useTrackMutedIndicator, useVisualStableUpdate } from "@livekit/components-react";
@@ -5,6 +5,7 @@
5
5
  */
6
6
  export function registerGlobals(): void;
7
7
  export * from "./hooks";
8
+ export * from "./components/BarVisualizer";
8
9
  export * from "./components/LiveKitRoom";
9
10
  export * from "./components/VideoTrack";
10
11
  export * from "./components/VideoView";
@@ -0,0 +1,2 @@
1
+ declare const LiveKitModule: any;
2
+ export default LiveKitModule;
@@ -0,0 +1,49 @@
1
+ import { type AgentState, type TrackReferenceOrPlaceholder } from '@livekit/components-react';
2
+ import { type ColorValue, type DimensionValue, type ViewStyle } from 'react-native';
3
+ import React from 'react';
4
+ export type BarVisualizerOptions = {
5
+ /** decimal values from 0 to 1 */
6
+ maxHeight?: number;
7
+ /** decimal values from 0 to 1 */
8
+ minHeight?: number;
9
+ barColor?: ColorValue;
10
+ barWidth?: DimensionValue;
11
+ barBorderRadius?: number;
12
+ };
13
+ /**
14
+ * @beta
15
+ */
16
+ export interface BarVisualizerProps {
17
+ /** If set, the visualizer will transition between different voice assistant states */
18
+ state?: AgentState;
19
+ /** Number of bars that show up in the visualizer */
20
+ barCount?: number;
21
+ trackRef?: TrackReferenceOrPlaceholder;
22
+ options?: BarVisualizerOptions;
23
+ /**
24
+ * Custom React Native styles for the container.
25
+ */
26
+ style?: ViewStyle;
27
+ }
28
+ /**
29
+ * Visualizes audio signals from a TrackReference as bars.
30
+ * If the `state` prop is set, it automatically transitions between VoiceAssistant states.
31
+ * @beta
32
+ *
33
+ * @remarks For VoiceAssistant state transitions this component requires a voice assistant agent running with livekit-agents \>= 0.9.0
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * function SimpleVoiceAssistant() {
38
+ * const { state, audioTrack } = useVoiceAssistant();
39
+ * return (
40
+ * <BarVisualizer
41
+ * state={state}
42
+ * trackRef={audioTrack}
43
+ * />
44
+ * );
45
+ * }
46
+ * ```
47
+ */
48
+ export declare const BarVisualizer: ({ style, state, barCount, trackRef, options, }: BarVisualizerProps) => React.JSX.Element;
49
+ export declare const useBarAnimator: (state: AgentState | undefined, columns: number, interval: number) => number[];
@@ -0,0 +1,6 @@
1
+ export declare function setupNativeEvents(): void;
2
+ type EventHandler = (event: unknown) => void;
3
+ type Listener = unknown;
4
+ export declare function addListener(listener: Listener, eventName: string, eventHandler: EventHandler): void;
5
+ export declare function removeListener(listener: Listener): void;
6
+ export {};
@@ -0,0 +1,31 @@
1
+ import { type TrackReferenceOrPlaceholder } from '@livekit/components-react';
2
+ import { type LocalAudioTrack, type RemoteAudioTrack } from 'livekit-client';
3
+ /**
4
+ * Interface for configuring options for the useMultibandTrackVolume hook.
5
+ * @alpha
6
+ */
7
+ export interface MultiBandTrackVolumeOptions {
8
+ /**
9
+ * the number of bands to split the audio into
10
+ */
11
+ bands?: number;
12
+ /**
13
+ * cut off frequency on the lower end
14
+ */
15
+ minFrequency?: number;
16
+ /**
17
+ * cut off frequency on the higher end
18
+ */
19
+ maxFrequency?: number;
20
+ /**
21
+ * update should run every x ms
22
+ */
23
+ updateInterval?: number;
24
+ }
25
+ /**
26
+ * A hook for tracking the volume of an audio track across multiple frequency bands.
27
+ *
28
+ * @param trackOrTrackReference
29
+ * @returns A number array containing the volume for each frequency band.
30
+ */
31
+ export declare function useMultibandTrackVolume(trackOrTrackReference?: LocalAudioTrack | RemoteAudioTrack | TrackReferenceOrPlaceholder, options?: MultiBandTrackVolumeOptions): number[];
@@ -0,0 +1,9 @@
1
+ import { type TrackReferenceOrPlaceholder } from '@livekit/components-react';
2
+ import { type LocalAudioTrack, type RemoteAudioTrack } from 'livekit-client';
3
+ /**
4
+ * A hook for tracking the volume of an audio track.
5
+ *
6
+ * @param trackOrTrackReference
7
+ * @returns A number between 0-1 representing the volume.
8
+ */
9
+ export declare function useTrackVolume(trackOrTrackReference?: LocalAudioTrack | RemoteAudioTrack | TrackReferenceOrPlaceholder): number;
@@ -2,4 +2,6 @@ export { useConnectionState, useDataChannel, useIsSpeaking, useLocalParticipant,
2
2
  export type { UseLocalParticipantOptions, UseParticipantInfoOptions, UseParticipantsOptions, UseRemoteParticipantOptions, UseRemoteParticipantsOptions, UseTracksOptions, TrackReference, TrackReferenceOrPlaceholder, UseVisualStableUpdateOptions, } from '@livekit/components-react';
3
3
  export type { ReceivedDataMessage } from '@livekit/components-core';
4
4
  export * from './hooks/useE2EEManager';
5
+ export * from './hooks/useTrackVolume';
6
+ export * from './hooks/useMultibandTrackVolume';
5
7
  export type { UseRNE2EEManagerOptions } from './hooks/useE2EEManager';
@@ -11,6 +11,7 @@ import RNKeyProvider, { type RNKeyProviderOptions } from './e2ee/RNKeyProvider';
11
11
  */
12
12
  export declare function registerGlobals(): void;
13
13
  export * from './hooks';
14
+ export * from './components/BarVisualizer';
14
15
  export * from './components/LiveKitRoom';
15
16
  export * from './components/VideoTrack';
16
17
  export * from './components/VideoView';
@@ -1,6 +1,7 @@
1
1
  require "json"
2
2
 
3
3
  package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+ folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
4
5
 
5
6
  Pod::Spec.new do |s|
6
7
  s.name = "livekit-react-native"
@@ -13,15 +14,34 @@ Pod::Spec.new do |s|
13
14
  s.platforms = { :ios => "10.0" }
14
15
  s.source = { :git => "https://github.com/livekit/client-sdk-react-native.git", :tag => "#{s.version}" }
15
16
 
16
- s.source_files = "ios/**/*.{h,m,mm}"
17
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
17
18
 
18
19
  s.framework = 'AVFAudio'
19
20
 
20
- s.dependency "React-Core"
21
+ # Swift/Objective-C compatibility
22
+ s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
23
+
21
24
  s.dependency "livekit-react-native-webrtc"
22
25
 
23
- # Swift/Objective-C compatibility
24
- s.pod_target_xcconfig = {
25
- 'DEFINES_MODULE' => 'YES'
26
- }
26
+ # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
27
+ # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
28
+ if respond_to?(:install_modules_dependencies, true)
29
+ install_modules_dependencies(s)
30
+ else
31
+ s.dependency "React-Core"
32
+ # Don't install the dependencies when we run `pod install` in the old architecture.
33
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
34
+ s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
35
+ s.pod_target_xcconfig = {
36
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
37
+ "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
38
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
39
+ }
40
+ s.dependency "React-Codegen"
41
+ s.dependency "RCT-Folly"
42
+ s.dependency "RCTRequired"
43
+ s.dependency "RCTTypeSafety"
44
+ s.dependency "ReactCommon/turbomodule/core"
45
+ end
46
+ end
27
47
  end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livekit/react-native",
3
- "version": "2.5.1",
3
+ "version": "2.6.1",
4
4
  "description": "LiveKit for React Native",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -42,10 +42,10 @@
42
42
  "android"
43
43
  ],
44
44
  "dependencies": {
45
- "@livekit/components-react": "^2.0.6",
45
+ "@livekit/components-react": "^2.8.1",
46
46
  "array.prototype.at": "^1.1.1",
47
47
  "events": "^3.3.0",
48
- "livekit-client": "^2.7.5",
48
+ "livekit-client": "^2.9.0",
49
49
  "loglevel": "^1.8.0",
50
50
  "promise.allsettled": "^1.0.5",
51
51
  "react-native-url-polyfill": "^1.3.0",
@@ -57,7 +57,7 @@
57
57
  "@babel/preset-env": "^7.20.0",
58
58
  "@babel/runtime": "^7.20.0",
59
59
  "@commitlint/config-conventional": "^16.2.1",
60
- "@livekit/react-native-webrtc": "^125.0.7",
60
+ "@livekit/react-native-webrtc": "^125.0.8",
61
61
  "@react-native/babel-preset": "0.74.84",
62
62
  "@react-native/eslint-config": "0.74.84",
63
63
  "@react-native/metro-config": "0.74.84",
@@ -83,7 +83,7 @@
83
83
  "typescript": "5.0.4"
84
84
  },
85
85
  "peerDependencies": {
86
- "@livekit/react-native-webrtc": "^125.0.7",
86
+ "@livekit/react-native-webrtc": "^125.0.8",
87
87
  "react": "*",
88
88
  "react-native": "*"
89
89
  },
@@ -0,0 +1,19 @@
1
+ import { NativeModules, Platform } from 'react-native';
2
+ const LINKING_ERROR =
3
+ `The package '@livekit/react-native' doesn't seem to be linked. Make sure: \n\n` +
4
+ Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
5
+ '- You rebuilt the app after installing the package\n' +
6
+ '- You are not using Expo managed workflow\n';
7
+
8
+ const LiveKitModule = NativeModules.LivekitReactNativeModule
9
+ ? NativeModules.LivekitReactNativeModule
10
+ : new Proxy(
11
+ {},
12
+ {
13
+ get() {
14
+ throw new Error(LINKING_ERROR);
15
+ },
16
+ }
17
+ );
18
+
19
+ export default LiveKitModule;
@@ -1,20 +1,5 @@
1
- import { NativeModules, Platform } from 'react-native';
2
- const LINKING_ERROR =
3
- `The package '@livekit/react-native' doesn't seem to be linked. Make sure: \n\n` +
4
- Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
5
- '- You rebuilt the app after installing the package\n' +
6
- '- You are not using Expo managed workflow\n';
7
-
8
- const LivekitReactNative = NativeModules.LivekitReactNative
9
- ? NativeModules.LivekitReactNative
10
- : new Proxy(
11
- {},
12
- {
13
- get() {
14
- throw new Error(LINKING_ERROR);
15
- },
16
- }
17
- );
1
+ import { Platform } from 'react-native';
2
+ import LiveKitModule from '../LKNativeModule';
18
3
 
19
4
  /**
20
5
  * Configuration for the underlying AudioSession.
@@ -252,21 +237,21 @@ export default class AudioSession {
252
237
  * See also useIOSAudioManagement for automatic configuration of iOS audio options.
253
238
  */
254
239
  static configureAudio = async (config: AudioConfiguration) => {
255
- await LivekitReactNative.configureAudio(config);
240
+ await LiveKitModule.configureAudio(config);
256
241
  };
257
242
 
258
243
  /**
259
244
  * Starts an AudioSession.
260
245
  */
261
246
  static startAudioSession = async () => {
262
- await LivekitReactNative.startAudioSession();
247
+ await LiveKitModule.startAudioSession();
263
248
  };
264
249
 
265
250
  /**
266
251
  * Stops the existing AudioSession.
267
252
  */
268
253
  static stopAudioSession = async () => {
269
- await LivekitReactNative.stopAudioSession();
254
+ await LiveKitModule.stopAudioSession();
270
255
  };
271
256
 
272
257
  /**
@@ -297,7 +282,7 @@ export default class AudioSession {
297
282
  if (Platform.OS === 'ios') {
298
283
  return ['default', 'force_speaker'];
299
284
  } else if (Platform.OS === 'android') {
300
- return (await LivekitReactNative.getAudioOutputs()) as string[];
285
+ return (await LiveKitModule.getAudioOutputs()) as string[];
301
286
  } else {
302
287
  return [];
303
288
  }
@@ -311,7 +296,7 @@ export default class AudioSession {
311
296
  * @param deviceId A deviceId retrieved from {@link getAudioOutputs}
312
297
  */
313
298
  static selectAudioOutput = async (deviceId: string) => {
314
- await LivekitReactNative.selectAudioOutput(deviceId);
299
+ await LiveKitModule.selectAudioOutput(deviceId);
315
300
  };
316
301
 
317
302
  /**
@@ -321,7 +306,7 @@ export default class AudioSession {
321
306
  */
322
307
  static showAudioRoutePicker = async () => {
323
308
  if (Platform.OS === 'ios') {
324
- await LivekitReactNative.showAudioRoutePicker();
309
+ await LiveKitModule.showAudioRoutePicker();
325
310
  }
326
311
  };
327
312
 
@@ -335,7 +320,7 @@ export default class AudioSession {
335
320
  config: AppleAudioConfiguration
336
321
  ) => {
337
322
  if (Platform.OS === 'ios') {
338
- await LivekitReactNative.setAppleAudioConfiguration(config);
323
+ await LiveKitModule.setAppleAudioConfiguration(config);
339
324
  }
340
325
  };
341
326
  }
@@ -0,0 +1,252 @@
1
+ import {
2
+ type AgentState,
3
+ type TrackReferenceOrPlaceholder,
4
+ useMaybeTrackRefContext,
5
+ } from '@livekit/components-react';
6
+ import {
7
+ Animated,
8
+ StyleSheet,
9
+ View,
10
+ type ColorValue,
11
+ type DimensionValue,
12
+ type ViewStyle,
13
+ } from 'react-native';
14
+ import { useMultibandTrackVolume } from '../hooks';
15
+ import React, { useEffect, useRef, useState } from 'react';
16
+ export type BarVisualizerOptions = {
17
+ /** decimal values from 0 to 1 */
18
+ maxHeight?: number;
19
+ /** decimal values from 0 to 1 */
20
+ minHeight?: number;
21
+
22
+ barColor?: ColorValue;
23
+ barWidth?: DimensionValue;
24
+ barBorderRadius?: number;
25
+ };
26
+
27
+ const defaultBarOptions = {
28
+ maxHeight: 1,
29
+ minHeight: 0.2,
30
+ barColor: '#888888',
31
+ barWidth: 24,
32
+ barBorderRadius: 12,
33
+ } as const satisfies BarVisualizerOptions;
34
+
35
+ const sequencerIntervals = new Map<AgentState, number>([
36
+ ['connecting', 2000],
37
+ ['initializing', 2000],
38
+ ['listening', 500],
39
+ ['thinking', 150],
40
+ ]);
41
+
42
+ const getSequencerInterval = (
43
+ state: AgentState | undefined,
44
+ barCount: number
45
+ ): number | undefined => {
46
+ if (state === undefined) {
47
+ return 1000;
48
+ }
49
+ let interval = sequencerIntervals.get(state);
50
+ if (interval) {
51
+ switch (state) {
52
+ case 'connecting':
53
+ // case 'thinking':
54
+ interval /= barCount;
55
+ break;
56
+
57
+ default:
58
+ break;
59
+ }
60
+ }
61
+ return interval;
62
+ };
63
+ /**
64
+ * @beta
65
+ */
66
+ export interface BarVisualizerProps {
67
+ /** If set, the visualizer will transition between different voice assistant states */
68
+ state?: AgentState;
69
+ /** Number of bars that show up in the visualizer */
70
+ barCount?: number;
71
+ trackRef?: TrackReferenceOrPlaceholder;
72
+ options?: BarVisualizerOptions;
73
+ /**
74
+ * Custom React Native styles for the container.
75
+ */
76
+ style?: ViewStyle;
77
+ }
78
+
79
+ /**
80
+ * Visualizes audio signals from a TrackReference as bars.
81
+ * If the `state` prop is set, it automatically transitions between VoiceAssistant states.
82
+ * @beta
83
+ *
84
+ * @remarks For VoiceAssistant state transitions this component requires a voice assistant agent running with livekit-agents \>= 0.9.0
85
+ *
86
+ * @example
87
+ * ```tsx
88
+ * function SimpleVoiceAssistant() {
89
+ * const { state, audioTrack } = useVoiceAssistant();
90
+ * return (
91
+ * <BarVisualizer
92
+ * state={state}
93
+ * trackRef={audioTrack}
94
+ * />
95
+ * );
96
+ * }
97
+ * ```
98
+ */
99
+ export const BarVisualizer = ({
100
+ style = {},
101
+ state,
102
+ barCount = 5,
103
+ trackRef,
104
+ options,
105
+ }: BarVisualizerProps) => {
106
+ let trackReference = useMaybeTrackRefContext();
107
+
108
+ if (trackRef) {
109
+ trackReference = trackRef;
110
+ }
111
+
112
+ const opacityAnimations = useRef<Animated.Value[]>([]).current;
113
+ let magnitudes = useMultibandTrackVolume(trackReference, { bands: barCount });
114
+
115
+ let opts = { ...defaultBarOptions, ...options };
116
+
117
+ const highlightedIndices = useBarAnimator(
118
+ state,
119
+ barCount,
120
+ getSequencerInterval(state, barCount) ?? 100
121
+ );
122
+
123
+ useEffect(() => {
124
+ let animations = [];
125
+ for (let i = 0; i < barCount; i++) {
126
+ if (!opacityAnimations[i]) {
127
+ opacityAnimations[i] = new Animated.Value(0.3);
128
+ }
129
+ let targetOpacity = 0.3;
130
+ if (highlightedIndices.includes(i)) {
131
+ targetOpacity = 1;
132
+ }
133
+ animations.push(
134
+ Animated.timing(opacityAnimations[i], {
135
+ toValue: targetOpacity,
136
+ duration: 250,
137
+ useNativeDriver: true,
138
+ })
139
+ );
140
+ }
141
+
142
+ let parallel = Animated.parallel(animations);
143
+ parallel.start();
144
+ return () => {
145
+ parallel.stop();
146
+ };
147
+ }, [highlightedIndices, barCount, opacityAnimations]);
148
+
149
+ let bars: React.ReactNode[] = [];
150
+ magnitudes.forEach((value, index) => {
151
+ let coerced = Math.min(opts.maxHeight, Math.max(opts.minHeight, value));
152
+ let coercedPercent = Math.min(100, Math.max(0, coerced * 100 + 5));
153
+ let opacity = opacityAnimations[index] ?? new Animated.Value(0.3);
154
+ let barStyle = {
155
+ opacity: opacity,
156
+ backgroundColor: opts.barColor,
157
+ borderRadius: opts.barBorderRadius,
158
+ width: opts.barWidth,
159
+ };
160
+ bars.push(
161
+ <Animated.View
162
+ key={index}
163
+ style={[
164
+ { height: `${coercedPercent}%` },
165
+ barStyle,
166
+ styles.volumeIndicator,
167
+ ]}
168
+ />
169
+ );
170
+ });
171
+
172
+ return <View style={{ ...style, ...styles.container }}>{bars}</View>;
173
+ };
174
+ const styles = StyleSheet.create({
175
+ container: {
176
+ flexDirection: 'row',
177
+ alignItems: 'center',
178
+ justifyContent: 'space-evenly',
179
+ },
180
+ volumeIndicator: {
181
+ borderRadius: 12,
182
+ },
183
+ });
184
+
185
+ export const useBarAnimator = (
186
+ state: AgentState | undefined,
187
+ columns: number,
188
+ interval: number
189
+ ): number[] => {
190
+ const [index, setIndex] = useState(0);
191
+ const [sequence, setSequence] = useState<number[][]>([[]]);
192
+
193
+ useEffect(() => {
194
+ if (state === 'thinking') {
195
+ setSequence(generateListeningSequenceBar(columns));
196
+ } else if (state === 'connecting' || state === 'initializing') {
197
+ const seq = [...generateConnectingSequenceBar(columns)];
198
+ setSequence(seq);
199
+ } else if (state === 'listening') {
200
+ setSequence(generateListeningSequenceBar(columns));
201
+ } else if (state === undefined) {
202
+ // highlight everything
203
+ setSequence([new Array(columns).fill(0).map((_, idx) => idx)]);
204
+ } else {
205
+ setSequence([[]]);
206
+ }
207
+ setIndex(0);
208
+ }, [state, columns]);
209
+
210
+ const animationFrameId = useRef<number | null>(null);
211
+ useEffect(() => {
212
+ let startTime = performance.now();
213
+
214
+ const animate = (time: number) => {
215
+ const timeElapsed = time - startTime;
216
+
217
+ if (timeElapsed >= interval) {
218
+ setIndex((prev) => prev + 1);
219
+ startTime = time;
220
+ }
221
+
222
+ animationFrameId.current = requestAnimationFrame(animate);
223
+ };
224
+
225
+ animationFrameId.current = requestAnimationFrame(animate);
226
+
227
+ return () => {
228
+ if (animationFrameId.current !== null) {
229
+ cancelAnimationFrame(animationFrameId.current);
230
+ }
231
+ };
232
+ }, [interval, columns, state, sequence.length]);
233
+
234
+ return sequence[index % sequence.length];
235
+ };
236
+
237
+ const generateListeningSequenceBar = (columns: number): number[][] => {
238
+ const center = Math.floor(columns / 2);
239
+ const noIndex = -1;
240
+
241
+ return [[center], [noIndex]];
242
+ };
243
+
244
+ const generateConnectingSequenceBar = (columns: number): number[][] => {
245
+ const seq: number[][] = [[]];
246
+
247
+ for (let x = 0; x < columns; x++) {
248
+ seq.push([x, columns - 1 - x]);
249
+ }
250
+
251
+ return seq;
252
+ };