@stream-io/video-react-native-sdk 1.45.1 → 1.46.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 (118) hide show
  1. package/CHANGELOG.md +36 -2
  2. package/android/build.gradle +7 -1
  3. package/dist/commonjs/components/Call/CallContent/CallContent.js +1 -1
  4. package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
  5. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +78 -7
  6. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
  7. package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js +3 -0
  8. package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
  9. package/dist/commonjs/components/Call/RingingCallContent/OutgoingCall.js +4 -12
  10. package/dist/commonjs/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
  11. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +27 -4
  12. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
  13. package/dist/commonjs/index.js +14 -1
  14. package/dist/commonjs/index.js.map +1 -1
  15. package/dist/commonjs/modules/encryption/EncryptionManager.js +305 -0
  16. package/dist/commonjs/modules/encryption/EncryptionManager.js.map +1 -0
  17. package/dist/commonjs/modules/encryption/eventMapping.js +143 -0
  18. package/dist/commonjs/modules/encryption/eventMapping.js.map +1 -0
  19. package/dist/commonjs/modules/encryption/index.js +13 -0
  20. package/dist/commonjs/modules/encryption/index.js.map +1 -0
  21. package/dist/commonjs/modules/encryption/parity.js +6 -0
  22. package/dist/commonjs/modules/encryption/parity.js.map +1 -0
  23. package/dist/commonjs/utils/StreamVideoRN/index.js +23 -0
  24. package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
  25. package/dist/commonjs/utils/internal/callingx/callingx.js +29 -4
  26. package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
  27. package/dist/commonjs/utils/internal/iosPipTrack.js +41 -0
  28. package/dist/commonjs/utils/internal/iosPipTrack.js.map +1 -0
  29. package/dist/commonjs/utils/internal/registerSDKGlobals.js +6 -0
  30. package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
  31. package/dist/commonjs/utils/internal/ringingCallLifecycle.js +142 -0
  32. package/dist/commonjs/utils/internal/ringingCallLifecycle.js.map +1 -0
  33. package/dist/commonjs/utils/push/internal/utils.js +4 -0
  34. package/dist/commonjs/utils/push/internal/utils.js.map +1 -1
  35. package/dist/commonjs/version.js +1 -1
  36. package/dist/module/components/Call/CallContent/CallContent.js +1 -1
  37. package/dist/module/components/Call/CallContent/CallContent.js.map +1 -1
  38. package/dist/module/components/Call/CallContent/RTCViewPipIOS.js +80 -9
  39. package/dist/module/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
  40. package/dist/module/components/Call/CallContent/RTCViewPipNative.js +3 -0
  41. package/dist/module/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
  42. package/dist/module/components/Call/RingingCallContent/OutgoingCall.js +4 -12
  43. package/dist/module/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
  44. package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +28 -5
  45. package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
  46. package/dist/module/index.js +5 -0
  47. package/dist/module/index.js.map +1 -1
  48. package/dist/module/modules/encryption/EncryptionManager.js +301 -0
  49. package/dist/module/modules/encryption/EncryptionManager.js.map +1 -0
  50. package/dist/module/modules/encryption/eventMapping.js +135 -0
  51. package/dist/module/modules/encryption/eventMapping.js.map +1 -0
  52. package/dist/module/modules/encryption/index.js +4 -0
  53. package/dist/module/modules/encryption/index.js.map +1 -0
  54. package/dist/module/modules/encryption/parity.js +4 -0
  55. package/dist/module/modules/encryption/parity.js.map +1 -0
  56. package/dist/module/utils/StreamVideoRN/index.js +23 -0
  57. package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
  58. package/dist/module/utils/internal/callingx/callingx.js +29 -4
  59. package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
  60. package/dist/module/utils/internal/iosPipTrack.js +35 -0
  61. package/dist/module/utils/internal/iosPipTrack.js.map +1 -0
  62. package/dist/module/utils/internal/registerSDKGlobals.js +6 -0
  63. package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
  64. package/dist/module/utils/internal/ringingCallLifecycle.js +134 -0
  65. package/dist/module/utils/internal/ringingCallLifecycle.js.map +1 -0
  66. package/dist/module/utils/push/internal/utils.js +4 -0
  67. package/dist/module/utils/push/internal/utils.js.map +1 -1
  68. package/dist/module/version.js +1 -1
  69. package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -1
  70. package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts +12 -0
  71. package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts.map +1 -1
  72. package/dist/typescript/components/Call/RingingCallContent/OutgoingCall.d.ts.map +1 -1
  73. package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts +11 -0
  74. package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts.map +1 -1
  75. package/dist/typescript/index.d.ts +1 -0
  76. package/dist/typescript/index.d.ts.map +1 -1
  77. package/dist/typescript/modules/encryption/EncryptionManager.d.ts +190 -0
  78. package/dist/typescript/modules/encryption/EncryptionManager.d.ts.map +1 -0
  79. package/dist/typescript/modules/encryption/eventMapping.d.ts +38 -0
  80. package/dist/typescript/modules/encryption/eventMapping.d.ts.map +1 -0
  81. package/dist/typescript/modules/encryption/index.d.ts +2 -0
  82. package/dist/typescript/modules/encryption/index.d.ts.map +1 -0
  83. package/dist/typescript/modules/encryption/parity.d.ts +55 -0
  84. package/dist/typescript/modules/encryption/parity.d.ts.map +1 -0
  85. package/dist/typescript/utils/StreamVideoRN/index.d.ts +20 -1
  86. package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
  87. package/dist/typescript/utils/StreamVideoRN/types.d.ts +67 -0
  88. package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
  89. package/dist/typescript/utils/internal/callingx/callingx.d.ts +1 -1
  90. package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
  91. package/dist/typescript/utils/internal/iosPipTrack.d.ts +9 -0
  92. package/dist/typescript/utils/internal/iosPipTrack.d.ts.map +1 -0
  93. package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
  94. package/dist/typescript/utils/internal/ringingCallLifecycle.d.ts +27 -0
  95. package/dist/typescript/utils/internal/ringingCallLifecycle.d.ts.map +1 -0
  96. package/dist/typescript/utils/push/internal/utils.d.ts.map +1 -1
  97. package/dist/typescript/version.d.ts +1 -1
  98. package/ios/RTCViewPip.swift +33 -8
  99. package/ios/RTCViewPipManager.mm +1 -0
  100. package/package.json +8 -8
  101. package/src/components/Call/CallContent/CallContent.tsx +1 -1
  102. package/src/components/Call/CallContent/RTCViewPipIOS.tsx +92 -10
  103. package/src/components/Call/CallContent/RTCViewPipNative.tsx +13 -0
  104. package/src/components/Call/RingingCallContent/OutgoingCall.tsx +4 -12
  105. package/src/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.tsx +58 -5
  106. package/src/index.ts +5 -0
  107. package/src/modules/encryption/EncryptionManager.ts +360 -0
  108. package/src/modules/encryption/eventMapping.ts +151 -0
  109. package/src/modules/encryption/index.ts +1 -0
  110. package/src/modules/encryption/parity.ts +83 -0
  111. package/src/utils/StreamVideoRN/index.ts +24 -1
  112. package/src/utils/StreamVideoRN/types.ts +68 -0
  113. package/src/utils/internal/callingx/callingx.ts +37 -2
  114. package/src/utils/internal/iosPipTrack.ts +39 -0
  115. package/src/utils/internal/registerSDKGlobals.ts +2 -0
  116. package/src/utils/internal/ringingCallLifecycle.ts +148 -0
  117. package/src/utils/push/internal/utils.ts +4 -0
  118. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-react-native-sdk",
3
- "version": "1.45.1",
3
+ "version": "1.46.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/",
@@ -52,8 +52,8 @@
52
52
  "!**/.*"
53
53
  ],
54
54
  "dependencies": {
55
- "@stream-io/video-client": "1.59.1",
56
- "@stream-io/video-react-bindings": "1.20.2",
55
+ "@stream-io/video-client": "1.61.0",
56
+ "@stream-io/video-react-bindings": "1.20.4",
57
57
  "intl-pluralrules": "2.0.1",
58
58
  "react-native-url-polyfill": "^3.0.0",
59
59
  "rxjs": "~7.8.2",
@@ -65,7 +65,7 @@
65
65
  "@react-native-firebase/messaging": ">=17.5.0",
66
66
  "@stream-io/noise-cancellation-react-native": ">=0.1.0",
67
67
  "@stream-io/react-native-callingx": ">=0.1.0",
68
- "@stream-io/react-native-webrtc": "^145.3.1",
68
+ "@stream-io/react-native-webrtc": "^145.4.1",
69
69
  "@stream-io/video-filters-react-native": ">=0.1.0",
70
70
  "expo": ">=47.0.0",
71
71
  "expo-notifications": "*",
@@ -117,11 +117,11 @@
117
117
  "@react-native/babel-preset": "0.86.2",
118
118
  "@react-native/jest-preset": "0.86.2",
119
119
  "@react-native/metro-config": "0.86.2",
120
- "@stream-io/noise-cancellation-react-native": "^0.11.0",
121
- "@stream-io/react-native-callingx": "^0.11.3",
122
- "@stream-io/react-native-webrtc": "145.3.1",
120
+ "@stream-io/noise-cancellation-react-native": "^0.11.1",
121
+ "@stream-io/react-native-callingx": "^0.11.5",
122
+ "@stream-io/react-native-webrtc": "145.4.1",
123
123
  "@stream-io/typescript-config": "^0.0.0",
124
- "@stream-io/video-filters-react-native": "^0.16.0",
124
+ "@stream-io/video-filters-react-native": "^0.16.2",
125
125
  "@testing-library/jest-native": "^5.4.3",
126
126
  "@testing-library/react-native": "13.3.3",
127
127
  "@tsconfig/node18": "^18.2.6",
@@ -267,7 +267,7 @@ export const CallContent = ({
267
267
 
268
268
  return (
269
269
  <>
270
- {!disablePictureInPicture && (
270
+ {Platform.OS === 'ios' && !disablePictureInPicture && (
271
271
  <RTCViewPipIOS
272
272
  includeLocalParticipantVideo={iOSPiPIncludeLocalParticipantVideo}
273
273
  mirror={mirror}
@@ -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}
@@ -3,12 +3,12 @@ import { StyleSheet, Text, View, type ViewStyle } from 'react-native';
3
3
  import { UserInfo } from './UserInfo';
4
4
  import { Z_INDEX } from '../../../constants';
5
5
  import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings';
6
- import { MediaStream, RTCView } from '@stream-io/react-native-webrtc';
7
6
  import { useTheme } from '../../../contexts/ThemeContext';
8
7
  import {
9
8
  OutgoingCallControls as DefaultOutgoingCallControls,
10
9
  type OutgoingCallControlsProps,
11
10
  } from '../CallControls';
11
+ import { LobbyCameraPreview } from '../Lobby';
12
12
 
13
13
  /**
14
14
  * Props for the OutgoingCall Component.
@@ -107,11 +107,9 @@ const Background = () => {
107
107
  theme: { colors, outgoingCall },
108
108
  } = useTheme();
109
109
  const { useCameraState } = useCallStateHooks();
110
- const { isMute, camera } = useCameraState();
111
- const localVideoStream = camera.state.mediaStream as unknown as
112
- MediaStream | undefined;
110
+ const { optimisticIsMute } = useCameraState();
113
111
 
114
- if (isMute || !localVideoStream) {
112
+ if (optimisticIsMute) {
115
113
  return (
116
114
  <View
117
115
  style={[
@@ -130,13 +128,7 @@ const Background = () => {
130
128
  outgoingCall.background,
131
129
  ]}
132
130
  >
133
- <RTCView
134
- streamURL={localVideoStream.toURL()}
135
- zOrder={Z_INDEX.IN_BACK}
136
- style={StyleSheet.absoluteFill}
137
- mirror
138
- objectFit="cover"
139
- />
131
+ <LobbyCameraPreview />
140
132
  </View>
141
133
  );
142
134
  };
@@ -1,4 +1,5 @@
1
1
  import { useEffect } from 'react';
2
+ import { Platform } from 'react-native';
2
3
  import {
3
4
  Call,
4
5
  CallingState,
@@ -16,7 +17,12 @@ import {
16
17
  distinctUntilKeyChanged,
17
18
  filter,
18
19
  map,
20
+ of,
19
21
  } from 'rxjs';
22
+ import {
23
+ getIosPipTrack$,
24
+ setIosPipTrack,
25
+ } from '../../../../utils/internal/iosPipTrack';
20
26
 
21
27
  type TrackSubscriberProps = {
22
28
  participantSessionId: string;
@@ -28,6 +34,13 @@ type TrackSubscriberProps = {
28
34
  * the last reported layout survives a remount of this component.
29
35
  */
30
36
  dimensions$: BehaviorSubject<SfuModels.VideoDimension | undefined>;
37
+ /**
38
+ * Set by the native iOS Picture in Picture window, whose bounds this
39
+ * subscriber requests. While it does, the inline views of the same track
40
+ * stop requesting their own, hidden layout. Only rendered on iOS; on other
41
+ * platforms the gate is always open.
42
+ */
43
+ isPipWriter?: boolean;
31
44
  };
32
45
 
33
46
  /**
@@ -40,10 +53,20 @@ type TrackSubscriberProps = {
40
53
  * This component is used to unsubscribe to video track and subscribe only to the audio track of the participant (by passing undefined dimensions) in the following cases:
41
54
  * 1. When the participant stops publishing the video track
42
55
  * 2. When the participant becomes invisible
56
+ *
57
+ * The same component serves the native iOS Picture in Picture window, which
58
+ * renders one of the tracks too. Its bounds take precedence over the inline
59
+ * views of that track for as long as it is on screen.
43
60
  */
44
61
  const TrackSubscriber = (props: TrackSubscriberProps) => {
45
- const { call, participantSessionId, trackType, isVisible, dimensions$ } =
46
- props;
62
+ const {
63
+ call,
64
+ participantSessionId,
65
+ trackType,
66
+ isVisible,
67
+ dimensions$,
68
+ isPipWriter,
69
+ } = props;
47
70
 
48
71
  useEffect(() => {
49
72
  const requestTrackWithDimensions = (
@@ -75,12 +98,28 @@ const TrackSubscriber = (props: TrackSubscriberProps) => {
75
98
  map((callingState) => callingState === CallingState.JOINED),
76
99
  );
77
100
 
101
+ const trackKey = { sessionId: participantSessionId, trackType };
102
+ const pipTrack$ = Platform.OS === 'ios' ? getIosPipTrack$(call) : undefined;
103
+ if (isPipWriter && pipTrack$) setIosPipTrack(call, trackKey);
104
+ const canWrite$ = pipTrack$
105
+ ? pipTrack$.pipe(
106
+ map((track) => {
107
+ const sameTrack =
108
+ track?.sessionId === participantSessionId &&
109
+ track?.trackType === trackType;
110
+ return isPipWriter ? sameTrack : !sameTrack;
111
+ }),
112
+ distinctUntilChanged(),
113
+ )
114
+ : of(true);
115
+
78
116
  const subscription = combineLatest([
79
117
  dimensions$,
80
118
  isPublishingTrack$,
81
119
  isJoinedState$,
82
- ]).subscribe(([dimension, isPublishing, isJoined]) => {
83
- if (isJoined) {
120
+ canWrite$,
121
+ ]).subscribe(([dimension, isPublishing, isJoined, canWrite]) => {
122
+ if (isJoined && canWrite) {
84
123
  if (!isVisible || !isPublishing) {
85
124
  requestTrackWithDimensions(DebounceType.MEDIUM, undefined);
86
125
  } else if (dimension) {
@@ -91,8 +130,22 @@ const TrackSubscriber = (props: TrackSubscriberProps) => {
91
130
 
92
131
  return () => {
93
132
  subscription.unsubscribe();
133
+ if (isPipWriter && pipTrack$) {
134
+ // Let sibling inline views finish unmounting before handing back demand.
135
+ queueMicrotask(() => {
136
+ if (pipTrack$.getValue() === trackKey)
137
+ setIosPipTrack(call, undefined);
138
+ });
139
+ }
94
140
  };
95
- }, [call, participantSessionId, trackType, isVisible, dimensions$]);
141
+ }, [
142
+ call,
143
+ participantSessionId,
144
+ trackType,
145
+ isVisible,
146
+ dimensions$,
147
+ isPipWriter,
148
+ ]);
96
149
 
97
150
  return null;
98
151
  };
package/src/index.ts CHANGED
@@ -36,6 +36,11 @@ export * from './modules/call-manager';
36
36
  // Explicitly re-exporting to resolve ambiguity.
37
37
  export { StreamVideo } from './providers/StreamVideo';
38
38
  export { StreamCall } from './providers/StreamCall';
39
+ // Overriding 'EncryptionManager' from '@stream-io/video-client' with the React
40
+ // Native implementation, which is backed by the native WebRTC transform. React
41
+ // Native Web is not supported: `isSupported()` returns false there rather than
42
+ // falling back to the web manager.
43
+ export { EncryptionManager } from './modules/encryption';
39
44
  export * from './providers/NoiseCancellation';
40
45
 
41
46
  setClientDetails();