@stream-io/video-react-native-sdk 1.29.4-beta.0 → 1.30.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 (276) hide show
  1. package/CHANGELOG.md +3162 -0
  2. package/android/src/main/AndroidManifest.xml +1 -8
  3. package/android/src/main/AndroidManifestNew.xml +0 -11
  4. package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt +5 -42
  5. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/WebRtcAudioUtils.kt +6 -70
  6. package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +4 -6
  7. package/android/src/main/java/com/streamvideo/reactnative/util/CallAliveServiceChecker.kt +95 -0
  8. package/dist/commonjs/components/Call/CallContent/CallContent.js +13 -7
  9. package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
  10. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +50 -14
  11. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
  12. package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js +27 -0
  13. package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
  14. package/dist/commonjs/components/Call/CallLayout/CallParticipantsGrid.js +19 -10
  15. package/dist/commonjs/components/Call/CallLayout/CallParticipantsGrid.js.map +1 -1
  16. package/dist/commonjs/components/Call/CallLayout/CallParticipantsSpotlight.js +12 -9
  17. package/dist/commonjs/components/Call/CallLayout/CallParticipantsSpotlight.js.map +1 -1
  18. package/dist/commonjs/components/Call/CallParticipantsList/CallParticipantsList.js +19 -4
  19. package/dist/commonjs/components/Call/CallParticipantsList/CallParticipantsList.js.map +1 -1
  20. package/dist/commonjs/hooks/push/index.js +2 -0
  21. package/dist/commonjs/hooks/push/index.js.map +1 -1
  22. package/dist/commonjs/hooks/push/useIosCallkeepWithCallingStateEffect.js +160 -0
  23. package/dist/commonjs/hooks/push/useIosCallkeepWithCallingStateEffect.js.map +1 -0
  24. package/dist/commonjs/hooks/push/useIosVoipPushEventsSetupEffect.js +31 -18
  25. package/dist/commonjs/hooks/push/useIosVoipPushEventsSetupEffect.js.map +1 -1
  26. package/dist/commonjs/hooks/push/useProcessPushCallEffect.js +67 -0
  27. package/dist/commonjs/hooks/push/useProcessPushCallEffect.js.map +1 -0
  28. package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js +97 -64
  29. package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
  30. package/dist/commonjs/index.js +0 -1
  31. package/dist/commonjs/index.js.map +1 -1
  32. package/dist/commonjs/modules/call-manager/CallManager.js +0 -26
  33. package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -1
  34. package/dist/commonjs/providers/StreamCall/index.js +6 -6
  35. package/dist/commonjs/providers/StreamCall/index.js.map +1 -1
  36. package/dist/commonjs/utils/StreamVideoRN/index.js +21 -33
  37. package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
  38. package/dist/commonjs/utils/hooks/index.js +0 -11
  39. package/dist/commonjs/utils/hooks/index.js.map +1 -1
  40. package/dist/commonjs/utils/internal/registerSDKGlobals.js +3 -52
  41. package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
  42. package/dist/commonjs/utils/push/android.js +202 -151
  43. package/dist/commonjs/utils/push/android.js.map +1 -1
  44. package/dist/commonjs/utils/push/internal/ios.js +34 -17
  45. package/dist/commonjs/utils/push/internal/ios.js.map +1 -1
  46. package/dist/commonjs/utils/push/internal/rxSubjects.js +45 -1
  47. package/dist/commonjs/utils/push/internal/rxSubjects.js.map +1 -1
  48. package/dist/commonjs/utils/push/internal/utils.js +20 -32
  49. package/dist/commonjs/utils/push/internal/utils.js.map +1 -1
  50. package/dist/commonjs/utils/push/ios.js.map +1 -1
  51. package/dist/commonjs/utils/push/libs/callkeep.js +17 -0
  52. package/dist/commonjs/utils/push/libs/callkeep.js.map +1 -0
  53. package/dist/commonjs/utils/push/libs/index.js +19 -8
  54. package/dist/commonjs/utils/push/libs/index.js.map +1 -1
  55. package/dist/commonjs/utils/push/libs/notifee/index.js +19 -0
  56. package/dist/commonjs/utils/push/libs/notifee/index.js.map +1 -1
  57. package/dist/commonjs/utils/push/libs/voipPushNotification.js +17 -0
  58. package/dist/commonjs/utils/push/libs/voipPushNotification.js.map +1 -0
  59. package/dist/commonjs/utils/push/setupIosCallKeepEvents.js +205 -0
  60. package/dist/commonjs/utils/push/setupIosCallKeepEvents.js.map +1 -0
  61. package/dist/commonjs/utils/push/setupIosVoipPushEvents.js +6 -7
  62. package/dist/commonjs/utils/push/setupIosVoipPushEvents.js.map +1 -1
  63. package/dist/commonjs/version.js +1 -1
  64. package/dist/commonjs/version.js.map +1 -1
  65. package/dist/module/components/Call/CallContent/CallContent.js +10 -4
  66. package/dist/module/components/Call/CallContent/CallContent.js.map +1 -1
  67. package/dist/module/components/Call/CallContent/RTCViewPipIOS.js +52 -16
  68. package/dist/module/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
  69. package/dist/module/components/Call/CallContent/RTCViewPipNative.js +27 -0
  70. package/dist/module/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
  71. package/dist/module/components/Call/CallLayout/CallParticipantsGrid.js +19 -10
  72. package/dist/module/components/Call/CallLayout/CallParticipantsGrid.js.map +1 -1
  73. package/dist/module/components/Call/CallLayout/CallParticipantsSpotlight.js +15 -12
  74. package/dist/module/components/Call/CallLayout/CallParticipantsSpotlight.js.map +1 -1
  75. package/dist/module/components/Call/CallParticipantsList/CallParticipantsList.js +20 -5
  76. package/dist/module/components/Call/CallParticipantsList/CallParticipantsList.js.map +1 -1
  77. package/dist/module/hooks/push/index.js +2 -0
  78. package/dist/module/hooks/push/index.js.map +1 -1
  79. package/dist/module/hooks/push/useIosCallkeepWithCallingStateEffect.js +153 -0
  80. package/dist/module/hooks/push/useIosCallkeepWithCallingStateEffect.js.map +1 -0
  81. package/dist/module/hooks/push/useIosVoipPushEventsSetupEffect.js +31 -18
  82. package/dist/module/hooks/push/useIosVoipPushEventsSetupEffect.js.map +1 -1
  83. package/dist/module/hooks/push/useProcessPushCallEffect.js +60 -0
  84. package/dist/module/hooks/push/useProcessPushCallEffect.js.map +1 -0
  85. package/dist/module/hooks/useAndroidKeepCallAliveEffect.js +99 -66
  86. package/dist/module/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
  87. package/dist/module/index.js +0 -1
  88. package/dist/module/index.js.map +1 -1
  89. package/dist/module/modules/call-manager/CallManager.js +0 -26
  90. package/dist/module/modules/call-manager/CallManager.js.map +1 -1
  91. package/dist/module/providers/StreamCall/index.js +6 -6
  92. package/dist/module/providers/StreamCall/index.js.map +1 -1
  93. package/dist/module/utils/StreamVideoRN/index.js +21 -33
  94. package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
  95. package/dist/module/utils/hooks/index.js +0 -1
  96. package/dist/module/utils/hooks/index.js.map +1 -1
  97. package/dist/module/utils/internal/registerSDKGlobals.js +3 -52
  98. package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
  99. package/dist/module/utils/push/android.js +204 -153
  100. package/dist/module/utils/push/android.js.map +1 -1
  101. package/dist/module/utils/push/internal/ios.js +34 -17
  102. package/dist/module/utils/push/internal/ios.js.map +1 -1
  103. package/dist/module/utils/push/internal/rxSubjects.js +44 -0
  104. package/dist/module/utils/push/internal/rxSubjects.js.map +1 -1
  105. package/dist/module/utils/push/internal/utils.js +19 -29
  106. package/dist/module/utils/push/internal/utils.js.map +1 -1
  107. package/dist/module/utils/push/ios.js.map +1 -1
  108. package/dist/module/utils/push/libs/callkeep.js +11 -0
  109. package/dist/module/utils/push/libs/callkeep.js.map +1 -0
  110. package/dist/module/utils/push/libs/index.js +2 -1
  111. package/dist/module/utils/push/libs/index.js.map +1 -1
  112. package/dist/module/utils/push/libs/notifee/index.js +18 -0
  113. package/dist/module/utils/push/libs/notifee/index.js.map +1 -1
  114. package/dist/module/utils/push/libs/voipPushNotification.js +11 -0
  115. package/dist/module/utils/push/libs/voipPushNotification.js.map +1 -0
  116. package/dist/module/utils/push/setupIosCallKeepEvents.js +199 -0
  117. package/dist/module/utils/push/setupIosCallKeepEvents.js.map +1 -0
  118. package/dist/module/utils/push/setupIosVoipPushEvents.js +6 -7
  119. package/dist/module/utils/push/setupIosVoipPushEvents.js.map +1 -1
  120. package/dist/module/version.js +1 -1
  121. package/dist/module/version.js.map +1 -1
  122. package/dist/typescript/components/Call/CallContent/CallContent.d.ts.map +1 -1
  123. package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -1
  124. package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts +18 -0
  125. package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts.map +1 -1
  126. package/dist/typescript/components/Call/CallLayout/CallParticipantsGrid.d.ts.map +1 -1
  127. package/dist/typescript/components/Call/CallLayout/CallParticipantsSpotlight.d.ts.map +1 -1
  128. package/dist/typescript/components/Call/CallParticipantsList/CallParticipantsList.d.ts.map +1 -1
  129. package/dist/typescript/hooks/push/index.d.ts.map +1 -1
  130. package/dist/typescript/hooks/push/useIosCallkeepWithCallingStateEffect.d.ts +5 -0
  131. package/dist/typescript/hooks/push/useIosCallkeepWithCallingStateEffect.d.ts.map +1 -0
  132. package/dist/typescript/hooks/push/useIosVoipPushEventsSetupEffect.d.ts.map +1 -1
  133. package/dist/typescript/hooks/push/useProcessPushCallEffect.d.ts +8 -0
  134. package/dist/typescript/hooks/push/useProcessPushCallEffect.d.ts.map +1 -0
  135. package/dist/typescript/hooks/useAndroidKeepCallAliveEffect.d.ts.map +1 -1
  136. package/dist/typescript/index.d.ts +0 -1
  137. package/dist/typescript/index.d.ts.map +1 -1
  138. package/dist/typescript/modules/call-manager/CallManager.d.ts +0 -5
  139. package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
  140. package/dist/typescript/utils/StreamVideoRN/index.d.ts +2 -20
  141. package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
  142. package/dist/typescript/utils/StreamVideoRN/types.d.ts +29 -54
  143. package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
  144. package/dist/typescript/utils/hooks/index.d.ts +0 -1
  145. package/dist/typescript/utils/hooks/index.d.ts.map +1 -1
  146. package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
  147. package/dist/typescript/utils/push/android.d.ts +2 -1
  148. package/dist/typescript/utils/push/android.d.ts.map +1 -1
  149. package/dist/typescript/utils/push/internal/ios.d.ts.map +1 -1
  150. package/dist/typescript/utils/push/internal/rxSubjects.d.ts +33 -0
  151. package/dist/typescript/utils/push/internal/rxSubjects.d.ts.map +1 -1
  152. package/dist/typescript/utils/push/internal/utils.d.ts +1 -8
  153. package/dist/typescript/utils/push/internal/utils.d.ts.map +1 -1
  154. package/dist/typescript/utils/push/ios.d.ts +2 -1
  155. package/dist/typescript/utils/push/ios.d.ts.map +1 -1
  156. package/dist/typescript/utils/push/libs/callkeep.d.ts +3 -0
  157. package/dist/typescript/utils/push/libs/callkeep.d.ts.map +1 -0
  158. package/dist/typescript/utils/push/libs/index.d.ts +2 -1
  159. package/dist/typescript/utils/push/libs/index.d.ts.map +1 -1
  160. package/dist/typescript/utils/push/libs/notifee/index.d.ts +1 -0
  161. package/dist/typescript/utils/push/libs/notifee/index.d.ts.map +1 -1
  162. package/dist/typescript/utils/push/libs/voipPushNotification.d.ts +3 -0
  163. package/dist/typescript/utils/push/libs/voipPushNotification.d.ts.map +1 -0
  164. package/dist/typescript/utils/push/setupIosCallKeepEvents.d.ts +6 -0
  165. package/dist/typescript/utils/push/setupIosCallKeepEvents.d.ts.map +1 -0
  166. package/dist/typescript/utils/push/setupIosVoipPushEvents.d.ts.map +1 -1
  167. package/dist/typescript/version.d.ts +1 -1
  168. package/dist/typescript/version.d.ts.map +1 -1
  169. package/expo-config-plugin/dist/withAndroidManifest.js +33 -1
  170. package/expo-config-plugin/dist/withAndroidPermissions.js +7 -2
  171. package/expo-config-plugin/dist/withAppDelegate.js +197 -19
  172. package/expo-config-plugin/dist/withMainActivity.js +1 -1
  173. package/expo-config-plugin/dist/withiOSInfoPlist.js +3 -2
  174. package/ios/PictureInPicture/PictureInPictureAvatarView.swift +273 -0
  175. package/ios/PictureInPicture/PictureInPictureConnectionQualityIndicator.swift +162 -0
  176. package/ios/PictureInPicture/PictureInPictureContent.swift +173 -0
  177. package/ios/PictureInPicture/PictureInPictureContentState.swift +123 -0
  178. package/ios/PictureInPicture/PictureInPictureDelegateProxy.swift +89 -0
  179. package/ios/PictureInPicture/PictureInPictureEnforcedStopAdapter.swift +166 -0
  180. package/ios/PictureInPicture/PictureInPictureLogger.swift +16 -0
  181. package/ios/PictureInPicture/PictureInPictureParticipantOverlayView.swift +217 -0
  182. package/ios/PictureInPicture/PictureInPictureReconnectionView.swift +193 -0
  183. package/ios/PictureInPicture/StreamAVPictureInPictureVideoCallViewController.swift +125 -7
  184. package/ios/PictureInPicture/StreamPictureInPictureController.swift +237 -63
  185. package/ios/PictureInPicture/StreamPictureInPictureControllerProtocol.swift +30 -0
  186. package/ios/PictureInPicture/StreamPictureInPictureVideoRenderer.swift +384 -12
  187. package/ios/RTCViewPip.swift +187 -21
  188. package/ios/RTCViewPipManager.mm +9 -0
  189. package/ios/RTCViewPipManager.swift +3 -3
  190. package/ios/StreamInCallManager.m +0 -2
  191. package/ios/StreamInCallManager.swift +7 -19
  192. package/ios/StreamVideoReactNative.h +4 -7
  193. package/ios/StreamVideoReactNative.m +82 -189
  194. package/package.json +19 -14
  195. package/src/components/Call/CallContent/CallContent.tsx +16 -8
  196. package/src/components/Call/CallContent/RTCViewPipIOS.tsx +81 -15
  197. package/src/components/Call/CallContent/RTCViewPipNative.tsx +36 -0
  198. package/src/components/Call/CallLayout/CallParticipantsGrid.tsx +28 -14
  199. package/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx +19 -10
  200. package/src/components/Call/CallParticipantsList/CallParticipantsList.tsx +20 -5
  201. package/src/hooks/push/index.ts +2 -0
  202. package/src/hooks/push/useIosCallkeepWithCallingStateEffect.ts +235 -0
  203. package/src/hooks/push/useIosVoipPushEventsSetupEffect.ts +34 -21
  204. package/src/hooks/push/useProcessPushCallEffect.ts +108 -0
  205. package/src/hooks/useAndroidKeepCallAliveEffect.ts +120 -94
  206. package/src/index.ts +0 -1
  207. package/src/modules/call-manager/CallManager.ts +0 -36
  208. package/src/modules/call-manager/native-module.d.ts +0 -7
  209. package/src/providers/StreamCall/index.tsx +6 -6
  210. package/src/utils/StreamVideoRN/index.ts +30 -40
  211. package/src/utils/StreamVideoRN/types.ts +29 -56
  212. package/src/utils/hooks/index.ts +0 -1
  213. package/src/utils/internal/registerSDKGlobals.ts +4 -47
  214. package/src/utils/push/android.ts +309 -227
  215. package/src/utils/push/internal/ios.ts +44 -28
  216. package/src/utils/push/internal/rxSubjects.ts +61 -0
  217. package/src/utils/push/internal/utils.ts +26 -45
  218. package/src/utils/push/ios.ts +6 -1
  219. package/src/utils/push/libs/callkeep.ts +16 -0
  220. package/src/utils/push/libs/index.ts +2 -1
  221. package/src/utils/push/libs/notifee/index.ts +27 -0
  222. package/src/utils/push/libs/voipPushNotification.ts +17 -0
  223. package/src/utils/push/setupIosCallKeepEvents.ts +252 -0
  224. package/src/utils/push/setupIosVoipPushEvents.ts +7 -11
  225. package/src/version.ts +1 -1
  226. package/android/src/main/java/com/streamvideo/reactnative/keepalive/KeepAliveNotification.kt +0 -83
  227. package/android/src/main/java/com/streamvideo/reactnative/keepalive/StreamCallKeepAliveHeadlessService.kt +0 -149
  228. package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js +0 -121
  229. package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js.map +0 -1
  230. package/dist/commonjs/utils/hooks/useDebouncedValue.js +0 -24
  231. package/dist/commonjs/utils/hooks/useDebouncedValue.js.map +0 -1
  232. package/dist/commonjs/utils/internal/callingx/audioSessionPromise.js +0 -58
  233. package/dist/commonjs/utils/internal/callingx/audioSessionPromise.js.map +0 -1
  234. package/dist/commonjs/utils/internal/callingx/callingx.js +0 -109
  235. package/dist/commonjs/utils/internal/callingx/callingx.js.map +0 -1
  236. package/dist/commonjs/utils/keepCallAliveHeadlessTask.js +0 -48
  237. package/dist/commonjs/utils/keepCallAliveHeadlessTask.js.map +0 -1
  238. package/dist/commonjs/utils/push/libs/callingx.js +0 -75
  239. package/dist/commonjs/utils/push/libs/callingx.js.map +0 -1
  240. package/dist/commonjs/utils/push/setupCallingExpEvents.js +0 -108
  241. package/dist/commonjs/utils/push/setupCallingExpEvents.js.map +0 -1
  242. package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js +0 -114
  243. package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js.map +0 -1
  244. package/dist/module/utils/hooks/useDebouncedValue.js +0 -19
  245. package/dist/module/utils/hooks/useDebouncedValue.js.map +0 -1
  246. package/dist/module/utils/internal/callingx/audioSessionPromise.js +0 -51
  247. package/dist/module/utils/internal/callingx/audioSessionPromise.js.map +0 -1
  248. package/dist/module/utils/internal/callingx/callingx.js +0 -100
  249. package/dist/module/utils/internal/callingx/callingx.js.map +0 -1
  250. package/dist/module/utils/keepCallAliveHeadlessTask.js +0 -42
  251. package/dist/module/utils/keepCallAliveHeadlessTask.js.map +0 -1
  252. package/dist/module/utils/push/libs/callingx.js +0 -67
  253. package/dist/module/utils/push/libs/callingx.js.map +0 -1
  254. package/dist/module/utils/push/setupCallingExpEvents.js +0 -102
  255. package/dist/module/utils/push/setupCallingExpEvents.js.map +0 -1
  256. package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts +0 -5
  257. package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts.map +0 -1
  258. package/dist/typescript/utils/hooks/useDebouncedValue.d.ts +0 -8
  259. package/dist/typescript/utils/hooks/useDebouncedValue.d.ts.map +0 -1
  260. package/dist/typescript/utils/internal/callingx/audioSessionPromise.d.ts +0 -16
  261. package/dist/typescript/utils/internal/callingx/audioSessionPromise.d.ts.map +0 -1
  262. package/dist/typescript/utils/internal/callingx/callingx.d.ts +0 -14
  263. package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +0 -1
  264. package/dist/typescript/utils/keepCallAliveHeadlessTask.d.ts +0 -10
  265. package/dist/typescript/utils/keepCallAliveHeadlessTask.d.ts.map +0 -1
  266. package/dist/typescript/utils/push/libs/callingx.d.ts +0 -9
  267. package/dist/typescript/utils/push/libs/callingx.d.ts.map +0 -1
  268. package/dist/typescript/utils/push/setupCallingExpEvents.d.ts +0 -8
  269. package/dist/typescript/utils/push/setupCallingExpEvents.d.ts.map +0 -1
  270. package/src/hooks/push/useCallingExpWithCallingStateEffect.ts +0 -147
  271. package/src/utils/hooks/useDebouncedValue.ts +0 -21
  272. package/src/utils/internal/callingx/audioSessionPromise.ts +0 -53
  273. package/src/utils/internal/callingx/callingx.ts +0 -146
  274. package/src/utils/keepCallAliveHeadlessTask.ts +0 -54
  275. package/src/utils/push/libs/callingx.ts +0 -90
  276. package/src/utils/push/setupCallingExpEvents.ts +0 -130
@@ -0,0 +1,162 @@
1
+ //
2
+ // Copyright © 2024 Stream.io Inc. All rights reserved.
3
+ //
4
+
5
+ import UIKit
6
+
7
+ /// A view representing a connection quality indicator for Picture-in-Picture.
8
+ /// Displays three vertical bars that indicate connection quality levels:
9
+ /// - Excellent: All 3 bars green
10
+ /// - Good: 2 bars green, 1 bar gray
11
+ /// - Poor: 1 bar red, 2 bars gray
12
+ /// - Unknown: All bars hidden
13
+ /// This aligns with upstream stream-video-swift ConnectionQualityIndicator.
14
+ final class PictureInPictureConnectionQualityIndicator: UIView {
15
+
16
+ // MARK: - Connection Quality Enum
17
+
18
+ /// Connection quality levels matching the stream-video-swift/video-client enum
19
+ enum ConnectionQuality: Int {
20
+ case unspecified = 0 // Unknown
21
+ case poor = 1
22
+ case good = 2
23
+ case excellent = 3
24
+ }
25
+
26
+ // MARK: - Properties
27
+
28
+ /// The current connection quality level
29
+ var connectionQuality: ConnectionQuality = .unspecified {
30
+ didSet {
31
+ updateIndicator()
32
+ }
33
+ }
34
+
35
+ /// Size of the indicator view
36
+ private let indicatorSize: CGFloat = 24
37
+
38
+ /// Width of each bar
39
+ private let barWidth: CGFloat = 3
40
+
41
+ /// Spacing between bars
42
+ private let barSpacing: CGFloat = 2
43
+
44
+ // MARK: - Colors
45
+
46
+ private let goodColor = UIColor(red: 0.2, green: 0.8, blue: 0.4, alpha: 1.0) // Green
47
+ private let badColor = UIColor(red: 0.9, green: 0.3, blue: 0.3, alpha: 1.0) // Red
48
+ private let inactiveColor = UIColor.white.withAlphaComponent(0.5)
49
+
50
+ // MARK: - UI Components
51
+
52
+ /// Background container with rounded corner
53
+ private lazy var containerView: UIView = {
54
+ let view = UIView()
55
+ view.translatesAutoresizingMaskIntoConstraints = false
56
+ view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
57
+ // Apply rounded corner only to top-left
58
+ view.layer.cornerRadius = 8
59
+ view.layer.maskedCorners = [.layerMinXMinYCorner] // top-left only
60
+ return view
61
+ }()
62
+
63
+ /// Stack view containing the three bars
64
+ private lazy var barsStackView: UIStackView = {
65
+ let stack = UIStackView()
66
+ stack.translatesAutoresizingMaskIntoConstraints = false
67
+ stack.axis = .horizontal
68
+ stack.alignment = .bottom
69
+ stack.spacing = barSpacing
70
+ stack.distribution = .equalSpacing
71
+ return stack
72
+ }()
73
+
74
+ /// First (shortest) bar
75
+ private lazy var bar1: UIView = {
76
+ createBar(height: barWidth * 2)
77
+ }()
78
+
79
+ /// Second (medium) bar
80
+ private lazy var bar2: UIView = {
81
+ createBar(height: barWidth * 3)
82
+ }()
83
+
84
+ /// Third (tallest) bar
85
+ private lazy var bar3: UIView = {
86
+ createBar(height: barWidth * 4)
87
+ }()
88
+
89
+ // MARK: - Initialization
90
+
91
+ override init(frame: CGRect) {
92
+ super.init(frame: frame)
93
+ setUp()
94
+ }
95
+
96
+ required init?(coder: NSCoder) {
97
+ fatalError("init(coder:) has not been implemented")
98
+ }
99
+
100
+ // MARK: - Private Methods
101
+
102
+ private func setUp() {
103
+ isUserInteractionEnabled = false
104
+ isHidden = true // Hidden by default (unknown quality)
105
+
106
+ addSubview(containerView)
107
+ containerView.addSubview(barsStackView)
108
+
109
+ barsStackView.addArrangedSubview(bar1)
110
+ barsStackView.addArrangedSubview(bar2)
111
+ barsStackView.addArrangedSubview(bar3)
112
+
113
+ NSLayoutConstraint.activate([
114
+ containerView.trailingAnchor.constraint(equalTo: trailingAnchor),
115
+ containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
116
+ containerView.widthAnchor.constraint(equalToConstant: indicatorSize),
117
+ containerView.heightAnchor.constraint(equalToConstant: indicatorSize),
118
+
119
+ barsStackView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
120
+ barsStackView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
121
+ ])
122
+
123
+ updateIndicator()
124
+ }
125
+
126
+ private func createBar(height: CGFloat) -> UIView {
127
+ let bar = UIView()
128
+ bar.translatesAutoresizingMaskIntoConstraints = false
129
+ bar.backgroundColor = inactiveColor
130
+ bar.layer.cornerRadius = 1
131
+ bar.layer.masksToBounds = true
132
+
133
+ NSLayoutConstraint.activate([
134
+ bar.widthAnchor.constraint(equalToConstant: barWidth),
135
+ bar.heightAnchor.constraint(equalToConstant: height)
136
+ ])
137
+
138
+ return bar
139
+ }
140
+
141
+ private func updateIndicator() {
142
+ switch connectionQuality {
143
+ case .excellent:
144
+ isHidden = false
145
+ bar1.backgroundColor = goodColor
146
+ bar2.backgroundColor = goodColor
147
+ bar3.backgroundColor = goodColor
148
+ case .good:
149
+ isHidden = false
150
+ bar1.backgroundColor = goodColor
151
+ bar2.backgroundColor = goodColor
152
+ bar3.backgroundColor = inactiveColor
153
+ case .poor:
154
+ isHidden = false
155
+ bar1.backgroundColor = badColor
156
+ bar2.backgroundColor = inactiveColor
157
+ bar3.backgroundColor = inactiveColor
158
+ case .unspecified:
159
+ isHidden = true
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,173 @@
1
+ //
2
+ // Copyright © 2024 Stream.io Inc. All rights reserved.
3
+ //
4
+ // Adapted from stream-video-swift for React Native SDK
5
+ // Original: https://github.com/GetStream/stream-video-swift/blob/develop/Sources/StreamVideoSwiftUI/Utils/PictureInPicture/PictureInPictureContent.swift
6
+ //
7
+
8
+ import Foundation
9
+
10
+ /// Represents the content state for the Picture-in-Picture window.
11
+ ///
12
+ /// This enum defines the different states that the PiP window can display:
13
+ /// - `inactive`: No content is being shown (PiP is not active)
14
+ /// - `video`: Live video from a participant (camera or screen share)
15
+ /// - `avatar`: Participant avatar placeholder (when video is disabled)
16
+ /// - `screenSharing`: Screen share content with indicator overlay
17
+ /// - `reconnecting`: Connection recovery indicator
18
+ ///
19
+ /// The React Native SDK receives content state from the JavaScript layer through
20
+ /// the bridge, unlike the upstream Swift SDK which observes call state internally.
21
+ enum PictureInPictureContent: Equatable, CustomStringConvertible {
22
+ /// No content - PiP is inactive or transitioning
23
+ case inactive
24
+
25
+ /// Video content from a participant
26
+ /// - Parameters:
27
+ /// - track: The WebRTC video track to render
28
+ /// - participantName: The participant's display name (for fallback)
29
+ /// - participantImageURL: URL to participant's profile image (for fallback)
30
+ case video(track: RTCVideoTrack?, participantName: String?, participantImageURL: String?)
31
+
32
+ /// Screen sharing content
33
+ /// - Parameters:
34
+ /// - track: The WebRTC video track containing screen share
35
+ /// - participantName: Name of the participant sharing their screen
36
+ case screenSharing(track: RTCVideoTrack?, participantName: String?)
37
+
38
+ /// Avatar placeholder shown when video is disabled
39
+ /// - Parameters:
40
+ /// - participantName: The participant's display name (for initials)
41
+ /// - participantImageURL: URL to participant's profile image
42
+ case avatar(participantName: String?, participantImageURL: String?)
43
+
44
+ /// Connection recovery indicator
45
+ case reconnecting
46
+
47
+ // MARK: - CustomStringConvertible
48
+
49
+ var description: String {
50
+ switch self {
51
+ case .inactive:
52
+ return ".inactive"
53
+ case let .video(track, name, _):
54
+ return ".video(track:\(track?.trackId ?? "nil"), name:\(name ?? "-"))"
55
+ case let .screenSharing(track, name):
56
+ return ".screenSharing(track:\(track?.trackId ?? "nil"), name:\(name ?? "-"))"
57
+ case let .avatar(name, _):
58
+ return ".avatar(name:\(name ?? "-"))"
59
+ case .reconnecting:
60
+ return ".reconnecting"
61
+ }
62
+ }
63
+
64
+ // MARK: - Equatable
65
+
66
+ static func == (lhs: PictureInPictureContent, rhs: PictureInPictureContent) -> Bool {
67
+ switch (lhs, rhs) {
68
+ case (.inactive, .inactive):
69
+ return true
70
+ case let (.video(lhsTrack, lhsName, lhsImage), .video(rhsTrack, rhsName, rhsImage)):
71
+ return isSameTrackInstance(lhsTrack, rhsTrack)
72
+ && lhsName == rhsName
73
+ && lhsImage == rhsImage
74
+ case let (.screenSharing(lhsTrack, lhsName), .screenSharing(rhsTrack, rhsName)):
75
+ return isSameTrackInstance(lhsTrack, rhsTrack)
76
+ && lhsName == rhsName
77
+ case let (.avatar(lhsName, lhsImage), .avatar(rhsName, rhsImage)):
78
+ return lhsName == rhsName
79
+ && lhsImage == rhsImage
80
+ case (.reconnecting, .reconnecting):
81
+ return true
82
+ default:
83
+ return false
84
+ }
85
+ }
86
+
87
+ /// Track identity must be reference-based so reconnect-created tracks
88
+ /// with reused `trackId` still propagate through content updates.
89
+ private static func isSameTrackInstance(_ lhs: RTCVideoTrack?, _ rhs: RTCVideoTrack?) -> Bool {
90
+ switch (lhs, rhs) {
91
+ case (nil, nil):
92
+ return true
93
+ case let (lhsTrack?, rhsTrack?):
94
+ return lhsTrack === rhsTrack
95
+ default:
96
+ return false
97
+ }
98
+ }
99
+
100
+ // MARK: - Convenience Properties
101
+
102
+ /// Returns the video track if this content has one, nil otherwise
103
+ var track: RTCVideoTrack? {
104
+ switch self {
105
+ case let .video(track, _, _):
106
+ return track
107
+ case let .screenSharing(track, _):
108
+ return track
109
+ case .inactive, .avatar, .reconnecting:
110
+ return nil
111
+ }
112
+ }
113
+
114
+ /// Returns the participant name if available
115
+ var participantName: String? {
116
+ switch self {
117
+ case let .video(_, name, _):
118
+ return name
119
+ case let .screenSharing(_, name):
120
+ return name
121
+ case let .avatar(name, _):
122
+ return name
123
+ case .inactive, .reconnecting:
124
+ return nil
125
+ }
126
+ }
127
+
128
+ /// Returns the participant image URL if available
129
+ var participantImageURL: String? {
130
+ switch self {
131
+ case let .video(_, _, imageURL):
132
+ return imageURL
133
+ case let .avatar(_, imageURL):
134
+ return imageURL
135
+ case .inactive, .screenSharing, .reconnecting:
136
+ return nil
137
+ }
138
+ }
139
+
140
+ /// Whether this content represents an active video stream
141
+ var hasActiveVideo: Bool {
142
+ switch self {
143
+ case .video, .screenSharing:
144
+ return true
145
+ case .inactive, .avatar, .reconnecting:
146
+ return false
147
+ }
148
+ }
149
+
150
+ /// Whether this content is screen sharing
151
+ var isScreenSharing: Bool {
152
+ if case .screenSharing = self {
153
+ return true
154
+ }
155
+ return false
156
+ }
157
+
158
+ /// Whether this content shows an avatar
159
+ var isShowingAvatar: Bool {
160
+ if case .avatar = self {
161
+ return true
162
+ }
163
+ return false
164
+ }
165
+
166
+ /// Whether this content shows the reconnection view
167
+ var isReconnecting: Bool {
168
+ if case .reconnecting = self {
169
+ return true
170
+ }
171
+ return false
172
+ }
173
+ }
@@ -0,0 +1,123 @@
1
+ //
2
+ // Copyright © 2024 Stream.io Inc. All rights reserved.
3
+ //
4
+ // Adapted from stream-video-swift PictureInPictureStore for React Native SDK
5
+ // The React Native SDK receives state from the JavaScript bridge rather than
6
+ // observing call state internally, so this is a simplified state container.
7
+ //
8
+
9
+ import Combine
10
+ import Foundation
11
+
12
+ /// Manages the content state for the Picture-in-Picture window.
13
+ ///
14
+ /// This class provides centralized state management for the PiP content view system.
15
+ /// Unlike the upstream `PictureInPictureStore` which uses a Flux-like action/dispatch pattern,
16
+ /// this implementation is optimized for the React Native bridge where state updates come
17
+ /// from the JavaScript layer.
18
+ ///
19
+ /// State changes are published via Combine to allow reactive updates in the view layer.
20
+ ///
21
+ /// Concurrency model:
22
+ /// - This state container is main-thread confined.
23
+ /// - `RTCVideoTrack` references are never sent across queues.
24
+ final class PictureInPictureContentState {
25
+
26
+ /// A full state snapshot that can be applied atomically.
27
+ struct Snapshot {
28
+ var track: RTCVideoTrack?
29
+ var participantName: String?
30
+ var participantImageURL: String?
31
+ var isVideoEnabled: Bool
32
+ var isScreenSharing: Bool
33
+ var isReconnecting: Bool
34
+ }
35
+
36
+ // MARK: - Published State
37
+
38
+ /// The current content being displayed in the PiP window.
39
+ @Published private(set) var content: PictureInPictureContent = .inactive
40
+
41
+ /// Publisher for observing content changes.
42
+ var contentPublisher: AnyPublisher<PictureInPictureContent, Never> {
43
+ $content.eraseToAnyPublisher()
44
+ }
45
+
46
+ // MARK: - Private
47
+
48
+ private var snapshot: Snapshot = makeDefaultSnapshot()
49
+
50
+ // MARK: - Initialization
51
+
52
+ init() {}
53
+
54
+ // MARK: - State Update
55
+
56
+ /// Applies all content inputs in one step to avoid parallel update paths.
57
+ func apply(_ snapshot: Snapshot) {
58
+ ensureMainThread()
59
+ self.snapshot = snapshot
60
+ publishIfNeeded(for: snapshot)
61
+ }
62
+
63
+ /// Resets all state to defaults.
64
+ /// Called when cleaning up after a call ends.
65
+ func reset() {
66
+ ensureMainThread()
67
+ snapshot = Self.makeDefaultSnapshot()
68
+ if content != .inactive {
69
+ content = .inactive
70
+ }
71
+ }
72
+
73
+ /// Computes and publishes content based on the latest snapshot.
74
+ private func publishIfNeeded(for snapshot: Snapshot) {
75
+ let newContent: PictureInPictureContent
76
+
77
+ // Priority order: reconnecting > screen sharing > avatar (video disabled) > video > avatar fallback
78
+ if snapshot.isReconnecting {
79
+ newContent = .reconnecting
80
+ } else if snapshot.isScreenSharing {
81
+ newContent = .screenSharing(
82
+ track: snapshot.track,
83
+ participantName: snapshot.participantName
84
+ )
85
+ } else if !snapshot.isVideoEnabled {
86
+ newContent = .avatar(
87
+ participantName: snapshot.participantName,
88
+ participantImageURL: snapshot.participantImageURL
89
+ )
90
+ } else if snapshot.isVideoEnabled, snapshot.track != nil {
91
+ newContent = .video(
92
+ track: snapshot.track,
93
+ participantName: snapshot.participantName,
94
+ participantImageURL: snapshot.participantImageURL
95
+ )
96
+ } else {
97
+ newContent = .avatar(
98
+ participantName: snapshot.participantName,
99
+ participantImageURL: snapshot.participantImageURL
100
+ )
101
+ }
102
+
103
+ if content != newContent {
104
+ content = newContent
105
+ }
106
+ }
107
+
108
+ /// PiP content state is expected to be mutated on the main thread only.
109
+ private func ensureMainThread() {
110
+ dispatchPrecondition(condition: .onQueue(.main))
111
+ }
112
+
113
+ private static func makeDefaultSnapshot() -> Snapshot {
114
+ Snapshot(
115
+ track: nil,
116
+ participantName: nil,
117
+ participantImageURL: nil,
118
+ isVideoEnabled: true,
119
+ isScreenSharing: false,
120
+ isReconnecting: false
121
+ )
122
+ }
123
+ }
@@ -0,0 +1,89 @@
1
+ //
2
+ // Copyright © 2024 Stream.io Inc. All rights reserved.
3
+ //
4
+
5
+ import AVKit
6
+ import Combine
7
+
8
+ /// A wrapper around AVPictureInPictureControllerDelegate that publishes all
9
+ /// delegate method calls via a single Combine publisher.
10
+ ///
11
+ /// This proxy enables reactive handling of PiP lifecycle events and allows
12
+ /// multiple subscribers to observe PiP state changes through a unified interface.
13
+ final class PictureInPictureDelegateProxy: NSObject, AVPictureInPictureControllerDelegate {
14
+
15
+ /// Enum representing each AVPictureInPictureControllerDelegate method call
16
+ /// with its respective associated values.
17
+ enum Event: CustomStringConvertible {
18
+ case willStart(AVPictureInPictureController)
19
+ case didStart(AVPictureInPictureController)
20
+ case failedToStart(AVPictureInPictureController, Error)
21
+ case willStop(AVPictureInPictureController)
22
+ case didStop(AVPictureInPictureController)
23
+ case restoreUI(AVPictureInPictureController, (Bool) -> Void)
24
+
25
+ var description: String {
26
+ switch self {
27
+ case .willStart:
28
+ return ".willStart"
29
+ case .didStart:
30
+ return ".didStart"
31
+ case let .failedToStart(_, error):
32
+ return ".failedToStart(error: \(error.localizedDescription))"
33
+ case .willStop:
34
+ return ".willStop"
35
+ case .didStop:
36
+ return ".didStop"
37
+ case .restoreUI:
38
+ return ".restoreUI"
39
+ }
40
+ }
41
+ }
42
+
43
+ /// The Combine publisher that emits Picture-in-Picture delegate events.
44
+ var publisher: AnyPublisher<Event, Never> {
45
+ eventSubject.eraseToAnyPublisher()
46
+ }
47
+
48
+ private let eventSubject = PassthroughSubject<Event, Never>()
49
+
50
+ // MARK: - AVPictureInPictureControllerDelegate
51
+
52
+ func pictureInPictureControllerWillStartPictureInPicture(
53
+ _ pictureInPictureController: AVPictureInPictureController
54
+ ) {
55
+ eventSubject.send(.willStart(pictureInPictureController))
56
+ }
57
+
58
+ func pictureInPictureControllerDidStartPictureInPicture(
59
+ _ pictureInPictureController: AVPictureInPictureController
60
+ ) {
61
+ eventSubject.send(.didStart(pictureInPictureController))
62
+ }
63
+
64
+ func pictureInPictureController(
65
+ _ pictureInPictureController: AVPictureInPictureController,
66
+ failedToStartPictureInPictureWithError error: Error
67
+ ) {
68
+ eventSubject.send(.failedToStart(pictureInPictureController, error))
69
+ }
70
+
71
+ func pictureInPictureControllerWillStopPictureInPicture(
72
+ _ pictureInPictureController: AVPictureInPictureController
73
+ ) {
74
+ eventSubject.send(.willStop(pictureInPictureController))
75
+ }
76
+
77
+ func pictureInPictureControllerDidStopPictureInPicture(
78
+ _ pictureInPictureController: AVPictureInPictureController
79
+ ) {
80
+ eventSubject.send(.didStop(pictureInPictureController))
81
+ }
82
+
83
+ func pictureInPictureController(
84
+ _ pictureInPictureController: AVPictureInPictureController,
85
+ restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
86
+ ) {
87
+ eventSubject.send(.restoreUI(pictureInPictureController, completionHandler))
88
+ }
89
+ }