@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.
- package/README.md +4 -3
- package/android/build.gradle +2 -1
- package/android/src/main/java/com/livekit/reactnative/LiveKitReactNative.kt +61 -5
- package/android/src/main/java/com/livekit/reactnative/LivekitReactNativeModule.kt +81 -4
- package/android/src/main/java/com/livekit/reactnative/audio/events/Events.kt +6 -0
- package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioFormat.kt +2 -0
- package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioProcessingController.kt +27 -0
- package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioProcessorInterface.kt +52 -0
- package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioRecordSamplesDispatcher.kt +72 -0
- package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioSinkManager.kt +75 -0
- package/android/src/main/java/com/livekit/reactnative/audio/processing/CustomAudioProcessingFactory.kt +78 -0
- package/android/src/main/java/com/livekit/reactnative/audio/processing/MultibandVolumeProcessor.kt +181 -0
- package/android/src/main/java/com/livekit/reactnative/audio/processing/VolumeProcessor.kt +67 -0
- package/android/src/main/java/com/livekit/reactnative/audio/processing/fft/FFTAudioAnalyzer.kt +224 -0
- package/ios/AudioUtils.swift +49 -0
- package/ios/LKAudioProcessingAdapter.h +26 -0
- package/ios/LKAudioProcessingAdapter.m +117 -0
- package/ios/LKAudioProcessingManager.h +34 -0
- package/ios/LKAudioProcessingManager.m +63 -0
- package/ios/LiveKitReactNativeModule.swift +234 -0
- package/ios/LivekitReactNative-Bridging-Header.h +5 -2
- package/ios/LivekitReactNative.h +2 -6
- package/ios/LivekitReactNative.m +3 -166
- package/ios/LivekitReactNativeModule.m +42 -0
- package/ios/Logging.swift +4 -0
- package/ios/audio/AVAudioPCMBuffer.swift +136 -0
- package/ios/audio/AudioProcessing.swift +163 -0
- package/ios/audio/AudioRendererManager.swift +71 -0
- package/ios/audio/FFTProcessor.swift +147 -0
- package/ios/audio/MultibandVolumeAudioRenderer.swift +67 -0
- package/ios/audio/RingBuffer.swift +51 -0
- package/ios/audio/VolumeAudioRenderer.swift +50 -0
- package/lib/commonjs/LKNativeModule.js +18 -0
- package/lib/commonjs/LKNativeModule.js.map +1 -0
- package/lib/commonjs/audio/AudioSession.js +9 -17
- package/lib/commonjs/audio/AudioSession.js.map +1 -1
- package/lib/commonjs/components/BarVisualizer.js +192 -0
- package/lib/commonjs/components/BarVisualizer.js.map +1 -0
- package/lib/commonjs/events/EventEmitter.js +45 -0
- package/lib/commonjs/events/EventEmitter.js.map +1 -0
- package/lib/commonjs/hooks/useMultibandTrackVolume.js +64 -0
- package/lib/commonjs/hooks/useMultibandTrackVolume.js.map +1 -0
- package/lib/commonjs/hooks/useTrackVolume.js +45 -0
- package/lib/commonjs/hooks/useTrackVolume.js.map +1 -0
- package/lib/commonjs/hooks.js +24 -0
- package/lib/commonjs/hooks.js.map +1 -1
- package/lib/commonjs/index.js +14 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/LKNativeModule.js +12 -0
- package/lib/module/LKNativeModule.js.map +1 -0
- package/lib/module/audio/AudioSession.js +9 -17
- package/lib/module/audio/AudioSession.js.map +1 -1
- package/lib/module/components/BarVisualizer.js +182 -0
- package/lib/module/components/BarVisualizer.js.map +1 -0
- package/lib/module/events/EventEmitter.js +36 -0
- package/lib/module/events/EventEmitter.js.map +1 -0
- package/lib/module/hooks/useMultibandTrackVolume.js +58 -0
- package/lib/module/hooks/useMultibandTrackVolume.js.map +1 -0
- package/lib/module/hooks/useTrackVolume.js +39 -0
- package/lib/module/hooks/useTrackVolume.js.map +1 -0
- package/lib/module/hooks.js +2 -0
- package/lib/module/hooks.js.map +1 -1
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/lib/commonjs/LKNativeModule.d.ts +3 -0
- package/lib/typescript/lib/commonjs/components/BarVisualizer.d.ts +32 -0
- package/lib/typescript/lib/commonjs/events/EventEmitter.d.ts +4 -0
- package/lib/typescript/lib/commonjs/hooks/useMultibandTrackVolume.d.ts +8 -0
- package/lib/typescript/lib/commonjs/hooks/useTrackVolume.d.ts +8 -0
- package/lib/typescript/lib/module/LKNativeModule.d.ts +2 -0
- package/lib/typescript/lib/module/components/BarVisualizer.d.ts +10 -0
- package/lib/typescript/lib/module/events/EventEmitter.d.ts +3 -0
- package/lib/typescript/lib/module/hooks/useMultibandTrackVolume.d.ts +7 -0
- package/lib/typescript/lib/module/hooks/useTrackVolume.d.ts +7 -0
- package/lib/typescript/lib/module/hooks.d.ts +2 -0
- package/lib/typescript/lib/module/index.d.ts +1 -0
- package/lib/typescript/src/LKNativeModule.d.ts +2 -0
- package/lib/typescript/src/components/BarVisualizer.d.ts +49 -0
- package/lib/typescript/src/events/EventEmitter.d.ts +6 -0
- package/lib/typescript/src/hooks/useMultibandTrackVolume.d.ts +31 -0
- package/lib/typescript/src/hooks/useTrackVolume.d.ts +9 -0
- package/lib/typescript/src/hooks.d.ts +2 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/livekit-react-native.podspec +26 -6
- package/package.json +5 -5
- package/src/LKNativeModule.ts +19 -0
- package/src/audio/AudioSession.ts +9 -24
- package/src/components/BarVisualizer.tsx +252 -0
- package/src/events/EventEmitter.ts +51 -0
- package/src/hooks/useMultibandTrackVolume.ts +97 -0
- package/src/hooks/useTrackVolume.ts +62 -0
- package/src/hooks.ts +2 -0
- package/src/index.tsx +3 -0
- package/ios/AudioUtils.h +0 -9
- 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,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,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,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[];
|
|
@@ -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";
|
|
@@ -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
|
-
|
|
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
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
2
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
};
|