@livekit/react-native 1.1.2 → 1.3.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 +45 -5
- 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 +68 -5
- package/android/src/main/java/com/livekit/reactnative/audio/AudioManagerUtils.kt +116 -0
- package/android/src/main/java/com/livekit/reactnative/audio/AudioSwitchManager.java +130 -4
- package/android/src/main/java/com/livekit/reactnative/audio/AudioType.kt +46 -0
- package/ios/LivekitReactNative.xcodeproj/project.xcworkspace/xcuserdata/davidliu.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/lib/commonjs/audio/AudioSession.js +30 -5
- package/lib/commonjs/audio/AudioSession.js.map +1 -1
- package/lib/commonjs/index.js +35 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/audio/AudioSession.js +27 -4
- package/lib/module/audio/AudioSession.js.map +1 -1
- package/lib/module/index.js +17 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/audio/AudioSession.d.ts +80 -11
- package/lib/typescript/index.d.ts +3 -2
- package/package.json +5 -2
- package/src/audio/AudioSession.ts +140 -11
- package/src/index.tsx +23 -2
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ This library depends on `@livekit/react-native-webrtc`, which has additional ins
|
|
|
29
29
|
- [iOS Installation Guide](https://github.com/livekit/react-native-webrtc/blob/master/Documentation/iOSInstallation.md)
|
|
30
30
|
- [Android Installation Guide](https://github.com/livekit/react-native-webrtc/blob/master/Documentation/AndroidInstallation.md)
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
---
|
|
33
33
|
|
|
34
34
|
Once the `@livekit/react-native-webrtc` dependency is installed, one last step is needed to finish the installation:
|
|
35
35
|
|
|
@@ -45,7 +45,7 @@ public class MainApplication extends Application implements ReactApplication {
|
|
|
45
45
|
@Override
|
|
46
46
|
public void onCreate() {
|
|
47
47
|
// Place this above any other RN related initialization
|
|
48
|
-
LiveKitReactNative.setup();
|
|
48
|
+
LiveKitReactNative.setup(this);
|
|
49
49
|
|
|
50
50
|
//...
|
|
51
51
|
}
|
|
@@ -106,8 +106,12 @@ const [room] = useState(() => new Room());
|
|
|
106
106
|
const { participants } = useRoom(room);
|
|
107
107
|
|
|
108
108
|
useEffect(() => {
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
let connect = async () => {
|
|
110
|
+
await AudioSession.startAudioSession();
|
|
111
|
+
await room.connect(url, token, {});
|
|
112
|
+
console.log('connected to ', url, ' ', token);
|
|
113
|
+
};
|
|
114
|
+
connect();
|
|
111
115
|
return () => {
|
|
112
116
|
room.disconnect();
|
|
113
117
|
AudioSession.stopAudioSession();
|
|
@@ -126,6 +130,40 @@ const videoView = participants.length > 0 && (
|
|
|
126
130
|
|
|
127
131
|
Additional documentation for the LiveKit SDK can be found at https://docs.livekit.io/references/client-sdks/
|
|
128
132
|
|
|
133
|
+
## Audio sessions
|
|
134
|
+
|
|
135
|
+
As seen in the above example, we've introduced a new class `AudioSession` that helps
|
|
136
|
+
to manage the audio session on native platforms. This class wraps either [AudioManager](https://developer.android.com/reference/android/media/AudioManager) on Android, or [AVAudioSession](https://developer.apple.com/documentation/avfaudio/avaudiosession) on iOS.
|
|
137
|
+
|
|
138
|
+
You can customize the configuration of the audio session with `configureAudio`.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
let connect = async () => {
|
|
143
|
+
// configure audio session prior to starting it.
|
|
144
|
+
await AudioSession.configureAudio({
|
|
145
|
+
android: {
|
|
146
|
+
preferredOutputList: ['earpiece'],
|
|
147
|
+
// See [AudioManager](https://developer.android.com/reference/android/media/AudioManager)
|
|
148
|
+
// for details on audio and focus modes.
|
|
149
|
+
audioMode: 'normal',
|
|
150
|
+
audioFocusMode: 'gain',
|
|
151
|
+
},
|
|
152
|
+
ios: {
|
|
153
|
+
defaultOutput: 'earpiece',
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
await AudioSession.startAudioSession();
|
|
157
|
+
await room.connect(url, token, {});
|
|
158
|
+
};
|
|
159
|
+
connect();
|
|
160
|
+
return () => {
|
|
161
|
+
room.disconnect();
|
|
162
|
+
AudioSession.stopAudioSession();
|
|
163
|
+
};
|
|
164
|
+
}, [url, token, room]);
|
|
165
|
+
```
|
|
166
|
+
|
|
129
167
|
## Screenshare
|
|
130
168
|
|
|
131
169
|
Enabling screenshare requires extra installation steps:
|
|
@@ -196,10 +234,12 @@ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the
|
|
|
196
234
|
Apache License 2.0
|
|
197
235
|
|
|
198
236
|
<!--BEGIN_REPO_NAV-->
|
|
237
|
+
|
|
199
238
|
<br/><table>
|
|
239
|
+
|
|
200
240
|
<thead><tr><th colspan="2">LiveKit Ecosystem</th></tr></thead>
|
|
201
241
|
<tbody>
|
|
202
|
-
<tr><td>Client SDKs</td><td><a href="https://github.com/livekit/components-js">Components</a> · <a href="https://github.com/livekit/client-sdk-js">JavaScript</a> · <a href="https://github.com/livekit/client-sdk-
|
|
242
|
+
<tr><td>Client SDKs</td><td><a href="https://github.com/livekit/components-js">Components</a> · <a href="https://github.com/livekit/client-sdk-js">JavaScript</a> · <a href="https://github.com/livekit/client-sdk-swift">iOS/macOS</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/client-sdk-rust">Rust</a> · <a href="https://github.com/livekit/client-sdk-python">Python</a> · <a href="https://github.com/livekit/client-sdk-unity-web">Unity (web)</a> · <a href="https://github.com/livekit/client-sdk-unity">Unity (beta)</a></td></tr><tr></tr>
|
|
203
243
|
<tr><td>Server SDKs</td><td><a href="https://github.com/livekit/server-sdk-js">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/agence104/livekit-server-sdk-php">PHP (community)</a> · <a href="https://github.com/tradablebits/livekit-server-sdk-python">Python (community)</a></td></tr><tr></tr>
|
|
204
244
|
<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></td></tr><tr></tr>
|
|
205
245
|
<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/oss/deployment">Self-hosting</a> · <a href="https://github.com/livekit/livekit-cli">CLI</a></td></tr>
|
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:104.5112.
|
|
132
|
+
api 'com.github.davidliu:audioswitch:d18e3e31d427c27f1593030e024b370bf24480fd'
|
|
133
|
+
api 'io.github.webrtc-sdk:android:104.5112.10'
|
|
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,7 +1,10 @@
|
|
|
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
|
|
7
|
+
import com.livekit.reactnative.audio.AudioManagerUtils
|
|
5
8
|
import com.livekit.reactnative.audio.AudioSwitchManager
|
|
6
9
|
|
|
7
10
|
|
|
@@ -16,12 +19,72 @@ class LivekitReactNativeModule(reactContext: ReactApplicationContext) : ReactCon
|
|
|
16
19
|
fun configureAudio(config: ReadableMap) {
|
|
17
20
|
val androidConfig = config.getMap("android") ?: return
|
|
18
21
|
|
|
19
|
-
androidConfig.
|
|
20
|
-
|
|
21
|
-
val
|
|
22
|
-
|
|
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
|
|
29
|
+
}
|
|
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)
|
|
38
|
+
}
|
|
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)
|
|
23
87
|
}
|
|
24
|
-
audioManager.preferredDeviceList = preferredDeviceList
|
|
25
88
|
}
|
|
26
89
|
}
|
|
27
90
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
package com.livekit.reactnative.audio
|
|
2
|
+
|
|
3
|
+
import android.media.AudioAttributes
|
|
4
|
+
import android.media.AudioManager
|
|
5
|
+
import android.util.Log
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
object AudioManagerUtils {
|
|
9
|
+
private const val TAG = "AudioManagerUtils"
|
|
10
|
+
|
|
11
|
+
fun audioModeFromString(audioModeString: String?): Int? {
|
|
12
|
+
if (audioModeString == null) {
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
var audioMode: Int? = null
|
|
17
|
+
when (audioModeString) {
|
|
18
|
+
"normal" -> audioMode = AudioManager.MODE_NORMAL
|
|
19
|
+
"callScreening" -> audioMode = AudioManager.MODE_CALL_SCREENING
|
|
20
|
+
"inCall" -> audioMode = AudioManager.MODE_IN_CALL
|
|
21
|
+
"inCommunication" -> audioMode = AudioManager.MODE_IN_COMMUNICATION
|
|
22
|
+
"ringtone" -> audioMode = AudioManager.MODE_RINGTONE
|
|
23
|
+
else -> Log.w(TAG, "Unknown audio mode: $audioModeString")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return audioMode
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fun focusModeFromString(focusModeString: String?): Int? {
|
|
30
|
+
if (focusModeString == null) {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var focusMode: Int? = null
|
|
35
|
+
when (focusModeString) {
|
|
36
|
+
"gain" -> focusMode = AudioManager.AUDIOFOCUS_GAIN
|
|
37
|
+
"gainTransient" -> focusMode = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
|
|
38
|
+
"gainTransientExclusive" -> focusMode = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
|
|
39
|
+
"gainTransientMayDuck" -> focusMode = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
|
|
40
|
+
else -> Log.w(TAG, "Unknown audio focus mode: $focusModeString")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return focusMode
|
|
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
|
+
}
|
|
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,73 @@ 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
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The audio focus mode to use while started.
|
|
58
|
+
*
|
|
59
|
+
* Defaults to [AudioManager.AUDIOFOCUS_GAIN].
|
|
60
|
+
*/
|
|
61
|
+
private int focusMode = AudioManager.AUDIOFOCUS_GAIN;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The audio mode to use while started.
|
|
65
|
+
*
|
|
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.
|
|
79
|
+
*/
|
|
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;
|
|
115
|
+
|
|
46
116
|
public AudioSwitchManager(@NonNull Context context) {
|
|
47
117
|
this.context = context;
|
|
48
118
|
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
|
@@ -64,6 +134,13 @@ public class AudioSwitchManager {
|
|
|
64
134
|
audioFocusChangeListener,
|
|
65
135
|
preferredDeviceList
|
|
66
136
|
);
|
|
137
|
+
audioSwitch.setManageAudioFocus(manageAudioFocus);
|
|
138
|
+
audioSwitch.setFocusMode(focusMode);
|
|
139
|
+
audioSwitch.setAudioMode(audioMode);
|
|
140
|
+
audioSwitch.setAudioStreamType(audioStreamType);
|
|
141
|
+
audioSwitch.setAudioAttributeContentType(audioAttributeContentType);
|
|
142
|
+
audioSwitch.setAudioAttributeUsageType(audioAttributeUsageType);
|
|
143
|
+
audioSwitch.setForceHandleAudioRouting(forceHandleAudioRouting);
|
|
67
144
|
audioSwitch.start(audioDeviceChangeListener);
|
|
68
145
|
audioSwitch.activate();
|
|
69
146
|
});
|
|
@@ -80,7 +157,7 @@ public class AudioSwitchManager {
|
|
|
80
157
|
});
|
|
81
158
|
}
|
|
82
159
|
|
|
83
|
-
public void setMicrophoneMute(boolean mute){
|
|
160
|
+
public void setMicrophoneMute(boolean mute) {
|
|
84
161
|
audioManager.setMicrophoneMute(mute);
|
|
85
162
|
}
|
|
86
163
|
|
|
@@ -125,16 +202,65 @@ public class AudioSwitchManager {
|
|
|
125
202
|
}
|
|
126
203
|
|
|
127
204
|
public void enableSpeakerphone(boolean enable) {
|
|
128
|
-
if(enable) {
|
|
205
|
+
if (enable) {
|
|
129
206
|
audioManager.setSpeakerphoneOn(true);
|
|
130
207
|
} else {
|
|
131
208
|
audioManager.setSpeakerphoneOn(false);
|
|
132
209
|
}
|
|
133
210
|
}
|
|
134
|
-
|
|
211
|
+
|
|
135
212
|
public void selectAudioOutput(@Nullable AudioDeviceKind kind) {
|
|
136
213
|
if (kind != null) {
|
|
137
214
|
selectAudioOutput(kind.audioDeviceClass);
|
|
138
215
|
}
|
|
139
216
|
}
|
|
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
|
+
|
|
225
|
+
public void setFocusMode(int focusMode) {
|
|
226
|
+
this.focusMode = focusMode;
|
|
227
|
+
if (audioSwitch != null) {
|
|
228
|
+
Objects.requireNonNull(audioSwitch).setFocusMode(this.focusMode);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
public void setAudioMode(int audioMode) {
|
|
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
|
+
}
|
|
265
|
+
}
|
|
140
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
|
+
}
|
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.default = void 0;
|
|
6
|
+
exports.default = exports.AndroidAudioTypePresets = void 0;
|
|
7
7
|
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
|
|
@@ -29,10 +29,15 @@ const LivekitReactNative = _reactNative.NativeModules.LivekitReactNative ? _reac
|
|
|
29
29
|
* This is ignored when an output is manually selected with {@link AudioSession.selectAudioOutput}.
|
|
30
30
|
*
|
|
31
31
|
* By default, the order is set to:
|
|
32
|
-
* 1. `"
|
|
33
|
-
* 2. `"
|
|
34
|
-
* 3. `"
|
|
35
|
-
* 4. `"
|
|
32
|
+
* 1. `"bluetooth"
|
|
33
|
+
* 2. `"headset"``
|
|
34
|
+
* 3. `"speaker"`
|
|
35
|
+
* 4. `"earpiece"`
|
|
36
|
+
*
|
|
37
|
+
* * audioTypeOptions - An {@link AndroidAudioTypeOptions} object which provides the
|
|
38
|
+
* audio options to use on Android.
|
|
39
|
+
*
|
|
40
|
+
* See {@link AndroidAudioTypePresets} for pre-configured values.
|
|
36
41
|
*
|
|
37
42
|
* ----
|
|
38
43
|
* iOS
|
|
@@ -42,6 +47,26 @@ const LivekitReactNative = _reactNative.NativeModules.LivekitReactNative ? _reac
|
|
|
42
47
|
* By default, this is set to `"speaker"`
|
|
43
48
|
*/
|
|
44
49
|
|
|
50
|
+
const AndroidAudioTypePresets = {
|
|
51
|
+
communication: {
|
|
52
|
+
manageAudioFocus: true,
|
|
53
|
+
audioMode: 'inCommunication',
|
|
54
|
+
audioFocusMode: 'gain',
|
|
55
|
+
audioStreamType: 'voiceCall',
|
|
56
|
+
audioAttributesUsageType: 'voiceCommunication',
|
|
57
|
+
audioAttributesContentType: 'speech'
|
|
58
|
+
},
|
|
59
|
+
media: {
|
|
60
|
+
manageAudioFocus: true,
|
|
61
|
+
audioMode: 'normal',
|
|
62
|
+
audioFocusMode: 'gain',
|
|
63
|
+
audioStreamType: 'music',
|
|
64
|
+
audioAttributesUsageType: 'media',
|
|
65
|
+
audioAttributesContentType: 'unknown'
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
exports.AndroidAudioTypePresets = AndroidAudioTypePresets;
|
|
69
|
+
|
|
45
70
|
class AudioSession {}
|
|
46
71
|
|
|
47
72
|
exports.default = AudioSession;
|