@revrag-ai/embed-react-native 1.0.5 → 1.0.6

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 (75) hide show
  1. package/dist/commonjs/Event/onwid.js +70 -0
  2. package/dist/commonjs/NativeOnwid.js +5 -0
  3. package/dist/commonjs/button.json +1 -0
  4. package/dist/commonjs/component/OnwidButton.js +507 -0
  5. package/dist/commonjs/component/audiowave.js +153 -0
  6. package/dist/commonjs/component/voice.js +127 -0
  7. package/dist/commonjs/hooks/initialize.js +96 -0
  8. package/dist/commonjs/hooks/initialize.types.js +2 -0
  9. package/{lib/module → dist/commonjs}/hooks/initializelivekit.js +7 -4
  10. package/dist/commonjs/hooks/voiceAgent.js +353 -0
  11. package/dist/commonjs/hooks/voiceAgent.types.js +4 -0
  12. package/dist/commonjs/index.d.js +22 -0
  13. package/dist/commonjs/index.js +34 -0
  14. package/dist/commonjs/onwidApi/api.js +185 -0
  15. package/dist/commonjs/onwidApi/api.types.js +2 -0
  16. package/dist/commonjs/package.json +1 -0
  17. package/dist/commonjs/store.key.js +38 -0
  18. package/dist/commonjs/style/onwidButton.style.js +243 -0
  19. package/dist/commonjs/utils/reanimatedHelpers.js +94 -0
  20. package/dist/commonjs/utils/utils.js +2 -0
  21. package/dist/module/Event/onwid.js +70 -0
  22. package/dist/module/NativeOnwid.js +5 -0
  23. package/dist/module/button.json +1 -0
  24. package/dist/module/component/OnwidButton.js +507 -0
  25. package/dist/module/component/audiowave.js +153 -0
  26. package/dist/module/component/voice.js +127 -0
  27. package/dist/module/hooks/initialize.js +96 -0
  28. package/dist/module/hooks/initialize.types.js +2 -0
  29. package/dist/module/hooks/initializelivekit.js +17 -0
  30. package/dist/module/hooks/voiceAgent.js +353 -0
  31. package/dist/module/hooks/voiceAgent.types.js +4 -0
  32. package/dist/module/index.d.js +22 -0
  33. package/dist/module/index.js +34 -0
  34. package/dist/module/onwidApi/api.js +185 -0
  35. package/dist/module/onwidApi/api.types.js +2 -0
  36. package/dist/module/store.key.js +38 -0
  37. package/dist/module/style/onwidButton.style.js +243 -0
  38. package/dist/module/utils/reanimatedHelpers.js +94 -0
  39. package/dist/module/utils/utils.js +2 -0
  40. package/{lib → dist}/typescript/Event/onwid.d.ts +1 -0
  41. package/{lib → dist}/typescript/NativeOnwid.d.ts +1 -0
  42. package/{lib → dist}/typescript/component/OnwidButton.d.ts +1 -0
  43. package/{lib → dist}/typescript/component/audiowave.d.ts +1 -0
  44. package/{lib → dist}/typescript/component/voice.d.ts +1 -0
  45. package/{lib → dist}/typescript/hooks/initialize.d.ts +1 -0
  46. package/{lib → dist}/typescript/hooks/initialize.types.d.ts +1 -0
  47. package/{lib → dist}/typescript/hooks/initializelivekit.d.ts +1 -0
  48. package/{lib → dist}/typescript/hooks/voiceAgent.d.ts +1 -0
  49. package/{lib → dist}/typescript/hooks/voiceAgent.types.d.ts +1 -0
  50. package/{lib → dist}/typescript/index.d.ts +3 -3
  51. package/{lib → dist}/typescript/onwidApi/api.d.ts +1 -0
  52. package/{lib → dist}/typescript/onwidApi/api.types.d.ts +1 -0
  53. package/{lib → dist}/typescript/store.key.d.ts +1 -0
  54. package/{lib → dist}/typescript/style/onwidButton.style.d.ts +1 -0
  55. package/{lib → dist}/typescript/utils/reanimatedHelpers.d.ts +1 -0
  56. package/dist/typescript/utils/utils.d.ts +1 -0
  57. package/package.json +29 -21
  58. package/lib/index.d.ts +0 -77
  59. package/lib/module/Event/onwid.js +0 -74
  60. package/lib/module/NativeOnwid.js +0 -4
  61. package/lib/module/component/OnwidButton.js +0 -366
  62. package/lib/module/component/audiowave.js +0 -137
  63. package/lib/module/component/voice.js +0 -103
  64. package/lib/module/hooks/initialize.js +0 -92
  65. package/lib/module/hooks/initialize.types.js +0 -2
  66. package/lib/module/hooks/voiceAgent.js +0 -334
  67. package/lib/module/hooks/voiceAgent.types.js +0 -2
  68. package/lib/module/index.js +0 -61
  69. package/lib/module/onwidApi/api.js +0 -184
  70. package/lib/module/onwidApi/api.types.js +0 -2
  71. package/lib/module/store.key.js +0 -47
  72. package/lib/module/style/onwidButton.style.js +0 -230
  73. package/lib/module/utils/reanimatedHelpers.js +0 -87
  74. package/lib/module/utils/utils.js +0 -1
  75. package/lib/typescript/utils/utils.d.ts +0 -0
@@ -0,0 +1,507 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * @file OnwidButton.tsx
5
+ * @description A customizable floating action button component for React Native applications.
6
+ * This component provides a draggable, expandable button with animation support and gradient styling.
7
+ */
8
+
9
+ import LottieView from 'lottie-react-native';
10
+ import { useEffect, useMemo, useRef, useState } from 'react';
11
+ import { Dimensions, Image, Text, TouchableOpacity, View } from 'react-native';
12
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
13
+ import LinearGradient from 'react-native-linear-gradient';
14
+ import Voice from '../component/voice';
15
+ import { useVoiceAgent } from '../hooks/voiceAgent';
16
+ import { getAgentData } from '../store.key';
17
+ import { createOnwidButtonStyles } from '../style/onwidButton.style';
18
+ import { getReanimatedAPI, showReanimatedSetupError } from '../utils/reanimatedHelpers';
19
+ import { WaveformVisualizer } from './audiowave';
20
+
21
+ // Get reanimated API with fallbacks
22
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
23
+ const {
24
+ useSharedValue,
25
+ useAnimatedStyle,
26
+ withTiming,
27
+ withSpring,
28
+ withRepeat,
29
+ withSequence,
30
+ runOnJS,
31
+ Easing,
32
+ Animated,
33
+ isAvailable: isReanimatedAvailable
34
+ } = getReanimatedAPI();
35
+
36
+ // Show warning if reanimated is not available
37
+ if (!isReanimatedAvailable) {
38
+ showReanimatedSetupError();
39
+ }
40
+ const {
41
+ width: SCREEN_WIDTH,
42
+ height: SCREEN_HEIGHT
43
+ } = Dimensions.get('window');
44
+ const BUTTON_WIDTH = 60;
45
+ const EXPANDED_WIDTH = SCREEN_WIDTH * 0.9;
46
+ const BUTTON_HEIGHT = 60;
47
+ const GRADIENT_COLORS = ['#1E0844', '#B391F3'];
48
+
49
+ // Define mic icons as base64 images for portability
50
+ const MIC_ON_ICON = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/Mute+button.png';
51
+ const MIC_OFF_ICON = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/unmute.png';
52
+
53
+ // Add end call icon
54
+ const END_CALL_ICON = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/end+button.png';
55
+ const AMPLIFY_ANIMATION = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/amplify.json';
56
+
57
+ /**
58
+ * Default styles configuration for the button
59
+ */
60
+ const defaultStyles = {
61
+ buttonWidth: 60,
62
+ buttonHeight: 60,
63
+ borderRadius: 100,
64
+ marginBottom: 20,
65
+ spacing: {
66
+ SMALL: 10,
67
+ MEDIUM: 15,
68
+ LARGE: 25
69
+ }
70
+ };
71
+
72
+ /**
73
+ * OnwidButton Component
74
+ *
75
+ * A floating action button that can be dragged around the screen and expanded to show additional content.
76
+ * Features include:
77
+ * - Draggable functionality
78
+ * - Expandable menu
79
+ * - Animated transitions
80
+ * - Gradient background
81
+ * - Customizable styling
82
+ *
83
+ * @component
84
+ * @example
85
+ * ```tsx
86
+ * <OnwidButton
87
+ * isOpen={false}
88
+ * onPress={(isOpen) => console.log('Button pressed:', isOpen)}
89
+ * menuComponent={<YourMenuComponent />}
90
+ * />
91
+ * ```
92
+ */
93
+ export function OnwidButton() {
94
+ const {
95
+ initializeVoiceAgent,
96
+ tokenDetails,
97
+ endCall,
98
+ isLoading,
99
+ isMicMuted,
100
+ muteMic,
101
+ unmuteMic,
102
+ connectionState,
103
+ roomRef
104
+ } = useVoiceAgent();
105
+ // State management
106
+ const [configData, setConfigData] = useState(null);
107
+ const [isOpen, setIsOpen] = useState(false);
108
+ const [callDuration, setCallDuration] = useState(0);
109
+ const timerRef = useRef(null);
110
+ const lottieRef = useRef(null);
111
+ console.log('roomRef', roomRef.current?.localParticipant);
112
+
113
+ // Animation values
114
+ const isPressed = useSharedValue(false);
115
+ const offset = useSharedValue({
116
+ x: 0,
117
+ y: 0
118
+ });
119
+ const start = useSharedValue({
120
+ x: 0,
121
+ y: 0
122
+ });
123
+ const menuAnimation = useSharedValue(0);
124
+ const buttonWidth = useSharedValue(BUTTON_WIDTH);
125
+ const buttonScale = useSharedValue(1);
126
+ // Styles
127
+ const styles = createOnwidButtonStyles(defaultStyles);
128
+ const [isAutoOpen, setIsAutoOpen] = useState(false);
129
+ useEffect(() => {
130
+ const autoOpenTimer = setTimeout(() => {
131
+ if (!isOpen) {
132
+ console.log('autoOpenTimer', isOpen);
133
+ setIsAutoOpen(true);
134
+ }
135
+ }, 15000); // 15 seconds
136
+
137
+ return () => {
138
+ clearTimeout(autoOpenTimer);
139
+ };
140
+ }, [isOpen]);
141
+
142
+ /**
143
+ * Fetch agent configuration data
144
+ */
145
+ useEffect(() => {
146
+ const fetchAgentData = async () => {
147
+ try {
148
+ const data = await getAgentData('@config_data');
149
+ setConfigData(data?.ui_config);
150
+ } catch (error) {
151
+ console.error('Error retrieving agent data:', error);
152
+ }
153
+ };
154
+ fetchAgentData();
155
+ }, []);
156
+
157
+ /**
158
+ * Set up a timer to track call duration when connected
159
+ */
160
+ useEffect(() => {
161
+ if (connectionState === 'connected' && !timerRef.current) {
162
+ timerRef.current = setInterval(() => {
163
+ setCallDuration(prev => prev + 1);
164
+ }, 1000);
165
+ } else if (connectionState !== 'connected' && timerRef.current) {
166
+ clearInterval(timerRef.current);
167
+ timerRef.current = null;
168
+
169
+ // If we were previously connected and now disconnected, show an error
170
+ if (callDuration > 0) {
171
+ console.log('Call unexpectedly disconnected after', callDuration, 'seconds');
172
+ }
173
+ }
174
+ return () => {
175
+ if (timerRef.current) {
176
+ clearInterval(timerRef.current);
177
+ timerRef.current = null;
178
+ }
179
+ };
180
+ }, [connectionState, callDuration]);
181
+
182
+ /**
183
+ * Handle menu animation and button width transitions
184
+ */
185
+ useEffect(() => {
186
+ menuAnimation.value = withTiming(isOpen ? 0.8 : 0, {
187
+ duration: 300
188
+ });
189
+ buttonWidth.value = withTiming(isOpen ? EXPANDED_WIDTH : BUTTON_WIDTH);
190
+ }, [isOpen, menuAnimation, buttonWidth]);
191
+
192
+ // Add breathing animation when button is closed but isAutoOpen is true
193
+ useEffect(() => {
194
+ if (!isOpen && isAutoOpen) {
195
+ // Start breathing animation with faster speed
196
+ buttonScale.value = withRepeat(withSequence(withTiming(1.1, {
197
+ duration: 1500,
198
+ // Reduced from 1000ms to 600ms
199
+ easing: Easing.inOut(Easing.ease)
200
+ }), withTiming(1, {
201
+ duration: 1500,
202
+ // Reduced from 1000ms to 600ms
203
+ easing: Easing.inOut(Easing.ease)
204
+ })), -1,
205
+ // Infinite repeat
206
+ false // Don't reverse
207
+ );
208
+ } else {
209
+ // Reset animation
210
+ buttonScale.value = withTiming(1, {
211
+ duration: 300
212
+ });
213
+ }
214
+ }, [isOpen, isAutoOpen]);
215
+
216
+ /**
217
+ * Animated styles for the button
218
+ */
219
+ const animatedStyles = useAnimatedStyle(() => {
220
+ const maxX = SCREEN_WIDTH - (isOpen ? EXPANDED_WIDTH : BUTTON_WIDTH) - 35;
221
+ const clampedX = Math.min(Math.max(offset.value.x, -maxX), 0);
222
+ return {
223
+ width: buttonWidth.value,
224
+ height: BUTTON_HEIGHT,
225
+ transform: [{
226
+ translateX: clampedX
227
+ }, {
228
+ translateY: offset.value.y
229
+ }, {
230
+ scale: withSpring(isPressed.value ? 0.95 : buttonScale.value)
231
+ }],
232
+ justifyContent: isOpen ? 'space-between' : 'flex-start',
233
+ overflow: 'hidden'
234
+ };
235
+ });
236
+
237
+ /**
238
+ * Animated styles for the text
239
+ */
240
+ const animatedTextStyles = useAnimatedStyle(() => {
241
+ const maxX = SCREEN_WIDTH;
242
+ const clampedX = Math.min(Math.max(offset.value.x, -maxX), 0);
243
+ return {
244
+ transform: [{
245
+ translateX: clampedX
246
+ }, {
247
+ translateY: offset.value.y
248
+ }, {
249
+ scale: withSpring(isPressed.value ? 1 : 1)
250
+ }]
251
+ };
252
+ });
253
+
254
+ /**
255
+ * Pan gesture handler for drag functionality
256
+ */
257
+ const gesture = Gesture.Pan().onBegin(() => {
258
+ isPressed.value = true;
259
+ if (isAutoOpen) {
260
+ runOnJS(setIsAutoOpen)(false);
261
+ }
262
+ }).onUpdate(e => {
263
+ const maxX = SCREEN_WIDTH - (isOpen ? EXPANDED_WIDTH : BUTTON_WIDTH) - 0;
264
+ const newX = Math.min(Math.max(e.translationX + start.value.x, -maxX), 0);
265
+ const maxY = SCREEN_HEIGHT - 150;
266
+ const newY = Math.min(Math.max(e.translationY + start.value.y, -maxY), 0);
267
+ offset.value = {
268
+ x: newX,
269
+ y: newY
270
+ };
271
+ }).onEnd(() => {
272
+ start.value = {
273
+ x: offset.value.x,
274
+ y: offset.value.y
275
+ };
276
+ }).onFinalize(() => {
277
+ isPressed.value = false;
278
+ });
279
+
280
+ /**
281
+ * Handle button press events
282
+ */
283
+ const handlePress = () => {
284
+ console.log('handlePress', isOpen);
285
+ console.log('isAutoOpen', isAutoOpen);
286
+ // cleanup();
287
+ setIsOpen(!isOpen);
288
+ setIsAutoOpen(false);
289
+ };
290
+ const handleStartCall = async () => {
291
+ setCallDuration(0);
292
+ await initializeVoiceAgent();
293
+ };
294
+
295
+ /**
296
+ * Render the button icon/animation
297
+ */
298
+
299
+ const remoteSource = useMemo(() => ({
300
+ uri: configData?.icon_animation || AMPLIFY_ANIMATION
301
+ }), [configData?.icon_animation]);
302
+ const renderIcon = () => {
303
+ // When isAutoOpen is true, we don't play the Lottie animation
304
+ return /*#__PURE__*/_jsx(View, {
305
+ children: /*#__PURE__*/_jsx(LottieView, {
306
+ ref: lottieRef,
307
+ source: remoteSource,
308
+ autoPlay: true,
309
+ loop: true,
310
+ style: styles.iconImage,
311
+ enableMergePathsAndroidForKitKatAndAbove: true,
312
+ enableSafeModeAndroid: true
313
+ })
314
+ });
315
+ };
316
+ const handleConnected = () => {
317
+ console.log('Call connected');
318
+ };
319
+ const handleEndCall = async () => {
320
+ setIsOpen(false);
321
+ if (timerRef.current) {
322
+ clearInterval(timerRef.current);
323
+ timerRef.current = null;
324
+ }
325
+ setCallDuration(0);
326
+ await endCall();
327
+ };
328
+ const handleMicToggle = () => {
329
+ if (isMicMuted) {
330
+ unmuteMic();
331
+ } else {
332
+ muteMic();
333
+ }
334
+ };
335
+
336
+ // Format duration to MM:SS
337
+ const formatDuration = seconds => {
338
+ const minutes = Math.floor(seconds / 60);
339
+ const remainingSeconds = seconds % 60;
340
+ return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
341
+ };
342
+
343
+ // Get the status text based on current state
344
+ const getStatusText = () => {
345
+ if (isLoading) {
346
+ return 'Connecting...';
347
+ } else if (tokenDetails?.token) {
348
+ return `${formatDuration(callDuration)}`;
349
+ } else {
350
+ return configData?.agent_type || 'Onboarding Agent';
351
+ }
352
+ };
353
+ if (!configData) return null;
354
+ return /*#__PURE__*/_jsxs(View, {
355
+ style: styles.container,
356
+ children: [isAutoOpen && !isOpen && /*#__PURE__*/_jsx(Animated.View, {
357
+ style: [animatedTextStyles, {
358
+ position: 'absolute',
359
+ borderRadius: 5,
360
+ paddingVertical: 2,
361
+ paddingHorizontal: 10,
362
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
363
+ bottom: BUTTON_HEIGHT + 40
364
+ // right: Math.abs(offset.value.x) + BUTTON_WIDTH + 0,
365
+ }],
366
+ children: /*#__PURE__*/_jsx(Text, {
367
+ style: {
368
+ color: 'white',
369
+ fontSize: 10,
370
+ fontWeight: '500'
371
+ },
372
+ children: configData?.popup_description || 'Revrag'
373
+ })
374
+ }), /*#__PURE__*/_jsx(GestureDetector, {
375
+ gesture: gesture,
376
+ children: /*#__PURE__*/_jsx(Animated.View, {
377
+ style: [styles.button, animatedStyles, styles.buttonContent, {
378
+ pointerEvents: 'auto'
379
+ }],
380
+ children: /*#__PURE__*/_jsxs(LinearGradient, {
381
+ colors: configData?.gradient || GRADIENT_COLORS,
382
+ start: {
383
+ x: 0,
384
+ y: 0
385
+ },
386
+ end: {
387
+ x: 1,
388
+ y: 0
389
+ },
390
+ style: [styles.linearGradient, {
391
+ width: '100%',
392
+ flexDirection: 'row',
393
+ alignItems: 'center',
394
+ paddingHorizontal: 0,
395
+ paddingLeft: 0,
396
+ paddingRight: isOpen ? 5 : 0
397
+ }],
398
+ angle: 0,
399
+ angleCenter: {
400
+ x: 0.5,
401
+ y: 0.5
402
+ },
403
+ children: [tokenDetails?.token && /*#__PURE__*/_jsx(Voice, {
404
+ url: tokenDetails?.server_url,
405
+ token: tokenDetails?.token,
406
+ onDisconnected: handleEndCall,
407
+ roomRef: roomRef,
408
+ onConnected: handleConnected
409
+ }), /*#__PURE__*/_jsx(View, {
410
+ style: [styles.rowContainer, {
411
+ flexShrink: 0,
412
+ width: BUTTON_WIDTH,
413
+ padding: 0,
414
+ margin: 0
415
+ }],
416
+ children: /*#__PURE__*/_jsx(TouchableOpacity, {
417
+ onPress: handlePress,
418
+ style: styles.pressable,
419
+ children: renderIcon()
420
+ })
421
+ }), isOpen && /*#__PURE__*/_jsxs(View, {
422
+ style: {
423
+ flex: 1,
424
+ flexDirection: 'row',
425
+ height: BUTTON_HEIGHT - 10,
426
+ marginLeft: 0,
427
+ marginRight: 0
428
+ },
429
+ children: [/*#__PURE__*/_jsxs(View, {
430
+ style: {
431
+ flex: 1,
432
+ justifyContent: 'center',
433
+ alignItems: 'flex-start',
434
+ paddingLeft: 8,
435
+ paddingRight: 4
436
+ },
437
+ children: [/*#__PURE__*/_jsx(Text, {
438
+ style: [styles.agentNameText, {
439
+ flexShrink: 1,
440
+ textAlign: 'left'
441
+ }],
442
+ children: configData?.agent_name || 'Revrag'
443
+ }), /*#__PURE__*/_jsx(Text, {
444
+ style: [styles.statusText, {
445
+ flexShrink: 1,
446
+ textAlign: 'left'
447
+ }],
448
+ children: getStatusText()
449
+ })]
450
+ }), /*#__PURE__*/_jsx(View, {
451
+ style: {
452
+ flex: 1,
453
+ justifyContent: 'center',
454
+ alignItems: 'center',
455
+ paddingHorizontal: 4
456
+ },
457
+ children: /*#__PURE__*/_jsx(WaveformVisualizer, {
458
+ roomRef: roomRef
459
+ })
460
+ }), /*#__PURE__*/_jsxs(View, {
461
+ style: {
462
+ flex: 1,
463
+ justifyContent: 'center',
464
+ alignItems: 'flex-end',
465
+ paddingLeft: 4,
466
+ paddingRight: 8
467
+ },
468
+ children: [!tokenDetails?.token && /*#__PURE__*/_jsx(TouchableOpacity, {
469
+ onPress: handleStartCall,
470
+ style: styles.startCallButton,
471
+ children: /*#__PURE__*/_jsx(Text, {
472
+ style: styles.startCallText,
473
+ children: configData?.start_call_text || 'Start Call'
474
+ })
475
+ }), tokenDetails?.token && /*#__PURE__*/_jsxs(View, {
476
+ style: [styles.buttonContainer],
477
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
478
+ style: styles.muteButton,
479
+ onPress: handleMicToggle,
480
+ children: /*#__PURE__*/_jsx(Image, {
481
+ source: {
482
+ uri: isMicMuted ? MIC_OFF_ICON : MIC_ON_ICON
483
+ },
484
+ style: styles.buttonImage
485
+ })
486
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
487
+ onPress: handleEndCall,
488
+ style: styles.endCallButton,
489
+ children: /*#__PURE__*/_jsx(Image, {
490
+ source: {
491
+ uri: END_CALL_ICON
492
+ },
493
+ style: styles.buttonImage
494
+ })
495
+ })]
496
+ })]
497
+ })]
498
+ })]
499
+ })
500
+ })
501
+ })]
502
+ });
503
+ }
504
+
505
+ // Export default for easier imports
506
+ export default OnwidButton;
507
+ //# sourceMappingURL=OnwidButton.js.map
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { View, Animated } from 'react-native';
5
+ import { jsx as _jsx } from "react/jsx-runtime";
6
+ // React Native compatible waveform simulator
7
+ const useReactNativeAudioWaveform = roomRef => {
8
+ const [isAudioActive, setIsAudioActive] = useState(false);
9
+ const intervalRef = useRef(null);
10
+ const [currentHeights, setCurrentHeights] = useState(Array(10).fill(0));
11
+
12
+ // Create animated values for each bar
13
+ const barCount = 10;
14
+ const animatedBars = useRef(Array(barCount).fill(0).map(() => new Animated.Value(0))).current;
15
+ useEffect(() => {
16
+ // Check if there's an active room connection AND if agent is talking
17
+ const checkAudioActivity = () => {
18
+ const room = roomRef.current;
19
+ if (room?.state !== 'connected') {
20
+ setIsAudioActive(false);
21
+ return;
22
+ }
23
+
24
+ // Check if any remote participant is currently speaking
25
+ let isAgentSpeaking = false;
26
+
27
+ // Loop through all remote participants
28
+ room.remoteParticipants.forEach(participant => {
29
+ const audioTrackPublications = Array.from(participant.getTrackPublications().values());
30
+ const remoteAudioTrack = audioTrackPublications.find(pub => pub.track?.kind === 'audio');
31
+
32
+ // Check if this participant has audio track, is not muted, and is actively speaking
33
+ if (remoteAudioTrack?.track && !remoteAudioTrack?.isMuted) {
34
+ // Check audio level to detect actual speech (optional but more accurate)
35
+ const audioLevel = participant.audioLevel || 0;
36
+ if (audioLevel > 0.05) {
37
+ // Threshold for detecting speech
38
+ isAgentSpeaking = true;
39
+ }
40
+ }
41
+ });
42
+ setIsAudioActive(isAgentSpeaking);
43
+ };
44
+
45
+ // Initial check
46
+ checkAudioActivity();
47
+
48
+ // Set up periodic checking for room state changes
49
+ intervalRef.current = setInterval(checkAudioActivity, 500);
50
+
51
+ // Clean up on unmount
52
+ return () => {
53
+ if (intervalRef.current) {
54
+ clearInterval(intervalRef.current);
55
+ intervalRef.current = null;
56
+ }
57
+ setIsAudioActive(false);
58
+ };
59
+ }, [roomRef]);
60
+
61
+ // Continuous smooth animation
62
+ useEffect(() => {
63
+ const animateWaveform = () => {
64
+ // Generate smooth waveform data - stop animation completely when not active
65
+ const targetHeights = isAudioActive ? Array(barCount).fill(0).map((_, index) => {
66
+ const timeOffset = Date.now() / 800 + index * 0.3;
67
+ const baseHeight = 0.5;
68
+ const amplitude = 0.5;
69
+ const height = baseHeight + amplitude * Math.abs(Math.sin(timeOffset));
70
+ return Math.max(0.1, Math.min(1.0, height));
71
+ }) : Array(barCount).fill(0); // Completely freeze animation when mic is muted
72
+
73
+ // Update current heights for conditional logic
74
+ setCurrentHeights(targetHeights);
75
+ const animations = animatedBars.map((animatedValue, index) => {
76
+ const targetHeight = targetHeights[index] || 0;
77
+ return Animated.timing(animatedValue, {
78
+ toValue: targetHeight,
79
+ duration: isAudioActive ? 400 : 600,
80
+ // Slower fade out when going inactive
81
+ useNativeDriver: false
82
+ });
83
+ });
84
+ Animated.parallel(animations).start();
85
+ };
86
+
87
+ // Start animation immediately and repeat
88
+ animateWaveform();
89
+ const animationInterval = setInterval(animateWaveform, 300);
90
+ return () => {
91
+ clearInterval(animationInterval);
92
+ };
93
+ }, [isAudioActive, animatedBars]);
94
+ return {
95
+ animatedBars,
96
+ currentHeights,
97
+ isActive: isAudioActive
98
+ };
99
+ };
100
+ export const WaveformVisualizer = ({
101
+ roomRef
102
+ }) => {
103
+ const {
104
+ animatedBars,
105
+ currentHeights,
106
+ isActive
107
+ } = useReactNativeAudioWaveform(roomRef);
108
+ console.log('animatedBars', animatedBars);
109
+ return /*#__PURE__*/_jsx(View, {
110
+ style: {
111
+ flexDirection: 'row',
112
+ alignItems: 'center',
113
+ height: '100%',
114
+ alignSelf: 'center',
115
+ // width: '100%',
116
+ // flex: 1,
117
+ justifyContent: 'center',
118
+ // position: 'absolute',
119
+ zIndex: 1000
120
+ },
121
+ children: animatedBars.map((animatedHeight, idx) => {
122
+ // Use the tracked height values instead of trying to access animated value directly
123
+ const currentHeightValue = currentHeights[idx] || 0.1;
124
+
125
+ // Apply conditional logic based on height
126
+ let conditionalValue;
127
+ if (currentHeightValue > 0.7) {
128
+ conditionalValue = 1;
129
+ } else if (currentHeightValue >= 0.4 && currentHeightValue <= 0.5) {
130
+ conditionalValue = 5;
131
+ } else {
132
+ conditionalValue = 1;
133
+ }
134
+
135
+ // You can use conditionalValue for width, color, or other properties
136
+ return /*#__PURE__*/_jsx(Animated.View, {
137
+ style: {
138
+ width: conditionalValue === 10 ? 4 : 4,
139
+ borderRadius: 100,
140
+ // Example: wider bars for value 5
141
+ height: animatedHeight.interpolate({
142
+ inputRange: [0, 1],
143
+ outputRange: [0, 25]
144
+ }),
145
+ alignSelf: 'center',
146
+ backgroundColor: idx <= 1 || idx >= 8 ? 'rgba(255, 255, 255, 0.5)' : 'white',
147
+ margin: 1.5
148
+ }
149
+ }, idx);
150
+ })
151
+ });
152
+ };
153
+ //# sourceMappingURL=audiowave.js.map