Skip to content

Commit 32cf80b

Browse files
authored
feat: android audio handling for non-communication audio modes (#97)
* feat: android audio handling for non-communication audio modes * fix: ci compile
1 parent 626d169 commit 32cf80b

13 files changed

Lines changed: 460 additions & 54 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class MainApplication extends Application implements ReactApplication {
4545
@Override
4646
public void onCreate() {
4747
// Place this above any other RN related initialization
48-
LiveKitReactNative.setup();
48+
LiveKitReactNative.setup(this);
4949
5050
//...
5151
}

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ dependencies {
129129
// noinspection GradleDynamicVersion
130130
api 'com.facebook.react:react-native:+'
131131
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
132-
api 'com.github.davidliu:audioswitch:1689af118f69dcd8c8dc95e5a711dd0a7a626e69'
132+
api 'com.github.davidliu:audioswitch:d18e3e31d427c27f1593030e024b370bf24480fd'
133133
api 'io.github.webrtc-sdk:android:104.5112.10'
134134
implementation project(':livekit_react-native-webrtc')
135135
implementation "androidx.annotation:annotation:1.4.0"
Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
11
package com.livekit.reactnative
22

3+
import android.app.Application
4+
import android.content.Context
5+
import android.os.Build
6+
import com.livekit.reactnative.audio.AudioType
37
import com.livekit.reactnative.video.SimulcastVideoEncoderFactoryWrapper
48
import com.livekit.reactnative.video.WrappedVideoDecoderFactoryProxy
59
import com.oney.WebRTCModule.WebRTCModuleOptions
6-
10+
import org.webrtc.audio.JavaAudioDeviceModule
711

812
object LiveKitReactNative {
913

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+
*/
1020
@JvmStatic
11-
fun setup() {
21+
@JvmOverloads
22+
fun setup(context: Context, audioType: AudioType = AudioType.CommunicationAudioType()) {
1223
val options = WebRTCModuleOptions.getInstance()
1324
options.videoEncoderFactory = SimulcastVideoEncoderFactoryWrapper(null, true, true)
1425
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()
1534
}
1635
}

android/src/main/java/com/livekit/reactnative/LivekitReactNativeModule.kt

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.livekit.reactnative
22

3+
import android.annotation.SuppressLint
4+
import android.content.Context
35
import com.facebook.react.bridge.*
46
import com.livekit.reactnative.audio.AudioDeviceKind
57
import com.livekit.reactnative.audio.AudioManagerUtils
@@ -17,24 +19,71 @@ class LivekitReactNativeModule(reactContext: ReactApplicationContext) : ReactCon
1719
fun configureAudio(config: ReadableMap) {
1820
val androidConfig = config.getMap("android") ?: return
1921

20-
androidConfig.getArray("preferredOutputList")?.let { preferredOutputList ->
21-
val preferredDeviceList = preferredOutputList.toArrayList().mapNotNull { output ->
22-
val outputStr = output as? String
23-
AudioDeviceKind.fromTypeName(outputStr)?.audioDeviceClass
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
2429
}
25-
audioManager.preferredDeviceList = preferredDeviceList
2630
}
2731

28-
androidConfig.getString("audioMode")?.let { audioModeString ->
29-
val audioMode = AudioManagerUtils.audioModeFromString(audioModeString)
30-
if (audioMode != null) {
31-
audioManager.setAudioMode(audioMode)
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)
3238
}
33-
}
34-
androidConfig.getString("audioFocusMode")?.let { focusModeString ->
35-
val focusMode = AudioManagerUtils.focusModeFromString(focusModeString)
36-
if (focusMode != null) {
37-
audioManager.setFocusMode(focusMode)
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)
3887
}
3988
}
4089
}

android/src/main/java/com/livekit/reactnative/audio/AudioManagerUtils.kt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.livekit.reactnative.audio
22

3+
import android.media.AudioAttributes
34
import android.media.AudioManager
45
import android.util.Log
56

@@ -41,4 +42,75 @@ object AudioManagerUtils {
4142

4243
return focusMode
4344
}
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+
}
44116
}

0 commit comments

Comments
 (0)