@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.
Files changed (219) hide show
  1. package/android/gradle.properties +3 -3
  2. package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt +10 -0
  3. package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativePackage.kt +2 -1
  4. package/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt +592 -0
  5. package/android/src/main/java/com/streamvideo/reactnative/audio/BluetoothManager.kt +755 -0
  6. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioDeviceEndpointUtils.kt +192 -0
  7. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt +62 -0
  8. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioManagerUtil.kt +159 -0
  9. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioSetupStoreUtil.kt +41 -0
  10. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/WebRtcAudioUtils.kt +250 -0
  11. package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +191 -0
  12. package/android/src/main/java/com/streamvideo/reactnative/model/AudioDeviceEndpoint.kt +136 -0
  13. package/android/src/main/java/org/webrtc/audio/WebRtcAudioTrackHelper.kt +12 -0
  14. package/dist/commonjs/components/Call/CallContent/CallContent.js +13 -9
  15. package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
  16. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +9 -2
  17. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
  18. package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js +3 -0
  19. package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
  20. package/dist/commonjs/components/Livestream/HostLivestream/HostLivestream.js +10 -6
  21. package/dist/commonjs/components/Livestream/HostLivestream/HostLivestream.js.map +1 -1
  22. package/dist/commonjs/components/Livestream/LivestreamControls/ViewerLivestreamControls.js +8 -4
  23. package/dist/commonjs/components/Livestream/LivestreamControls/ViewerLivestreamControls.js.map +1 -1
  24. package/dist/commonjs/components/Livestream/LivestreamLayout/LivestreamLayout.js +26 -27
  25. package/dist/commonjs/components/Livestream/LivestreamLayout/LivestreamLayout.js.map +1 -1
  26. package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLivestream.js +10 -6
  27. package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLivestream.js.map +1 -1
  28. package/dist/commonjs/hooks/useIsInPiPMode.js +3 -3
  29. package/dist/commonjs/hooks/useIsInPiPMode.js.map +1 -1
  30. package/dist/commonjs/hooks/usePermissionNotification.js +5 -5
  31. package/dist/commonjs/hooks/usePermissionNotification.js.map +1 -1
  32. package/dist/commonjs/index.js +12 -0
  33. package/dist/commonjs/index.js.map +1 -1
  34. package/dist/commonjs/modules/call-manager/CallManager.js +113 -0
  35. package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -0
  36. package/dist/commonjs/modules/call-manager/PrevLibDetection.js +18 -0
  37. package/dist/commonjs/modules/call-manager/PrevLibDetection.js.map +1 -0
  38. package/dist/commonjs/modules/call-manager/index.js +24 -0
  39. package/dist/commonjs/modules/call-manager/index.js.map +1 -0
  40. package/dist/commonjs/modules/call-manager/native-module.d.js +4 -0
  41. package/dist/commonjs/modules/call-manager/native-module.d.js.map +1 -0
  42. package/dist/commonjs/modules/call-manager/types.js +2 -0
  43. package/dist/commonjs/modules/call-manager/types.js.map +1 -0
  44. package/dist/commonjs/providers/StreamCall/AppStateListener.js +5 -5
  45. package/dist/commonjs/providers/StreamCall/AppStateListener.js.map +1 -1
  46. package/dist/commonjs/theme/theme.js.map +1 -1
  47. package/dist/commonjs/utils/internal/rxSubjects.js +2 -2
  48. package/dist/commonjs/utils/internal/rxSubjects.js.map +1 -1
  49. package/dist/commonjs/version.js +1 -1
  50. package/dist/commonjs/version.js.map +1 -1
  51. package/dist/module/components/Call/CallContent/CallContent.js +14 -9
  52. package/dist/module/components/Call/CallContent/CallContent.js.map +1 -1
  53. package/dist/module/components/Call/CallContent/RTCViewPipIOS.js +9 -2
  54. package/dist/module/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
  55. package/dist/module/components/Call/CallContent/RTCViewPipNative.js +3 -0
  56. package/dist/module/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
  57. package/dist/module/components/Livestream/HostLivestream/HostLivestream.js +10 -5
  58. package/dist/module/components/Livestream/HostLivestream/HostLivestream.js.map +1 -1
  59. package/dist/module/components/Livestream/LivestreamControls/ViewerLivestreamControls.js +9 -4
  60. package/dist/module/components/Livestream/LivestreamControls/ViewerLivestreamControls.js.map +1 -1
  61. package/dist/module/components/Livestream/LivestreamLayout/LivestreamLayout.js +26 -27
  62. package/dist/module/components/Livestream/LivestreamLayout/LivestreamLayout.js.map +1 -1
  63. package/dist/module/components/Livestream/ViewerLivestream/ViewerLivestream.js +10 -5
  64. package/dist/module/components/Livestream/ViewerLivestream/ViewerLivestream.js.map +1 -1
  65. package/dist/module/hooks/useIsInPiPMode.js +4 -4
  66. package/dist/module/hooks/useIsInPiPMode.js.map +1 -1
  67. package/dist/module/hooks/usePermissionNotification.js +7 -6
  68. package/dist/module/hooks/usePermissionNotification.js.map +1 -1
  69. package/dist/module/icons/Back.js +1 -1
  70. package/dist/module/icons/Back.js.map +1 -1
  71. package/dist/module/icons/CameraSwitch.js +1 -1
  72. package/dist/module/icons/CameraSwitch.js.map +1 -1
  73. package/dist/module/icons/Mic.js +1 -1
  74. package/dist/module/icons/Mic.js.map +1 -1
  75. package/dist/module/icons/MicOff.js +1 -1
  76. package/dist/module/icons/MicOff.js.map +1 -1
  77. package/dist/module/icons/Phone.js +1 -1
  78. package/dist/module/icons/Phone.js.map +1 -1
  79. package/dist/module/icons/PinVertical.js +1 -1
  80. package/dist/module/icons/PinVertical.js.map +1 -1
  81. package/dist/module/icons/Reaction.js +1 -1
  82. package/dist/module/icons/Reaction.js.map +1 -1
  83. package/dist/module/icons/Spotlight.js +1 -1
  84. package/dist/module/icons/Spotlight.js.map +1 -1
  85. package/dist/module/icons/Video.js +1 -1
  86. package/dist/module/icons/Video.js.map +1 -1
  87. package/dist/module/icons/VideoSlash.js +1 -1
  88. package/dist/module/icons/VideoSlash.js.map +1 -1
  89. package/dist/module/index.js +1 -0
  90. package/dist/module/index.js.map +1 -1
  91. package/dist/module/modules/call-manager/CallManager.js +106 -0
  92. package/dist/module/modules/call-manager/CallManager.js.map +1 -0
  93. package/dist/module/modules/call-manager/PrevLibDetection.js +12 -0
  94. package/dist/module/modules/call-manager/PrevLibDetection.js.map +1 -0
  95. package/dist/module/modules/call-manager/index.js +4 -0
  96. package/dist/module/modules/call-manager/index.js.map +1 -0
  97. package/dist/module/modules/call-manager/native-module.d.js +2 -0
  98. package/dist/module/modules/call-manager/native-module.d.js.map +1 -0
  99. package/dist/module/modules/call-manager/types.js +2 -0
  100. package/dist/module/modules/call-manager/types.js.map +1 -0
  101. package/dist/module/providers/StreamCall/AppStateListener.js +6 -6
  102. package/dist/module/providers/StreamCall/AppStateListener.js.map +1 -1
  103. package/dist/module/theme/theme.js.map +1 -1
  104. package/dist/module/utils/internal/rxSubjects.js +1 -1
  105. package/dist/module/utils/internal/rxSubjects.js.map +1 -1
  106. package/dist/module/version.js +1 -1
  107. package/dist/module/version.js.map +1 -1
  108. package/dist/typescript/components/Call/CallContent/CallContent.d.ts +3 -2
  109. package/dist/typescript/components/Call/CallContent/CallContent.d.ts.map +1 -1
  110. package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts +5 -0
  111. package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -1
  112. package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts +6 -0
  113. package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts.map +1 -1
  114. package/dist/typescript/components/Livestream/HostLivestream/HostLivestream.d.ts.map +1 -1
  115. package/dist/typescript/components/Livestream/LivestreamControls/ViewerLivestreamControls.d.ts.map +1 -1
  116. package/dist/typescript/components/Livestream/LivestreamLayout/LivestreamLayout.d.ts.map +1 -1
  117. package/dist/typescript/components/Livestream/ViewerLivestream/ViewerLivestream.d.ts.map +1 -1
  118. package/dist/typescript/hooks/usePermissionNotification.d.ts.map +1 -1
  119. package/dist/typescript/icons/Back.d.ts +1 -1
  120. package/dist/typescript/icons/Back.d.ts.map +1 -1
  121. package/dist/typescript/icons/BadNetwork.d.ts +1 -1
  122. package/dist/typescript/icons/BadNetwork.d.ts.map +1 -1
  123. package/dist/typescript/icons/CameraSwitch.d.ts +1 -1
  124. package/dist/typescript/icons/CameraSwitch.d.ts.map +1 -1
  125. package/dist/typescript/icons/LivestreamControls.d.ts +1 -1
  126. package/dist/typescript/icons/LivestreamControls.d.ts.map +1 -1
  127. package/dist/typescript/icons/Lock.d.ts +1 -1
  128. package/dist/typescript/icons/Lock.d.ts.map +1 -1
  129. package/dist/typescript/icons/Maximize.d.ts +1 -1
  130. package/dist/typescript/icons/Maximize.d.ts.map +1 -1
  131. package/dist/typescript/icons/Mic.d.ts +1 -1
  132. package/dist/typescript/icons/Mic.d.ts.map +1 -1
  133. package/dist/typescript/icons/MicOff.d.ts +1 -1
  134. package/dist/typescript/icons/MicOff.d.ts.map +1 -1
  135. package/dist/typescript/icons/Phone.d.ts +1 -1
  136. package/dist/typescript/icons/Phone.d.ts.map +1 -1
  137. package/dist/typescript/icons/PhoneDown.d.ts +1 -1
  138. package/dist/typescript/icons/PhoneDown.d.ts.map +1 -1
  139. package/dist/typescript/icons/PinVertical.d.ts +1 -1
  140. package/dist/typescript/icons/PinVertical.d.ts.map +1 -1
  141. package/dist/typescript/icons/Reaction.d.ts +1 -1
  142. package/dist/typescript/icons/Reaction.d.ts.map +1 -1
  143. package/dist/typescript/icons/ScreenShare.d.ts +1 -1
  144. package/dist/typescript/icons/ScreenShare.d.ts.map +1 -1
  145. package/dist/typescript/icons/ScreenShareIndicator.d.ts +1 -1
  146. package/dist/typescript/icons/ScreenShareIndicator.d.ts.map +1 -1
  147. package/dist/typescript/icons/Spotlight.d.ts +1 -1
  148. package/dist/typescript/icons/Spotlight.d.ts.map +1 -1
  149. package/dist/typescript/icons/StopScreenShare.d.ts +1 -1
  150. package/dist/typescript/icons/StopScreenShare.d.ts.map +1 -1
  151. package/dist/typescript/icons/Video.d.ts +1 -1
  152. package/dist/typescript/icons/Video.d.ts.map +1 -1
  153. package/dist/typescript/icons/VideoSlash.d.ts +1 -1
  154. package/dist/typescript/icons/VideoSlash.d.ts.map +1 -1
  155. package/dist/typescript/index.d.ts +1 -0
  156. package/dist/typescript/index.d.ts.map +1 -1
  157. package/dist/typescript/modules/call-manager/CallManager.d.ts +67 -0
  158. package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -0
  159. package/dist/typescript/modules/call-manager/PrevLibDetection.d.ts +13 -0
  160. package/dist/typescript/modules/call-manager/PrevLibDetection.d.ts.map +1 -0
  161. package/dist/typescript/modules/call-manager/index.d.ts +4 -0
  162. package/dist/typescript/modules/call-manager/index.d.ts.map +1 -0
  163. package/dist/typescript/modules/call-manager/types.d.ts +15 -0
  164. package/dist/typescript/modules/call-manager/types.d.ts.map +1 -0
  165. package/dist/typescript/providers/StreamCall/AppStateListener.d.ts.map +1 -1
  166. package/dist/typescript/theme/theme.d.ts +1 -2
  167. package/dist/typescript/theme/theme.d.ts.map +1 -1
  168. package/dist/typescript/utils/internal/rxSubjects.d.ts +1 -1
  169. package/dist/typescript/utils/internal/rxSubjects.d.ts.map +1 -1
  170. package/dist/typescript/version.d.ts +1 -1
  171. package/dist/typescript/version.d.ts.map +1 -1
  172. package/ios/PictureInPicture/StreamPictureInPictureController.swift +5 -0
  173. package/ios/RTCViewPip.swift +15 -0
  174. package/ios/RTCViewPipManager.mm +1 -0
  175. package/ios/StreamInCallManager.m +26 -0
  176. package/ios/StreamInCallManager.swift +303 -0
  177. package/ios/StreamVideoReactNative-Bridging-Header.h +1 -0
  178. package/ios/StreamVideoReactNative.m +6 -5
  179. package/ios/StreamVideoReactNative.xcodeproj/project.xcworkspace/xcuserdata/santhoshvaiyapuri.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  180. package/ios/StreamVideoReactNative.xcodeproj/xcuserdata/santhoshvaiyapuri.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  181. package/package.json +33 -35
  182. package/src/components/Call/CallContent/CallContent.tsx +14 -10
  183. package/src/components/Call/CallContent/RTCViewPipIOS.tsx +17 -2
  184. package/src/components/Call/CallContent/RTCViewPipNative.tsx +8 -0
  185. package/src/components/Livestream/HostLivestream/HostLivestream.tsx +8 -3
  186. package/src/components/Livestream/LivestreamControls/ViewerLivestreamControls.tsx +11 -5
  187. package/src/components/Livestream/LivestreamLayout/LivestreamLayout.tsx +38 -29
  188. package/src/components/Livestream/ViewerLivestream/ViewerLivestream.tsx +8 -3
  189. package/src/hooks/useIsInPiPMode.tsx +4 -4
  190. package/src/hooks/usePermissionNotification.tsx +7 -12
  191. package/src/icons/Back.tsx +2 -2
  192. package/src/icons/BadNetwork.tsx +1 -1
  193. package/src/icons/CameraSwitch.tsx +2 -2
  194. package/src/icons/LivestreamControls.tsx +1 -1
  195. package/src/icons/Lock.tsx +1 -1
  196. package/src/icons/Maximize.tsx +1 -1
  197. package/src/icons/Mic.tsx +2 -2
  198. package/src/icons/MicOff.tsx +2 -2
  199. package/src/icons/Phone.tsx +2 -2
  200. package/src/icons/PhoneDown.tsx +1 -1
  201. package/src/icons/PinVertical.tsx +2 -2
  202. package/src/icons/Reaction.tsx +2 -2
  203. package/src/icons/ScreenShare.tsx +1 -1
  204. package/src/icons/ScreenShareIndicator.tsx +1 -1
  205. package/src/icons/Spotlight.tsx +2 -2
  206. package/src/icons/StopScreenShare.tsx +1 -1
  207. package/src/icons/Video.tsx +2 -2
  208. package/src/icons/VideoSlash.tsx +2 -2
  209. package/src/index.ts +1 -0
  210. package/src/modules/call-manager/CallManager.ts +116 -0
  211. package/src/modules/call-manager/PrevLibDetection.ts +27 -0
  212. package/src/modules/call-manager/index.ts +5 -0
  213. package/src/modules/call-manager/native-module.d.ts +80 -0
  214. package/src/modules/call-manager/types.ts +25 -0
  215. package/src/providers/StreamCall/AppStateListener.tsx +6 -9
  216. package/src/theme/theme.ts +2 -2
  217. package/src/utils/internal/rxSubjects.ts +1 -1
  218. package/src/version.ts +1 -1
  219. package/CHANGELOG.md +0 -2783
@@ -1,5 +1,5 @@
1
1
  StreamVideoReactNative_kotlinVersion=1.9.25
2
2
  StreamVideoReactNative_minSdkVersion=24
3
- StreamVideoReactNative_targetSdkVersion=34
4
- StreamVideoReactNative_compileSdkVersion=34
5
- StreamVideoReactNative_ndkversion=21.4.7075529
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
+ }