@livekit/react-native 2.5.1 → 2.6.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.
- 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/LKAudioProcessingAdapter.h +26 -0
- package/ios/LKAudioProcessingAdapter.m +117 -0
- package/ios/LKAudioProcessingManager.h +34 -0
- package/ios/LKAudioProcessingManager.m +63 -0
- package/ios/LivekitReactNative-Bridging-Header.h +2 -0
- package/ios/LivekitReactNative.h +9 -4
- package/ios/LivekitReactNative.m +83 -5
- 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 +72 -0
- package/ios/audio/FFTProcessor.swift +147 -0
- package/ios/audio/MultibandVolumeAudioRenderer.swift +65 -0
- package/ios/audio/RingBuffer.swift +51 -0
- package/ios/audio/VolumeAudioRenderer.swift +48 -0
- package/lib/commonjs/LKNativeModule.js +18 -0
- package/lib/commonjs/LKNativeModule.js.map +1 -0
- 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/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 +1 -1
- package/package.json +5 -5
- package/src/LKNativeModule.ts +19 -0
- 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
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { NativeEventEmitter, type EmitterSubscription } from 'react-native';
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter';
|
|
4
|
+
import LiveKitModule from '../LKNativeModule';
|
|
5
|
+
|
|
6
|
+
// This emitter is going to be used to listen to all the native events (once) and then
|
|
7
|
+
// re-emit them on a JS-only emitter.
|
|
8
|
+
const nativeEmitter = new NativeEventEmitter(LiveKitModule);
|
|
9
|
+
|
|
10
|
+
const NATIVE_EVENTS = ['LK_VOLUME_PROCESSED', 'LK_MULTIBAND_PROCESSED'];
|
|
11
|
+
|
|
12
|
+
const eventEmitter = new EventEmitter();
|
|
13
|
+
|
|
14
|
+
export function setupNativeEvents() {
|
|
15
|
+
for (const eventName of NATIVE_EVENTS) {
|
|
16
|
+
nativeEmitter.addListener(eventName, (...args) => {
|
|
17
|
+
eventEmitter.emit(eventName, ...args);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type EventHandler = (event: unknown) => void;
|
|
23
|
+
type Listener = unknown;
|
|
24
|
+
|
|
25
|
+
const _subscriptions: Map<Listener, EmitterSubscription[]> = new Map();
|
|
26
|
+
|
|
27
|
+
export function addListener(
|
|
28
|
+
listener: Listener,
|
|
29
|
+
eventName: string,
|
|
30
|
+
eventHandler: EventHandler
|
|
31
|
+
): void {
|
|
32
|
+
if (!NATIVE_EVENTS.includes(eventName)) {
|
|
33
|
+
throw new Error(`Invalid event: ${eventName}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!_subscriptions.has(listener)) {
|
|
37
|
+
_subscriptions.set(listener, []);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_subscriptions
|
|
41
|
+
.get(listener)
|
|
42
|
+
?.push(eventEmitter.addListener(eventName, eventHandler));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function removeListener(listener: Listener): void {
|
|
46
|
+
_subscriptions.get(listener)?.forEach((sub) => {
|
|
47
|
+
sub.remove();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
_subscriptions.delete(listener);
|
|
51
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type TrackReferenceOrPlaceholder } from '@livekit/components-react';
|
|
2
|
+
import {
|
|
3
|
+
Track,
|
|
4
|
+
type LocalAudioTrack,
|
|
5
|
+
type RemoteAudioTrack,
|
|
6
|
+
} from 'livekit-client';
|
|
7
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
8
|
+
import { addListener, removeListener } from '../events/EventEmitter';
|
|
9
|
+
import LiveKitModule from '../LKNativeModule';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Interface for configuring options for the useMultibandTrackVolume hook.
|
|
13
|
+
* @alpha
|
|
14
|
+
*/
|
|
15
|
+
export interface MultiBandTrackVolumeOptions {
|
|
16
|
+
/**
|
|
17
|
+
* the number of bands to split the audio into
|
|
18
|
+
*/
|
|
19
|
+
bands?: number;
|
|
20
|
+
/**
|
|
21
|
+
* cut off frequency on the lower end
|
|
22
|
+
*/
|
|
23
|
+
minFrequency?: number;
|
|
24
|
+
/**
|
|
25
|
+
* cut off frequency on the higher end
|
|
26
|
+
*/
|
|
27
|
+
maxFrequency?: number;
|
|
28
|
+
/**
|
|
29
|
+
* update should run every x ms
|
|
30
|
+
*/
|
|
31
|
+
updateInterval?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const multibandDefaults = {
|
|
35
|
+
bands: 5,
|
|
36
|
+
minFrequency: 1000,
|
|
37
|
+
maxFrequency: 8000,
|
|
38
|
+
updateInterval: 40,
|
|
39
|
+
} as const satisfies MultiBandTrackVolumeOptions;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A hook for tracking the volume of an audio track across multiple frequency bands.
|
|
43
|
+
*
|
|
44
|
+
* @param trackOrTrackReference
|
|
45
|
+
* @returns A number array containing the volume for each frequency band.
|
|
46
|
+
*/
|
|
47
|
+
export function useMultibandTrackVolume(
|
|
48
|
+
trackOrTrackReference?:
|
|
49
|
+
| LocalAudioTrack
|
|
50
|
+
| RemoteAudioTrack
|
|
51
|
+
| TrackReferenceOrPlaceholder,
|
|
52
|
+
options: MultiBandTrackVolumeOptions = {}
|
|
53
|
+
) {
|
|
54
|
+
const track =
|
|
55
|
+
trackOrTrackReference instanceof Track
|
|
56
|
+
? trackOrTrackReference
|
|
57
|
+
: <LocalAudioTrack | RemoteAudioTrack | undefined>(
|
|
58
|
+
trackOrTrackReference?.publication?.track
|
|
59
|
+
);
|
|
60
|
+
const opts = useMemo(() => {
|
|
61
|
+
return { ...multibandDefaults, ...options };
|
|
62
|
+
}, [options]);
|
|
63
|
+
const mediaStreamTrack = track?.mediaStreamTrack;
|
|
64
|
+
|
|
65
|
+
let [magnitudes, setMagnitudes] = useState<number[]>([]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
let listener = Object();
|
|
68
|
+
let reactTag: string | null = null;
|
|
69
|
+
if (mediaStreamTrack) {
|
|
70
|
+
reactTag = LiveKitModule.createMultibandVolumeProcessor(
|
|
71
|
+
opts,
|
|
72
|
+
mediaStreamTrack._peerConnectionId ?? -1,
|
|
73
|
+
mediaStreamTrack.id
|
|
74
|
+
);
|
|
75
|
+
addListener(listener, 'LK_MULTIBAND_PROCESSED', (event: any) => {
|
|
76
|
+
if (event.magnitudes && reactTag && event.id === reactTag) {
|
|
77
|
+
console.log('event received: ', event.magnitudes[0]);
|
|
78
|
+
setMagnitudes(event.magnitudes);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return () => {
|
|
83
|
+
if (mediaStreamTrack) {
|
|
84
|
+
removeListener(listener);
|
|
85
|
+
if (reactTag) {
|
|
86
|
+
LiveKitModule.deleteMultibandVolumeProcessor(
|
|
87
|
+
reactTag,
|
|
88
|
+
mediaStreamTrack._peerConnectionId ?? -1,
|
|
89
|
+
mediaStreamTrack.id
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}, [mediaStreamTrack, opts]);
|
|
95
|
+
|
|
96
|
+
return magnitudes;
|
|
97
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type TrackReferenceOrPlaceholder } from '@livekit/components-react';
|
|
2
|
+
import {
|
|
3
|
+
Track,
|
|
4
|
+
type LocalAudioTrack,
|
|
5
|
+
type RemoteAudioTrack,
|
|
6
|
+
} from 'livekit-client';
|
|
7
|
+
import { useEffect, useState } from 'react';
|
|
8
|
+
import { addListener, removeListener } from '../events/EventEmitter';
|
|
9
|
+
import LiveKitModule from '../LKNativeModule';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A hook for tracking the volume of an audio track.
|
|
13
|
+
*
|
|
14
|
+
* @param trackOrTrackReference
|
|
15
|
+
* @returns A number between 0-1 representing the volume.
|
|
16
|
+
*/
|
|
17
|
+
export function useTrackVolume(
|
|
18
|
+
trackOrTrackReference?:
|
|
19
|
+
| LocalAudioTrack
|
|
20
|
+
| RemoteAudioTrack
|
|
21
|
+
| TrackReferenceOrPlaceholder
|
|
22
|
+
) {
|
|
23
|
+
const track =
|
|
24
|
+
trackOrTrackReference instanceof Track
|
|
25
|
+
? trackOrTrackReference
|
|
26
|
+
: <LocalAudioTrack | RemoteAudioTrack | undefined>(
|
|
27
|
+
trackOrTrackReference?.publication?.track
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const mediaStreamTrack = track?.mediaStreamTrack;
|
|
31
|
+
|
|
32
|
+
let [volume, setVolume] = useState(0.0);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
let listener = Object();
|
|
35
|
+
let reactTag: string | null = null;
|
|
36
|
+
if (mediaStreamTrack) {
|
|
37
|
+
reactTag = LiveKitModule.createVolumeProcessor(
|
|
38
|
+
mediaStreamTrack._peerConnectionId ?? -1,
|
|
39
|
+
mediaStreamTrack.id
|
|
40
|
+
);
|
|
41
|
+
addListener(listener, 'LK_VOLUME_PROCESSED', (event: any) => {
|
|
42
|
+
if (event.volume && reactTag && event.id === reactTag) {
|
|
43
|
+
setVolume(event.volume);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return () => {
|
|
48
|
+
if (mediaStreamTrack) {
|
|
49
|
+
removeListener(listener);
|
|
50
|
+
if (reactTag) {
|
|
51
|
+
LiveKitModule.deleteVolumeProcessor(
|
|
52
|
+
reactTag,
|
|
53
|
+
mediaStreamTrack._peerConnectionId ?? -1,
|
|
54
|
+
mediaStreamTrack.id
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}, [mediaStreamTrack]);
|
|
60
|
+
|
|
61
|
+
return volume;
|
|
62
|
+
}
|
package/src/hooks.ts
CHANGED
|
@@ -42,4 +42,6 @@ export type {
|
|
|
42
42
|
|
|
43
43
|
export type { ReceivedDataMessage } from '@livekit/components-core';
|
|
44
44
|
export * from './hooks/useE2EEManager';
|
|
45
|
+
export * from './hooks/useTrackVolume';
|
|
46
|
+
export * from './hooks/useMultibandTrackVolume';
|
|
45
47
|
export type { UseRNE2EEManagerOptions } from './hooks/useE2EEManager';
|
package/src/index.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import { type LiveKitReactNativeInfo } from 'livekit-client';
|
|
|
17
17
|
import type { LogLevel, SetLogLevelOptions } from './logger';
|
|
18
18
|
import RNE2EEManager from './e2ee/RNE2EEManager';
|
|
19
19
|
import RNKeyProvider, { type RNKeyProviderOptions } from './e2ee/RNKeyProvider';
|
|
20
|
+
import { setupNativeEvents } from './events/EventEmitter';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Registers the required globals needed for LiveKit to work.
|
|
@@ -33,6 +34,7 @@ export function registerGlobals() {
|
|
|
33
34
|
shimArrayAt();
|
|
34
35
|
shimAsyncIterator();
|
|
35
36
|
shimIterator();
|
|
37
|
+
setupNativeEvents();
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -99,6 +101,7 @@ function shimIterator() {
|
|
|
99
101
|
shim();
|
|
100
102
|
}
|
|
101
103
|
export * from './hooks';
|
|
104
|
+
export * from './components/BarVisualizer';
|
|
102
105
|
export * from './components/LiveKitRoom';
|
|
103
106
|
export * from './components/VideoTrack';
|
|
104
107
|
export * from './components/VideoView'; // deprecated
|