@livekit/react-native 2.7.6 → 2.9.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 +56 -7
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/livekit/reactnative/LivekitReactNativeModule.kt +31 -2
- package/android/src/main/java/com/livekit/reactnative/audio/events/Events.kt +1 -0
- package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioSinkProcessor.kt +57 -0
- package/ios/LiveKitReactNativeModule.swift +21 -1
- package/ios/LivekitReactNativeModule.m +6 -0
- package/ios/audio/AudioSinkRenderer.swift +51 -0
- package/lib/commonjs/audio/MediaRecorder.js +132 -0
- package/lib/commonjs/audio/MediaRecorder.js.map +1 -0
- package/lib/commonjs/components/BarVisualizer.js +2 -5
- package/lib/commonjs/components/BarVisualizer.js.map +1 -1
- package/lib/commonjs/components/LiveKitRoom.js.map +1 -1
- package/lib/commonjs/components/VideoTrack.js.map +1 -1
- package/lib/commonjs/events/EventEmitter.js +1 -1
- package/lib/commonjs/events/EventEmitter.js.map +1 -1
- package/lib/commonjs/index.js +2 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/polyfills/MediaRecorderShim.js +13 -0
- package/lib/commonjs/polyfills/MediaRecorderShim.js.map +1 -0
- package/lib/module/audio/MediaRecorder.js +125 -0
- package/lib/module/audio/MediaRecorder.js.map +1 -0
- package/lib/module/components/BarVisualizer.js +2 -5
- package/lib/module/components/BarVisualizer.js.map +1 -1
- package/lib/module/components/LiveKitRoom.js.map +1 -1
- package/lib/module/components/VideoTrack.js.map +1 -1
- package/lib/module/events/EventEmitter.js +1 -1
- package/lib/module/events/EventEmitter.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/polyfills/MediaRecorderShim.js +11 -0
- package/lib/module/polyfills/MediaRecorderShim.js.map +1 -0
- package/lib/typescript/lib/commonjs/audio/MediaRecorder.d.ts +24 -0
- package/lib/typescript/lib/commonjs/polyfills/MediaRecorderShim.d.ts +1 -0
- package/lib/typescript/lib/module/audio/MediaRecorder.d.ts +22 -0
- package/lib/typescript/lib/module/polyfills/MediaRecorderShim.d.ts +1 -0
- package/lib/typescript/src/audio/MediaRecorder.d.ts +54 -0
- package/lib/typescript/src/components/LiveKitRoom.d.ts +1 -1
- package/lib/typescript/src/components/VideoTrack.d.ts +1 -1
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/polyfills/MediaRecorderShim.d.ts +1 -0
- package/package.json +7 -5
- package/src/audio/MediaRecorder.ts +158 -0
- package/src/components/BarVisualizer.tsx +2 -9
- package/src/components/LiveKitRoom.tsx +1 -1
- package/src/components/VideoTrack.tsx +3 -2
- package/src/events/EventEmitter.ts +5 -1
- package/src/index.tsx +2 -0
- package/src/polyfills/MediaRecorderShim.ts +12 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
<!--END_BANNER_IMAGE-->
|
|
10
10
|
|
|
11
|
-
#
|
|
11
|
+
# React Native client SDK for LiveKit
|
|
12
12
|
|
|
13
13
|
<!--BEGIN_DESCRIPTION-->
|
|
14
14
|
Use this SDK to add realtime video, audio and data features to your React Native app. By connecting to <a href="https://livekit.io/">LiveKit</a> Cloud or a self-hosted server, you can quickly build applications such as multi-modal AI, live streaming, or video calls with just a few lines of code.
|
|
@@ -43,9 +43,12 @@ Once the `@livekit/react-native-webrtc` dependency is installed, one last step i
|
|
|
43
43
|
|
|
44
44
|
### Android
|
|
45
45
|
|
|
46
|
+
<details>
|
|
47
|
+
|
|
48
|
+
<summary>Java</summary>
|
|
49
|
+
|
|
46
50
|
In your [MainApplication.java](https://github.com/livekit/client-sdk-react-native/blob/main/example/android/app/src/main/java/com/example/livekitreactnative/MainApplication.java) file:
|
|
47
51
|
|
|
48
|
-
#### Java
|
|
49
52
|
```java
|
|
50
53
|
import com.livekit.reactnative.LiveKitReactNative;
|
|
51
54
|
import com.livekit.reactnative.audio.AudioType;
|
|
@@ -64,8 +67,14 @@ public class MainApplication extends Application implements ReactApplication {
|
|
|
64
67
|
}
|
|
65
68
|
```
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
</details>
|
|
71
|
+
|
|
72
|
+
<details>
|
|
73
|
+
|
|
74
|
+
<summary>Kotlin</summary>
|
|
75
|
+
|
|
76
|
+
In your **MainApplication.kt** file:
|
|
77
|
+
|
|
69
78
|
```kotlin
|
|
70
79
|
import com.livekit.reactnative.LiveKitReactNative
|
|
71
80
|
import com.livekit.reactnative.audio.AudioType
|
|
@@ -81,10 +90,15 @@ class MainApplication : Application, ReactApplication() {
|
|
|
81
90
|
}
|
|
82
91
|
}
|
|
83
92
|
```
|
|
84
|
-
|
|
93
|
+
|
|
94
|
+
</details>
|
|
85
95
|
|
|
86
96
|
### iOS
|
|
87
97
|
|
|
98
|
+
<details>
|
|
99
|
+
|
|
100
|
+
<summary>Objective-C</summary>
|
|
101
|
+
|
|
88
102
|
In your [AppDelegate.m](https://github.com/livekit/client-sdk-react-native/blob/main/example/ios/LivekitReactNativeExample/AppDelegate.mm) file:
|
|
89
103
|
|
|
90
104
|
```objc
|
|
@@ -108,6 +122,41 @@ In your [AppDelegate.m](https://github.com/livekit/client-sdk-react-native/blob/
|
|
|
108
122
|
}
|
|
109
123
|
```
|
|
110
124
|
|
|
125
|
+
</details>
|
|
126
|
+
|
|
127
|
+
<details>
|
|
128
|
+
|
|
129
|
+
<summary>Swift</summary>
|
|
130
|
+
|
|
131
|
+
In your **AppDelegate.swift** file:
|
|
132
|
+
```swift
|
|
133
|
+
import livekit_react_native
|
|
134
|
+
import livekit_react_native_webrtc
|
|
135
|
+
|
|
136
|
+
@main
|
|
137
|
+
class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
138
|
+
func application(
|
|
139
|
+
_ application: UIApplication,
|
|
140
|
+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
|
141
|
+
) -> Bool {
|
|
142
|
+
|
|
143
|
+
// Place this above any other RN related initialization
|
|
144
|
+
LivekitReactNative.setup()
|
|
145
|
+
|
|
146
|
+
// Uncomment the following lines if you want to use the camera in the background
|
|
147
|
+
// Requires voip background mode and iOS 18+.
|
|
148
|
+
|
|
149
|
+
// let options = WebRTCModuleOptions.sharedInstance()
|
|
150
|
+
// options.enableMultitaskingCameraAccess = true
|
|
151
|
+
|
|
152
|
+
// ...
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
</details>
|
|
159
|
+
|
|
111
160
|
### Expo
|
|
112
161
|
|
|
113
162
|
LiveKit is available on Expo through development builds. You can find our Expo plugin and setup instructions [here](https://github.com/livekit/client-sdk-react-native-expo-plugin).
|
|
@@ -423,9 +472,9 @@ Apache License 2.0
|
|
|
423
472
|
<br/><table>
|
|
424
473
|
<thead><tr><th colspan="2">LiveKit Ecosystem</th></tr></thead>
|
|
425
474
|
<tbody>
|
|
426
|
-
<tr><td>LiveKit SDKs</td><td><a href="https://github.com/livekit/client-sdk-js">Browser</a> · <a href="https://github.com/livekit/client-sdk-swift">iOS/macOS/visionOS</a> · <a href="https://github.com/livekit/client-sdk-android">Android</a> · <a href="https://github.com/livekit/client-sdk-flutter">Flutter</a> · <b>React Native</b> · <a href="https://github.com/livekit/rust-sdks">Rust</a> · <a href="https://github.com/livekit/node-sdks">Node.js</a> · <a href="https://github.com/livekit/python-sdks">Python</a> · <a href="https://github.com/livekit/client-sdk-unity">Unity</a> · <a href="https://github.com/livekit/client-sdk-unity-web">Unity (WebGL)</a></td></tr><tr></tr>
|
|
475
|
+
<tr><td>LiveKit SDKs</td><td><a href="https://github.com/livekit/client-sdk-js">Browser</a> · <a href="https://github.com/livekit/client-sdk-swift">iOS/macOS/visionOS</a> · <a href="https://github.com/livekit/client-sdk-android">Android</a> · <a href="https://github.com/livekit/client-sdk-flutter">Flutter</a> · <b>React Native</b> · <a href="https://github.com/livekit/rust-sdks">Rust</a> · <a href="https://github.com/livekit/node-sdks">Node.js</a> · <a href="https://github.com/livekit/python-sdks">Python</a> · <a href="https://github.com/livekit/client-sdk-unity">Unity</a> · <a href="https://github.com/livekit/client-sdk-unity-web">Unity (WebGL)</a> · <a href="https://github.com/livekit/client-sdk-esp32">ESP32</a></td></tr><tr></tr>
|
|
427
476
|
<tr><td>Server APIs</td><td><a href="https://github.com/livekit/node-sdks">Node.js</a> · <a href="https://github.com/livekit/server-sdk-go">Golang</a> · <a href="https://github.com/livekit/server-sdk-ruby">Ruby</a> · <a href="https://github.com/livekit/server-sdk-kotlin">Java/Kotlin</a> · <a href="https://github.com/livekit/python-sdks">Python</a> · <a href="https://github.com/livekit/rust-sdks">Rust</a> · <a href="https://github.com/agence104/livekit-server-sdk-php">PHP (community)</a> · <a href="https://github.com/pabloFuente/livekit-server-sdk-dotnet">.NET (community)</a></td></tr><tr></tr>
|
|
428
|
-
<tr><td>UI Components</td><td><a href="https://github.com/livekit/components-js">React</a> · <a href="https://github.com/livekit/components-android">Android Compose</a> · <a href="https://github.com/livekit/components-swift">SwiftUI</a></td></tr><tr></tr>
|
|
477
|
+
<tr><td>UI Components</td><td><a href="https://github.com/livekit/components-js">React</a> · <a href="https://github.com/livekit/components-android">Android Compose</a> · <a href="https://github.com/livekit/components-swift">SwiftUI</a> · <a href="https://github.com/livekit/components-flutter">Flutter</a></td></tr><tr></tr>
|
|
429
478
|
<tr><td>Agents Frameworks</td><td><a href="https://github.com/livekit/agents">Python</a> · <a href="https://github.com/livekit/agents-js">Node.js</a> · <a href="https://github.com/livekit/agent-playground">Playground</a></td></tr><tr></tr>
|
|
430
479
|
<tr><td>Services</td><td><a href="https://github.com/livekit/livekit">LiveKit server</a> · <a href="https://github.com/livekit/egress">Egress</a> · <a href="https://github.com/livekit/ingress">Ingress</a> · <a href="https://github.com/livekit/sip">SIP</a></td></tr><tr></tr>
|
|
431
480
|
<tr><td>Resources</td><td><a href="https://docs.livekit.io">Docs</a> · <a href="https://github.com/livekit-examples">Example apps</a> · <a href="https://livekit.io/cloud">Cloud</a> · <a href="https://docs.livekit.io/home/self-hosting/deployment">Self-hosting</a> · <a href="https://github.com/livekit/livekit-cli">CLI</a></td></tr>
|
package/android/build.gradle
CHANGED
|
@@ -131,7 +131,7 @@ dependencies {
|
|
|
131
131
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
132
132
|
api 'com.github.davidliu:audioswitch:89582c47c9a04c62f90aa5e57251af4800a62c9a'
|
|
133
133
|
api 'io.github.webrtc-sdk:android:125.6422.02'
|
|
134
|
-
implementation "
|
|
134
|
+
implementation "io.livekit:noise:2.0.0"
|
|
135
135
|
implementation project(':livekit_react-native-webrtc')
|
|
136
136
|
implementation "androidx.annotation:annotation:1.9.1"
|
|
137
137
|
}
|
|
@@ -8,16 +8,24 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
8
8
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
9
9
|
import com.facebook.react.bridge.ReactMethod
|
|
10
10
|
import com.facebook.react.bridge.ReadableMap
|
|
11
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
11
12
|
import com.livekit.reactnative.audio.AudioDeviceKind
|
|
12
13
|
import com.livekit.reactnative.audio.AudioManagerUtils
|
|
13
14
|
import com.livekit.reactnative.audio.AudioSwitchManager
|
|
15
|
+
import com.livekit.reactnative.audio.events.Events
|
|
14
16
|
import com.livekit.reactnative.audio.processing.AudioSinkManager
|
|
17
|
+
import com.livekit.reactnative.audio.processing.AudioSinkProcessor
|
|
15
18
|
import com.livekit.reactnative.audio.processing.MultibandVolumeProcessor
|
|
16
19
|
import com.livekit.reactnative.audio.processing.VolumeProcessor
|
|
17
20
|
import com.oney.WebRTCModule.WebRTCModuleOptions
|
|
18
21
|
import org.webrtc.audio.WebRtcAudioTrackHelper
|
|
22
|
+
import java.lang.Thread.sleep
|
|
23
|
+
import kotlin.concurrent.thread
|
|
19
24
|
import kotlin.time.Duration.Companion.milliseconds
|
|
20
25
|
|
|
26
|
+
// NOTE: As of 0.80 react-native new architecture requires all
|
|
27
|
+
// @ReactMethod(isBlockingSynchronousMethod = true)
|
|
28
|
+
// annotated methods to be non-void.
|
|
21
29
|
|
|
22
30
|
class LivekitReactNativeModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
23
31
|
|
|
@@ -131,6 +139,23 @@ class LivekitReactNativeModule(reactContext: ReactApplicationContext) : ReactCon
|
|
|
131
139
|
promise.resolve(null)
|
|
132
140
|
}
|
|
133
141
|
|
|
142
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
143
|
+
fun createAudioSinkListener(pcId: Int, trackId: String): String {
|
|
144
|
+
val processor = AudioSinkProcessor(reactApplicationContext)
|
|
145
|
+
val reactTag = audioSinkManager.registerSink(processor)
|
|
146
|
+
audioSinkManager.attachSinkToTrack(processor, pcId, trackId)
|
|
147
|
+
processor.reactTag = reactTag
|
|
148
|
+
|
|
149
|
+
return reactTag
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
153
|
+
fun deleteAudioSinkListener(reactTag: String, pcId: Int, trackId: String): Boolean {
|
|
154
|
+
audioSinkManager.detachSinkFromTrack(reactTag, pcId, trackId)
|
|
155
|
+
audioSinkManager.unregisterSink(reactTag)
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
|
|
134
159
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
135
160
|
fun createVolumeProcessor(pcId: Int, trackId: String): String {
|
|
136
161
|
val processor = VolumeProcessor(reactApplicationContext)
|
|
@@ -142,9 +167,10 @@ class LivekitReactNativeModule(reactContext: ReactApplicationContext) : ReactCon
|
|
|
142
167
|
}
|
|
143
168
|
|
|
144
169
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
145
|
-
fun deleteVolumeProcessor(reactTag: String, pcId: Int, trackId: String) {
|
|
170
|
+
fun deleteVolumeProcessor(reactTag: String, pcId: Int, trackId: String): Boolean {
|
|
146
171
|
audioSinkManager.detachSinkFromTrack(reactTag, pcId, trackId)
|
|
147
172
|
audioSinkManager.unregisterSink(reactTag)
|
|
173
|
+
return true
|
|
148
174
|
}
|
|
149
175
|
|
|
150
176
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
@@ -171,7 +197,7 @@ class LivekitReactNativeModule(reactContext: ReactApplicationContext) : ReactCon
|
|
|
171
197
|
}
|
|
172
198
|
|
|
173
199
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
174
|
-
fun deleteMultibandVolumeProcessor(reactTag: String, pcId: Int, trackId: String) {
|
|
200
|
+
fun deleteMultibandVolumeProcessor(reactTag: String, pcId: Int, trackId: String): Boolean {
|
|
175
201
|
val volumeProcessor =
|
|
176
202
|
audioSinkManager.getSink(reactTag) ?: throw IllegalArgumentException("Can't find volume processor for $reactTag")
|
|
177
203
|
audioSinkManager.detachSinkFromTrack(volumeProcessor, pcId, trackId)
|
|
@@ -182,7 +208,10 @@ class LivekitReactNativeModule(reactContext: ReactApplicationContext) : ReactCon
|
|
|
182
208
|
multibandVolumeProcessor.release()
|
|
183
209
|
} else {
|
|
184
210
|
Log.w(name, "deleteMultibandVolumeProcessor called, but non-MultibandVolumeProcessor found?!")
|
|
211
|
+
return false
|
|
185
212
|
}
|
|
213
|
+
|
|
214
|
+
return true
|
|
186
215
|
}
|
|
187
216
|
|
|
188
217
|
@ReactMethod
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package com.livekit.reactnative.audio.processing
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.ReactContext
|
|
5
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
6
|
+
import com.livekit.reactnative.audio.events.Events
|
|
7
|
+
import org.webrtc.AudioTrackSink
|
|
8
|
+
import java.nio.ByteBuffer
|
|
9
|
+
import java.util.Arrays
|
|
10
|
+
import kotlin.io.encoding.Base64
|
|
11
|
+
import kotlin.io.encoding.ExperimentalEncodingApi
|
|
12
|
+
|
|
13
|
+
class AudioSinkProcessor(private val reactContext: ReactContext) : BaseAudioSinkProcessor() {
|
|
14
|
+
var reactTag: String? = null
|
|
15
|
+
|
|
16
|
+
@OptIn(ExperimentalEncodingApi::class)
|
|
17
|
+
override fun onAudioData(byteArray: ByteArray) {
|
|
18
|
+
val reactTag = this.reactTag ?: return
|
|
19
|
+
|
|
20
|
+
val encodedString = Base64.encode(byteArray)
|
|
21
|
+
val event = Arguments.createMap().apply {
|
|
22
|
+
putString("data", encodedString)
|
|
23
|
+
putString("id", reactTag)
|
|
24
|
+
}
|
|
25
|
+
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
26
|
+
.emit(Events.LK_AUDIO_DATA.name, event)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
abstract class BaseAudioSinkProcessor : AudioTrackSink {
|
|
31
|
+
abstract fun onAudioData(byteArray: ByteArray)
|
|
32
|
+
|
|
33
|
+
override fun onData(
|
|
34
|
+
audioData: ByteBuffer,
|
|
35
|
+
bitsPerSample: Int,
|
|
36
|
+
sampleRate: Int,
|
|
37
|
+
numberOfChannels: Int,
|
|
38
|
+
numberOfFrames: Int,
|
|
39
|
+
absoluteCaptureTimestampMs: Long
|
|
40
|
+
) {
|
|
41
|
+
val byteArray: ByteArray
|
|
42
|
+
|
|
43
|
+
if (audioData.hasArray()) {
|
|
44
|
+
val audioArray = audioData.array()
|
|
45
|
+
byteArray = Arrays.copyOfRange(audioArray, audioData.arrayOffset(), audioArray.size)
|
|
46
|
+
} else {
|
|
47
|
+
audioData.mark()
|
|
48
|
+
audioData.position(0)
|
|
49
|
+
|
|
50
|
+
byteArray = ByteArray(audioData.remaining())
|
|
51
|
+
audioData.get(byteArray)
|
|
52
|
+
audioData.reset()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onAudioData(byteArray)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -6,6 +6,7 @@ import React
|
|
|
6
6
|
struct LKEvents {
|
|
7
7
|
static let kEventVolumeProcessed = "LK_VOLUME_PROCESSED";
|
|
8
8
|
static let kEventMultibandProcessed = "LK_MULTIBAND_PROCESSED";
|
|
9
|
+
static let kEventAudioData = "LK_AUDIO_DATA";
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
@objc(LivekitReactNativeModule)
|
|
@@ -178,6 +179,24 @@ public class LivekitReactNativeModule: RCTEventEmitter {
|
|
|
178
179
|
session.unlockForConfiguration()
|
|
179
180
|
}
|
|
180
181
|
|
|
182
|
+
@objc(createAudioSinkListener:trackId:)
|
|
183
|
+
public func createAudioSinkListener(_ pcId: NSNumber, trackId: String) -> String {
|
|
184
|
+
let renderer = AudioSinkRenderer(eventEmitter: self)
|
|
185
|
+
let reactTag = self.audioRendererManager.registerRenderer(renderer)
|
|
186
|
+
renderer.reactTag = reactTag
|
|
187
|
+
self.audioRendererManager.attach(renderer: renderer, pcId: pcId, trackId: trackId)
|
|
188
|
+
|
|
189
|
+
return reactTag
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@objc(deleteAudioSinkListener:pcId:trackId:)
|
|
193
|
+
public func deleteAudioSinkListener(_ reactTag: String, pcId: NSNumber, trackId: String) -> Any? {
|
|
194
|
+
self.audioRendererManager.detach(rendererByTag: reactTag, pcId: pcId, trackId: trackId)
|
|
195
|
+
self.audioRendererManager.unregisterRenderer(forReactTag: reactTag)
|
|
196
|
+
|
|
197
|
+
return nil
|
|
198
|
+
}
|
|
199
|
+
|
|
181
200
|
@objc(createVolumeProcessor:trackId:)
|
|
182
201
|
public func createVolumeProcessor(_ pcId: NSNumber, trackId: String) -> String {
|
|
183
202
|
let renderer = VolumeAudioRenderer(intervalMs: 40.0, eventEmitter: self)
|
|
@@ -195,7 +214,7 @@ public class LivekitReactNativeModule: RCTEventEmitter {
|
|
|
195
214
|
|
|
196
215
|
return nil
|
|
197
216
|
}
|
|
198
|
-
|
|
217
|
+
|
|
199
218
|
@objc(createMultibandVolumeProcessor:pcId:trackId:)
|
|
200
219
|
public func createMultibandVolumeProcessor(_ options: NSDictionary, pcId: NSNumber, trackId: String) -> String {
|
|
201
220
|
let bands = (options["bands"] as? NSNumber)?.intValue ?? 5
|
|
@@ -237,6 +256,7 @@ public class LivekitReactNativeModule: RCTEventEmitter {
|
|
|
237
256
|
return [
|
|
238
257
|
LKEvents.kEventVolumeProcessed,
|
|
239
258
|
LKEvents.kEventMultibandProcessed,
|
|
259
|
+
LKEvents.kEventAudioData,
|
|
240
260
|
]
|
|
241
261
|
}
|
|
242
262
|
}
|
|
@@ -21,6 +21,12 @@ RCT_EXTERN_METHOD(selectAudioOutput:(NSString *)deviceId
|
|
|
21
21
|
/// Configure audio config for WebRTC
|
|
22
22
|
RCT_EXTERN_METHOD(setAppleAudioConfiguration:(NSDictionary *) configuration)
|
|
23
23
|
|
|
24
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(createAudioSinkListener:(nonnull NSNumber *)pcId
|
|
25
|
+
trackId:(nonnull NSString *)trackId)
|
|
26
|
+
|
|
27
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(deleteAudioSinkListener:(nonnull NSString *)reactTag
|
|
28
|
+
pcId:(nonnull NSNumber *)pcId
|
|
29
|
+
trackId:(nonnull NSString *)trackId)
|
|
24
30
|
|
|
25
31
|
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(createVolumeProcessor:(nonnull NSNumber *)pcId
|
|
26
32
|
trackId:(nonnull NSString *)trackId)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import livekit_react_native_webrtc
|
|
2
|
+
import React
|
|
3
|
+
|
|
4
|
+
@objc
|
|
5
|
+
public class AudioSinkRenderer: BaseAudioSinkRenderer {
|
|
6
|
+
private let eventEmitter: RCTEventEmitter
|
|
7
|
+
|
|
8
|
+
@objc
|
|
9
|
+
public var reactTag: String? = nil
|
|
10
|
+
|
|
11
|
+
@objc
|
|
12
|
+
public init(eventEmitter: RCTEventEmitter) {
|
|
13
|
+
self.eventEmitter = eventEmitter
|
|
14
|
+
super.init()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override public func onData(_ pcmBuffer: AVAudioPCMBuffer) {
|
|
18
|
+
guard pcmBuffer.format.commonFormat == .pcmFormatInt16,
|
|
19
|
+
let channelData = pcmBuffer.int16ChannelData else {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
let channelCount = Int(pcmBuffer.format.channelCount)
|
|
23
|
+
let channels = UnsafeBufferPointer(start: channelData, count: channelCount)
|
|
24
|
+
let length = Int(pcmBuffer.frameCapacity * pcmBuffer.format.streamDescription.pointee.mBytesPerFrame)
|
|
25
|
+
let data = NSData(bytes: channels[0], length: length)
|
|
26
|
+
let base64 = data.base64EncodedString()
|
|
27
|
+
NSLog("AUDIO DATA!!!!")
|
|
28
|
+
NSLog("\(data.length)")
|
|
29
|
+
NSLog(base64)
|
|
30
|
+
NSLog("\(base64.count)")
|
|
31
|
+
NSLog("\(length)")
|
|
32
|
+
eventEmitter.sendEvent(withName: LKEvents.kEventAudioData, body: [
|
|
33
|
+
"data": base64,
|
|
34
|
+
"id": reactTag
|
|
35
|
+
])
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public class BaseAudioSinkRenderer: NSObject, RTCAudioRenderer {
|
|
40
|
+
|
|
41
|
+
public override init() {
|
|
42
|
+
super.init()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public func render(pcmBuffer: AVAudioPCMBuffer) {
|
|
46
|
+
onData(pcmBuffer)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public func onData(_ pcmBuffer: AVAudioPCMBuffer) {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.MediaRecorder = void 0;
|
|
7
|
+
var _EventEmitter = require("../events/EventEmitter");
|
|
8
|
+
var _index = require("event-target-shim/index");
|
|
9
|
+
var _reactNativeQuickBase = require("react-native-quick-base64");
|
|
10
|
+
var _LKNativeModule = _interopRequireDefault(require("../LKNativeModule"));
|
|
11
|
+
var _logger = require("../logger");
|
|
12
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
14
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
15
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
16
|
+
// typeof MediaRecorder
|
|
17
|
+
// const Tester = (stream: MediaStream) => {
|
|
18
|
+
// return new AudioRecorder(stream) satisfies MediaRecorder;
|
|
19
|
+
// };
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A MediaRecord implementation only meant for recording audio streams.
|
|
23
|
+
*
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
26
|
+
class MediaRecorder extends _index.EventTarget {
|
|
27
|
+
constructor(stream) {
|
|
28
|
+
super();
|
|
29
|
+
_defineProperty(this, "mimeType", 'audio/pcm');
|
|
30
|
+
_defineProperty(this, "audioBitsPerSecond", 0);
|
|
31
|
+
// TODO?
|
|
32
|
+
_defineProperty(this, "state", 'inactive');
|
|
33
|
+
_defineProperty(this, "stream", void 0);
|
|
34
|
+
_defineProperty(this, "videoBitsPerSecond", 0);
|
|
35
|
+
// TODO?
|
|
36
|
+
_defineProperty(this, "audioBitrateMode", 'constant');
|
|
37
|
+
_defineProperty(this, "_reactTag", undefined);
|
|
38
|
+
_defineProperty(this, "_parts", []);
|
|
39
|
+
this.stream = stream;
|
|
40
|
+
}
|
|
41
|
+
registerListener() {
|
|
42
|
+
let audioTracks = this.stream.getAudioTracks();
|
|
43
|
+
if (audioTracks.length !== 1) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const mediaStreamTrack = audioTracks[0];
|
|
47
|
+
const peerConnectionId = mediaStreamTrack._peerConnectionId ?? -1;
|
|
48
|
+
const mediaStreamTrackId = mediaStreamTrack === null || mediaStreamTrack === void 0 ? void 0 : mediaStreamTrack.id;
|
|
49
|
+
this._reactTag = _LKNativeModule.default.createAudioSinkListener(peerConnectionId, mediaStreamTrackId);
|
|
50
|
+
(0, _EventEmitter.addListener)(this, 'LK_AUDIO_DATA', event => {
|
|
51
|
+
if (this._reactTag && event.id === this._reactTag && this.state === 'recording') {
|
|
52
|
+
let str = event.data;
|
|
53
|
+
this._parts.push(str);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
unregisterListener() {
|
|
58
|
+
if (this._reactTag) {
|
|
59
|
+
let audioTracks = this.stream.getAudioTracks();
|
|
60
|
+
if (audioTracks.length !== 1) {
|
|
61
|
+
_logger.log.error("couldn't find any audio tracks to record from!");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const mediaStreamTrack = audioTracks[0];
|
|
65
|
+
const peerConnectionId = mediaStreamTrack._peerConnectionId ?? -1;
|
|
66
|
+
const mediaStreamTrackId = mediaStreamTrack === null || mediaStreamTrack === void 0 ? void 0 : mediaStreamTrack.id;
|
|
67
|
+
_LKNativeModule.default.deleteAudioSinkListener(this._reactTag, peerConnectionId, mediaStreamTrackId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
pause() {
|
|
71
|
+
this.state = 'paused';
|
|
72
|
+
this.dispatchEvent(new _index.Event('pause'));
|
|
73
|
+
}
|
|
74
|
+
resume() {
|
|
75
|
+
this.state = 'recording';
|
|
76
|
+
this.dispatchEvent(new _index.Event('resume'));
|
|
77
|
+
}
|
|
78
|
+
start() {
|
|
79
|
+
this.registerListener();
|
|
80
|
+
this.state = 'recording';
|
|
81
|
+
this.dispatchEvent(new _index.Event('start'));
|
|
82
|
+
}
|
|
83
|
+
stop() {
|
|
84
|
+
// dispatch data must come before stopping.
|
|
85
|
+
this.dispatchData();
|
|
86
|
+
this.unregisterListener();
|
|
87
|
+
this.state = 'inactive';
|
|
88
|
+
this.dispatchEvent(new _index.Event('stop'));
|
|
89
|
+
}
|
|
90
|
+
requestData() {
|
|
91
|
+
this.dispatchData();
|
|
92
|
+
}
|
|
93
|
+
dispatchData() {
|
|
94
|
+
let combinedStr = this._parts.reduce((sum, cur) => sum + cur, '');
|
|
95
|
+
let data = (0, _reactNativeQuickBase.toByteArray)(combinedStr);
|
|
96
|
+
this._parts = [];
|
|
97
|
+
this.dispatchEvent(new BlobEvent('dataavailable', {
|
|
98
|
+
data: {
|
|
99
|
+
byteArray: data
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @eventClass
|
|
107
|
+
* This event is fired whenever the Track is changed in PeerConnection.
|
|
108
|
+
* @param {TRACK_EVENTS} type - The type of event.
|
|
109
|
+
* @param {IRTCTrackEventInitDict} eventInitDict - The event init properties.
|
|
110
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/track_event MDN} for details.
|
|
111
|
+
*/
|
|
112
|
+
exports.MediaRecorder = MediaRecorder;
|
|
113
|
+
class BlobEvent extends _index.Event {
|
|
114
|
+
constructor(type, eventInitDict) {
|
|
115
|
+
super(type, eventInitDict);
|
|
116
|
+
/** @eventProperty */
|
|
117
|
+
_defineProperty(this, "data", void 0);
|
|
118
|
+
this.data = eventInitDict.data;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Define the `onxxx` event handlers.
|
|
124
|
+
*/
|
|
125
|
+
const proto = MediaRecorder.prototype;
|
|
126
|
+
(0, _index.defineEventAttribute)(proto, 'dataavailable');
|
|
127
|
+
(0, _index.defineEventAttribute)(proto, 'error');
|
|
128
|
+
(0, _index.defineEventAttribute)(proto, 'pause');
|
|
129
|
+
(0, _index.defineEventAttribute)(proto, 'resume');
|
|
130
|
+
(0, _index.defineEventAttribute)(proto, 'start');
|
|
131
|
+
(0, _index.defineEventAttribute)(proto, 'stop');
|
|
132
|
+
//# sourceMappingURL=MediaRecorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_EventEmitter","require","_index","_reactNativeQuickBase","_LKNativeModule","_interopRequireDefault","_logger","e","__esModule","default","_defineProperty","r","t","_toPropertyKey","Object","defineProperty","value","enumerable","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","MediaRecorder","EventTarget","constructor","stream","undefined","registerListener","audioTracks","getAudioTracks","length","mediaStreamTrack","peerConnectionId","_peerConnectionId","mediaStreamTrackId","id","_reactTag","LiveKitModule","createAudioSinkListener","addListener","event","state","str","data","_parts","push","unregisterListener","log","error","deleteAudioSinkListener","pause","dispatchEvent","Event","resume","start","stop","dispatchData","requestData","combinedStr","reduce","sum","cur","toByteArray","BlobEvent","byteArray","exports","type","eventInitDict","proto","prototype","defineEventAttribute"],"sources":["MediaRecorder.ts"],"sourcesContent":["import type { MediaStream } from '@livekit/react-native-webrtc';\nimport { addListener } from '../events/EventEmitter';\nimport {\n EventTarget,\n Event,\n defineEventAttribute,\n} from 'event-target-shim/index';\nimport { toByteArray } from 'react-native-quick-base64';\nimport LiveKitModule from '../LKNativeModule';\nimport { log } from '../logger';\n\n// typeof MediaRecorder\n// const Tester = (stream: MediaStream) => {\n// return new AudioRecorder(stream) satisfies MediaRecorder;\n// };\n\ntype MediaRecorderState = 'inactive' | 'recording' | 'paused';\ntype MediaRecorderEventMap = {\n dataavailable: BlobEvent<'dataavailable'>;\n error: Event<'error'>;\n pause: Event<'pause'>;\n resume: Event<'resume'>;\n start: Event<'start'>;\n stop: Event<'stop'>;\n};\n\n/**\n * A MediaRecord implementation only meant for recording audio streams.\n *\n * @private\n */\nexport class MediaRecorder extends EventTarget<MediaRecorderEventMap> {\n mimeType: String = 'audio/pcm';\n audioBitsPerSecond: number = 0; // TODO?\n state: MediaRecorderState = 'inactive';\n stream: MediaStream;\n videoBitsPerSecond: number = 0; // TODO?\n audioBitrateMode = 'constant';\n\n _reactTag: string | undefined = undefined;\n _parts: string[] = [];\n constructor(stream: MediaStream) {\n super();\n this.stream = stream;\n }\n\n registerListener() {\n let audioTracks = this.stream.getAudioTracks();\n if (audioTracks.length !== 1) {\n return;\n }\n const mediaStreamTrack = audioTracks[0];\n const peerConnectionId = mediaStreamTrack._peerConnectionId ?? -1;\n const mediaStreamTrackId = mediaStreamTrack?.id;\n this._reactTag = LiveKitModule.createAudioSinkListener(\n peerConnectionId,\n mediaStreamTrackId\n );\n addListener(this, 'LK_AUDIO_DATA', (event: any) => {\n if (\n this._reactTag &&\n event.id === this._reactTag &&\n this.state === 'recording'\n ) {\n let str = event.data as string;\n this._parts.push(str);\n }\n });\n }\n\n unregisterListener() {\n if (this._reactTag) {\n let audioTracks = this.stream.getAudioTracks();\n if (audioTracks.length !== 1) {\n log.error(\"couldn't find any audio tracks to record from!\");\n return;\n }\n const mediaStreamTrack = audioTracks[0];\n const peerConnectionId = mediaStreamTrack._peerConnectionId ?? -1;\n const mediaStreamTrackId = mediaStreamTrack?.id;\n\n LiveKitModule.deleteAudioSinkListener(\n this._reactTag,\n peerConnectionId,\n mediaStreamTrackId\n );\n }\n }\n\n pause() {\n this.state = 'paused';\n this.dispatchEvent(new Event('pause'));\n }\n\n resume() {\n this.state = 'recording';\n this.dispatchEvent(new Event('resume'));\n }\n\n start() {\n this.registerListener();\n this.state = 'recording';\n this.dispatchEvent(new Event('start'));\n }\n\n stop() {\n // dispatch data must come before stopping.\n this.dispatchData();\n\n this.unregisterListener();\n this.state = 'inactive';\n this.dispatchEvent(new Event('stop'));\n }\n\n requestData() {\n this.dispatchData();\n }\n dispatchData() {\n let combinedStr = this._parts.reduce((sum, cur) => sum + cur, '');\n let data = toByteArray(combinedStr);\n this._parts = [];\n this.dispatchEvent(\n new BlobEvent('dataavailable', { data: { byteArray: data } })\n );\n }\n}\n\n/**\n * @eventClass\n * This event is fired whenever the Track is changed in PeerConnection.\n * @param {TRACK_EVENTS} type - The type of event.\n * @param {IRTCTrackEventInitDict} eventInitDict - The event init properties.\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/track_event MDN} for details.\n */\nclass BlobEvent<TEventType extends string> extends Event<TEventType> {\n /** @eventProperty */\n readonly data: { byteArray: Uint8Array };\n\n constructor(\n type: TEventType,\n eventInitDict: { data: { byteArray: Uint8Array } } & Event.EventInit\n ) {\n super(type, eventInitDict);\n this.data = eventInitDict.data;\n }\n}\n\n/**\n * Define the `onxxx` event handlers.\n */\nconst proto = MediaRecorder.prototype;\n\ndefineEventAttribute(proto, 'dataavailable');\ndefineEventAttribute(proto, 'error');\ndefineEventAttribute(proto, 'pause');\ndefineEventAttribute(proto, 'resume');\ndefineEventAttribute(proto, 'start');\ndefineEventAttribute(proto, 'stop');\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -104,7 +104,7 @@ const BarVisualizer = ({
|
|
|
104
104
|
let bars = [];
|
|
105
105
|
magnitudes.forEach((value, index) => {
|
|
106
106
|
let coerced = Math.min(opts.maxHeight, Math.max(opts.minHeight, value));
|
|
107
|
-
let coercedPercent = Math.min(100, Math.max(0, coerced * 100
|
|
107
|
+
let coercedPercent = Math.min(100, Math.max(0, coerced * 100));
|
|
108
108
|
let opacity = opacityAnimations[index] ?? new _reactNative.Animated.Value(0.3);
|
|
109
109
|
let barStyle = {
|
|
110
110
|
opacity: opacity,
|
|
@@ -116,7 +116,7 @@ const BarVisualizer = ({
|
|
|
116
116
|
key: index,
|
|
117
117
|
style: [{
|
|
118
118
|
height: `${coercedPercent}%`
|
|
119
|
-
}, barStyle
|
|
119
|
+
}, barStyle]
|
|
120
120
|
}));
|
|
121
121
|
});
|
|
122
122
|
return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
|
|
@@ -132,9 +132,6 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
132
132
|
flexDirection: 'row',
|
|
133
133
|
alignItems: 'center',
|
|
134
134
|
justifyContent: 'space-evenly'
|
|
135
|
-
},
|
|
136
|
-
volumeIndicator: {
|
|
137
|
-
borderRadius: 12
|
|
138
135
|
}
|
|
139
136
|
});
|
|
140
137
|
const useBarAnimator = (state, columns, interval) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_componentsReact","require","_reactNative","_hooks","_react","_interopRequireWildcard","_getRequireWildcardCache","e","WeakMap","r","t","__esModule","default","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","hasOwnProperty","call","i","set","defaultBarOptions","maxHeight","minHeight","barColor","barWidth","barBorderRadius","sequencerIntervals","Map","getSequencerInterval","state","barCount","undefined","interval","BarVisualizer","style","trackRef","options","trackReference","useMaybeTrackRefContext","opacityAnimations","useRef","current","magnitudes","useMultibandTrackVolume","bands","opts","highlightedIndices","useBarAnimator","useEffect","animations","Animated","Value","targetOpacity","includes","push","timing","toValue","duration","useNativeDriver","parallel","start","stop","bars","forEach","value","index","coerced","Math","min","max","coercedPercent","opacity","barStyle","backgroundColor","borderRadius","width","createElement","View","key","height","styles","volumeIndicator","container","exports","StyleSheet","create","flexDirection","alignItems","justifyContent","columns","setIndex","useState","sequence","setSequence","generateListeningSequenceBar","seq","generateConnectingSequenceBar","Array","fill","map","_","idx","animationFrameId","startTime","performance","now","animate","time","timeElapsed","prev","requestAnimationFrame","cancelAnimationFrame","length","center","floor","noIndex","x"],"sources":["BarVisualizer.tsx"],"sourcesContent":["import {\n type AgentState,\n type TrackReferenceOrPlaceholder,\n useMaybeTrackRefContext,\n} from '@livekit/components-react';\nimport {\n Animated,\n StyleSheet,\n View,\n type ColorValue,\n type DimensionValue,\n type ViewStyle,\n} from 'react-native';\nimport { useMultibandTrackVolume } from '../hooks';\nimport React, { useEffect, useRef, useState } from 'react';\nexport type BarVisualizerOptions = {\n /** decimal values from 0 to 1 */\n maxHeight?: number;\n /** decimal values from 0 to 1 */\n minHeight?: number;\n\n barColor?: ColorValue;\n barWidth?: DimensionValue;\n barBorderRadius?: number;\n};\n\nconst defaultBarOptions = {\n maxHeight: 1,\n minHeight: 0.2,\n barColor: '#888888',\n barWidth: 24,\n barBorderRadius: 12,\n} as const satisfies BarVisualizerOptions;\n\nconst sequencerIntervals = new Map<AgentState, number>([\n ['connecting', 2000],\n ['initializing', 2000],\n ['listening', 500],\n ['thinking', 150],\n]);\n\nconst getSequencerInterval = (\n state: AgentState | undefined,\n barCount: number\n): number | undefined => {\n if (state === undefined) {\n return 1000;\n }\n let interval = sequencerIntervals.get(state);\n if (interval) {\n switch (state) {\n case 'connecting':\n // case 'thinking':\n interval /= barCount;\n break;\n\n default:\n break;\n }\n }\n return interval;\n};\n/**\n * @beta\n */\nexport interface BarVisualizerProps {\n /** If set, the visualizer will transition between different voice assistant states */\n state?: AgentState;\n /** Number of bars that show up in the visualizer */\n barCount?: number;\n trackRef?: TrackReferenceOrPlaceholder;\n options?: BarVisualizerOptions;\n /**\n * Custom React Native styles for the container.\n */\n style?: ViewStyle;\n}\n\n/**\n * Visualizes audio signals from a TrackReference as bars.\n * If the `state` prop is set, it automatically transitions between VoiceAssistant states.\n * @beta\n *\n * @remarks For VoiceAssistant state transitions this component requires a voice assistant agent running with livekit-agents \\>= 0.9.0\n *\n * @example\n * ```tsx\n * function SimpleVoiceAssistant() {\n * const { state, audioTrack } = useVoiceAssistant();\n * return (\n * <BarVisualizer\n * state={state}\n * trackRef={audioTrack}\n * />\n * );\n * }\n * ```\n */\nexport const BarVisualizer = ({\n style = {},\n state,\n barCount = 5,\n trackRef,\n options,\n}: BarVisualizerProps) => {\n let trackReference = useMaybeTrackRefContext();\n\n if (trackRef) {\n trackReference = trackRef;\n }\n\n const opacityAnimations = useRef<Animated.Value[]>([]).current;\n let magnitudes = useMultibandTrackVolume(trackReference, { bands: barCount });\n\n let opts = { ...defaultBarOptions, ...options };\n\n const highlightedIndices = useBarAnimator(\n state,\n barCount,\n getSequencerInterval(state, barCount) ?? 100\n );\n\n useEffect(() => {\n let animations = [];\n for (let i = 0; i < barCount; i++) {\n if (!opacityAnimations[i]) {\n opacityAnimations[i] = new Animated.Value(0.3);\n }\n let targetOpacity = 0.3;\n if (highlightedIndices.includes(i)) {\n targetOpacity = 1;\n }\n animations.push(\n Animated.timing(opacityAnimations[i], {\n toValue: targetOpacity,\n duration: 250,\n useNativeDriver: true,\n })\n );\n }\n\n let parallel = Animated.parallel(animations);\n parallel.start();\n return () => {\n parallel.stop();\n };\n }, [highlightedIndices, barCount, opacityAnimations]);\n\n let bars: React.ReactNode[] = [];\n magnitudes.forEach((value, index) => {\n let coerced = Math.min(opts.maxHeight, Math.max(opts.minHeight, value));\n let coercedPercent = Math.min(100, Math.max(0, coerced * 100 + 5));\n let opacity = opacityAnimations[index] ?? new Animated.Value(0.3);\n let barStyle = {\n opacity: opacity,\n backgroundColor: opts.barColor,\n borderRadius: opts.barBorderRadius,\n width: opts.barWidth,\n };\n bars.push(\n <Animated.View\n key={index}\n style={[\n { height: `${coercedPercent}%` },\n barStyle,\n styles.volumeIndicator,\n ]}\n />\n );\n });\n\n return <View style={{ ...style, ...styles.container }}>{bars}</View>;\n};\nconst styles = StyleSheet.create({\n container: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-evenly',\n },\n volumeIndicator: {\n borderRadius: 12,\n },\n});\n\nexport const useBarAnimator = (\n state: AgentState | undefined,\n columns: number,\n interval: number\n): number[] => {\n const [index, setIndex] = useState(0);\n const [sequence, setSequence] = useState<number[][]>([[]]);\n\n useEffect(() => {\n if (state === 'thinking') {\n setSequence(generateListeningSequenceBar(columns));\n } else if (state === 'connecting' || state === 'initializing') {\n const seq = [...generateConnectingSequenceBar(columns)];\n setSequence(seq);\n } else if (state === 'listening') {\n setSequence(generateListeningSequenceBar(columns));\n } else if (state === undefined) {\n // highlight everything\n setSequence([new Array(columns).fill(0).map((_, idx) => idx)]);\n } else {\n setSequence([[]]);\n }\n setIndex(0);\n }, [state, columns]);\n\n const animationFrameId = useRef<number | null>(null);\n useEffect(() => {\n let startTime = performance.now();\n\n const animate = (time: number) => {\n const timeElapsed = time - startTime;\n\n if (timeElapsed >= interval) {\n setIndex((prev) => prev + 1);\n startTime = time;\n }\n\n animationFrameId.current = requestAnimationFrame(animate);\n };\n\n animationFrameId.current = requestAnimationFrame(animate);\n\n return () => {\n if (animationFrameId.current !== null) {\n cancelAnimationFrame(animationFrameId.current);\n }\n };\n }, [interval, columns, state, sequence.length]);\n\n return sequence[index % sequence.length];\n};\n\nconst generateListeningSequenceBar = (columns: number): number[][] => {\n const center = Math.floor(columns / 2);\n const noIndex = -1;\n\n return [[center], [noIndex]];\n};\n\nconst generateConnectingSequenceBar = (columns: number): number[][] => {\n const seq: number[][] = [[]];\n\n for (let x = 0; x < columns; x++) {\n seq.push([x, columns - 1 - x]);\n }\n\n return seq;\n};\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["_componentsReact","require","_reactNative","_hooks","_react","_interopRequireWildcard","_getRequireWildcardCache","e","WeakMap","r","t","__esModule","default","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","hasOwnProperty","call","i","set","defaultBarOptions","maxHeight","minHeight","barColor","barWidth","barBorderRadius","sequencerIntervals","Map","getSequencerInterval","state","barCount","undefined","interval","BarVisualizer","style","trackRef","options","trackReference","useMaybeTrackRefContext","opacityAnimations","useRef","current","magnitudes","useMultibandTrackVolume","bands","opts","highlightedIndices","useBarAnimator","useEffect","animations","Animated","Value","targetOpacity","includes","push","timing","toValue","duration","useNativeDriver","parallel","start","stop","bars","forEach","value","index","coerced","Math","min","max","coercedPercent","opacity","barStyle","backgroundColor","borderRadius","width","createElement","View","key","height","styles","container","exports","StyleSheet","create","flexDirection","alignItems","justifyContent","columns","setIndex","useState","sequence","setSequence","generateListeningSequenceBar","seq","generateConnectingSequenceBar","Array","fill","map","_","idx","animationFrameId","startTime","performance","now","animate","time","timeElapsed","prev","requestAnimationFrame","cancelAnimationFrame","length","center","floor","noIndex","x"],"sources":["BarVisualizer.tsx"],"sourcesContent":["import {\n type AgentState,\n type TrackReferenceOrPlaceholder,\n useMaybeTrackRefContext,\n} from '@livekit/components-react';\nimport {\n Animated,\n StyleSheet,\n View,\n type ColorValue,\n type DimensionValue,\n type ViewStyle,\n} from 'react-native';\nimport { useMultibandTrackVolume } from '../hooks';\nimport React, { useEffect, useRef, useState } from 'react';\nexport type BarVisualizerOptions = {\n /** decimal values from 0 to 1 */\n maxHeight?: number;\n /** decimal values from 0 to 1 */\n minHeight?: number;\n\n barColor?: ColorValue;\n barWidth?: DimensionValue;\n barBorderRadius?: number;\n};\n\nconst defaultBarOptions = {\n maxHeight: 1,\n minHeight: 0.2,\n barColor: '#888888',\n barWidth: 24,\n barBorderRadius: 12,\n} as const satisfies BarVisualizerOptions;\n\nconst sequencerIntervals = new Map<AgentState, number>([\n ['connecting', 2000],\n ['initializing', 2000],\n ['listening', 500],\n ['thinking', 150],\n]);\n\nconst getSequencerInterval = (\n state: AgentState | undefined,\n barCount: number\n): number | undefined => {\n if (state === undefined) {\n return 1000;\n }\n let interval = sequencerIntervals.get(state);\n if (interval) {\n switch (state) {\n case 'connecting':\n // case 'thinking':\n interval /= barCount;\n break;\n\n default:\n break;\n }\n }\n return interval;\n};\n/**\n * @beta\n */\nexport interface BarVisualizerProps {\n /** If set, the visualizer will transition between different voice assistant states */\n state?: AgentState;\n /** Number of bars that show up in the visualizer */\n barCount?: number;\n trackRef?: TrackReferenceOrPlaceholder;\n options?: BarVisualizerOptions;\n /**\n * Custom React Native styles for the container.\n */\n style?: ViewStyle;\n}\n\n/**\n * Visualizes audio signals from a TrackReference as bars.\n * If the `state` prop is set, it automatically transitions between VoiceAssistant states.\n * @beta\n *\n * @remarks For VoiceAssistant state transitions this component requires a voice assistant agent running with livekit-agents \\>= 0.9.0\n *\n * @example\n * ```tsx\n * function SimpleVoiceAssistant() {\n * const { state, audioTrack } = useVoiceAssistant();\n * return (\n * <BarVisualizer\n * state={state}\n * trackRef={audioTrack}\n * />\n * );\n * }\n * ```\n */\nexport const BarVisualizer = ({\n style = {},\n state,\n barCount = 5,\n trackRef,\n options,\n}: BarVisualizerProps) => {\n let trackReference = useMaybeTrackRefContext();\n\n if (trackRef) {\n trackReference = trackRef;\n }\n\n const opacityAnimations = useRef<Animated.Value[]>([]).current;\n let magnitudes = useMultibandTrackVolume(trackReference, { bands: barCount });\n\n let opts = { ...defaultBarOptions, ...options };\n\n const highlightedIndices = useBarAnimator(\n state,\n barCount,\n getSequencerInterval(state, barCount) ?? 100\n );\n\n useEffect(() => {\n let animations = [];\n for (let i = 0; i < barCount; i++) {\n if (!opacityAnimations[i]) {\n opacityAnimations[i] = new Animated.Value(0.3);\n }\n let targetOpacity = 0.3;\n if (highlightedIndices.includes(i)) {\n targetOpacity = 1;\n }\n animations.push(\n Animated.timing(opacityAnimations[i], {\n toValue: targetOpacity,\n duration: 250,\n useNativeDriver: true,\n })\n );\n }\n\n let parallel = Animated.parallel(animations);\n parallel.start();\n return () => {\n parallel.stop();\n };\n }, [highlightedIndices, barCount, opacityAnimations]);\n\n let bars: React.ReactNode[] = [];\n magnitudes.forEach((value, index) => {\n let coerced = Math.min(opts.maxHeight, Math.max(opts.minHeight, value));\n let coercedPercent = Math.min(100, Math.max(0, coerced * 100));\n let opacity = opacityAnimations[index] ?? new Animated.Value(0.3);\n let barStyle = {\n opacity: opacity,\n backgroundColor: opts.barColor,\n borderRadius: opts.barBorderRadius,\n width: opts.barWidth,\n };\n bars.push(\n <Animated.View\n key={index}\n style={[{ height: `${coercedPercent}%` }, barStyle]}\n />\n );\n });\n\n return <View style={{ ...style, ...styles.container }}>{bars}</View>;\n};\nconst styles = StyleSheet.create({\n container: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-evenly',\n },\n});\n\nexport const useBarAnimator = (\n state: AgentState | undefined,\n columns: number,\n interval: number\n): number[] => {\n const [index, setIndex] = useState(0);\n const [sequence, setSequence] = useState<number[][]>([[]]);\n\n useEffect(() => {\n if (state === 'thinking') {\n setSequence(generateListeningSequenceBar(columns));\n } else if (state === 'connecting' || state === 'initializing') {\n const seq = [...generateConnectingSequenceBar(columns)];\n setSequence(seq);\n } else if (state === 'listening') {\n setSequence(generateListeningSequenceBar(columns));\n } else if (state === undefined) {\n // highlight everything\n setSequence([new Array(columns).fill(0).map((_, idx) => idx)]);\n } else {\n setSequence([[]]);\n }\n setIndex(0);\n }, [state, columns]);\n\n const animationFrameId = useRef<number | null>(null);\n useEffect(() => {\n let startTime = performance.now();\n\n const animate = (time: number) => {\n const timeElapsed = time - startTime;\n\n if (timeElapsed >= interval) {\n setIndex((prev) => prev + 1);\n startTime = time;\n }\n\n animationFrameId.current = requestAnimationFrame(animate);\n };\n\n animationFrameId.current = requestAnimationFrame(animate);\n\n return () => {\n if (animationFrameId.current !== null) {\n cancelAnimationFrame(animationFrameId.current);\n }\n };\n }, [interval, columns, state, sequence.length]);\n\n return sequence[index % sequence.length];\n};\n\nconst generateListeningSequenceBar = (columns: number): number[][] => {\n const center = Math.floor(columns / 2);\n const noIndex = -1;\n\n return [[center], [noIndex]];\n};\n\nconst generateConnectingSequenceBar = (columns: number): number[][] => {\n const seq: number[][] = [[]];\n\n for (let x = 0; x < columns; x++) {\n seq.push([x, columns - 1 - x]);\n }\n\n return seq;\n};\n"],"mappings":"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","ignoreList":[]}
|