@livekit/react-native 1.2.0 → 1.4.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 +53 -10
- package/android/build.gradle +2 -2
- package/android/src/main/java/com/livekit/reactnative/LiveKitReactNative.kt +21 -2
- package/android/src/main/java/com/livekit/reactnative/LivekitReactNativeModule.kt +63 -14
- package/android/src/main/java/com/livekit/reactnative/audio/AudioManagerUtils.kt +72 -0
- package/android/src/main/java/com/livekit/reactnative/audio/AudioSwitchManager.java +108 -6
- package/android/src/main/java/com/livekit/reactnative/audio/AudioType.kt +46 -0
- package/android/src/main/java/com/livekit/reactnative/video/SimulcastVideoEncoderFactoryWrapper.kt +2 -1
- package/ios/AudioUtils.h +9 -0
- package/ios/AudioUtils.m +48 -0
- package/ios/LivekitReactNative.m +45 -0
- package/ios/LivekitReactNative.xcodeproj/project.xcworkspace/xcuserdata/davidliu.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/lib/commonjs/audio/AudioManager.js +108 -0
- package/lib/commonjs/audio/AudioManager.js.map +1 -0
- package/lib/commonjs/audio/AudioSession.js +59 -10
- package/lib/commonjs/audio/AudioSession.js.map +1 -1
- package/lib/commonjs/index.js +105 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/logger.js +32 -0
- package/lib/commonjs/logger.js.map +1 -0
- package/lib/module/audio/AudioManager.js +92 -0
- package/lib/module/audio/AudioManager.js.map +1 -0
- package/lib/module/audio/AudioSession.js +54 -9
- package/lib/module/audio/AudioSession.js.map +1 -1
- package/lib/module/index.js +19 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/logger.js +18 -0
- package/lib/module/logger.js.map +1 -0
- package/lib/typescript/audio/AudioManager.d.ts +11 -0
- package/lib/typescript/audio/AudioSession.d.ts +85 -14
- package/lib/typescript/index.d.ts +6 -2
- package/lib/typescript/logger.d.ts +13 -0
- package/package.json +9 -5
- package/src/audio/AudioManager.ts +119 -0
- package/src/audio/AudioSession.ts +206 -23
- package/src/index.tsx +41 -2
- package/src/logger.ts +23 -0
package/README.md
CHANGED
|
@@ -39,13 +39,16 @@ In your [MainApplication.java](https://github.com/livekit/client-sdk-react-nativ
|
|
|
39
39
|
|
|
40
40
|
```
|
|
41
41
|
import com.livekit.reactnative.LiveKitReactNative;
|
|
42
|
+
import com.livekit.reactnative.audio.AudioType;
|
|
42
43
|
|
|
43
44
|
public class MainApplication extends Application implements ReactApplication {
|
|
44
45
|
|
|
45
46
|
@Override
|
|
46
47
|
public void onCreate() {
|
|
47
48
|
// Place this above any other RN related initialization
|
|
48
|
-
|
|
49
|
+
// When AudioType is omitted, it'll default to CommunicationAudioType
|
|
50
|
+
// use MediaAudioType if user is only consuming audio, and not publishing
|
|
51
|
+
LiveKitReactNative.setup(this, new AudioType.CommunicationAudioType());
|
|
49
52
|
|
|
50
53
|
//...
|
|
51
54
|
}
|
|
@@ -137,20 +140,26 @@ to manage the audio session on native platforms. This class wraps either [AudioM
|
|
|
137
140
|
|
|
138
141
|
You can customize the configuration of the audio session with `configureAudio`.
|
|
139
142
|
|
|
143
|
+
### Media playback
|
|
144
|
+
|
|
145
|
+
By default, the audio session is set up for bidirectional communication. In this mode, the audio framework exhibits the following behaviors:
|
|
146
|
+
|
|
147
|
+
- The volume cannot be reduced to 0.
|
|
148
|
+
- Echo cancellation is available and is enabled by default.
|
|
149
|
+
- A microphone indicator can be displayed, depending on the platform.
|
|
150
|
+
|
|
151
|
+
If you're leveraging LiveKit primarily for media playback, you have the option to reconfigure the audio session to better suit media playback. Here's how:
|
|
152
|
+
|
|
153
|
+
Note: iOS audio session customization is in development, and will be documented here when released.
|
|
154
|
+
|
|
140
155
|
```js
|
|
141
156
|
useEffect(() => {
|
|
142
157
|
let connect = async () => {
|
|
143
158
|
// configure audio session prior to starting it.
|
|
144
159
|
await AudioSession.configureAudio({
|
|
145
160
|
android: {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// for details on audio and focus modes.
|
|
149
|
-
audioMode: 'normal',
|
|
150
|
-
audioFocusMode: 'gain',
|
|
151
|
-
},
|
|
152
|
-
ios: {
|
|
153
|
-
defaultOutput: 'earpiece',
|
|
161
|
+
// currently supports .media and .communication presets
|
|
162
|
+
audioTypeOptions: AndroidAudioTypePresets.media,
|
|
154
163
|
},
|
|
155
164
|
});
|
|
156
165
|
await AudioSession.startAudioSession();
|
|
@@ -164,6 +173,29 @@ useEffect(() => {
|
|
|
164
173
|
}, [url, token, room]);
|
|
165
174
|
```
|
|
166
175
|
|
|
176
|
+
### Customizing audio session
|
|
177
|
+
|
|
178
|
+
Instead of using our presets, you can further customize the audio session to suit your specific needs.
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
await AudioSession.configureAudio({
|
|
182
|
+
android: {
|
|
183
|
+
preferredOutputList: ['earpiece'],
|
|
184
|
+
// See [AudioManager](https://developer.android.com/reference/android/media/AudioManager)
|
|
185
|
+
// for details on audio and focus modes.
|
|
186
|
+
audioTypeOptions: {
|
|
187
|
+
manageAudioFocus: true,
|
|
188
|
+
audioMode: 'normal',
|
|
189
|
+
audioFocusMode: 'gain',
|
|
190
|
+
audioStreamType: 'music',
|
|
191
|
+
audioAttributesUsageType: 'media',
|
|
192
|
+
audioAttributesContentType: 'unknown',
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
await AudioSession.startAudioSession();
|
|
197
|
+
```
|
|
198
|
+
|
|
167
199
|
## Screenshare
|
|
168
200
|
|
|
169
201
|
Enabling screenshare requires extra installation steps:
|
|
@@ -223,7 +255,18 @@ return (
|
|
|
223
255
|
|
|
224
256
|
### Note
|
|
225
257
|
|
|
226
|
-
|
|
258
|
+
You will not be able to publish camera or microphone tracks on iOS Simulator.
|
|
259
|
+
|
|
260
|
+
## Troubleshooting
|
|
261
|
+
|
|
262
|
+
#### Cannot read properties of undefined (reading 'split')
|
|
263
|
+
|
|
264
|
+
This error could happen if you are using yarn and have incompatible versions of dependencies with livekit-client.
|
|
265
|
+
|
|
266
|
+
To fix this, you can either:
|
|
267
|
+
|
|
268
|
+
- use another package manager, like npm
|
|
269
|
+
- use [yarn-deduplicate](https://www.npmjs.com/package/yarn-deduplicate) to deduplicate dependencies
|
|
227
270
|
|
|
228
271
|
## Contributing
|
|
229
272
|
|
package/android/build.gradle
CHANGED
|
@@ -129,8 +129,8 @@ dependencies {
|
|
|
129
129
|
// noinspection GradleDynamicVersion
|
|
130
130
|
api 'com.facebook.react:react-native:+'
|
|
131
131
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
132
|
-
api 'com.github.davidliu:audioswitch:
|
|
133
|
-
api 'io.github.webrtc-sdk:android:
|
|
132
|
+
api 'com.github.davidliu:audioswitch:89582c47c9a04c62f90aa5e57251af4800a62c9a'
|
|
133
|
+
api 'io.github.webrtc-sdk:android:114.5735.05'
|
|
134
134
|
implementation project(':livekit_react-native-webrtc')
|
|
135
135
|
implementation "androidx.annotation:annotation:1.4.0"
|
|
136
136
|
}
|
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
package com.livekit.reactnative
|
|
2
2
|
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import com.livekit.reactnative.audio.AudioType
|
|
3
7
|
import com.livekit.reactnative.video.SimulcastVideoEncoderFactoryWrapper
|
|
4
8
|
import com.livekit.reactnative.video.WrappedVideoDecoderFactoryProxy
|
|
5
9
|
import com.oney.WebRTCModule.WebRTCModuleOptions
|
|
6
|
-
|
|
10
|
+
import org.webrtc.audio.JavaAudioDeviceModule
|
|
7
11
|
|
|
8
12
|
object LiveKitReactNative {
|
|
9
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Initializes components required for LiveKit to work on Android.
|
|
16
|
+
*
|
|
17
|
+
* Must be called from your [Application.onCreate] method before any other react-native
|
|
18
|
+
* initialization.
|
|
19
|
+
*/
|
|
10
20
|
@JvmStatic
|
|
11
|
-
|
|
21
|
+
@JvmOverloads
|
|
22
|
+
fun setup(context: Context, audioType: AudioType = AudioType.CommunicationAudioType()) {
|
|
12
23
|
val options = WebRTCModuleOptions.getInstance()
|
|
13
24
|
options.videoEncoderFactory = SimulcastVideoEncoderFactoryWrapper(null, true, true)
|
|
14
25
|
options.videoDecoderFactory = WrappedVideoDecoderFactoryProxy()
|
|
26
|
+
|
|
27
|
+
val useHardwareAudioProcessing = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
|
28
|
+
|
|
29
|
+
options.audioDeviceModule = JavaAudioDeviceModule.builder(context)
|
|
30
|
+
.setUseHardwareAcousticEchoCanceler(useHardwareAudioProcessing)
|
|
31
|
+
.setUseHardwareNoiseSuppressor(useHardwareAudioProcessing)
|
|
32
|
+
.setAudioAttributes(audioType.audioAttributes)
|
|
33
|
+
.createAudioDeviceModule()
|
|
15
34
|
}
|
|
16
35
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.livekit.reactnative
|
|
2
2
|
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
3
5
|
import com.facebook.react.bridge.*
|
|
4
6
|
import com.livekit.reactnative.audio.AudioDeviceKind
|
|
5
7
|
import com.livekit.reactnative.audio.AudioManagerUtils
|
|
@@ -17,24 +19,71 @@ class LivekitReactNativeModule(reactContext: ReactApplicationContext) : ReactCon
|
|
|
17
19
|
fun configureAudio(config: ReadableMap) {
|
|
18
20
|
val androidConfig = config.getMap("android") ?: return
|
|
19
21
|
|
|
20
|
-
androidConfig.
|
|
21
|
-
|
|
22
|
-
val
|
|
23
|
-
|
|
22
|
+
if (androidConfig.hasKey("preferredOutputList")) {
|
|
23
|
+
androidConfig.getArray("preferredOutputList")?.let { preferredOutputList ->
|
|
24
|
+
val preferredDeviceList = preferredOutputList.toArrayList().mapNotNull { output ->
|
|
25
|
+
val outputStr = output as? String
|
|
26
|
+
AudioDeviceKind.fromTypeName(outputStr)?.audioDeviceClass
|
|
27
|
+
}
|
|
28
|
+
audioManager.preferredDeviceList = preferredDeviceList
|
|
24
29
|
}
|
|
25
|
-
audioManager.preferredDeviceList = preferredDeviceList
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
androidConfig.
|
|
29
|
-
val
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
if (androidConfig.hasKey("audioTypeOptions")) {
|
|
33
|
+
val audioTypeOptions = androidConfig.getMap("audioTypeOptions") ?: return
|
|
34
|
+
|
|
35
|
+
if (audioTypeOptions.hasKey("manageAudioFocus")) {
|
|
36
|
+
val manageFocus = audioTypeOptions.getBoolean("manageAudioFocus")
|
|
37
|
+
audioManager.setManageAudioFocus(manageFocus)
|
|
32
38
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
if (audioTypeOptions.hasKey("audioMode")) {
|
|
40
|
+
audioTypeOptions.getString("audioMode")?.let { audioModeString ->
|
|
41
|
+
val audioMode = AudioManagerUtils.audioModeFromString(audioModeString)
|
|
42
|
+
if (audioMode != null) {
|
|
43
|
+
audioManager.setAudioMode(audioMode)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (audioTypeOptions.hasKey("audioFocusMode")) {
|
|
49
|
+
audioTypeOptions.getString("audioFocusMode")?.let { focusModeString ->
|
|
50
|
+
val focusMode = AudioManagerUtils.focusModeFromString(focusModeString)
|
|
51
|
+
if (focusMode != null) {
|
|
52
|
+
audioManager.setFocusMode(focusMode)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (audioTypeOptions.hasKey("audioStreamType")) {
|
|
58
|
+
audioTypeOptions.getString("audioStreamType")?.let { streamTypeString ->
|
|
59
|
+
val streamType = AudioManagerUtils.audioStreamTypeFromString(streamTypeString)
|
|
60
|
+
if (streamType != null) {
|
|
61
|
+
audioManager.setAudioStreamType(streamType)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (audioTypeOptions.hasKey("audioAttributesUsageType")) {
|
|
67
|
+
audioTypeOptions.getString("audioAttributesUsageType")?.let { usageTypeString ->
|
|
68
|
+
val usageType = AudioManagerUtils.audioAttributesUsageTypeFromString(usageTypeString)
|
|
69
|
+
if (usageType != null) {
|
|
70
|
+
audioManager.setAudioAttributesUsageType(usageType)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (audioTypeOptions.hasKey("audioAttributesContentType")) {
|
|
76
|
+
audioTypeOptions.getString("audioAttributesContentType")?.let { contentTypeString ->
|
|
77
|
+
val contentType = AudioManagerUtils.audioAttributesContentTypeFromString(contentTypeString)
|
|
78
|
+
if (contentType != null) {
|
|
79
|
+
audioManager.setAudioAttributesContentType(contentType)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (audioTypeOptions.hasKey("forceHandleAudioRouting")) {
|
|
85
|
+
val force = audioTypeOptions.getBoolean("forceHandleAudioRouting")
|
|
86
|
+
audioManager.setForceHandleAudioRouting(force)
|
|
38
87
|
}
|
|
39
88
|
}
|
|
40
89
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.livekit.reactnative.audio
|
|
2
2
|
|
|
3
|
+
import android.media.AudioAttributes
|
|
3
4
|
import android.media.AudioManager
|
|
4
5
|
import android.util.Log
|
|
5
6
|
|
|
@@ -41,4 +42,75 @@ object AudioManagerUtils {
|
|
|
41
42
|
|
|
42
43
|
return focusMode
|
|
43
44
|
}
|
|
45
|
+
|
|
46
|
+
fun audioAttributesUsageTypeFromString(usageTypeString: String?): Int? {
|
|
47
|
+
if (usageTypeString == null) {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
val usageType: Int? = when (usageTypeString) {
|
|
52
|
+
"alarm" -> AudioAttributes.USAGE_ALARM
|
|
53
|
+
"assistanceAccessibility" -> AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY
|
|
54
|
+
"assistanceNavigationGuidance" -> AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
|
|
55
|
+
"assistanceSonification" -> AudioAttributes.USAGE_ASSISTANCE_SONIFICATION
|
|
56
|
+
"assistant" -> AudioAttributes.USAGE_ASSISTANT
|
|
57
|
+
"game" -> AudioAttributes.USAGE_GAME
|
|
58
|
+
"media" -> AudioAttributes.USAGE_MEDIA
|
|
59
|
+
"notification" -> AudioAttributes.USAGE_NOTIFICATION
|
|
60
|
+
"notificationEvent" -> AudioAttributes.USAGE_NOTIFICATION_EVENT
|
|
61
|
+
"notificationRingtone" -> AudioAttributes.USAGE_NOTIFICATION_RINGTONE
|
|
62
|
+
"unknown" -> AudioAttributes.USAGE_UNKNOWN
|
|
63
|
+
"voiceCommunication" -> AudioAttributes.USAGE_VOICE_COMMUNICATION
|
|
64
|
+
"voiceCommunicationSignalling" -> AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING
|
|
65
|
+
else -> {
|
|
66
|
+
Log.w(TAG, "Unknown audio attributes usage type: $usageTypeString")
|
|
67
|
+
null
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return usageType
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fun audioAttributesContentTypeFromString(contentTypeString: String?): Int? {
|
|
75
|
+
if (contentTypeString == null) {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
val contentType = when (contentTypeString) {
|
|
80
|
+
"movie" -> AudioAttributes.CONTENT_TYPE_MOVIE
|
|
81
|
+
"music" -> AudioAttributes.CONTENT_TYPE_MUSIC
|
|
82
|
+
"sonification" -> AudioAttributes.CONTENT_TYPE_SONIFICATION
|
|
83
|
+
"speech" -> AudioAttributes.CONTENT_TYPE_SPEECH
|
|
84
|
+
"unknown" -> AudioAttributes.CONTENT_TYPE_UNKNOWN
|
|
85
|
+
else -> {
|
|
86
|
+
Log.w(TAG, "Unknown audio attributes content type: $contentTypeString")
|
|
87
|
+
null
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return contentType
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fun audioStreamTypeFromString(streamTypeString: String?): Int? {
|
|
95
|
+
if (streamTypeString == null) {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
val streamType = when (streamTypeString) {
|
|
100
|
+
"accessibility" -> AudioManager.STREAM_ACCESSIBILITY
|
|
101
|
+
"alarm" -> AudioManager.STREAM_ALARM
|
|
102
|
+
"dtmf" -> AudioManager.STREAM_DTMF
|
|
103
|
+
"music" -> AudioManager.STREAM_MUSIC
|
|
104
|
+
"notification" -> AudioManager.STREAM_NOTIFICATION
|
|
105
|
+
"ring" -> AudioManager.STREAM_RING
|
|
106
|
+
"system" -> AudioManager.STREAM_SYSTEM
|
|
107
|
+
"voiceCall" -> AudioManager.STREAM_VOICE_CALL
|
|
108
|
+
else -> {
|
|
109
|
+
Log.w(TAG, "Unknown audio stream type: $streamTypeString")
|
|
110
|
+
null
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return streamType
|
|
115
|
+
}
|
|
44
116
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.livekit.reactnative.audio;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
|
+
import android.media.AudioAttributes;
|
|
4
5
|
import android.media.AudioManager;
|
|
5
6
|
import android.os.Handler;
|
|
6
7
|
import android.os.Looper;
|
|
@@ -14,6 +15,7 @@ import com.twilio.audioswitch.AudioSwitch;
|
|
|
14
15
|
import java.util.ArrayList;
|
|
15
16
|
import java.util.Collections;
|
|
16
17
|
import java.util.List;
|
|
18
|
+
import java.util.Objects;
|
|
17
19
|
|
|
18
20
|
import kotlin.Unit;
|
|
19
21
|
import kotlin.jvm.functions.Function2;
|
|
@@ -32,7 +34,8 @@ public class AudioSwitchManager {
|
|
|
32
34
|
Unit> audioDeviceChangeListener = (devices, currentDevice) -> null;
|
|
33
35
|
|
|
34
36
|
@NonNull
|
|
35
|
-
public AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = (i -> {
|
|
37
|
+
public AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = (i -> {
|
|
38
|
+
});
|
|
36
39
|
|
|
37
40
|
@NonNull
|
|
38
41
|
public List<Class<? extends AudioDevice>> preferredDeviceList;
|
|
@@ -43,6 +46,13 @@ public class AudioSwitchManager {
|
|
|
43
46
|
@Nullable
|
|
44
47
|
private AudioSwitch audioSwitch;
|
|
45
48
|
|
|
49
|
+
/**
|
|
50
|
+
* When true, AudioSwitchHandler will request audio focus on start and abandon on stop.
|
|
51
|
+
*
|
|
52
|
+
* Defaults to true.
|
|
53
|
+
*/
|
|
54
|
+
private boolean manageAudioFocus = true;
|
|
55
|
+
|
|
46
56
|
/**
|
|
47
57
|
* The audio focus mode to use while started.
|
|
48
58
|
*
|
|
@@ -53,9 +63,55 @@ public class AudioSwitchManager {
|
|
|
53
63
|
/**
|
|
54
64
|
* The audio mode to use while started.
|
|
55
65
|
*
|
|
56
|
-
* Defaults to
|
|
66
|
+
* Defaults to AudioManager.MODE_IN_COMMUNICATION.
|
|
67
|
+
*/
|
|
68
|
+
private int audioMode = AudioManager.MODE_IN_COMMUNICATION;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The audio stream type to use when requesting audio focus on pre-O devices.
|
|
72
|
+
*
|
|
73
|
+
* Defaults to [AudioManager.STREAM_VOICE_CALL].
|
|
74
|
+
*
|
|
75
|
+
* Refer to this [compatibility table](https://source.android.com/docs/core/audio/attributes#compatibility)
|
|
76
|
+
* to ensure that your values match between android versions.
|
|
77
|
+
*
|
|
78
|
+
* Note: Manual audio routing may not work appropriately when using non-default values.
|
|
57
79
|
*/
|
|
58
|
-
private int
|
|
80
|
+
private int audioStreamType = AudioManager.STREAM_VOICE_CALL;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The audio attribute usage type to use when requesting audio focus on devices O and beyond.
|
|
84
|
+
*
|
|
85
|
+
* Defaults to [AudioAttributes.USAGE_VOICE_COMMUNICATION].
|
|
86
|
+
*
|
|
87
|
+
* Refer to this [compatibility table](https://source.android.com/docs/core/audio/attributes#compatibility)
|
|
88
|
+
* to ensure that your values match between android versions.
|
|
89
|
+
*
|
|
90
|
+
* Note: Manual audio routing may not work appropriately when using non-default values.
|
|
91
|
+
*/
|
|
92
|
+
private int audioAttributeUsageType = AudioAttributes.USAGE_VOICE_COMMUNICATION;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The audio attribute content type to use when requesting audio focus on devices O and beyond.
|
|
96
|
+
*
|
|
97
|
+
* Defaults to [AudioAttributes.CONTENT_TYPE_SPEECH].
|
|
98
|
+
*
|
|
99
|
+
* Refer to this [compatibility table](https://source.android.com/docs/core/audio/attributes#compatibility)
|
|
100
|
+
* to ensure that your values match between android versions.
|
|
101
|
+
*
|
|
102
|
+
* Note: Manual audio routing may not work appropriately when using non-default values.
|
|
103
|
+
*/
|
|
104
|
+
private int audioAttributeContentType = AudioAttributes.CONTENT_TYPE_SPEECH;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* On certain Android devices, audio routing does not function properly and bluetooth microphones will not work
|
|
108
|
+
* unless audio mode is set to [AudioManager.MODE_IN_COMMUNICATION] or [AudioManager.MODE_IN_CALL].
|
|
109
|
+
*
|
|
110
|
+
* AudioSwitchManager by default will not handle audio routing in those cases to avoid audio issues.
|
|
111
|
+
*
|
|
112
|
+
* If this set to true, AudioSwitchManager will attempt to do audio routing, though behavior is undefined.
|
|
113
|
+
*/
|
|
114
|
+
private boolean forceHandleAudioRouting = false;
|
|
59
115
|
|
|
60
116
|
public AudioSwitchManager(@NonNull Context context) {
|
|
61
117
|
this.context = context;
|
|
@@ -78,8 +134,13 @@ public class AudioSwitchManager {
|
|
|
78
134
|
audioFocusChangeListener,
|
|
79
135
|
preferredDeviceList
|
|
80
136
|
);
|
|
137
|
+
audioSwitch.setManageAudioFocus(manageAudioFocus);
|
|
81
138
|
audioSwitch.setFocusMode(focusMode);
|
|
82
139
|
audioSwitch.setAudioMode(audioMode);
|
|
140
|
+
audioSwitch.setAudioStreamType(audioStreamType);
|
|
141
|
+
audioSwitch.setAudioAttributeContentType(audioAttributeContentType);
|
|
142
|
+
audioSwitch.setAudioAttributeUsageType(audioAttributeUsageType);
|
|
143
|
+
audioSwitch.setForceHandleAudioRouting(forceHandleAudioRouting);
|
|
83
144
|
audioSwitch.start(audioDeviceChangeListener);
|
|
84
145
|
audioSwitch.activate();
|
|
85
146
|
});
|
|
@@ -96,7 +157,7 @@ public class AudioSwitchManager {
|
|
|
96
157
|
});
|
|
97
158
|
}
|
|
98
159
|
|
|
99
|
-
public void setMicrophoneMute(boolean mute){
|
|
160
|
+
public void setMicrophoneMute(boolean mute) {
|
|
100
161
|
audioManager.setMicrophoneMute(mute);
|
|
101
162
|
}
|
|
102
163
|
|
|
@@ -141,24 +202,65 @@ public class AudioSwitchManager {
|
|
|
141
202
|
}
|
|
142
203
|
|
|
143
204
|
public void enableSpeakerphone(boolean enable) {
|
|
144
|
-
if(enable) {
|
|
205
|
+
if (enable) {
|
|
145
206
|
audioManager.setSpeakerphoneOn(true);
|
|
146
207
|
} else {
|
|
147
208
|
audioManager.setSpeakerphoneOn(false);
|
|
148
209
|
}
|
|
149
210
|
}
|
|
150
|
-
|
|
211
|
+
|
|
151
212
|
public void selectAudioOutput(@Nullable AudioDeviceKind kind) {
|
|
152
213
|
if (kind != null) {
|
|
153
214
|
selectAudioOutput(kind.audioDeviceClass);
|
|
154
215
|
}
|
|
155
216
|
}
|
|
156
217
|
|
|
218
|
+
public void setManageAudioFocus(boolean manage) {
|
|
219
|
+
this.manageAudioFocus = manage;
|
|
220
|
+
if (audioSwitch != null) {
|
|
221
|
+
Objects.requireNonNull(audioSwitch).setManageAudioFocus(this.manageAudioFocus);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
157
225
|
public void setFocusMode(int focusMode) {
|
|
158
226
|
this.focusMode = focusMode;
|
|
227
|
+
if (audioSwitch != null) {
|
|
228
|
+
Objects.requireNonNull(audioSwitch).setFocusMode(this.focusMode);
|
|
229
|
+
}
|
|
159
230
|
}
|
|
160
231
|
|
|
161
232
|
public void setAudioMode(int audioMode) {
|
|
162
233
|
this.audioMode = audioMode;
|
|
234
|
+
if (audioSwitch != null) {
|
|
235
|
+
Objects.requireNonNull(audioSwitch).setAudioMode(this.audioMode);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public void setAudioStreamType(int streamType) {
|
|
240
|
+
this.audioStreamType = streamType;
|
|
241
|
+
if (audioSwitch != null) {
|
|
242
|
+
Objects.requireNonNull(audioSwitch).setAudioStreamType(this.audioStreamType);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
public void setAudioAttributesUsageType(int usageType) {
|
|
247
|
+
this.audioAttributeUsageType = usageType;
|
|
248
|
+
if (audioSwitch != null) {
|
|
249
|
+
Objects.requireNonNull(audioSwitch).setAudioAttributeUsageType(this.audioAttributeUsageType);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
public void setAudioAttributesContentType(int contentType) {
|
|
254
|
+
this.audioAttributeContentType = contentType;
|
|
255
|
+
if (audioSwitch != null) {
|
|
256
|
+
Objects.requireNonNull(audioSwitch).setAudioAttributeContentType(this.audioAttributeContentType);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
public void setForceHandleAudioRouting(boolean force) {
|
|
261
|
+
this.forceHandleAudioRouting = force;
|
|
262
|
+
if (audioSwitch != null) {
|
|
263
|
+
Objects.requireNonNull(audioSwitch).setForceHandleAudioRouting(this.forceHandleAudioRouting);
|
|
264
|
+
}
|
|
163
265
|
}
|
|
164
266
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package com.livekit.reactnative.audio
|
|
2
|
+
|
|
3
|
+
import android.media.AudioAttributes
|
|
4
|
+
import android.media.AudioManager
|
|
5
|
+
|
|
6
|
+
sealed class AudioType(
|
|
7
|
+
val audioMode: Int,
|
|
8
|
+
val audioAttributes: AudioAttributes,
|
|
9
|
+
val audioStreamType: Int
|
|
10
|
+
) {
|
|
11
|
+
/**
|
|
12
|
+
* An audio type for general media playback usage (i.e. listener-only use cases).
|
|
13
|
+
*
|
|
14
|
+
* Audio routing is handled automatically by the system in normal media mode,
|
|
15
|
+
* and bluetooth microphones may not work on some devices.
|
|
16
|
+
*/
|
|
17
|
+
class MediaAudioType : AudioType(
|
|
18
|
+
AudioManager.MODE_NORMAL,
|
|
19
|
+
AudioAttributes.Builder()
|
|
20
|
+
.setUsage(AudioAttributes.USAGE_MEDIA)
|
|
21
|
+
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
|
22
|
+
.build(),
|
|
23
|
+
AudioManager.STREAM_MUSIC
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* An audio type for communications (i.e. participating a call or otherwise
|
|
28
|
+
* publishing local microphone).
|
|
29
|
+
*
|
|
30
|
+
* Audio routing can be manually controlled.
|
|
31
|
+
*/
|
|
32
|
+
class CommunicationAudioType : AudioType(
|
|
33
|
+
AudioManager.MODE_IN_COMMUNICATION,
|
|
34
|
+
AudioAttributes.Builder()
|
|
35
|
+
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
|
36
|
+
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
|
37
|
+
.build(),
|
|
38
|
+
AudioManager.STREAM_VOICE_CALL
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* An audio type that takes in a user-defined [AudioAttributes] and audio stream type.
|
|
43
|
+
*/
|
|
44
|
+
class CustomAudioType(audioMode: Int, audioAttributes: AudioAttributes, audioStreamType: Int) :
|
|
45
|
+
AudioType(audioMode, audioAttributes, audioStreamType)
|
|
46
|
+
}
|
package/android/src/main/java/com/livekit/reactnative/video/SimulcastVideoEncoderFactoryWrapper.kt
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.livekit.reactnative.video
|
|
2
2
|
|
|
3
3
|
import android.util.Log
|
|
4
|
+
import com.oney.WebRTCModule.webrtcutils.SoftwareVideoEncoderFactoryProxy
|
|
4
5
|
import org.webrtc.EglBase
|
|
5
6
|
import org.webrtc.HardwareVideoEncoderFactory
|
|
6
7
|
import org.webrtc.SimulcastVideoEncoderFactory
|
|
@@ -64,7 +65,7 @@ open class SimulcastVideoEncoderFactoryWrapper(
|
|
|
64
65
|
private class FallbackFactory(private val hardwareVideoEncoderFactory: VideoEncoderFactory) :
|
|
65
66
|
VideoEncoderFactory {
|
|
66
67
|
|
|
67
|
-
private val softwareVideoEncoderFactory: VideoEncoderFactory =
|
|
68
|
+
private val softwareVideoEncoderFactory: VideoEncoderFactory = SoftwareVideoEncoderFactoryProxy()
|
|
68
69
|
|
|
69
70
|
override fun createEncoder(info: VideoCodecInfo): VideoEncoder? {
|
|
70
71
|
val softwareEncoder = softwareVideoEncoderFactory.createEncoder(info)
|
package/ios/AudioUtils.h
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#if TARGET_OS_IPHONE
|
|
2
|
+
#import <AVFoundation/AVFoundation.h>
|
|
3
|
+
|
|
4
|
+
@interface AudioUtils : NSObject
|
|
5
|
+
+ (AVAudioSessionMode)audioSessionModeFromString:(NSString*)mode;
|
|
6
|
+
+ (AVAudioSessionCategory)audioSessionCategoryFromString:(NSString *)category;
|
|
7
|
+
@end
|
|
8
|
+
|
|
9
|
+
#endif
|
package/ios/AudioUtils.m
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#if TARGET_OS_IPHONE
|
|
2
|
+
#import "AudioUtils.h"
|
|
3
|
+
#import <AVFoundation/AVFoundation.h>
|
|
4
|
+
|
|
5
|
+
@implementation AudioUtils
|
|
6
|
+
|
|
7
|
+
+ (AVAudioSessionMode)audioSessionModeFromString:(NSString*)mode {
|
|
8
|
+
if([@"default_" isEqualToString:mode]) {
|
|
9
|
+
return AVAudioSessionModeDefault;
|
|
10
|
+
} else if([@"voicePrompt" isEqualToString:mode]) {
|
|
11
|
+
return AVAudioSessionModeVoicePrompt;
|
|
12
|
+
} else if([@"videoRecording" isEqualToString:mode]) {
|
|
13
|
+
return AVAudioSessionModeVideoRecording;
|
|
14
|
+
} else if([@"videoChat" isEqualToString:mode]) {
|
|
15
|
+
return AVAudioSessionModeVideoChat;
|
|
16
|
+
} else if([@"voiceChat" isEqualToString:mode]) {
|
|
17
|
+
return AVAudioSessionModeVoiceChat;
|
|
18
|
+
} else if([@"gameChat" isEqualToString:mode]) {
|
|
19
|
+
return AVAudioSessionModeGameChat;
|
|
20
|
+
} else if([@"measurement" isEqualToString:mode]) {
|
|
21
|
+
return AVAudioSessionModeMeasurement;
|
|
22
|
+
} else if([@"moviePlayback" isEqualToString:mode]) {
|
|
23
|
+
return AVAudioSessionModeMoviePlayback;
|
|
24
|
+
} else if([@"spokenAudio" isEqualToString:mode]) {
|
|
25
|
+
return AVAudioSessionModeSpokenAudio;
|
|
26
|
+
}
|
|
27
|
+
return AVAudioSessionModeDefault;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
+ (AVAudioSessionCategory)audioSessionCategoryFromString:(NSString *)category {
|
|
31
|
+
if([@"ambient" isEqualToString:category]) {
|
|
32
|
+
return AVAudioSessionCategoryAmbient;
|
|
33
|
+
} else if([@"soloAmbient" isEqualToString:category]) {
|
|
34
|
+
return AVAudioSessionCategorySoloAmbient;
|
|
35
|
+
} else if([@"playback" isEqualToString:category]) {
|
|
36
|
+
return AVAudioSessionCategoryPlayback;
|
|
37
|
+
} else if([@"record" isEqualToString:category]) {
|
|
38
|
+
return AVAudioSessionCategoryRecord;
|
|
39
|
+
} else if([@"playAndRecord" isEqualToString:category]) {
|
|
40
|
+
return AVAudioSessionCategoryPlayAndRecord;
|
|
41
|
+
} else if([@"multiRoute" isEqualToString:category]) {
|
|
42
|
+
return AVAudioSessionCategoryMultiRoute;
|
|
43
|
+
}
|
|
44
|
+
return AVAudioSessionCategoryAmbient;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@end
|
|
48
|
+
#endif
|