@stream-io/video-react-native-sdk 1.21.1 → 1.22.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 (214) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/android/gradle.properties +3 -3
  3. package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt +10 -0
  4. package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativePackage.kt +2 -1
  5. package/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt +592 -0
  6. package/android/src/main/java/com/streamvideo/reactnative/audio/BluetoothManager.kt +755 -0
  7. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioDeviceEndpointUtils.kt +192 -0
  8. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt +62 -0
  9. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioManagerUtil.kt +159 -0
  10. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioSetupStoreUtil.kt +41 -0
  11. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/WebRtcAudioUtils.kt +250 -0
  12. package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +191 -0
  13. package/android/src/main/java/com/streamvideo/reactnative/model/AudioDeviceEndpoint.kt +136 -0
  14. package/android/src/main/java/org/webrtc/audio/WebRtcAudioTrackHelper.kt +12 -0
  15. package/dist/commonjs/components/Call/CallContent/CallContent.js +13 -9
  16. package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
  17. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +9 -2
  18. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
  19. package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js +3 -0
  20. package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
  21. package/dist/commonjs/components/Livestream/HostLivestream/HostLivestream.js +10 -6
  22. package/dist/commonjs/components/Livestream/HostLivestream/HostLivestream.js.map +1 -1
  23. package/dist/commonjs/components/Livestream/LivestreamControls/ViewerLivestreamControls.js +8 -4
  24. package/dist/commonjs/components/Livestream/LivestreamControls/ViewerLivestreamControls.js.map +1 -1
  25. package/dist/commonjs/components/Livestream/LivestreamLayout/LivestreamLayout.js +26 -27
  26. package/dist/commonjs/components/Livestream/LivestreamLayout/LivestreamLayout.js.map +1 -1
  27. package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLivestream.js +10 -6
  28. package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLivestream.js.map +1 -1
  29. package/dist/commonjs/hooks/useIsInPiPMode.js +3 -3
  30. package/dist/commonjs/hooks/useIsInPiPMode.js.map +1 -1
  31. package/dist/commonjs/hooks/usePermissionNotification.js +5 -5
  32. package/dist/commonjs/hooks/usePermissionNotification.js.map +1 -1
  33. package/dist/commonjs/index.js +12 -0
  34. package/dist/commonjs/index.js.map +1 -1
  35. package/dist/commonjs/modules/call-manager/CallManager.js +113 -0
  36. package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -0
  37. package/dist/commonjs/modules/call-manager/PrevLibDetection.js +18 -0
  38. package/dist/commonjs/modules/call-manager/PrevLibDetection.js.map +1 -0
  39. package/dist/commonjs/modules/call-manager/index.js +24 -0
  40. package/dist/commonjs/modules/call-manager/index.js.map +1 -0
  41. package/dist/commonjs/modules/call-manager/native-module.d.js +4 -0
  42. package/dist/commonjs/modules/call-manager/native-module.d.js.map +1 -0
  43. package/dist/commonjs/modules/call-manager/types.js +2 -0
  44. package/dist/commonjs/modules/call-manager/types.js.map +1 -0
  45. package/dist/commonjs/providers/StreamCall/AppStateListener.js +5 -5
  46. package/dist/commonjs/providers/StreamCall/AppStateListener.js.map +1 -1
  47. package/dist/commonjs/theme/theme.js.map +1 -1
  48. package/dist/commonjs/utils/internal/rxSubjects.js +2 -2
  49. package/dist/commonjs/utils/internal/rxSubjects.js.map +1 -1
  50. package/dist/commonjs/version.js +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/typescript/components/Call/CallContent/CallContent.d.ts +3 -2
  108. package/dist/typescript/components/Call/CallContent/CallContent.d.ts.map +1 -1
  109. package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts +5 -0
  110. package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -1
  111. package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts +6 -0
  112. package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts.map +1 -1
  113. package/dist/typescript/components/Livestream/HostLivestream/HostLivestream.d.ts.map +1 -1
  114. package/dist/typescript/components/Livestream/LivestreamControls/ViewerLivestreamControls.d.ts.map +1 -1
  115. package/dist/typescript/components/Livestream/LivestreamLayout/LivestreamLayout.d.ts.map +1 -1
  116. package/dist/typescript/components/Livestream/ViewerLivestream/ViewerLivestream.d.ts.map +1 -1
  117. package/dist/typescript/hooks/usePermissionNotification.d.ts.map +1 -1
  118. package/dist/typescript/icons/Back.d.ts +1 -1
  119. package/dist/typescript/icons/Back.d.ts.map +1 -1
  120. package/dist/typescript/icons/BadNetwork.d.ts +1 -1
  121. package/dist/typescript/icons/BadNetwork.d.ts.map +1 -1
  122. package/dist/typescript/icons/CameraSwitch.d.ts +1 -1
  123. package/dist/typescript/icons/CameraSwitch.d.ts.map +1 -1
  124. package/dist/typescript/icons/LivestreamControls.d.ts +1 -1
  125. package/dist/typescript/icons/LivestreamControls.d.ts.map +1 -1
  126. package/dist/typescript/icons/Lock.d.ts +1 -1
  127. package/dist/typescript/icons/Lock.d.ts.map +1 -1
  128. package/dist/typescript/icons/Maximize.d.ts +1 -1
  129. package/dist/typescript/icons/Maximize.d.ts.map +1 -1
  130. package/dist/typescript/icons/Mic.d.ts +1 -1
  131. package/dist/typescript/icons/Mic.d.ts.map +1 -1
  132. package/dist/typescript/icons/MicOff.d.ts +1 -1
  133. package/dist/typescript/icons/MicOff.d.ts.map +1 -1
  134. package/dist/typescript/icons/Phone.d.ts +1 -1
  135. package/dist/typescript/icons/Phone.d.ts.map +1 -1
  136. package/dist/typescript/icons/PhoneDown.d.ts +1 -1
  137. package/dist/typescript/icons/PhoneDown.d.ts.map +1 -1
  138. package/dist/typescript/icons/PinVertical.d.ts +1 -1
  139. package/dist/typescript/icons/PinVertical.d.ts.map +1 -1
  140. package/dist/typescript/icons/Reaction.d.ts +1 -1
  141. package/dist/typescript/icons/Reaction.d.ts.map +1 -1
  142. package/dist/typescript/icons/ScreenShare.d.ts +1 -1
  143. package/dist/typescript/icons/ScreenShare.d.ts.map +1 -1
  144. package/dist/typescript/icons/ScreenShareIndicator.d.ts +1 -1
  145. package/dist/typescript/icons/ScreenShareIndicator.d.ts.map +1 -1
  146. package/dist/typescript/icons/Spotlight.d.ts +1 -1
  147. package/dist/typescript/icons/Spotlight.d.ts.map +1 -1
  148. package/dist/typescript/icons/StopScreenShare.d.ts +1 -1
  149. package/dist/typescript/icons/StopScreenShare.d.ts.map +1 -1
  150. package/dist/typescript/icons/Video.d.ts +1 -1
  151. package/dist/typescript/icons/Video.d.ts.map +1 -1
  152. package/dist/typescript/icons/VideoSlash.d.ts +1 -1
  153. package/dist/typescript/icons/VideoSlash.d.ts.map +1 -1
  154. package/dist/typescript/index.d.ts +1 -0
  155. package/dist/typescript/index.d.ts.map +1 -1
  156. package/dist/typescript/modules/call-manager/CallManager.d.ts +67 -0
  157. package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -0
  158. package/dist/typescript/modules/call-manager/PrevLibDetection.d.ts +13 -0
  159. package/dist/typescript/modules/call-manager/PrevLibDetection.d.ts.map +1 -0
  160. package/dist/typescript/modules/call-manager/index.d.ts +4 -0
  161. package/dist/typescript/modules/call-manager/index.d.ts.map +1 -0
  162. package/dist/typescript/modules/call-manager/types.d.ts +15 -0
  163. package/dist/typescript/modules/call-manager/types.d.ts.map +1 -0
  164. package/dist/typescript/providers/StreamCall/AppStateListener.d.ts.map +1 -1
  165. package/dist/typescript/theme/theme.d.ts +1 -2
  166. package/dist/typescript/theme/theme.d.ts.map +1 -1
  167. package/dist/typescript/utils/internal/rxSubjects.d.ts +1 -1
  168. package/dist/typescript/utils/internal/rxSubjects.d.ts.map +1 -1
  169. package/dist/typescript/version.d.ts +1 -1
  170. package/ios/PictureInPicture/StreamPictureInPictureController.swift +5 -0
  171. package/ios/RTCViewPip.swift +15 -0
  172. package/ios/RTCViewPipManager.mm +1 -0
  173. package/ios/StreamInCallManager.m +26 -0
  174. package/ios/StreamInCallManager.swift +303 -0
  175. package/ios/StreamVideoReactNative-Bridging-Header.h +1 -0
  176. package/ios/StreamVideoReactNative.m +6 -5
  177. package/package.json +32 -34
  178. package/src/components/Call/CallContent/CallContent.tsx +14 -10
  179. package/src/components/Call/CallContent/RTCViewPipIOS.tsx +17 -2
  180. package/src/components/Call/CallContent/RTCViewPipNative.tsx +8 -0
  181. package/src/components/Livestream/HostLivestream/HostLivestream.tsx +8 -3
  182. package/src/components/Livestream/LivestreamControls/ViewerLivestreamControls.tsx +11 -5
  183. package/src/components/Livestream/LivestreamLayout/LivestreamLayout.tsx +38 -29
  184. package/src/components/Livestream/ViewerLivestream/ViewerLivestream.tsx +8 -3
  185. package/src/hooks/useIsInPiPMode.tsx +4 -4
  186. package/src/hooks/usePermissionNotification.tsx +7 -12
  187. package/src/icons/Back.tsx +2 -2
  188. package/src/icons/BadNetwork.tsx +1 -1
  189. package/src/icons/CameraSwitch.tsx +2 -2
  190. package/src/icons/LivestreamControls.tsx +1 -1
  191. package/src/icons/Lock.tsx +1 -1
  192. package/src/icons/Maximize.tsx +1 -1
  193. package/src/icons/Mic.tsx +2 -2
  194. package/src/icons/MicOff.tsx +2 -2
  195. package/src/icons/Phone.tsx +2 -2
  196. package/src/icons/PhoneDown.tsx +1 -1
  197. package/src/icons/PinVertical.tsx +2 -2
  198. package/src/icons/Reaction.tsx +2 -2
  199. package/src/icons/ScreenShare.tsx +1 -1
  200. package/src/icons/ScreenShareIndicator.tsx +1 -1
  201. package/src/icons/Spotlight.tsx +2 -2
  202. package/src/icons/StopScreenShare.tsx +1 -1
  203. package/src/icons/Video.tsx +2 -2
  204. package/src/icons/VideoSlash.tsx +2 -2
  205. package/src/index.ts +1 -0
  206. package/src/modules/call-manager/CallManager.ts +116 -0
  207. package/src/modules/call-manager/PrevLibDetection.ts +27 -0
  208. package/src/modules/call-manager/index.ts +5 -0
  209. package/src/modules/call-manager/native-module.d.ts +80 -0
  210. package/src/modules/call-manager/types.ts +25 -0
  211. package/src/providers/StreamCall/AppStateListener.tsx +6 -9
  212. package/src/theme/theme.ts +2 -2
  213. package/src/utils/internal/rxSubjects.ts +1 -1
  214. package/src/version.ts +1 -1
@@ -0,0 +1,303 @@
1
+ import Foundation
2
+ import React
3
+ import UIKit
4
+ import AVFoundation
5
+ import stream_react_native_webrtc
6
+ import AVKit
7
+ import MediaPlayer
8
+
9
+ enum CallAudioRole {
10
+ case listener
11
+ case communicator
12
+ }
13
+
14
+ enum DefaultAudioDevice {
15
+ case speaker
16
+ case earpiece
17
+ }
18
+
19
+ @objc(StreamInCallManager)
20
+ class StreamInCallManager: RCTEventEmitter {
21
+
22
+ private let audioSessionQueue = DispatchQueue(label: "io.getstream.rn.audioSessionQueue")
23
+
24
+ private var audioManagerActivated = false
25
+ private var callAudioRole: CallAudioRole = .communicator
26
+ private var defaultAudioDevice: DefaultAudioDevice = .speaker
27
+ private var previousVolume: Float = 0.75
28
+
29
+ private struct AudioSessionState {
30
+ let category: AVAudioSession.Category
31
+ let mode: AVAudioSession.Mode
32
+ let options: AVAudioSession.CategoryOptions
33
+ }
34
+
35
+ private var previousAudioSessionState: AudioSessionState?
36
+
37
+ override func invalidate() {
38
+ stop()
39
+ super.invalidate()
40
+ }
41
+
42
+ override static func requiresMainQueueSetup() -> Bool {
43
+ return false
44
+ }
45
+
46
+
47
+ @objc(setAudioRole:)
48
+ func setAudioRole(audioRole: String) {
49
+ audioSessionQueue.async { [self] in
50
+ if audioManagerActivated {
51
+ log("AudioManager is already activated, audio role cannot be changed.")
52
+ return
53
+ }
54
+ self.callAudioRole = audioRole.lowercased() == "listener" ? .listener : .communicator
55
+ }
56
+ }
57
+
58
+ @objc(setDefaultAudioDeviceEndpointType:)
59
+ func setDefaultAudioDeviceEndpointType(endpointType: String) {
60
+ audioSessionQueue.async { [self] in
61
+ if audioManagerActivated {
62
+ log("AudioManager is already activated, default audio device cannot be changed.")
63
+ return
64
+ }
65
+ self.defaultAudioDevice = endpointType.lowercased() == "earpiece" ? .earpiece : .speaker
66
+ }
67
+ }
68
+
69
+ @objc
70
+ func start() {
71
+ audioSessionQueue.async { [self] in
72
+ if audioManagerActivated {
73
+ return
74
+ }
75
+ let session = AVAudioSession.sharedInstance()
76
+ previousAudioSessionState = AudioSessionState(
77
+ category: session.category,
78
+ mode: session.mode,
79
+ options: session.categoryOptions
80
+ )
81
+ configureAudioSession()
82
+ audioManagerActivated = true
83
+ }
84
+ }
85
+
86
+ @objc
87
+ func stop() {
88
+ audioSessionQueue.async { [self] in
89
+ if !audioManagerActivated {
90
+ return
91
+ }
92
+ if let prev = previousAudioSessionState {
93
+ let session = AVAudioSession.sharedInstance()
94
+ do {
95
+ try session.setCategory(prev.category, mode: prev.mode, options: prev.options)
96
+ } catch {
97
+ log("Error restoring previous audio session: \(error.localizedDescription)")
98
+ }
99
+ previousAudioSessionState = nil
100
+ }
101
+ audioManagerActivated = false
102
+ }
103
+ }
104
+
105
+ private func configureAudioSession() {
106
+ let intendedCategory: AVAudioSession.Category!
107
+ let intendedMode: AVAudioSession.Mode!
108
+ let intendedOptions: AVAudioSession.CategoryOptions!
109
+
110
+ if (callAudioRole == .listener) {
111
+ // enables high quality audio playback but disables microphone
112
+ intendedCategory = .playback
113
+ intendedMode = .default
114
+ intendedOptions = []
115
+ } else {
116
+ intendedCategory = .playAndRecord
117
+ intendedMode = .voiceChat
118
+
119
+ if (defaultAudioDevice == .speaker) {
120
+ // defaultToSpeaker will route to speaker if nothing else is connected
121
+ intendedOptions = [.allowBluetooth, .defaultToSpeaker]
122
+ } else {
123
+ // having no defaultToSpeaker makes sure audio goes to earpiece if nothing is connected
124
+ intendedOptions = [.allowBluetooth]
125
+ }
126
+ }
127
+
128
+ // START: set the config that webrtc must use when it takes control
129
+ let rtcConfig = RTCAudioSessionConfiguration.webRTC()
130
+ rtcConfig.category = intendedCategory.rawValue
131
+ rtcConfig.mode = intendedMode.rawValue
132
+ rtcConfig.categoryOptions = intendedOptions
133
+ RTCAudioSessionConfiguration.setWebRTC(rtcConfig)
134
+ // END
135
+
136
+ // START: compare current audio session with intended, and update if different
137
+ let session = RTCAudioSession.sharedInstance()
138
+ let currentCategory = session.category
139
+ let currentMode = session.mode
140
+ let currentOptions = session.categoryOptions
141
+ let currentIsActive = session.isActive
142
+
143
+ if currentCategory != intendedCategory.rawValue || currentMode != intendedMode.rawValue || currentOptions != intendedOptions || !currentIsActive {
144
+ session.lockForConfiguration()
145
+ do {
146
+ try session.setCategory(intendedCategory, mode: intendedMode, options: intendedOptions)
147
+ try session.setActive(true)
148
+ log("configureAudioSession: setCategory success \(intendedCategory.rawValue) \(intendedMode.rawValue) \(intendedOptions.rawValue)")
149
+ } catch {
150
+ log("configureAudioSession: setCategory failed due to: \(error.localizedDescription)")
151
+ do {
152
+ try session.setMode(intendedMode)
153
+ try session.setActive(true)
154
+ log("configureAudioSession: setMode success \(intendedMode.rawValue)")
155
+ } catch {
156
+ log("configureAudioSession: Error setting mode: \(error.localizedDescription)")
157
+ }
158
+ }
159
+ session.unlockForConfiguration()
160
+ } else {
161
+ log("configureAudioSession: no change needed")
162
+ }
163
+ // END
164
+ }
165
+
166
+ @objc(showAudioRoutePicker)
167
+ public func showAudioRoutePicker() {
168
+ guard #available(iOS 11.0, tvOS 11.0, macOS 10.15, *) else {
169
+ return
170
+ }
171
+ DispatchQueue.main.async {
172
+ // AVRoutePickerView is the default UI with a
173
+ // button that users tap to stream audio/video content to a media receiver
174
+ let routePicker = AVRoutePickerView()
175
+ // Send a touch up inside event to the button to trigger the audio route picker
176
+ (routePicker.subviews.first { $0 is UIButton } as? UIButton)?
177
+ .sendActions(for: .touchUpInside)
178
+ }
179
+ }
180
+
181
+ @objc(setForceSpeakerphoneOn:)
182
+ func setForceSpeakerphoneOn(enable: Bool) {
183
+ let session = AVAudioSession.sharedInstance()
184
+ do {
185
+ try session.overrideOutputAudioPort(enable ? .speaker : .none)
186
+ try session.setActive(true)
187
+ } catch {
188
+ log("Error setting speakerphone: \(error)")
189
+ }
190
+ }
191
+
192
+ @objc(setMicrophoneMute:)
193
+ func setMicrophoneMute(enable: Bool) {
194
+ log("iOS does not support setMicrophoneMute()")
195
+ }
196
+
197
+ @objc
198
+ func logAudioState() {
199
+ let session = AVAudioSession.sharedInstance()
200
+ let logString = """
201
+ Audio State:
202
+ Category: \(session.category.rawValue)
203
+ Mode: \(session.mode.rawValue)
204
+ Output Port: \(session.currentRoute.outputs.first?.portName ?? "N/A")
205
+ Input Port: \(session.currentRoute.inputs.first?.portName ?? "N/A")
206
+ Category Options: \(session.categoryOptions)
207
+ InputNumberOfChannels: \(session.inputNumberOfChannels)
208
+ OutputNumberOfChannels: \(session.outputNumberOfChannels)
209
+ """
210
+ log(logString)
211
+ }
212
+
213
+ @objc(muteAudioOutput)
214
+ func muteAudioOutput() {
215
+ DispatchQueue.main.async { [self] in
216
+ let volumeView = MPVolumeView()
217
+
218
+ // Add to a temporary view hierarchy to make it functional
219
+ if let window = getCurrentWindow() {
220
+ volumeView.frame = CGRect(x: -1000, y: -1000, width: 1, height: 1)
221
+ window.addSubview(volumeView)
222
+
223
+ // Give it a moment to initialize
224
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
225
+ if let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider {
226
+ self.previousVolume = slider.value
227
+ slider.setValue(0.0, animated: false)
228
+ slider.sendActions(for: .valueChanged)
229
+ self.log("Audio output muted via slider event")
230
+ } else {
231
+ self.log("Could not find volume slider")
232
+ }
233
+
234
+ // Remove from view hierarchy after use
235
+ volumeView.removeFromSuperview()
236
+ }
237
+ }
238
+ }
239
+ }
240
+
241
+ @objc(unmuteAudioOutput)
242
+ func unmuteAudioOutput() {
243
+ DispatchQueue.main.async { [self] in
244
+ let volumeView = MPVolumeView()
245
+
246
+ // Add to a temporary view hierarchy to make it functional
247
+ if let window = getCurrentWindow() {
248
+ volumeView.frame = CGRect(x: -1000, y: -1000, width: 1, height: 1)
249
+ window.addSubview(volumeView)
250
+
251
+ // Give it a moment to initialize
252
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
253
+ if let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider {
254
+ let targetVolume = self.previousVolume > 0 ? self.previousVolume : 0.75
255
+ slider.setValue(targetVolume, animated: false)
256
+ slider.sendActions(for: .valueChanged)
257
+ self.log("Audio output unmuted via slider event")
258
+ } else {
259
+ self.log("Could not find volume slider")
260
+ }
261
+
262
+ // Remove from view hierarchy after use
263
+ volumeView.removeFromSuperview()
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ // MARK: - RCTEventEmitter
270
+
271
+ override func supportedEvents() -> [String]! {
272
+ // TODO: list events that can be sent to JS
273
+ return []
274
+ }
275
+
276
+ @objc
277
+ override func addListener(_ eventName: String!) {
278
+ super.addListener(eventName)
279
+ }
280
+
281
+ @objc
282
+ override func removeListeners(_ count: Double) {
283
+ super.removeListeners(count)
284
+ }
285
+
286
+ // MARK: - Helper Methods
287
+ private func getCurrentWindow() -> UIWindow? {
288
+ if #available(iOS 13.0, *) {
289
+ return UIApplication.shared.connectedScenes
290
+ .compactMap({ $0 as? UIWindowScene })
291
+ .first?.windows
292
+ .first(where: { $0.isKeyWindow })
293
+ } else {
294
+ return UIApplication.shared.keyWindow
295
+ }
296
+ }
297
+
298
+ // MARK: - Logging Helper
299
+ private func log(_ message: String) {
300
+ NSLog("InCallManager: %@", message)
301
+ }
302
+
303
+ }
@@ -12,3 +12,4 @@
12
12
  #import <WebRTC/RTCVideoRenderer.h>
13
13
  #import <WebRTC/RTCVideoFrameBuffer.h>
14
14
  #import "WebRTCModule.h"
15
+ #import "WebRTCModuleOptions.h"
@@ -69,7 +69,7 @@ RCT_EXPORT_MODULE();
69
69
  if ((self = [super init])) {
70
70
  _notificationCenter = CFNotificationCenterGetDarwinNotifyCenter();
71
71
  [UIDevice currentDevice].batteryMonitoringEnabled = YES;
72
- [self setupObserver];
72
+ [self setupScreenshareEventObserver];
73
73
  [StreamVideoReactNative initializeSharedDictionaries];
74
74
  }
75
75
  return self;
@@ -85,7 +85,7 @@ RCT_EXPORT_METHOD(currentThermalState:(RCTPromiseResolveBlock)resolve rejecter:(
85
85
  resolve(thermalStateString);
86
86
  }
87
87
 
88
- -(void)dealloc {
88
+ -(void)invalidate {
89
89
  if (_busyTonePlayer) {
90
90
  if (_busyTonePlayer.isPlaying) {
91
91
  [_busyTonePlayer stop];
@@ -93,11 +93,12 @@ RCT_EXPORT_METHOD(currentThermalState:(RCTPromiseResolveBlock)resolve rejecter:(
93
93
  _busyTonePlayer = nil;
94
94
  [self removeAudioInterruptionHandling];
95
95
  }
96
- [self clearObserver];
96
+ [self clearScreenshareEventObserver];
97
+ [super invalidate];
97
98
  }
98
99
 
99
100
 
100
- -(void)setupObserver {
101
+ -(void)setupScreenshareEventObserver {
101
102
  CFNotificationCenterAddObserver(_notificationCenter,
102
103
  (__bridge const void *)(self),
103
104
  broadcastNotificationCallback,
@@ -112,7 +113,7 @@ RCT_EXPORT_METHOD(currentThermalState:(RCTPromiseResolveBlock)resolve rejecter:(
112
113
  CFNotificationSuspensionBehaviorDeliverImmediately);
113
114
  }
114
115
 
115
- -(void)clearObserver {
116
+ -(void)clearScreenshareEventObserver {
116
117
  CFNotificationCenterRemoveObserver(_notificationCenter,
117
118
  (__bridge const void *)(self),
118
119
  (__bridge CFStringRef)kBroadcastStartedNotification,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-react-native-sdk",
3
- "version": "1.21.1",
3
+ "version": "1.22.0",
4
4
  "description": "Stream Video SDK for React Native",
5
5
  "author": "https://getstream.io",
6
6
  "homepage": "https://getstream.io/video/docs/react-native/",
@@ -45,12 +45,12 @@
45
45
  "!**/.*"
46
46
  ],
47
47
  "dependencies": {
48
- "@stream-io/video-client": "1.33.0",
49
- "@stream-io/video-react-bindings": "1.9.0",
48
+ "@stream-io/video-client": "1.34.0",
49
+ "@stream-io/video-react-bindings": "1.10.0",
50
50
  "intl-pluralrules": "2.0.1",
51
51
  "lodash.merge": "^4.6.2",
52
- "react-native-url-polyfill": "1.3.0",
53
- "rxjs": "~7.8.1",
52
+ "react-native-url-polyfill": "^3.0.0",
53
+ "rxjs": "~7.8.2",
54
54
  "text-encoding-polyfill": "0.6.7"
55
55
  },
56
56
  "peerDependencies": {
@@ -60,16 +60,15 @@
60
60
  "@react-native-firebase/app": ">=17.5.0",
61
61
  "@react-native-firebase/messaging": ">=17.5.0",
62
62
  "@stream-io/noise-cancellation-react-native": ">=0.1.0",
63
- "@stream-io/react-native-webrtc": ">=125.4.3",
63
+ "@stream-io/react-native-webrtc": ">=125.4.4",
64
64
  "@stream-io/video-filters-react-native": ">=0.1.0",
65
65
  "expo": ">=47.0.0",
66
66
  "expo-build-properties": "*",
67
67
  "expo-notifications": "*",
68
68
  "react": ">=17.0.0",
69
- "react-native": ">=0.67.0",
69
+ "react-native": ">=0.73.0",
70
70
  "react-native-callkeep": ">=4.3.11",
71
71
  "react-native-gesture-handler": ">=2.8.0",
72
- "react-native-incall-manager": ">=4.2.0",
73
72
  "react-native-reanimated": ">=2.7.0",
74
73
  "react-native-svg": ">=13.6.0",
75
74
  "react-native-voip-push-notification": ">=3.3.1"
@@ -116,44 +115,43 @@
116
115
  }
117
116
  },
118
117
  "devDependencies": {
119
- "@expo/config-plugins": "10.0.2",
120
- "@expo/config-types": "^53.0.4",
121
- "@expo/plist": "^0.3.4",
118
+ "@babel/core": "^7.28.4",
119
+ "@expo/config-plugins": "54.0.2",
120
+ "@expo/config-types": "^54.0.8",
121
+ "@expo/plist": "^0.4.7",
122
122
  "@notifee/react-native": "9.1.8",
123
123
  "@react-native-community/netinfo": "11.4.1",
124
124
  "@react-native-community/push-notification-ios": "1.11.0",
125
- "@react-native-firebase/app": "^22.1.0",
126
- "@react-native-firebase/messaging": "^22.1.0",
127
- "@react-native/babel-preset": "^0.79.2",
128
- "@stream-io/noise-cancellation-react-native": "^0.3.0",
129
- "@stream-io/react-native-webrtc": "125.4.3",
130
- "@stream-io/video-filters-react-native": "^0.7.0",
125
+ "@react-native-firebase/app": "^23.4.0",
126
+ "@react-native-firebase/messaging": "^23.4.0",
127
+ "@react-native/babel-preset": "^0.81.4",
128
+ "@stream-io/noise-cancellation-react-native": "^0.4.0",
129
+ "@stream-io/react-native-webrtc": "125.4.4",
130
+ "@stream-io/video-filters-react-native": "^0.8.0",
131
131
  "@testing-library/jest-native": "^5.4.3",
132
- "@testing-library/react-native": "13.2.0",
133
- "@tsconfig/node14": "14.1.3",
132
+ "@testing-library/react-native": "13.3.3",
133
+ "@tsconfig/node18": "^18.2.4",
134
134
  "@types/jest": "^29.5.14",
135
135
  "@types/lodash.merge": "^4.6.9",
136
- "@types/react": "^19.1.3",
137
- "@types/react-native-incall-manager": "^4.0.3",
136
+ "@types/react": "~19.1.17",
138
137
  "@types/react-test-renderer": "^19.1.0",
139
- "expo": "~53.0.8",
140
- "expo-build-properties": "^0.13.2",
141
- "expo-module-scripts": "^4.0.5",
142
- "expo-modules-core": "2.2.3",
143
- "expo-notifications": "~0.29.14",
138
+ "expo": "~54.0.12",
139
+ "expo-build-properties": "^1.0.9",
140
+ "expo-module-scripts": "^5.0.7",
141
+ "expo-notifications": "~0.32.12",
144
142
  "jest": "^29.7.0",
145
- "react": "19.0.0",
146
- "react-native": "0.79.2",
143
+ "react": "19.1.0",
144
+ "react-native": "^0.81.4",
147
145
  "react-native-builder-bob": "~0.23",
148
146
  "react-native-callkeep": "^4.3.16",
149
- "react-native-gesture-handler": "^2.25.0",
150
- "react-native-incall-manager": "^4.2.1",
151
- "react-native-reanimated": "~3.17.5",
152
- "react-native-svg": "15.11.2",
147
+ "react-native-gesture-handler": "^2.28.0",
148
+ "react-native-reanimated": "~4.1.2",
149
+ "react-native-svg": "^15.14.0",
153
150
  "react-native-voip-push-notification": "3.3.3",
154
- "react-test-renderer": "19.0.0",
151
+ "react-native-worklets": "^0.5.0",
152
+ "react-test-renderer": "19.1.0",
155
153
  "rimraf": "^6.0.1",
156
- "typescript": "^5.8.3"
154
+ "typescript": "^5.9.3"
157
155
  },
158
156
  "react-native-builder-bob": {
159
157
  "source": "src",
@@ -1,12 +1,11 @@
1
1
  import React, { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import {
3
+ NativeModules,
4
+ Platform,
3
5
  StyleSheet,
4
6
  View,
5
- NativeModules,
6
7
  type ViewStyle,
7
- Platform,
8
8
  } from 'react-native';
9
- import InCallManager from 'react-native-incall-manager';
10
9
  import {
11
10
  CallParticipantsGrid,
12
11
  type CallParticipantsGridProps,
@@ -43,6 +42,7 @@ import {
43
42
  type ScreenShareOverlayProps,
44
43
  } from '../../utility/ScreenShareOverlay';
45
44
  import { RTCViewPipIOS } from './RTCViewPipIOS';
45
+ import { getRNInCallManagerLibNoThrow } from '../../../modules/call-manager/PrevLibDetection';
46
46
 
47
47
  export type StreamReactionType = StreamReaction & {
48
48
  icon: string;
@@ -95,7 +95,8 @@ export type CallContentProps = Pick<
95
95
  */
96
96
  disablePictureInPicture?: boolean;
97
97
  /**
98
- * Props to set the audio mode for the InCallManager.
98
+ * @deprecated This prop is deprecated and will be removed in the future. Use `StreamInCallManager` instead.
99
+ * Props to set the audio mode for the react-native-incall-manager library
99
100
  * If media type is video, audio is routed by default to speaker, otherwise it is routed to earpiece.
100
101
  * Changing the mode on the fly is not supported.
101
102
  * Manually invoke `InCallManager.start({ media })` to achieve this.
@@ -119,9 +120,9 @@ export const CallContent = ({
119
120
  layout = 'grid',
120
121
  landscape = false,
121
122
  supportedReactions,
123
+ initialInCallManagerAudioMode = 'video',
122
124
  iOSPiPIncludeLocalParticipantVideo,
123
125
  disablePictureInPicture,
124
- initialInCallManagerAudioMode = 'video',
125
126
  }: CallContentProps) => {
126
127
  const [
127
128
  showRemoteParticipantInFloatingView,
@@ -140,8 +141,6 @@ export const CallContent = ({
140
141
 
141
142
  useAutoEnterPiPEffect(disablePictureInPicture);
142
143
 
143
- const incallManagerModeRef = useRef(initialInCallManagerAudioMode);
144
-
145
144
  const _remoteParticipants = useRemoteParticipants();
146
145
  const remoteParticipants = useDebouncedValue(_remoteParticipants, 300); // we debounce the remote participants to avoid unnecessary rerenders that happen when participant tracks are all subscribed simultaneously
147
146
  const localParticipant = useLocalParticipant();
@@ -188,10 +187,15 @@ export const CallContent = ({
188
187
  /**
189
188
  * This hook is used to handle IncallManager specs of the application.
190
189
  */
190
+ const incallManagerModeRef = useRef(initialInCallManagerAudioMode);
191
191
  useEffect(() => {
192
- InCallManager.start({ media: incallManagerModeRef.current });
193
-
194
- return () => InCallManager.stop();
192
+ const prevInCallManager = getRNInCallManagerLibNoThrow();
193
+ if (prevInCallManager) {
194
+ prevInCallManager.start({ media: incallManagerModeRef.current });
195
+ return () => {
196
+ prevInCallManager.stop();
197
+ };
198
+ }
195
199
  }, []);
196
200
 
197
201
  const handleFloatingViewParticipantSwitch = () => {
@@ -18,13 +18,19 @@ import {
18
18
  import { useDebouncedValue } from '../../../utils/hooks';
19
19
  import { shouldDisableIOSLocalVideoOnBackgroundRef } from '../../../utils/internal/shouldDisableIOSLocalVideoOnBackground';
20
20
  import { useTrackDimensions } from '../../../hooks/useTrackDimensions';
21
+ import { isInPiPMode$ } from '../../../utils/internal/rxSubjects';
21
22
 
22
23
  type Props = {
23
24
  includeLocalParticipantVideo?: boolean;
25
+ /**
26
+ * Callback that is called when the PiP mode state changes.
27
+ * @param active - true when PiP started, false when PiP stopped
28
+ */
29
+ onPiPChange?: (active: boolean) => void;
24
30
  };
25
31
 
26
32
  export const RTCViewPipIOS = React.memo((props: Props) => {
27
- const { includeLocalParticipantVideo } = props;
33
+ const { includeLocalParticipantVideo, onPiPChange } = props;
28
34
  const call = useCall();
29
35
  const { useParticipants } = useCallStateHooks();
30
36
  const _allParticipants = useParticipants({
@@ -112,9 +118,18 @@ export const RTCViewPipIOS = React.memo((props: Props) => {
112
118
  return videoStreamToRender?.toURL();
113
119
  }, [videoStreamToRender]);
114
120
 
121
+ const handlePiPChange = (event: { nativeEvent: { active: boolean } }) => {
122
+ isInPiPMode$.next(event.nativeEvent.active);
123
+ onPiPChange?.(event.nativeEvent.active);
124
+ };
125
+
115
126
  return (
116
127
  <>
117
- <RTCViewPipNative streamURL={streamURL} ref={nativeRef} />
128
+ <RTCViewPipNative
129
+ streamURL={streamURL}
130
+ ref={nativeRef}
131
+ onPiPChange={handlePiPChange}
132
+ />
118
133
  {participantInSpotlight && (
119
134
  <DimensionsUpdatedRenderless
120
135
  participant={participantInSpotlight}
@@ -10,8 +10,13 @@ import {
10
10
 
11
11
  const COMPONENT_NAME = 'RTCViewPip';
12
12
 
13
+ export type PiPChangeEvent = {
14
+ active: boolean;
15
+ };
16
+
13
17
  type RTCViewPipNativeProps = {
14
18
  streamURL?: string;
19
+ onPiPChange?: (event: { nativeEvent: PiPChangeEvent }) => void;
15
20
  };
16
21
 
17
22
  const NativeComponent: HostComponent<RTCViewPipNativeProps> =
@@ -48,6 +53,7 @@ export const RTCViewPipNative = React.memo(
48
53
  React.Ref<any>,
49
54
  {
50
55
  streamURL?: string;
56
+ onPiPChange?: (event: { nativeEvent: PiPChangeEvent }) => void;
51
57
  }
52
58
  >((props, ref) => {
53
59
  if (Platform.OS !== 'ios') return null;
@@ -58,6 +64,8 @@ export const RTCViewPipNative = React.memo(
58
64
  pointerEvents={'none'}
59
65
  // eslint-disable-next-line react/prop-types
60
66
  streamURL={props.streamURL}
67
+ // eslint-disable-next-line react/prop-types
68
+ onPiPChange={props.onPiPChange}
61
69
  // @ts-expect-error - types issue
62
70
  ref={ref}
63
71
  />
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useMemo } from 'react';
2
2
  import { StyleSheet, View } from 'react-native';
3
- import InCallManager from 'react-native-incall-manager';
3
+ import { getRNInCallManagerLibNoThrow } from '../../../modules/call-manager/PrevLibDetection';
4
4
 
5
5
  import { useTheme } from '../../../contexts';
6
6
  import {
@@ -89,8 +89,13 @@ export const HostLivestream = ({
89
89
 
90
90
  // Automatically route audio to speaker devices as relevant for watching videos.
91
91
  useEffect(() => {
92
- InCallManager.start({ media: 'video' });
93
- return () => InCallManager.stop();
92
+ const prevInCallManager = getRNInCallManagerLibNoThrow();
93
+ if (prevInCallManager) {
94
+ prevInCallManager.start({ media: 'video' });
95
+ return () => {
96
+ prevInCallManager.stop();
97
+ };
98
+ }
94
99
  }, []);
95
100
 
96
101
  const [topViewHeight, setTopViewHeight] = React.useState<number>();
@@ -10,6 +10,7 @@ import {
10
10
  ViewerLeaveStreamButton as DefaultViewerLeaveStreamButton,
11
11
  type ViewerLeaveStreamButtonProps,
12
12
  } from './ViewerLeaveStreamButton';
13
+ import { callManager } from '../../../modules/call-manager';
13
14
  import { useTheme } from '../../../contexts';
14
15
  import { Z_INDEX } from '../../../constants';
15
16
  import {
@@ -18,12 +19,11 @@ import {
18
19
  LiveIndicator,
19
20
  } from '../LivestreamTopView';
20
21
  import { IconWrapper, Maximize } from '../../../icons';
21
- import InCallManager from 'react-native-incall-manager';
22
22
  import {
23
- VolumeOff,
24
- VolumeOn,
25
23
  PauseIcon,
26
24
  PlayIcon,
25
+ VolumeOff,
26
+ VolumeOn,
27
27
  } from '../../../icons/LivestreamControls';
28
28
 
29
29
  /**
@@ -104,10 +104,16 @@ export const ViewerLivestreamControls = ({
104
104
  };
105
105
 
106
106
  const toggleAudio = () => {
107
- setIsMuted(!isMuted);
108
- InCallManager.setForceSpeakerphoneOn(isMuted);
107
+ const shouldMute = !isMuted;
108
+ callManager.speaker.setMute(shouldMute);
109
+ setIsMuted(shouldMute);
109
110
  };
110
111
 
112
+ useEffect(() => {
113
+ // always unmute audio output on mount for consistency
114
+ callManager.speaker.setMute(false);
115
+ }, []);
116
+
111
117
  const togglePlayPause = () => {
112
118
  setIsPlaying(!isPlaying);
113
119
  showPlayPauseButtonWithTimeout();