@stream-io/video-react-native-sdk 2.0.0-beta.1 → 2.0.0-beta.2

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 (263) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +1 -1
  3. package/android/build.gradle +7 -1
  4. package/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt +110 -58
  5. package/android/src/main/java/com/streamvideo/reactnative/audio/CommunicationModeKeepAlive.kt +247 -0
  6. package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +16 -5
  7. package/dist/commonjs/components/Call/CallContent/CallContent.js +1 -1
  8. package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
  9. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +78 -7
  10. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
  11. package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js +3 -0
  12. package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
  13. package/dist/commonjs/components/Call/Lobby/JoinCallButton.js +3 -2
  14. package/dist/commonjs/components/Call/Lobby/JoinCallButton.js.map +1 -1
  15. package/dist/commonjs/components/Call/Lobby/Lobby.js +4 -3
  16. package/dist/commonjs/components/Call/Lobby/Lobby.js.map +1 -1
  17. package/dist/commonjs/components/Call/Lobby/LobbyFooter.js +10 -10
  18. package/dist/commonjs/components/Call/Lobby/LobbyFooter.js.map +1 -1
  19. package/dist/commonjs/components/Call/RingingCallContent/CallLeftIndicator.js +3 -3
  20. package/dist/commonjs/components/Call/RingingCallContent/CallLeftIndicator.js.map +1 -1
  21. package/dist/commonjs/components/Call/RingingCallContent/CallPreparingIndicator.js +3 -3
  22. package/dist/commonjs/components/Call/RingingCallContent/CallPreparingIndicator.js.map +1 -1
  23. package/dist/commonjs/components/Call/RingingCallContent/IncomingCall.js +3 -2
  24. package/dist/commonjs/components/Call/RingingCallContent/IncomingCall.js.map +1 -1
  25. package/dist/commonjs/components/Call/RingingCallContent/OutgoingCall.js +7 -14
  26. package/dist/commonjs/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
  27. package/dist/commonjs/components/Livestream/LivestreamControls/HostStartStreamButton.js +3 -2
  28. package/dist/commonjs/components/Livestream/LivestreamControls/HostStartStreamButton.js.map +1 -1
  29. package/dist/commonjs/components/Livestream/LivestreamPlayer/LivestreamEnded.js +4 -3
  30. package/dist/commonjs/components/Livestream/LivestreamPlayer/LivestreamEnded.js.map +1 -1
  31. package/dist/commonjs/components/Livestream/LivestreamTopView/LiveIndicator.js +3 -3
  32. package/dist/commonjs/components/Livestream/LivestreamTopView/LiveIndicator.js.map +1 -1
  33. package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLobby.js +9 -4
  34. package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLobby.js.map +1 -1
  35. package/dist/commonjs/components/Participant/ParticipantView/ParticipantLabel.js +4 -3
  36. package/dist/commonjs/components/Participant/ParticipantView/ParticipantLabel.js.map +1 -1
  37. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +27 -4
  38. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
  39. package/dist/commonjs/components/utility/ScreenShareOverlay.js +4 -3
  40. package/dist/commonjs/components/utility/ScreenShareOverlay.js.map +1 -1
  41. package/dist/commonjs/i18n/Streami18n.js +77 -0
  42. package/dist/commonjs/i18n/Streami18n.js.map +1 -0
  43. package/dist/commonjs/i18n/TranslationContext.js +52 -0
  44. package/dist/commonjs/i18n/TranslationContext.js.map +1 -0
  45. package/dist/commonjs/i18n/index.js +85 -0
  46. package/dist/commonjs/i18n/index.js.map +1 -0
  47. package/dist/commonjs/i18n/keys.js +2 -0
  48. package/dist/commonjs/i18n/keys.js.map +1 -0
  49. package/dist/commonjs/i18n/runtimeDefaults.js +24 -0
  50. package/dist/commonjs/i18n/runtimeDefaults.js.map +1 -0
  51. package/dist/commonjs/i18n/types.js +6 -0
  52. package/dist/commonjs/i18n/types.js.map +1 -0
  53. package/dist/commonjs/i18n/useStreami18n.js +61 -0
  54. package/dist/commonjs/i18n/useStreami18n.js.map +1 -0
  55. package/dist/commonjs/index.js +18 -5
  56. package/dist/commonjs/index.js.map +1 -1
  57. package/dist/commonjs/modules/encryption/EncryptionManager.js +305 -0
  58. package/dist/commonjs/modules/encryption/EncryptionManager.js.map +1 -0
  59. package/dist/commonjs/modules/encryption/eventMapping.js +143 -0
  60. package/dist/commonjs/modules/encryption/eventMapping.js.map +1 -0
  61. package/dist/commonjs/modules/encryption/index.js +13 -0
  62. package/dist/commonjs/modules/encryption/index.js.map +1 -0
  63. package/dist/commonjs/modules/encryption/parity.js +6 -0
  64. package/dist/commonjs/modules/encryption/parity.js.map +1 -0
  65. package/dist/commonjs/providers/NoiseCancellation/NoiseCancellationProvider.js +6 -2
  66. package/dist/commonjs/providers/NoiseCancellation/NoiseCancellationProvider.js.map +1 -1
  67. package/dist/commonjs/providers/StreamVideo.js +28 -12
  68. package/dist/commonjs/providers/StreamVideo.js.map +1 -1
  69. package/dist/commonjs/utils/StreamVideoRN/index.js +46 -0
  70. package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
  71. package/dist/commonjs/utils/internal/callingx/callingx.js +29 -4
  72. package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
  73. package/dist/commonjs/utils/internal/iosPipTrack.js +41 -0
  74. package/dist/commonjs/utils/internal/iosPipTrack.js.map +1 -0
  75. package/dist/commonjs/utils/internal/registerSDKGlobals.js +6 -0
  76. package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
  77. package/dist/commonjs/utils/internal/ringingCallLifecycle.js +142 -0
  78. package/dist/commonjs/utils/internal/ringingCallLifecycle.js.map +1 -0
  79. package/dist/commonjs/utils/push/internal/utils.js +4 -0
  80. package/dist/commonjs/utils/push/internal/utils.js.map +1 -1
  81. package/dist/commonjs/version.js +1 -1
  82. package/dist/module/components/Call/CallContent/CallContent.js +1 -1
  83. package/dist/module/components/Call/CallContent/CallContent.js.map +1 -1
  84. package/dist/module/components/Call/CallContent/RTCViewPipIOS.js +80 -9
  85. package/dist/module/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
  86. package/dist/module/components/Call/CallContent/RTCViewPipNative.js +3 -0
  87. package/dist/module/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
  88. package/dist/module/components/Call/Lobby/JoinCallButton.js +3 -2
  89. package/dist/module/components/Call/Lobby/JoinCallButton.js.map +1 -1
  90. package/dist/module/components/Call/Lobby/Lobby.js +4 -3
  91. package/dist/module/components/Call/Lobby/Lobby.js.map +1 -1
  92. package/dist/module/components/Call/Lobby/LobbyFooter.js +10 -10
  93. package/dist/module/components/Call/Lobby/LobbyFooter.js.map +1 -1
  94. package/dist/module/components/Call/RingingCallContent/CallLeftIndicator.js +2 -2
  95. package/dist/module/components/Call/RingingCallContent/CallLeftIndicator.js.map +1 -1
  96. package/dist/module/components/Call/RingingCallContent/CallPreparingIndicator.js +2 -2
  97. package/dist/module/components/Call/RingingCallContent/CallPreparingIndicator.js.map +1 -1
  98. package/dist/module/components/Call/RingingCallContent/IncomingCall.js +3 -2
  99. package/dist/module/components/Call/RingingCallContent/IncomingCall.js.map +1 -1
  100. package/dist/module/components/Call/RingingCallContent/OutgoingCall.js +7 -14
  101. package/dist/module/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
  102. package/dist/module/components/Livestream/LivestreamControls/HostStartStreamButton.js +3 -2
  103. package/dist/module/components/Livestream/LivestreamControls/HostStartStreamButton.js.map +1 -1
  104. package/dist/module/components/Livestream/LivestreamPlayer/LivestreamEnded.js +4 -3
  105. package/dist/module/components/Livestream/LivestreamPlayer/LivestreamEnded.js.map +1 -1
  106. package/dist/module/components/Livestream/LivestreamTopView/LiveIndicator.js +2 -2
  107. package/dist/module/components/Livestream/LivestreamTopView/LiveIndicator.js.map +1 -1
  108. package/dist/module/components/Livestream/ViewerLivestream/ViewerLobby.js +9 -4
  109. package/dist/module/components/Livestream/ViewerLivestream/ViewerLobby.js.map +1 -1
  110. package/dist/module/components/Participant/ParticipantView/ParticipantLabel.js +4 -3
  111. package/dist/module/components/Participant/ParticipantView/ParticipantLabel.js.map +1 -1
  112. package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +28 -5
  113. package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
  114. package/dist/module/components/utility/ScreenShareOverlay.js +4 -3
  115. package/dist/module/components/utility/ScreenShareOverlay.js.map +1 -1
  116. package/dist/module/i18n/Streami18n.js +73 -0
  117. package/dist/module/i18n/Streami18n.js.map +1 -0
  118. package/dist/module/i18n/TranslationContext.js +48 -0
  119. package/dist/module/i18n/TranslationContext.js.map +1 -0
  120. package/dist/module/i18n/index.js +20 -0
  121. package/dist/module/i18n/index.js.map +1 -0
  122. package/dist/module/i18n/keys.js +2 -0
  123. package/dist/module/i18n/keys.js.map +1 -0
  124. package/dist/module/i18n/runtimeDefaults.js +20 -0
  125. package/dist/module/i18n/runtimeDefaults.js.map +1 -0
  126. package/dist/module/i18n/types.js +4 -0
  127. package/dist/module/i18n/types.js.map +1 -0
  128. package/dist/module/i18n/useStreami18n.js +56 -0
  129. package/dist/module/i18n/useStreami18n.js.map +1 -0
  130. package/dist/module/index.js +6 -1
  131. package/dist/module/index.js.map +1 -1
  132. package/dist/module/modules/encryption/EncryptionManager.js +301 -0
  133. package/dist/module/modules/encryption/EncryptionManager.js.map +1 -0
  134. package/dist/module/modules/encryption/eventMapping.js +135 -0
  135. package/dist/module/modules/encryption/eventMapping.js.map +1 -0
  136. package/dist/module/modules/encryption/index.js +4 -0
  137. package/dist/module/modules/encryption/index.js.map +1 -0
  138. package/dist/module/modules/encryption/parity.js +4 -0
  139. package/dist/module/modules/encryption/parity.js.map +1 -0
  140. package/dist/module/providers/NoiseCancellation/NoiseCancellationProvider.js +6 -2
  141. package/dist/module/providers/NoiseCancellation/NoiseCancellationProvider.js.map +1 -1
  142. package/dist/module/providers/StreamVideo.js +29 -14
  143. package/dist/module/providers/StreamVideo.js.map +1 -1
  144. package/dist/module/utils/StreamVideoRN/index.js +46 -0
  145. package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
  146. package/dist/module/utils/internal/callingx/callingx.js +29 -4
  147. package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
  148. package/dist/module/utils/internal/iosPipTrack.js +35 -0
  149. package/dist/module/utils/internal/iosPipTrack.js.map +1 -0
  150. package/dist/module/utils/internal/registerSDKGlobals.js +6 -0
  151. package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
  152. package/dist/module/utils/internal/ringingCallLifecycle.js +134 -0
  153. package/dist/module/utils/internal/ringingCallLifecycle.js.map +1 -0
  154. package/dist/module/utils/push/internal/utils.js +4 -0
  155. package/dist/module/utils/push/internal/utils.js.map +1 -1
  156. package/dist/module/version.js +1 -1
  157. package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -1
  158. package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts +12 -0
  159. package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts.map +1 -1
  160. package/dist/typescript/components/Call/Lobby/JoinCallButton.d.ts.map +1 -1
  161. package/dist/typescript/components/Call/Lobby/Lobby.d.ts.map +1 -1
  162. package/dist/typescript/components/Call/Lobby/LobbyFooter.d.ts.map +1 -1
  163. package/dist/typescript/components/Call/RingingCallContent/OutgoingCall.d.ts.map +1 -1
  164. package/dist/typescript/components/Livestream/LivestreamControls/HostStartStreamButton.d.ts.map +1 -1
  165. package/dist/typescript/components/Livestream/LivestreamPlayer/LivestreamEnded.d.ts.map +1 -1
  166. package/dist/typescript/components/Livestream/ViewerLivestream/ViewerLobby.d.ts.map +1 -1
  167. package/dist/typescript/components/Participant/ParticipantView/ParticipantLabel.d.ts.map +1 -1
  168. package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts +11 -0
  169. package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts.map +1 -1
  170. package/dist/typescript/components/utility/ScreenShareOverlay.d.ts.map +1 -1
  171. package/dist/typescript/i18n/Streami18n.d.ts +61 -0
  172. package/dist/typescript/i18n/Streami18n.d.ts.map +1 -0
  173. package/dist/typescript/i18n/TranslationContext.d.ts +31 -0
  174. package/dist/typescript/i18n/TranslationContext.d.ts.map +1 -0
  175. package/dist/typescript/i18n/index.d.ts +17 -0
  176. package/dist/typescript/i18n/index.d.ts.map +1 -0
  177. package/dist/typescript/i18n/keys.d.ts +38 -0
  178. package/dist/typescript/i18n/keys.d.ts.map +1 -0
  179. package/dist/typescript/i18n/runtimeDefaults.d.ts +18 -0
  180. package/dist/typescript/i18n/runtimeDefaults.d.ts.map +1 -0
  181. package/dist/typescript/i18n/types.d.ts +46 -0
  182. package/dist/typescript/i18n/types.d.ts.map +1 -0
  183. package/dist/typescript/i18n/useStreami18n.d.ts +20 -0
  184. package/dist/typescript/i18n/useStreami18n.d.ts.map +1 -0
  185. package/dist/typescript/index.d.ts +2 -1
  186. package/dist/typescript/index.d.ts.map +1 -1
  187. package/dist/typescript/modules/encryption/EncryptionManager.d.ts +190 -0
  188. package/dist/typescript/modules/encryption/EncryptionManager.d.ts.map +1 -0
  189. package/dist/typescript/modules/encryption/eventMapping.d.ts +38 -0
  190. package/dist/typescript/modules/encryption/eventMapping.d.ts.map +1 -0
  191. package/dist/typescript/modules/encryption/index.d.ts +2 -0
  192. package/dist/typescript/modules/encryption/index.d.ts.map +1 -0
  193. package/dist/typescript/modules/encryption/parity.d.ts +55 -0
  194. package/dist/typescript/modules/encryption/parity.d.ts.map +1 -0
  195. package/dist/typescript/providers/NoiseCancellation/NoiseCancellationProvider.d.ts.map +1 -1
  196. package/dist/typescript/providers/StreamVideo.d.ts +17 -4
  197. package/dist/typescript/providers/StreamVideo.d.ts.map +1 -1
  198. package/dist/typescript/utils/StreamVideoRN/index.d.ts +33 -1
  199. package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
  200. package/dist/typescript/utils/StreamVideoRN/types.d.ts +67 -0
  201. package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
  202. package/dist/typescript/utils/internal/callingx/callingx.d.ts +1 -1
  203. package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
  204. package/dist/typescript/utils/internal/iosPipTrack.d.ts +9 -0
  205. package/dist/typescript/utils/internal/iosPipTrack.d.ts.map +1 -0
  206. package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
  207. package/dist/typescript/utils/internal/ringingCallLifecycle.d.ts +27 -0
  208. package/dist/typescript/utils/internal/ringingCallLifecycle.d.ts.map +1 -0
  209. package/dist/typescript/utils/push/internal/utils.d.ts.map +1 -1
  210. package/dist/typescript/version.d.ts +1 -1
  211. package/ios/RTCViewPip.swift +33 -8
  212. package/ios/RTCViewPipManager.mm +1 -0
  213. package/package.json +14 -10
  214. package/src/components/Call/CallContent/CallContent.tsx +1 -1
  215. package/src/components/Call/CallContent/RTCViewPipIOS.tsx +92 -10
  216. package/src/components/Call/CallContent/RTCViewPipNative.tsx +13 -0
  217. package/src/components/Call/Lobby/JoinCallButton.tsx +5 -2
  218. package/src/components/Call/Lobby/Lobby.tsx +6 -3
  219. package/src/components/Call/Lobby/LobbyFooter.tsx +15 -10
  220. package/src/components/Call/RingingCallContent/CallLeftIndicator.tsx +2 -2
  221. package/src/components/Call/RingingCallContent/CallPreparingIndicator.tsx +2 -2
  222. package/src/components/Call/RingingCallContent/IncomingCall.tsx +2 -2
  223. package/src/components/Call/RingingCallContent/OutgoingCall.tsx +7 -14
  224. package/src/components/Livestream/LivestreamControls/HostStartStreamButton.tsx +5 -8
  225. package/src/components/Livestream/LivestreamPlayer/LivestreamEnded.tsx +11 -3
  226. package/src/components/Livestream/LivestreamTopView/LiveIndicator.tsx +2 -2
  227. package/src/components/Livestream/ViewerLivestream/ViewerLobby.tsx +23 -10
  228. package/src/components/Participant/ParticipantView/ParticipantLabel.tsx +15 -6
  229. package/src/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.tsx +58 -5
  230. package/src/components/utility/ScreenShareOverlay.tsx +7 -3
  231. package/src/i18n/Streami18n.ts +71 -0
  232. package/src/i18n/TranslationContext.tsx +55 -0
  233. package/src/i18n/index.ts +22 -0
  234. package/src/i18n/keys.ts +42 -0
  235. package/src/i18n/runtimeDefaults.ts +17 -0
  236. package/src/i18n/types.ts +78 -0
  237. package/src/i18n/useStreami18n.ts +66 -0
  238. package/src/index.ts +6 -1
  239. package/src/modules/call-manager/native-module.d.ts +14 -0
  240. package/src/modules/encryption/EncryptionManager.ts +360 -0
  241. package/src/modules/encryption/eventMapping.ts +151 -0
  242. package/src/modules/encryption/index.ts +1 -0
  243. package/src/modules/encryption/parity.ts +83 -0
  244. package/src/providers/NoiseCancellation/NoiseCancellationProvider.tsx +8 -4
  245. package/src/providers/StreamVideo.tsx +39 -28
  246. package/src/utils/StreamVideoRN/index.ts +51 -1
  247. package/src/utils/StreamVideoRN/types.ts +68 -0
  248. package/src/utils/internal/callingx/callingx.ts +37 -2
  249. package/src/utils/internal/iosPipTrack.ts +39 -0
  250. package/src/utils/internal/registerSDKGlobals.ts +2 -0
  251. package/src/utils/internal/ringingCallLifecycle.ts +148 -0
  252. package/src/utils/push/internal/utils.ts +4 -0
  253. package/src/version.ts +1 -1
  254. package/dist/commonjs/translations/en.json +0 -32
  255. package/dist/commonjs/translations/index.js +0 -12
  256. package/dist/commonjs/translations/index.js.map +0 -1
  257. package/dist/module/translations/en.json +0 -32
  258. package/dist/module/translations/index.js +0 -7
  259. package/dist/module/translations/index.js.map +0 -1
  260. package/dist/typescript/translations/index.d.ts +0 -35
  261. package/dist/typescript/translations/index.d.ts.map +0 -1
  262. package/src/translations/en.json +0 -32
  263. package/src/translations/index.ts +0 -3
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type Call,
2
3
  CallingState,
3
4
  SfuModels,
4
5
  hasAudio,
@@ -12,17 +13,35 @@ import {
12
13
  } from '@stream-io/video-client';
13
14
  import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
14
15
  import type { MediaStream } from '@stream-io/react-native-webrtc';
15
- import React, { useEffect, useCallback, useState } from 'react';
16
+ import React, { useEffect, useCallback, useRef, useState } from 'react';
16
17
  import { findNodeHandle } from 'react-native';
17
18
  import {
18
19
  onNativeCallClosed,
19
20
  onNativeDimensionsUpdated,
21
+ type PiPBoundsChangeEvent,
22
+ type PiPChangeEvent,
20
23
  RTCViewPipNative,
21
24
  } from './RTCViewPipNative';
22
- import { debounceTime } from 'rxjs';
25
+ import { BehaviorSubject, debounceTime } from 'rxjs';
23
26
  import { shouldDisableIOSLocalVideoOnBackgroundRef } from '../../../utils/internal/shouldDisableIOSLocalVideoOnBackground';
24
27
  import { useTrackDimensions } from '../../../hooks/useTrackDimensions';
25
28
  import { isInPiPMode$ } from '../../../utils/internal/rxSubjects';
29
+ import TrackSubscriber from '../../Participant/ParticipantView/VideoRenderer/TrackSubscriber';
30
+
31
+ // One native view per Call instance. React Native drops the events of an
32
+ // unmounted view, so an event the previous call's controller queued before
33
+ // the replacement cannot reach the view of its successor.
34
+ const nativeViewKeys = new WeakMap<Call, number>();
35
+ let nextNativeViewKey = 0;
36
+ const nativeViewKeyOf = (call: Call | undefined) => {
37
+ if (!call) return 0;
38
+ let key = nativeViewKeys.get(call);
39
+ if (key === undefined) {
40
+ key = ++nextNativeViewKey;
41
+ nativeViewKeys.set(call, key);
42
+ }
43
+ return key;
44
+ };
26
45
 
27
46
  type Props = {
28
47
  includeLocalParticipantVideo?: boolean;
@@ -52,6 +71,39 @@ export const RTCViewPipIOS = React.memo((props: Props) => {
52
71
  StreamVideoParticipant[]
53
72
  >(call?.state.participants ?? []);
54
73
 
74
+ // bounds of the native window, in logical points. While the window is on
75
+ // screen they are the demand of the track it renders.
76
+ const [pipDimensions$] = useState(
77
+ () => new BehaviorSubject<SfuModels.VideoDimension | undefined>(undefined),
78
+ );
79
+ const [isPipActive, setIsPipActive] = useState(false);
80
+ // synchronous mirror of isPipActive: native may emit the same lifecycle
81
+ // state twice (e.g. failedToStart after didStop) before React re-renders.
82
+ const isPipActiveRef = useRef(false);
83
+ // set once the call is closed: later native events describe a disposed window.
84
+ const isClosedRef = useRef(false);
85
+ // the handlers below are bound to the view they were rendered for; an event
86
+ // of a replaced view is rejected even if it were delivered.
87
+ const nativeViewKey = nativeViewKeyOf(call);
88
+ const activeNativeViewKeyRef = useRef(nativeViewKey);
89
+ useEffect(() => {
90
+ activeNativeViewKeyRef.current = nativeViewKey;
91
+ }, [nativeViewKey]);
92
+ const isStaleNativeEvent = () =>
93
+ isClosedRef.current || nativeViewKey !== activeNativeViewKeyRef.current;
94
+ const onPiPChangeRef = useRef(onPiPChange);
95
+ useEffect(() => {
96
+ onPiPChangeRef.current = onPiPChange;
97
+ }, [onPiPChange]);
98
+
99
+ const updatePipState = useCallback((active: boolean) => {
100
+ if (isPipActiveRef.current === active) return;
101
+ isPipActiveRef.current = active;
102
+ setIsPipActive(active);
103
+ isInPiPMode$.next(active);
104
+ onPiPChangeRef.current?.(active);
105
+ }, []);
106
+
55
107
  // we debounce the participants to avoid unnecessary rerenders
56
108
  // that happen when participant tracks are all subscribed simultaneously
57
109
  useEffect(() => {
@@ -86,17 +138,19 @@ export const RTCViewPipIOS = React.memo((props: Props) => {
86
138
  const nativeRef = React.useRef<any>(null);
87
139
 
88
140
  React.useEffect(() => {
89
- let callClosedInvokedOnce = false;
141
+ const node = findNodeHandle(nativeRef.current);
142
+ isClosedRef.current = false;
90
143
  const onCallClosed = () => {
91
- if (callClosedInvokedOnce) {
144
+ if (isClosedRef.current) {
92
145
  return;
93
146
  }
94
- callClosedInvokedOnce = true;
95
- const node = findNodeHandle(nativeRef.current);
147
+ isClosedRef.current = true;
96
148
  if (node !== null) {
97
149
  onNativeCallClosed(node);
98
150
  }
99
151
  shouldDisableIOSLocalVideoOnBackgroundRef.current = true;
152
+ pipDimensions$.next(undefined);
153
+ updatePipState(false);
100
154
  };
101
155
  const unsubFunc = call?.on('call.ended', () => {
102
156
  videoLoggerSystem
@@ -117,7 +171,7 @@ export const RTCViewPipIOS = React.memo((props: Props) => {
117
171
  unsubFunc?.();
118
172
  subscription?.unsubscribe();
119
173
  };
120
- }, [call]);
174
+ }, [call, pipDimensions$, updatePipState]);
121
175
 
122
176
  const onDimensionsUpdated = useCallback((width: number, height: number) => {
123
177
  const node = findNodeHandle(nativeRef.current);
@@ -150,9 +204,17 @@ export const RTCViewPipIOS = React.memo((props: Props) => {
150
204
  ? mirrorOverride
151
205
  : !!participantInSpotlight?.isLocalParticipant && direction === 'front';
152
206
 
153
- const handlePiPChange = (event: { nativeEvent: { active: boolean } }) => {
154
- isInPiPMode$.next(event.nativeEvent.active);
155
- onPiPChange?.(event.nativeEvent.active);
207
+ const handlePiPChange = (event: { nativeEvent: PiPChangeEvent }) => {
208
+ if (isStaleNativeEvent()) return;
209
+ updatePipState(event.nativeEvent.active);
210
+ };
211
+
212
+ const handlePiPBoundsChange = (event: {
213
+ nativeEvent: PiPBoundsChangeEvent;
214
+ }) => {
215
+ if (isStaleNativeEvent()) return;
216
+ const { width, height } = event.nativeEvent;
217
+ pipDimensions$.next({ width, height });
156
218
  };
157
219
 
158
220
  // Get participant info for avatar placeholder
@@ -193,13 +255,23 @@ export const RTCViewPipIOS = React.memo((props: Props) => {
193
255
  participantInSpotlight?.connectionQuality ??
194
256
  SfuModels.ConnectionQuality.UNSPECIFIED;
195
257
 
258
+ // while the native window is on screen, its bounds - and not the hidden
259
+ // inline layout behind it - are the demand of the track it renders. The local
260
+ // preview is never subscribed to.
261
+ const pipTrackOwner =
262
+ isPipActive && participantInSpotlight?.isLocalParticipant !== true
263
+ ? participantInSpotlight
264
+ : undefined;
265
+
196
266
  return (
197
267
  <>
198
268
  <RTCViewPipNative
269
+ key={nativeViewKey}
199
270
  streamURL={streamURL}
200
271
  mirror={mirror}
201
272
  ref={nativeRef}
202
273
  onPiPChange={handlePiPChange}
274
+ onPiPBoundsChange={handlePiPBoundsChange}
203
275
  participantName={participantName}
204
276
  participantImageURL={participantImageURL}
205
277
  isReconnecting={isReconnecting}
@@ -210,6 +282,16 @@ export const RTCViewPipIOS = React.memo((props: Props) => {
210
282
  isSpeaking={participantIsSpeaking}
211
283
  connectionQuality={participantConnectionQuality}
212
284
  />
285
+ {pipTrackOwner && call && (
286
+ <TrackSubscriber
287
+ call={call}
288
+ participantSessionId={pipTrackOwner.sessionId}
289
+ trackType={trackType}
290
+ isVisible={true}
291
+ dimensions$={pipDimensions$}
292
+ isPipWriter
293
+ />
294
+ )}
213
295
  {participantInSpotlight && (
214
296
  <DimensionsUpdatedRenderless
215
297
  participant={participantInSpotlight}
@@ -14,10 +14,21 @@ export type PiPChangeEvent = {
14
14
  active: boolean;
15
15
  };
16
16
 
17
+ export type PiPBoundsChangeEvent = {
18
+ /** the laid out width of the native window, in logical points */
19
+ width: number;
20
+ /** the laid out height of the native window, in logical points */
21
+ height: number;
22
+ };
23
+
17
24
  type RTCViewPipNativeProps = {
18
25
  streamURL?: string;
19
26
  mirror?: boolean;
20
27
  onPiPChange?: (event: { nativeEvent: PiPChangeEvent }) => void;
28
+ /**
29
+ * Reports the actual laid out bounds of the native Picture in Picture window.
30
+ */
31
+ onPiPBoundsChange?: (event: { nativeEvent: PiPBoundsChangeEvent }) => void;
21
32
  /** The participant's name for the avatar placeholder when video is disabled */
22
33
  participantName?: string;
23
34
  /** The URL string for the participant's profile image */
@@ -84,6 +95,8 @@ export const RTCViewPipNative = React.memo(
84
95
  // eslint-disable-next-line react/prop-types
85
96
  onPiPChange={props.onPiPChange}
86
97
  // eslint-disable-next-line react/prop-types
98
+ onPiPBoundsChange={props.onPiPBoundsChange}
99
+ // eslint-disable-next-line react/prop-types
87
100
  participantName={props.participantName}
88
101
  // eslint-disable-next-line react/prop-types
89
102
  participantImageURL={props.participantImageURL}
@@ -1,7 +1,8 @@
1
1
  import React, { useMemo, useState } from 'react';
2
2
  import { type LobbyProps } from './Lobby';
3
3
  import { Pressable, StyleSheet, Text } from 'react-native';
4
- import { useCall, useI18n } from '@stream-io/video-react-bindings';
4
+ import { useCall } from '@stream-io/video-react-bindings';
5
+ import { useI18n } from '../../../i18n';
5
6
  import { useTheme } from '../../../contexts/ThemeContext';
6
7
  import { videoLoggerSystem } from '@stream-io/video-client';
7
8
 
@@ -64,7 +65,9 @@ export const JoinCallButton = ({
64
65
  joinCallButton.label,
65
66
  ]}
66
67
  >
67
- {isLoading ? t('Joining...') : t('Join')}
68
+ {isLoading
69
+ ? t('common.joining.text', 'Joining...')
70
+ : t('common.join.label', 'Join')}
68
71
  </Text>
69
72
  </Pressable>
70
73
  );
@@ -3,8 +3,8 @@ import { StyleSheet, Text, View } from 'react-native';
3
3
  import {
4
4
  useCallStateHooks,
5
5
  useConnectedUser,
6
- useI18n,
7
6
  } from '@stream-io/video-react-bindings';
7
+ import { useI18n } from '../../../i18n';
8
8
  import { Avatar } from '../../utility/Avatar';
9
9
  import type { StreamVideoParticipant } from '@stream-io/video-client';
10
10
  import { LobbyControls as DefaultLobbyControls } from '../CallControls/LobbyControls';
@@ -82,10 +82,13 @@ export const Lobby = ({
82
82
  {connectedUser && (
83
83
  <>
84
84
  <Text style={[styles.heading, typefaces.heading5, lobby.heading]}>
85
- {t('Before joining')}
85
+ {t('lobby.beforeJoining.title', 'Before joining')}
86
86
  </Text>
87
87
  <Text style={[styles.subHeading, lobby.subHeading]}>
88
- {t('Setup your audio and video')}
88
+ {t(
89
+ 'lobby.setupAudioVideo.description',
90
+ 'Setup your audio and video',
91
+ )}
89
92
  </Text>
90
93
  {isVideoEnabledInCall && (
91
94
  <View
@@ -2,7 +2,8 @@ import React, { useMemo } from 'react';
2
2
  import { type LobbyProps } from './Lobby';
3
3
  import { StyleSheet, Text, View } from 'react-native';
4
4
  import { useTheme } from '../../../contexts/ThemeContext';
5
- import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings';
5
+ import { useCallStateHooks } from '@stream-io/video-react-bindings';
6
+ import { useI18n } from '../../../i18n';
6
7
  import { Lock } from '../../../icons/Lock';
7
8
 
8
9
  /**
@@ -31,15 +32,17 @@ export const LobbyFooter = ({
31
32
 
32
33
  const participantsText = useMemo(() => {
33
34
  if (!numberOfParticipants) {
34
- return t('Currently there are no other participants in the call.');
35
+ return t(
36
+ 'lobby.footer.noOtherParticipants.text',
37
+ 'Currently there are no other participants in the call.',
38
+ );
35
39
  }
36
- if (numberOfParticipants === 1) {
37
- return t('There is {{numberOfParticipants}} more person in the call.', {
38
- numberOfParticipants,
39
- });
40
- }
41
- return t('There are {{numberOfParticipants}} more people in the call.', {
42
- numberOfParticipants,
40
+ // i18next picks the singular/plural form for the active language via
41
+ // Intl.PluralRules, so no `=== 1` branch is needed here.
42
+ return t('lobby.footer.otherParticipants.text', {
43
+ count: numberOfParticipants,
44
+ defaultValue_one: 'There is {{ count }} more person in the call.',
45
+ defaultValue_other: 'There are {{ count }} more people in the call.',
43
46
  });
44
47
  }, [numberOfParticipants, t]);
45
48
 
@@ -56,7 +59,9 @@ export const LobbyFooter = ({
56
59
  lobby.infoText,
57
60
  ]}
58
61
  >
59
- {t('You are about to join a call.') + ' ' + participantsText}
62
+ {t('lobby.footer.aboutToJoin.text', 'You are about to join a call.') +
63
+ ' ' +
64
+ participantsText}
60
65
  </Text>
61
66
  </View>
62
67
  {JoinCallButton && (
@@ -1,4 +1,4 @@
1
- import { useI18n } from '@stream-io/video-react-bindings';
1
+ import { useI18n } from '../../../i18n';
2
2
  import React from 'react';
3
3
  import {
4
4
  TextBasedIndicator,
@@ -14,7 +14,7 @@ export const CallLeftIndicator = (props: CallLeftIndicatorProps) => {
14
14
  const { t } = useI18n();
15
15
  return (
16
16
  <TextBasedIndicator
17
- text={t('You have left the call')}
17
+ text={t('ringingCall.leftCall.title', 'You have left the call')}
18
18
  onBackPress={props.onBackPress}
19
19
  />
20
20
  );
@@ -1,4 +1,4 @@
1
- import { useI18n } from '@stream-io/video-react-bindings';
1
+ import { useI18n } from '../../../i18n';
2
2
  import React from 'react';
3
3
  import {
4
4
  TextBasedIndicator,
@@ -14,7 +14,7 @@ export const CallPreparingIndicator = (props: CallPreparingIndicatorProps) => {
14
14
  const { t } = useI18n();
15
15
  return (
16
16
  <TextBasedIndicator
17
- text={t('Preparing call')}
17
+ text={t('ringingCall.preparing.title', 'Preparing call')}
18
18
  onBackPress={props.onBackPress}
19
19
  />
20
20
  );
@@ -3,8 +3,8 @@ import { Image, StyleSheet, Text, View, type ViewStyle } from 'react-native';
3
3
  import {
4
4
  useCallStateHooks,
5
5
  useConnectedUser,
6
- useI18n,
7
6
  } from '@stream-io/video-react-bindings';
7
+ import { useI18n } from '../../../i18n';
8
8
  import { UserInfo } from './UserInfo';
9
9
  import {
10
10
  IncomingCallControls as DefaultIncomingCallControls,
@@ -73,7 +73,7 @@ export const IncomingCall = ({
73
73
  incomingCall.incomingCallText,
74
74
  ]}
75
75
  >
76
- {t('Incoming Call...')}
76
+ {t('ringingCall.incoming.title', 'Incoming Call...')}
77
77
  </Text>
78
78
  </View>
79
79
  <View style={[styles.bottomContainer, incomingCall.bottomContainer]}>
@@ -2,13 +2,14 @@ import React from 'react';
2
2
  import { StyleSheet, Text, View, type ViewStyle } from 'react-native';
3
3
  import { UserInfo } from './UserInfo';
4
4
  import { Z_INDEX } from '../../../constants';
5
- import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings';
6
- import { MediaStream, RTCView } from '@stream-io/react-native-webrtc';
5
+ import { useCallStateHooks } from '@stream-io/video-react-bindings';
6
+ import { useI18n } from '../../../i18n';
7
7
  import { useTheme } from '../../../contexts/ThemeContext';
8
8
  import {
9
9
  OutgoingCallControls as DefaultOutgoingCallControls,
10
10
  type OutgoingCallControlsProps,
11
11
  } from '../CallControls';
12
+ import { LobbyCameraPreview } from '../Lobby';
12
13
 
13
14
  /**
14
15
  * Props for the OutgoingCall Component.
@@ -77,7 +78,7 @@ export const OutgoingCall = ({
77
78
  outgoingCall.callingText,
78
79
  ]}
79
80
  >
80
- {t('Calling...')}
81
+ {t('ringingCall.outgoing.title', 'Calling...')}
81
82
  </Text>
82
83
  </View>
83
84
  <View style={[styles.bottomContainer, outgoingCall.bottomContainer]}>
@@ -107,11 +108,9 @@ const Background = () => {
107
108
  theme: { colors, outgoingCall },
108
109
  } = useTheme();
109
110
  const { useCameraState } = useCallStateHooks();
110
- const { isMute, camera } = useCameraState();
111
- const localVideoStream = camera.state.mediaStream as unknown as
112
- MediaStream | undefined;
111
+ const { optimisticIsMute } = useCameraState();
113
112
 
114
- if (isMute || !localVideoStream) {
113
+ if (optimisticIsMute) {
115
114
  return (
116
115
  <View
117
116
  style={[
@@ -130,13 +129,7 @@ const Background = () => {
130
129
  outgoingCall.background,
131
130
  ]}
132
131
  >
133
- <RTCView
134
- streamURL={localVideoStream.toURL()}
135
- zOrder={Z_INDEX.IN_BACK}
136
- style={StyleSheet.absoluteFill}
137
- mirror
138
- objectFit="cover"
139
- />
132
+ <LobbyCameraPreview />
140
133
  </View>
141
134
  );
142
135
  };
@@ -1,8 +1,5 @@
1
- import {
2
- useCall,
3
- useCallStateHooks,
4
- useI18n,
5
- } from '@stream-io/video-react-bindings';
1
+ import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
2
+ import { useI18n } from '../../../i18n';
6
3
  import React, { useState } from 'react';
7
4
  import {
8
5
  ActivityIndicator,
@@ -149,10 +146,10 @@ export const HostStartStreamButton = ({
149
146
  ]}
150
147
  >
151
148
  {isAwaitingResponse
152
- ? t('Loading...')
149
+ ? t('common.loading.text', 'Loading...')
153
150
  : liveOrBroadcasting
154
- ? t('Stop Livestream')
155
- : t('Start Livestream')}
151
+ ? t('livestreamControls.stop.label', 'Stop Livestream')
152
+ : t('livestreamControls.start.label', 'Start Livestream')}
156
153
  </Text>
157
154
  </Pressable>
158
155
  );
@@ -1,5 +1,6 @@
1
1
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { useCall, useI18n } from '@stream-io/video-react-bindings';
2
+ import { useCall } from '@stream-io/video-react-bindings';
3
+ import { useI18n } from '../../../i18n';
3
4
  import { useTheme } from '../../../contexts';
4
5
  import { ListRecordingsResponse } from '@stream-io/video-client';
5
6
  import {
@@ -106,11 +107,18 @@ export const CallEndedView = () => {
106
107
 
107
108
  return (
108
109
  <View style={styles.container}>
109
- <Text style={styles.title}>{t('The livestream has ended.')}</Text>
110
+ <Text style={styles.title}>
111
+ {t('livestreamPlayer.ended.title', 'The livestream has ended.')}
112
+ </Text>
110
113
 
111
114
  {showRecordings && (
112
115
  <>
113
- <Text style={styles.subtitle}>{t('Watch recordings:')}</Text>
116
+ <Text style={styles.subtitle}>
117
+ {t(
118
+ 'livestreamPlayer.ended.watchRecordings.title',
119
+ 'Watch recordings:',
120
+ )}
121
+ </Text>
114
122
  <View style={styles.recordingsContainer}>
115
123
  <FlatList
116
124
  data={recordingsResponse.recordings}
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { StyleSheet, Text, View } from 'react-native';
3
3
  import { useTheme } from '../../../contexts';
4
- import { useI18n } from '@stream-io/video-react-bindings';
4
+ import { useI18n } from '../../../i18n';
5
5
 
6
6
  /**
7
7
  * Props for the LiveIndicator component.
@@ -33,7 +33,7 @@ export const LiveIndicator = ({}: LiveIndicatorProps) => {
33
33
  liveIndicator.label,
34
34
  ]}
35
35
  >
36
- {t('Live')}
36
+ {t('common.live.label', 'Live')}
37
37
  </Text>
38
38
  </View>
39
39
  );
@@ -1,8 +1,5 @@
1
- import {
2
- useCall,
3
- useCallStateHooks,
4
- useI18n,
5
- } from '@stream-io/video-react-bindings';
1
+ import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
2
+ import { useI18n } from '../../../i18n';
6
3
  import React, { useEffect, useMemo, useState } from 'react';
7
4
  import {
8
5
  ActivityIndicator,
@@ -99,10 +96,19 @@ export const ViewerLobby = ({ isLive }: LobbyProps) => {
99
96
  <View style={styles.container}>
100
97
  <Text style={styles.text}>
101
98
  {isLive
102
- ? t('Livestream is still in progress')
99
+ ? t(
100
+ 'livestreamViewer.stillInProgress.text',
101
+ 'Livestream is still in progress',
102
+ )
103
103
  : startsAt
104
- ? t('Livestream will start in:')
105
- : t('Livestream will start soon')}
104
+ ? t(
105
+ 'livestreamViewer.willStartIn.text',
106
+ 'Livestream will start in:',
107
+ )
108
+ : t(
109
+ 'livestreamViewer.willStartSoon.text',
110
+ 'Livestream will start soon',
111
+ )}
106
112
  </Text>
107
113
  {startsAt && !isLive && (
108
114
  <Text style={styles.countdownText}>{countdown}</Text>
@@ -110,12 +116,19 @@ export const ViewerLobby = ({ isLive }: LobbyProps) => {
110
116
  {!isLive && participants.length > 0 && (
111
117
  <>
112
118
  <Text style={styles.participantsText}>
113
- {`${participants.length} ${t('participants have joined early')}`}
119
+ {t('livestreamViewer.earlyParticipants.text', {
120
+ count: participants.length,
121
+ defaultValue_one: '{{ count }} participant has joined early',
122
+ defaultValue_other: '{{ count }} participants have joined early',
123
+ })}
114
124
  </Text>
115
125
  </>
116
126
  )}
117
127
  {isLive && (
118
- <Button title={t('Join Livestream')} onPress={() => call?.join()} />
128
+ <Button
129
+ title={t('livestreamViewer.join.label', 'Join Livestream')}
130
+ onPress={() => call?.join()}
131
+ />
119
132
  )}
120
133
  </View>
121
134
  );
@@ -15,10 +15,10 @@ import {
15
15
  } from '../../../icons';
16
16
  import {
17
17
  useCall,
18
- useI18n,
19
18
  useIsAudioConnecting,
20
19
  useIsVideoConnecting,
21
20
  } from '@stream-io/video-react-bindings';
21
+ import { useI18n } from '../../../i18n';
22
22
  import { ComponentTestIds } from '../../../constants/TestIds';
23
23
  import { type ParticipantViewProps } from './ParticipantView';
24
24
  import { Z_INDEX } from '../../../constants';
@@ -62,7 +62,9 @@ export const ParticipantLabel = ({
62
62
  const { t } = useI18n();
63
63
  const participantName = name ?? userId;
64
64
 
65
- const participantLabel = isLocalParticipant ? t('You') : participantName;
65
+ const participantLabel = isLocalParticipant
66
+ ? t('common.you.label', 'You')
67
+ : participantName;
66
68
  const isAudioMuted = !hasAudio(participant);
67
69
  const isVideoMuted = !hasVideo(participant);
68
70
  const isTrackPaused = trackType && hasPausedTrack(participant, trackType);
@@ -71,10 +73,17 @@ export const ParticipantLabel = ({
71
73
 
72
74
  if (trackType === 'screenShareTrack') {
73
75
  const screenShareText = isLocalParticipant
74
- ? t('You are sharing your screen')
75
- : t('{{ userName }} is sharing their screen', {
76
- userName: participantName,
77
- });
76
+ ? t(
77
+ 'participantView.screenShare.byYou.text',
78
+ 'You are sharing your screen',
79
+ )
80
+ : t(
81
+ 'participantView.screenShare.byUser.text',
82
+ '{{ userName }} is sharing their screen',
83
+ {
84
+ userName: participantName,
85
+ },
86
+ );
78
87
  return (
79
88
  <View
80
89
  style={[