@stream-io/video-react-native-sdk 1.21.2 → 1.22.1-alpha.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/android/gradle.properties +3 -3
- package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt +10 -0
- package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativePackage.kt +2 -1
- package/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt +592 -0
- package/android/src/main/java/com/streamvideo/reactnative/audio/BluetoothManager.kt +755 -0
- package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioDeviceEndpointUtils.kt +192 -0
- package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt +62 -0
- package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioManagerUtil.kt +159 -0
- package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioSetupStoreUtil.kt +41 -0
- package/android/src/main/java/com/streamvideo/reactnative/audio/utils/WebRtcAudioUtils.kt +250 -0
- package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +191 -0
- package/android/src/main/java/com/streamvideo/reactnative/model/AudioDeviceEndpoint.kt +136 -0
- package/android/src/main/java/org/webrtc/audio/WebRtcAudioTrackHelper.kt +12 -0
- package/dist/commonjs/components/Call/CallContent/CallContent.js +13 -9
- package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +9 -2
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
- package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js +3 -0
- package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
- package/dist/commonjs/components/Livestream/HostLivestream/HostLivestream.js +10 -6
- package/dist/commonjs/components/Livestream/HostLivestream/HostLivestream.js.map +1 -1
- package/dist/commonjs/components/Livestream/LivestreamControls/ViewerLivestreamControls.js +8 -4
- package/dist/commonjs/components/Livestream/LivestreamControls/ViewerLivestreamControls.js.map +1 -1
- package/dist/commonjs/components/Livestream/LivestreamLayout/LivestreamLayout.js +26 -27
- package/dist/commonjs/components/Livestream/LivestreamLayout/LivestreamLayout.js.map +1 -1
- package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLivestream.js +10 -6
- package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLivestream.js.map +1 -1
- package/dist/commonjs/hooks/useIsInPiPMode.js +3 -3
- package/dist/commonjs/hooks/useIsInPiPMode.js.map +1 -1
- package/dist/commonjs/hooks/usePermissionNotification.js +5 -5
- package/dist/commonjs/hooks/usePermissionNotification.js.map +1 -1
- package/dist/commonjs/index.js +12 -0
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/modules/call-manager/CallManager.js +113 -0
- package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -0
- package/dist/commonjs/modules/call-manager/PrevLibDetection.js +18 -0
- package/dist/commonjs/modules/call-manager/PrevLibDetection.js.map +1 -0
- package/dist/commonjs/modules/call-manager/index.js +24 -0
- package/dist/commonjs/modules/call-manager/index.js.map +1 -0
- package/dist/commonjs/modules/call-manager/native-module.d.js +4 -0
- package/dist/commonjs/modules/call-manager/native-module.d.js.map +1 -0
- package/dist/commonjs/modules/call-manager/types.js +2 -0
- package/dist/commonjs/modules/call-manager/types.js.map +1 -0
- package/dist/commonjs/providers/StreamCall/AppStateListener.js +5 -5
- package/dist/commonjs/providers/StreamCall/AppStateListener.js.map +1 -1
- package/dist/commonjs/theme/theme.js.map +1 -1
- package/dist/commonjs/utils/internal/rxSubjects.js +2 -2
- package/dist/commonjs/utils/internal/rxSubjects.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/commonjs/version.js.map +1 -1
- package/dist/module/components/Call/CallContent/CallContent.js +14 -9
- package/dist/module/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js +9 -2
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
- package/dist/module/components/Call/CallContent/RTCViewPipNative.js +3 -0
- package/dist/module/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
- package/dist/module/components/Livestream/HostLivestream/HostLivestream.js +10 -5
- package/dist/module/components/Livestream/HostLivestream/HostLivestream.js.map +1 -1
- package/dist/module/components/Livestream/LivestreamControls/ViewerLivestreamControls.js +9 -4
- package/dist/module/components/Livestream/LivestreamControls/ViewerLivestreamControls.js.map +1 -1
- package/dist/module/components/Livestream/LivestreamLayout/LivestreamLayout.js +26 -27
- package/dist/module/components/Livestream/LivestreamLayout/LivestreamLayout.js.map +1 -1
- package/dist/module/components/Livestream/ViewerLivestream/ViewerLivestream.js +10 -5
- package/dist/module/components/Livestream/ViewerLivestream/ViewerLivestream.js.map +1 -1
- package/dist/module/hooks/useIsInPiPMode.js +4 -4
- package/dist/module/hooks/useIsInPiPMode.js.map +1 -1
- package/dist/module/hooks/usePermissionNotification.js +7 -6
- package/dist/module/hooks/usePermissionNotification.js.map +1 -1
- package/dist/module/icons/Back.js +1 -1
- package/dist/module/icons/Back.js.map +1 -1
- package/dist/module/icons/CameraSwitch.js +1 -1
- package/dist/module/icons/CameraSwitch.js.map +1 -1
- package/dist/module/icons/Mic.js +1 -1
- package/dist/module/icons/Mic.js.map +1 -1
- package/dist/module/icons/MicOff.js +1 -1
- package/dist/module/icons/MicOff.js.map +1 -1
- package/dist/module/icons/Phone.js +1 -1
- package/dist/module/icons/Phone.js.map +1 -1
- package/dist/module/icons/PinVertical.js +1 -1
- package/dist/module/icons/PinVertical.js.map +1 -1
- package/dist/module/icons/Reaction.js +1 -1
- package/dist/module/icons/Reaction.js.map +1 -1
- package/dist/module/icons/Spotlight.js +1 -1
- package/dist/module/icons/Spotlight.js.map +1 -1
- package/dist/module/icons/Video.js +1 -1
- package/dist/module/icons/Video.js.map +1 -1
- package/dist/module/icons/VideoSlash.js +1 -1
- package/dist/module/icons/VideoSlash.js.map +1 -1
- package/dist/module/index.js +1 -0
- package/dist/module/index.js.map +1 -1
- package/dist/module/modules/call-manager/CallManager.js +106 -0
- package/dist/module/modules/call-manager/CallManager.js.map +1 -0
- package/dist/module/modules/call-manager/PrevLibDetection.js +12 -0
- package/dist/module/modules/call-manager/PrevLibDetection.js.map +1 -0
- package/dist/module/modules/call-manager/index.js +4 -0
- package/dist/module/modules/call-manager/index.js.map +1 -0
- package/dist/module/modules/call-manager/native-module.d.js +2 -0
- package/dist/module/modules/call-manager/native-module.d.js.map +1 -0
- package/dist/module/modules/call-manager/types.js +2 -0
- package/dist/module/modules/call-manager/types.js.map +1 -0
- package/dist/module/providers/StreamCall/AppStateListener.js +6 -6
- package/dist/module/providers/StreamCall/AppStateListener.js.map +1 -1
- package/dist/module/theme/theme.js.map +1 -1
- package/dist/module/utils/internal/rxSubjects.js +1 -1
- package/dist/module/utils/internal/rxSubjects.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/module/version.js.map +1 -1
- package/dist/typescript/components/Call/CallContent/CallContent.d.ts +3 -2
- package/dist/typescript/components/Call/CallContent/CallContent.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts +5 -0
- package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts +6 -0
- package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/HostLivestream/HostLivestream.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/LivestreamControls/ViewerLivestreamControls.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/LivestreamLayout/LivestreamLayout.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/ViewerLivestream/ViewerLivestream.d.ts.map +1 -1
- package/dist/typescript/hooks/usePermissionNotification.d.ts.map +1 -1
- package/dist/typescript/icons/Back.d.ts +1 -1
- package/dist/typescript/icons/Back.d.ts.map +1 -1
- package/dist/typescript/icons/BadNetwork.d.ts +1 -1
- package/dist/typescript/icons/BadNetwork.d.ts.map +1 -1
- package/dist/typescript/icons/CameraSwitch.d.ts +1 -1
- package/dist/typescript/icons/CameraSwitch.d.ts.map +1 -1
- package/dist/typescript/icons/LivestreamControls.d.ts +1 -1
- package/dist/typescript/icons/LivestreamControls.d.ts.map +1 -1
- package/dist/typescript/icons/Lock.d.ts +1 -1
- package/dist/typescript/icons/Lock.d.ts.map +1 -1
- package/dist/typescript/icons/Maximize.d.ts +1 -1
- package/dist/typescript/icons/Maximize.d.ts.map +1 -1
- package/dist/typescript/icons/Mic.d.ts +1 -1
- package/dist/typescript/icons/Mic.d.ts.map +1 -1
- package/dist/typescript/icons/MicOff.d.ts +1 -1
- package/dist/typescript/icons/MicOff.d.ts.map +1 -1
- package/dist/typescript/icons/Phone.d.ts +1 -1
- package/dist/typescript/icons/Phone.d.ts.map +1 -1
- package/dist/typescript/icons/PhoneDown.d.ts +1 -1
- package/dist/typescript/icons/PhoneDown.d.ts.map +1 -1
- package/dist/typescript/icons/PinVertical.d.ts +1 -1
- package/dist/typescript/icons/PinVertical.d.ts.map +1 -1
- package/dist/typescript/icons/Reaction.d.ts +1 -1
- package/dist/typescript/icons/Reaction.d.ts.map +1 -1
- package/dist/typescript/icons/ScreenShare.d.ts +1 -1
- package/dist/typescript/icons/ScreenShare.d.ts.map +1 -1
- package/dist/typescript/icons/ScreenShareIndicator.d.ts +1 -1
- package/dist/typescript/icons/ScreenShareIndicator.d.ts.map +1 -1
- package/dist/typescript/icons/Spotlight.d.ts +1 -1
- package/dist/typescript/icons/Spotlight.d.ts.map +1 -1
- package/dist/typescript/icons/StopScreenShare.d.ts +1 -1
- package/dist/typescript/icons/StopScreenShare.d.ts.map +1 -1
- package/dist/typescript/icons/Video.d.ts +1 -1
- package/dist/typescript/icons/Video.d.ts.map +1 -1
- package/dist/typescript/icons/VideoSlash.d.ts +1 -1
- package/dist/typescript/icons/VideoSlash.d.ts.map +1 -1
- package/dist/typescript/index.d.ts +1 -0
- package/dist/typescript/index.d.ts.map +1 -1
- package/dist/typescript/modules/call-manager/CallManager.d.ts +67 -0
- package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -0
- package/dist/typescript/modules/call-manager/PrevLibDetection.d.ts +13 -0
- package/dist/typescript/modules/call-manager/PrevLibDetection.d.ts.map +1 -0
- package/dist/typescript/modules/call-manager/index.d.ts +4 -0
- package/dist/typescript/modules/call-manager/index.d.ts.map +1 -0
- package/dist/typescript/modules/call-manager/types.d.ts +15 -0
- package/dist/typescript/modules/call-manager/types.d.ts.map +1 -0
- package/dist/typescript/providers/StreamCall/AppStateListener.d.ts.map +1 -1
- package/dist/typescript/theme/theme.d.ts +1 -2
- package/dist/typescript/theme/theme.d.ts.map +1 -1
- package/dist/typescript/utils/internal/rxSubjects.d.ts +1 -1
- package/dist/typescript/utils/internal/rxSubjects.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/dist/typescript/version.d.ts.map +1 -1
- package/ios/PictureInPicture/StreamPictureInPictureController.swift +5 -0
- package/ios/RTCViewPip.swift +15 -0
- package/ios/RTCViewPipManager.mm +1 -0
- package/ios/StreamInCallManager.m +26 -0
- package/ios/StreamInCallManager.swift +303 -0
- package/ios/StreamVideoReactNative-Bridging-Header.h +1 -0
- package/ios/StreamVideoReactNative.m +6 -5
- package/ios/StreamVideoReactNative.xcodeproj/project.xcworkspace/xcuserdata/santhoshvaiyapuri.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/StreamVideoReactNative.xcodeproj/xcuserdata/santhoshvaiyapuri.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/package.json +33 -35
- package/src/components/Call/CallContent/CallContent.tsx +14 -10
- package/src/components/Call/CallContent/RTCViewPipIOS.tsx +17 -2
- package/src/components/Call/CallContent/RTCViewPipNative.tsx +8 -0
- package/src/components/Livestream/HostLivestream/HostLivestream.tsx +8 -3
- package/src/components/Livestream/LivestreamControls/ViewerLivestreamControls.tsx +11 -5
- package/src/components/Livestream/LivestreamLayout/LivestreamLayout.tsx +38 -29
- package/src/components/Livestream/ViewerLivestream/ViewerLivestream.tsx +8 -3
- package/src/hooks/useIsInPiPMode.tsx +4 -4
- package/src/hooks/usePermissionNotification.tsx +7 -12
- package/src/icons/Back.tsx +2 -2
- package/src/icons/BadNetwork.tsx +1 -1
- package/src/icons/CameraSwitch.tsx +2 -2
- package/src/icons/LivestreamControls.tsx +1 -1
- package/src/icons/Lock.tsx +1 -1
- package/src/icons/Maximize.tsx +1 -1
- package/src/icons/Mic.tsx +2 -2
- package/src/icons/MicOff.tsx +2 -2
- package/src/icons/Phone.tsx +2 -2
- package/src/icons/PhoneDown.tsx +1 -1
- package/src/icons/PinVertical.tsx +2 -2
- package/src/icons/Reaction.tsx +2 -2
- package/src/icons/ScreenShare.tsx +1 -1
- package/src/icons/ScreenShareIndicator.tsx +1 -1
- package/src/icons/Spotlight.tsx +2 -2
- package/src/icons/StopScreenShare.tsx +1 -1
- package/src/icons/Video.tsx +2 -2
- package/src/icons/VideoSlash.tsx +2 -2
- package/src/index.ts +1 -0
- package/src/modules/call-manager/CallManager.ts +116 -0
- package/src/modules/call-manager/PrevLibDetection.ts +27 -0
- package/src/modules/call-manager/index.ts +5 -0
- package/src/modules/call-manager/native-module.d.ts +80 -0
- package/src/modules/call-manager/types.ts +25 -0
- package/src/providers/StreamCall/AppStateListener.tsx +6 -9
- package/src/theme/theme.ts +2 -2
- package/src/utils/internal/rxSubjects.ts +1 -1
- package/src/version.ts +1 -1
- package/CHANGELOG.md +0 -2783
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
StreamVideoReactNative_kotlinVersion=1.9.25
|
|
2
2
|
StreamVideoReactNative_minSdkVersion=24
|
|
3
|
-
StreamVideoReactNative_targetSdkVersion=
|
|
4
|
-
StreamVideoReactNative_compileSdkVersion=
|
|
5
|
-
StreamVideoReactNative_ndkversion=
|
|
3
|
+
StreamVideoReactNative_targetSdkVersion=36
|
|
4
|
+
StreamVideoReactNative_compileSdkVersion=36
|
|
5
|
+
StreamVideoReactNative_ndkversion=27.1.12297006
|
|
@@ -82,6 +82,7 @@ class StreamVideoReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
82
82
|
})
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
|
|
85
86
|
@ReactMethod
|
|
86
87
|
fun getDefaultRingtoneUrl(promise: Promise) {
|
|
87
88
|
val defaultRingtoneUri: Uri? =
|
|
@@ -131,6 +132,15 @@ class StreamVideoReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
131
132
|
fun removeListeners(count: Int) {
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
// This method was removed upstream in react-native 0.74+, replaced with invalidate
|
|
136
|
+
// We will leave this stub here for older react-native versions compatibility
|
|
137
|
+
// ...but it will just delegate to the new invalidate method
|
|
138
|
+
@Deprecated("Deprecated in Java", ReplaceWith("invalidate()"))
|
|
139
|
+
@Suppress("removal")
|
|
140
|
+
override fun onCatalystInstanceDestroy() {
|
|
141
|
+
invalidate()
|
|
142
|
+
}
|
|
143
|
+
|
|
134
144
|
override fun invalidate() {
|
|
135
145
|
StreamVideoReactNative.clearPipListeners()
|
|
136
146
|
reactApplicationContext.unregisterReceiver(powerReceiver)
|
|
@@ -4,11 +4,12 @@ import com.facebook.react.ReactPackage
|
|
|
4
4
|
import com.facebook.react.bridge.NativeModule
|
|
5
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
6
|
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
import com.streamvideo.reactnative.callmanager.StreamInCallManagerModule
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class StreamVideoReactNativePackage : ReactPackage {
|
|
10
11
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
11
|
-
return listOf(StreamVideoReactNativeModule(reactContext))
|
|
12
|
+
return listOf(StreamVideoReactNativeModule(reactContext), StreamInCallManagerModule(reactContext))
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 The Android Open Source Project
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
package com.streamvideo.reactnative.audio
|
|
18
|
+
|
|
19
|
+
import android.app.Activity
|
|
20
|
+
import android.content.Context
|
|
21
|
+
import android.media.AudioDeviceCallback
|
|
22
|
+
import android.media.AudioDeviceInfo
|
|
23
|
+
import android.media.AudioManager
|
|
24
|
+
import android.os.Build
|
|
25
|
+
import android.util.Log
|
|
26
|
+
import com.facebook.react.bridge.Arguments
|
|
27
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
28
|
+
import com.facebook.react.bridge.WritableMap
|
|
29
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
30
|
+
import com.streamvideo.reactnative.audio.utils.AudioDeviceEndpointUtils
|
|
31
|
+
import com.streamvideo.reactnative.audio.utils.AudioFocusUtil
|
|
32
|
+
import com.streamvideo.reactnative.audio.utils.AudioManagerUtil
|
|
33
|
+
import com.streamvideo.reactnative.audio.utils.AudioManagerUtil.Companion.getAvailableAudioDevices
|
|
34
|
+
import com.streamvideo.reactnative.audio.utils.AudioSetupStoreUtil
|
|
35
|
+
import com.streamvideo.reactnative.audio.utils.CallAudioRole
|
|
36
|
+
import com.streamvideo.reactnative.callmanager.StreamInCallManagerModule
|
|
37
|
+
import com.streamvideo.reactnative.model.AudioDeviceEndpoint
|
|
38
|
+
import com.streamvideo.reactnative.model.AudioDeviceEndpoint.Companion.EndpointType
|
|
39
|
+
import java.util.concurrent.ExecutorService
|
|
40
|
+
import java.util.concurrent.Executors
|
|
41
|
+
|
|
42
|
+
data class EndpointMaps(
|
|
43
|
+
// earpiece, speaker, unknown, wired_headset
|
|
44
|
+
val bluetoothEndpoints: HashMap<String, AudioDeviceEndpoint>,
|
|
45
|
+
// all bt endpoints
|
|
46
|
+
val nonBluetoothEndpoints: HashMap<@EndpointType Int, AudioDeviceEndpoint>
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
class AudioDeviceManager(
|
|
50
|
+
private val mReactContext: ReactApplicationContext
|
|
51
|
+
) : AutoCloseable, AudioDeviceCallback(), AudioManager.OnAudioFocusChangeListener {
|
|
52
|
+
|
|
53
|
+
private val mEndpointMaps by lazy {
|
|
54
|
+
val initialAudioDevices = getAvailableAudioDevices(mAudioManager)
|
|
55
|
+
val initialEndpoints =
|
|
56
|
+
AudioDeviceEndpointUtils.getEndpointsFromAudioDeviceInfo(initialAudioDevices)
|
|
57
|
+
val bluetoothEndpoints = HashMap<String, AudioDeviceEndpoint>()
|
|
58
|
+
val nonBluetoothEndpoints = HashMap<@EndpointType Int, AudioDeviceEndpoint>()
|
|
59
|
+
for (device in initialEndpoints) {
|
|
60
|
+
if (device.isBluetoothType()) {
|
|
61
|
+
bluetoothEndpoints[device.name] = device
|
|
62
|
+
} else {
|
|
63
|
+
nonBluetoothEndpoints[device.type] = device
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
EndpointMaps(bluetoothEndpoints, nonBluetoothEndpoints)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private var cachedAvailableEndpointNamesSet = setOf<String>()
|
|
70
|
+
|
|
71
|
+
/** Returns the currently selected audio device. */
|
|
72
|
+
private var _selectedAudioDeviceEndpoint: AudioDeviceEndpoint? = null
|
|
73
|
+
private var selectedAudioDeviceEndpoint: AudioDeviceEndpoint?
|
|
74
|
+
get() = _selectedAudioDeviceEndpoint
|
|
75
|
+
set(value) {
|
|
76
|
+
_selectedAudioDeviceEndpoint = value
|
|
77
|
+
// send an event to the frontend everytime this endpoint changes
|
|
78
|
+
sendAudioStatusEvent()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Default audio device; speaker phone for video calls or earpiece for audio only phone calls
|
|
82
|
+
@EndpointType
|
|
83
|
+
var defaultAudioDevice = AudioDeviceEndpoint.TYPE_SPEAKER
|
|
84
|
+
|
|
85
|
+
/** Contains the user-selected audio device which overrides the predefined selection scheme */
|
|
86
|
+
@EndpointType
|
|
87
|
+
private var userSelectedAudioDevice: Int? = null
|
|
88
|
+
|
|
89
|
+
private val mAudioManager =
|
|
90
|
+
mReactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Indicator that we have lost audio focus.
|
|
94
|
+
*/
|
|
95
|
+
private var audioFocusLost = false
|
|
96
|
+
|
|
97
|
+
private var audioFocusUtil = AudioFocusUtil(mAudioManager, this)
|
|
98
|
+
private var audioSetupStoreUtil = AudioSetupStoreUtil(mReactContext, mAudioManager, this)
|
|
99
|
+
|
|
100
|
+
var callAudioRole: CallAudioRole = CallAudioRole.Communicator
|
|
101
|
+
|
|
102
|
+
val bluetoothManager = BluetoothManager(mReactContext, this)
|
|
103
|
+
|
|
104
|
+
init {
|
|
105
|
+
// Note that we will immediately receive a call to onDevicesAdded with the list of
|
|
106
|
+
// devices which are currently connected.
|
|
107
|
+
mAudioManager.registerAudioDeviceCallback(this, null)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fun start(activity: Activity) {
|
|
111
|
+
runInAudioThread {
|
|
112
|
+
userSelectedAudioDevice = null
|
|
113
|
+
selectedAudioDeviceEndpoint = null
|
|
114
|
+
audioSetupStoreUtil.storeOriginalAudioSetup()
|
|
115
|
+
if (callAudioRole == CallAudioRole.Communicator) {
|
|
116
|
+
// Audio routing is manually controlled by the SDK in communication media mode
|
|
117
|
+
// and local microphone can be published
|
|
118
|
+
mAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION
|
|
119
|
+
activity.volumeControlStream = AudioManager.STREAM_VOICE_CALL
|
|
120
|
+
bluetoothManager.start()
|
|
121
|
+
mAudioManager.registerAudioDeviceCallback(this, null)
|
|
122
|
+
updateAudioDeviceState()
|
|
123
|
+
} else {
|
|
124
|
+
// Audio routing is handled automatically by the system in normal media mode
|
|
125
|
+
// and bluetooth microphones may not work on some devices.
|
|
126
|
+
mAudioManager.mode = AudioManager.MODE_NORMAL
|
|
127
|
+
activity.volumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
audioSetupStoreUtil.storeOriginalAudioSetup()
|
|
131
|
+
audioFocusUtil.requestFocus(callAudioRole, mReactContext)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fun stop() {
|
|
136
|
+
runInAudioThread {
|
|
137
|
+
if (callAudioRole == CallAudioRole.Communicator) {
|
|
138
|
+
if (Build.VERSION.SDK_INT >= 31) {
|
|
139
|
+
mAudioManager.clearCommunicationDevice()
|
|
140
|
+
} else {
|
|
141
|
+
mAudioManager.setSpeakerphoneOn(false)
|
|
142
|
+
}
|
|
143
|
+
bluetoothManager.stop()
|
|
144
|
+
}
|
|
145
|
+
audioSetupStoreUtil.restoreOriginalAudioSetup()
|
|
146
|
+
audioFocusUtil.abandonFocus()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fun setMicrophoneMute(enable: Boolean) {
|
|
151
|
+
if (enable != mAudioManager.isMicrophoneMute) {
|
|
152
|
+
mAudioManager.isMicrophoneMute = enable
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private fun getEndpointFromName(name: String): AudioDeviceEndpoint? {
|
|
157
|
+
val endpointType = AudioDeviceEndpointUtils.endpointStringToType(name)
|
|
158
|
+
val endpoint = when (endpointType) {
|
|
159
|
+
AudioDeviceEndpoint.TYPE_SPEAKER, AudioDeviceEndpoint.TYPE_EARPIECE, AudioDeviceEndpoint.TYPE_WIRED_HEADSET -> mEndpointMaps.nonBluetoothEndpoints[endpointType]
|
|
160
|
+
else -> mEndpointMaps.bluetoothEndpoints[name]
|
|
161
|
+
}
|
|
162
|
+
return endpoint
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fun setSpeakerphoneOn(enable: Boolean) {
|
|
166
|
+
if (enable) {
|
|
167
|
+
switchDeviceEndpointType(AudioDeviceEndpoint.TYPE_SPEAKER)
|
|
168
|
+
} else {
|
|
169
|
+
if (Build.VERSION.SDK_INT >= 31) {
|
|
170
|
+
mAudioManager.clearCommunicationDevice()
|
|
171
|
+
// sets the first device that is not speaker
|
|
172
|
+
getCurrentDeviceEndpoints().firstOrNull {
|
|
173
|
+
!it.isSpeakerType()
|
|
174
|
+
}?.also {
|
|
175
|
+
switchDeviceEndpointType(it.type)
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
mAudioManager.setSpeakerphoneOn(false)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private fun switchDeviceEndpointType(@EndpointType deviceType: Int) {
|
|
184
|
+
val newDevice = AudioManagerUtil.switchDeviceEndpointType(
|
|
185
|
+
deviceType,
|
|
186
|
+
mEndpointMaps,
|
|
187
|
+
mAudioManager,
|
|
188
|
+
bluetoothManager
|
|
189
|
+
)
|
|
190
|
+
this.selectedAudioDeviceEndpoint = newDevice
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
fun switchDeviceFromDeviceName(
|
|
194
|
+
deviceName: String
|
|
195
|
+
) {
|
|
196
|
+
Log.d(TAG, "switchDeviceFromDeviceName: deviceName = $deviceName")
|
|
197
|
+
Log.d(
|
|
198
|
+
TAG,
|
|
199
|
+
"switchDeviceFromDeviceName: mEndpointMaps.bluetoothEndpoints = ${mEndpointMaps.bluetoothEndpoints}"
|
|
200
|
+
)
|
|
201
|
+
runInAudioThread {
|
|
202
|
+
val btDevice = mEndpointMaps.bluetoothEndpoints[deviceName]
|
|
203
|
+
if (btDevice != null) {
|
|
204
|
+
if (Build.VERSION.SDK_INT >= 31) {
|
|
205
|
+
mAudioManager.setCommunicationDevice(btDevice.deviceInfo)
|
|
206
|
+
bluetoothManager.updateDevice()
|
|
207
|
+
this.selectedAudioDeviceEndpoint = btDevice
|
|
208
|
+
} else {
|
|
209
|
+
switchDeviceEndpointType(
|
|
210
|
+
AudioDeviceEndpoint.TYPE_BLUETOOTH
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
val endpointType = AudioDeviceEndpointUtils.endpointStringToType(deviceName)
|
|
215
|
+
switchDeviceEndpointType(
|
|
216
|
+
endpointType
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
override fun close() {
|
|
223
|
+
mAudioManager.unregisterAudioDeviceCallback(this)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
|
|
227
|
+
if (addedDevices != null) {
|
|
228
|
+
runInAudioThread {
|
|
229
|
+
endpointsAddedUpdate(
|
|
230
|
+
AudioDeviceEndpointUtils.getEndpointsFromAudioDeviceInfo(
|
|
231
|
+
addedDevices.toList()
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
|
|
239
|
+
if (removedDevices != null) {
|
|
240
|
+
runInAudioThread {
|
|
241
|
+
endpointsRemovedUpdate(
|
|
242
|
+
AudioDeviceEndpointUtils.getEndpointsFromAudioDeviceInfo(
|
|
243
|
+
removedDevices.toList()
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private fun endpointsAddedUpdate(addedCallEndpoints: List<AudioDeviceEndpoint>) {
|
|
251
|
+
// START tracking an endpoint
|
|
252
|
+
var addedDevicesCount = 0
|
|
253
|
+
for (maybeNewEndpoint in addedCallEndpoints) {
|
|
254
|
+
addedDevicesCount += maybeAddCallEndpoint(maybeNewEndpoint)
|
|
255
|
+
}
|
|
256
|
+
if (addedDevicesCount > 0) {
|
|
257
|
+
updateAudioDeviceState()
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private fun endpointsRemovedUpdate(removedCallEndpoints: List<AudioDeviceEndpoint>) {
|
|
262
|
+
// STOP tracking an endpoint
|
|
263
|
+
var removedDevicesCount = 0
|
|
264
|
+
for (maybeRemovedDevice in removedCallEndpoints) {
|
|
265
|
+
removedDevicesCount += maybeRemoveCallEndpoint(maybeRemovedDevice)
|
|
266
|
+
}
|
|
267
|
+
if (removedDevicesCount > 0) {
|
|
268
|
+
updateAudioDeviceState()
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private fun getCurrentDeviceEndpoints(): List<AudioDeviceEndpoint> {
|
|
273
|
+
if (Build.VERSION.SDK_INT >= 31) {
|
|
274
|
+
return (mEndpointMaps.bluetoothEndpoints.values + mEndpointMaps.nonBluetoothEndpoints.values).sorted()
|
|
275
|
+
} else {
|
|
276
|
+
val btEndpoint = mEndpointMaps.bluetoothEndpoints[bluetoothManager.getDeviceName()]
|
|
277
|
+
if (btEndpoint != null) {
|
|
278
|
+
val list = mutableListOf(btEndpoint)
|
|
279
|
+
list.addAll(mEndpointMaps.nonBluetoothEndpoints.values)
|
|
280
|
+
return list.sorted()
|
|
281
|
+
} else {
|
|
282
|
+
return mEndpointMaps.nonBluetoothEndpoints.values.sorted()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private fun maybeAddCallEndpoint(endpoint: AudioDeviceEndpoint): Int {
|
|
289
|
+
if (endpoint.isBluetoothType()) {
|
|
290
|
+
if (!mEndpointMaps.bluetoothEndpoints.containsKey(endpoint.name)) {
|
|
291
|
+
mEndpointMaps.bluetoothEndpoints[endpoint.name] = endpoint
|
|
292
|
+
Log.d(TAG, "maybeAddCallEndpoint: bluetooth endpoint added: " + endpoint.name)
|
|
293
|
+
return 1
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
if (!mEndpointMaps.nonBluetoothEndpoints.containsKey(endpoint.type)) {
|
|
297
|
+
mEndpointMaps.nonBluetoothEndpoints[endpoint.type] = endpoint
|
|
298
|
+
Log.d(TAG, "maybeAddCallEndpoint: non-bluetooth endpoint added: " + endpoint.name)
|
|
299
|
+
return 1
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return 0
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private fun maybeRemoveCallEndpoint(endpoint: AudioDeviceEndpoint): Int {
|
|
306
|
+
// TODO:: determine if it is necessary to cleanup listeners here
|
|
307
|
+
if (endpoint.isBluetoothType()) {
|
|
308
|
+
if (mEndpointMaps.bluetoothEndpoints.containsKey(endpoint.name)) {
|
|
309
|
+
mEndpointMaps.bluetoothEndpoints.remove(endpoint.name)
|
|
310
|
+
Log.d(TAG, "maybeRemoveCallEndpoint: bluetooth endpoint removed: " + endpoint.name)
|
|
311
|
+
return 1
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
if (mEndpointMaps.nonBluetoothEndpoints.containsKey(endpoint.type)) {
|
|
315
|
+
mEndpointMaps.nonBluetoothEndpoints.remove(endpoint.type)
|
|
316
|
+
Log.d(
|
|
317
|
+
TAG,
|
|
318
|
+
"maybeRemoveCallEndpoint: non-bluetooth endpoint removed: " + endpoint.name
|
|
319
|
+
)
|
|
320
|
+
return 1
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return 0
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
fun muteAudioOutput() {
|
|
327
|
+
mAudioManager.adjustStreamVolume(
|
|
328
|
+
if (callAudioRole === CallAudioRole.Communicator) AudioManager.STREAM_VOICE_CALL else AudioManager.STREAM_MUSIC,
|
|
329
|
+
AudioManager.ADJUST_MUTE,
|
|
330
|
+
AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE // Optional: prevents sound/vibration on mute
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
fun unmuteAudioOutput() {
|
|
335
|
+
mAudioManager.adjustStreamVolume(
|
|
336
|
+
if (callAudioRole === CallAudioRole.Communicator) AudioManager.STREAM_VOICE_CALL else AudioManager.STREAM_MUSIC,
|
|
337
|
+
AudioManager.ADJUST_UNMUTE,
|
|
338
|
+
AudioManager.FLAG_SHOW_UI
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Updates the list of available audio devices and selects a new audio device based on priority.
|
|
344
|
+
*
|
|
345
|
+
* This function performs the following actions:
|
|
346
|
+
* 1. Retrieves the current list of available audio device endpoints.
|
|
347
|
+
* 2. Compares the current list with the cached list to detect changes.
|
|
348
|
+
* 3. Updates the Bluetooth device state if necessary (especially for older Android platforms).
|
|
349
|
+
* 4. Determines the new audio device based on the following priority:
|
|
350
|
+
* - Wired Headset (WH)
|
|
351
|
+
* - Bluetooth (BT)
|
|
352
|
+
* - Default audio device (speaker or earpiece)
|
|
353
|
+
* 5. If a user has manually selected an audio device, that selection takes precedence.
|
|
354
|
+
* 6. Handles Bluetooth SCO (Synchronous Connection-Oriented) connection:
|
|
355
|
+
* - If the new device is Bluetooth and a headset is available, it attempts to start SCO audio.
|
|
356
|
+
* - If SCO connection fails, it reverts the Bluetooth selection and chooses an alternative device.
|
|
357
|
+
* - If a previous selection was Bluetooth and the new selection is not, it stops SCO audio.
|
|
358
|
+
* 7. Switches to the new audio device endpoint.
|
|
359
|
+
* 8. If the selected device or the list of available devices has changed, it sends an audio status event.
|
|
360
|
+
*
|
|
361
|
+
* The entire operation is performed on a dedicated audio thread to avoid blocking the main thread.
|
|
362
|
+
*/
|
|
363
|
+
fun updateAudioDeviceState() {
|
|
364
|
+
runInAudioThread {
|
|
365
|
+
val audioDevices = getCurrentDeviceEndpoints()
|
|
366
|
+
val audioDeviceNamesSet = audioDevices.map { it.name }.toSet()
|
|
367
|
+
val devicesChanged = if (cachedAvailableEndpointNamesSet.size != audioDevices.size) {
|
|
368
|
+
true
|
|
369
|
+
} else {
|
|
370
|
+
cachedAvailableEndpointNamesSet != audioDeviceNamesSet
|
|
371
|
+
}
|
|
372
|
+
cachedAvailableEndpointNamesSet = audioDeviceNamesSet
|
|
373
|
+
Log.d(
|
|
374
|
+
TAG,
|
|
375
|
+
("updateAudioDeviceState() Device status: available=$audioDevices, selected=$selectedAudioDeviceEndpoint, user selected=" + endpointTypeDebug(
|
|
376
|
+
userSelectedAudioDevice
|
|
377
|
+
))
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
if (devicesChanged) {
|
|
381
|
+
// notify the frontend that the available devices changed
|
|
382
|
+
this.sendAudioStatusEvent();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Double-check if any Bluetooth headset is connected once again (useful for older android platforms)
|
|
386
|
+
// TODO: we can possibly remove this, to be tested on older platforms
|
|
387
|
+
if (bluetoothManager.bluetoothState == BluetoothManager.State.HEADSET_AVAILABLE || bluetoothManager.bluetoothState == BluetoothManager.State.HEADSET_UNAVAILABLE) {
|
|
388
|
+
bluetoothManager.updateDevice()
|
|
389
|
+
}
|
|
390
|
+
/** sets newAudioDevice initially to this order: WH -> BT -> default(speaker or earpiece) */
|
|
391
|
+
var newAudioDevice = defaultAudioDevice
|
|
392
|
+
audioDevices.firstOrNull {
|
|
393
|
+
it.isWiredHeadsetType() || it.isBluetoothType()
|
|
394
|
+
}?.also {
|
|
395
|
+
newAudioDevice = it.type
|
|
396
|
+
}
|
|
397
|
+
var deviceSwitched = false
|
|
398
|
+
val userSelectedAudioDevice = this.userSelectedAudioDevice
|
|
399
|
+
if (userSelectedAudioDevice !== null && userSelectedAudioDevice != AudioDeviceEndpoint.TYPE_UNKNOWN) {
|
|
400
|
+
newAudioDevice = userSelectedAudioDevice
|
|
401
|
+
}
|
|
402
|
+
Log.d(
|
|
403
|
+
TAG, ("Decided newAudioDevice: ${endpointTypeDebug(newAudioDevice)}")
|
|
404
|
+
)
|
|
405
|
+
/** To be called when BT SCO connection fails
|
|
406
|
+
* Will do the following:
|
|
407
|
+
* 1 - revert user selection if needed
|
|
408
|
+
* 2 - sets newAudioDevice to something other than BT
|
|
409
|
+
* 3 - change the bt manager to device state from sco connection state
|
|
410
|
+
* */
|
|
411
|
+
fun revertBTSelection() {
|
|
412
|
+
val selectedAudioDeviceEndpoint = this.selectedAudioDeviceEndpoint
|
|
413
|
+
// BT connection, so revert user selection if needed
|
|
414
|
+
if (userSelectedAudioDevice == AudioDeviceEndpoint.TYPE_BLUETOOTH) {
|
|
415
|
+
this.userSelectedAudioDevice = null
|
|
416
|
+
}
|
|
417
|
+
// prev selection was not BT, but new was BT
|
|
418
|
+
// new can now be WiredHeadset or default if there was no selection before
|
|
419
|
+
if (selectedAudioDeviceEndpoint != null && selectedAudioDeviceEndpoint.type != AudioDeviceEndpoint.TYPE_UNKNOWN && selectedAudioDeviceEndpoint.type != AudioDeviceEndpoint.TYPE_BLUETOOTH) {
|
|
420
|
+
newAudioDevice = selectedAudioDeviceEndpoint.type
|
|
421
|
+
} else {
|
|
422
|
+
newAudioDevice = defaultAudioDevice
|
|
423
|
+
audioDevices.firstOrNull {
|
|
424
|
+
it.isWiredHeadsetType()
|
|
425
|
+
}?.also {
|
|
426
|
+
newAudioDevice = it.type
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// change the bt manager to device state from sco connection state
|
|
430
|
+
bluetoothManager.updateDevice()
|
|
431
|
+
Log.d(
|
|
432
|
+
TAG, ("revertBTSelection newAudioDevice: ${endpointTypeDebug(newAudioDevice)}")
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
var selectedAudioDeviceEndpoint = this.selectedAudioDeviceEndpoint
|
|
437
|
+
if (selectedAudioDeviceEndpoint == null || newAudioDevice != selectedAudioDeviceEndpoint.type) {
|
|
438
|
+
// --- stop bluetooth if prev selection was bluetooth
|
|
439
|
+
if (selectedAudioDeviceEndpoint?.type == AudioDeviceEndpoint.TYPE_BLUETOOTH && (bluetoothManager.bluetoothState == BluetoothManager.State.SCO_CONNECTED || bluetoothManager.bluetoothState == BluetoothManager.State.SCO_CONNECTING)) {
|
|
440
|
+
bluetoothManager.stopScoAudio()
|
|
441
|
+
bluetoothManager.updateDevice()
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// --- start bluetooth if new is BT and we have a headset
|
|
445
|
+
if (newAudioDevice == AudioDeviceEndpoint.TYPE_BLUETOOTH && bluetoothManager.bluetoothState == BluetoothManager.State.HEADSET_AVAILABLE) {
|
|
446
|
+
// Attempt to start Bluetooth SCO audio (takes a few second to start on older platforms).
|
|
447
|
+
if (!bluetoothManager.startScoAudio()) {
|
|
448
|
+
revertBTSelection()
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// already selected BT device
|
|
452
|
+
if (bluetoothManager.bluetoothState == BluetoothManager.State.SCO_CONNECTED) {
|
|
453
|
+
selectedAudioDeviceEndpoint =
|
|
454
|
+
getEndpointFromName(bluetoothManager.getDeviceName()!!)
|
|
455
|
+
this.selectedAudioDeviceEndpoint = selectedAudioDeviceEndpoint
|
|
456
|
+
deviceSwitched = true
|
|
457
|
+
} else if (
|
|
458
|
+
// still connecting (happens on older Android platforms)
|
|
459
|
+
bluetoothManager.bluetoothState == BluetoothManager.State.SCO_CONNECTING) {
|
|
460
|
+
// on older Android platforms
|
|
461
|
+
// it will call this update function again, once connected or disconnected
|
|
462
|
+
// so we can skip executing further
|
|
463
|
+
return@runInAudioThread
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** This check is meant for older Android platforms
|
|
468
|
+
* it would have called this device update function again on timer execution
|
|
469
|
+
* after two cases
|
|
470
|
+
* 1 - SCO_CONNECTED or
|
|
471
|
+
* 2 - SCO_DISCONNECTING
|
|
472
|
+
* Here we see if it was disconnected then we revert to non-bluetooth selection
|
|
473
|
+
* */
|
|
474
|
+
if (newAudioDevice == AudioDeviceEndpoint.TYPE_BLUETOOTH && selectedAudioDeviceEndpoint?.type != AudioDeviceEndpoint.TYPE_BLUETOOTH && bluetoothManager.bluetoothState == BluetoothManager.State.SCO_DISCONNECTING) {
|
|
475
|
+
revertBTSelection()
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (newAudioDevice != selectedAudioDeviceEndpoint?.type) {
|
|
479
|
+
// BT sco would be already connected at this point, so no need to switch again
|
|
480
|
+
if (newAudioDevice != AudioDeviceEndpoint.TYPE_BLUETOOTH) {
|
|
481
|
+
switchDeviceEndpointType(newAudioDevice)
|
|
482
|
+
}
|
|
483
|
+
deviceSwitched = true
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (deviceSwitched || devicesChanged) {
|
|
487
|
+
Log.d(
|
|
488
|
+
TAG,
|
|
489
|
+
("New device status: " + "available=" + audioDevices + ", " + "selected=" + this.selectedAudioDeviceEndpoint)
|
|
490
|
+
)
|
|
491
|
+
}
|
|
492
|
+
Log.d(
|
|
493
|
+
TAG, "--- updateAudioDeviceState done"
|
|
494
|
+
)
|
|
495
|
+
} else {
|
|
496
|
+
Log.d(
|
|
497
|
+
TAG, "--- updateAudioDeviceState: no change"
|
|
498
|
+
)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
override fun onAudioFocusChange(focusChange: Int) {
|
|
504
|
+
when (focusChange) {
|
|
505
|
+
AudioManager.AUDIOFOCUS_GAIN -> {
|
|
506
|
+
Log.d(TAG, "Audio focus gained")
|
|
507
|
+
// Some other application potentially stole our audio focus
|
|
508
|
+
// temporarily. Restore our mode.
|
|
509
|
+
if (audioFocusLost) {
|
|
510
|
+
// removing the currently selected device store, as its untrue
|
|
511
|
+
selectedAudioDeviceEndpoint = null
|
|
512
|
+
// removing the currently selected device store will make sure a device selection is made
|
|
513
|
+
updateAudioDeviceState()
|
|
514
|
+
}
|
|
515
|
+
audioFocusLost = false
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
AudioManager.AUDIOFOCUS_LOSS, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
|
|
519
|
+
Log.d(TAG, "Audio focus lost")
|
|
520
|
+
audioFocusLost = true
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private fun audioStatusMap(): WritableMap {
|
|
526
|
+
val endpoint = this.selectedAudioDeviceEndpoint
|
|
527
|
+
val availableEndpoints = Arguments.fromList(getCurrentDeviceEndpoints().map { it.name })
|
|
528
|
+
|
|
529
|
+
val data = Arguments.createMap()
|
|
530
|
+
data.putArray("devices", availableEndpoints)
|
|
531
|
+
data.putString("currentEndpointType", endpointTypeDebug(endpoint?.type))
|
|
532
|
+
data.putString("selectedDevice", endpoint?.name)
|
|
533
|
+
return data
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private fun sendAudioStatusEvent() {
|
|
537
|
+
try {
|
|
538
|
+
if (mReactContext.hasActiveReactInstance()) {
|
|
539
|
+
val payload = audioStatusMap()
|
|
540
|
+
Log.d(TAG, "sendAudioStatusEvent: $payload")
|
|
541
|
+
mReactContext.getJSModule(
|
|
542
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter::class.java
|
|
543
|
+
).emit("onAudioDeviceChanged", payload)
|
|
544
|
+
} else {
|
|
545
|
+
Log.e(TAG, "sendEvent(): reactContext is null or not having CatalystInstance yet.")
|
|
546
|
+
}
|
|
547
|
+
} catch (e: RuntimeException) {
|
|
548
|
+
Log.e(
|
|
549
|
+
TAG,
|
|
550
|
+
"sendEvent(): java.lang.RuntimeException: Trying to invoke JS before CatalystInstance has been set!",
|
|
551
|
+
e
|
|
552
|
+
)
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
companion object {
|
|
557
|
+
private val TAG: String =
|
|
558
|
+
StreamInCallManagerModule.TAG + ":" + AudioDeviceManager::class.java.simpleName.toString()
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Executor service for running audio-related tasks on a dedicated single thread.
|
|
562
|
+
*
|
|
563
|
+
* <p>This executor ensures that all audio processing, recording, playback,
|
|
564
|
+
* or other audio-related operations are executed sequentially and do not
|
|
565
|
+
* interfere with the main UI thread or other background tasks. Using a
|
|
566
|
+
* single-threaded executor for audio tasks helps prevent race conditions
|
|
567
|
+
* and simplifies synchronization when dealing with audio resources.
|
|
568
|
+
*
|
|
569
|
+
* <p>Tasks submitted to this executor will be processed in the order they
|
|
570
|
+
* are submitted.
|
|
571
|
+
*/
|
|
572
|
+
private val audioThreadExecutor: ExecutorService = Executors.newSingleThreadExecutor()
|
|
573
|
+
|
|
574
|
+
fun runInAudioThread(runnable: Runnable) {
|
|
575
|
+
audioThreadExecutor.execute(runnable)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Converts an endpoint type to a human-readable string for debugging purposes.
|
|
581
|
+
*
|
|
582
|
+
* @param endpointType The endpoint type to convert. Can be null.
|
|
583
|
+
* @return A string representation of the endpoint type, or "NULL" if the input is null.
|
|
584
|
+
*/
|
|
585
|
+
private fun endpointTypeDebug(@EndpointType endpointType: Int?): String {
|
|
586
|
+
if (endpointType == null) {
|
|
587
|
+
return "NULL"
|
|
588
|
+
}
|
|
589
|
+
return AudioDeviceEndpointUtils.endpointTypeToString(endpointType)
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|