@stream-io/video-react-native-sdk 0.10.4 → 0.10.6

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 (90) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/commonjs/components/Call/CallContent/CallContent.js +10 -5
  3. package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
  4. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +109 -0
  5. package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -0
  6. package/dist/commonjs/components/Call/CallContent/index.js +11 -0
  7. package/dist/commonjs/components/Call/CallContent/index.js.map +1 -1
  8. package/dist/commonjs/components/Call/CallLayout/CallParticipantsGrid.js +4 -3
  9. package/dist/commonjs/components/Call/CallLayout/CallParticipantsGrid.js.map +1 -1
  10. package/dist/commonjs/components/Call/CallLayout/CallParticipantsSpotlight.js +4 -3
  11. package/dist/commonjs/components/Call/CallLayout/CallParticipantsSpotlight.js.map +1 -1
  12. package/dist/commonjs/hooks/useAutoEnterPiPEffect.js +3 -3
  13. package/dist/commonjs/hooks/useAutoEnterPiPEffect.js.map +1 -1
  14. package/dist/commonjs/hooks/useIsInPiPMode.js +4 -4
  15. package/dist/commonjs/hooks/useIsInPiPMode.js.map +1 -1
  16. package/dist/commonjs/providers/StreamCall.js +5 -2
  17. package/dist/commonjs/providers/StreamCall.js.map +1 -1
  18. package/dist/commonjs/utils/internal/shouldDisableIOSLocalVideoOnBackground.js +10 -0
  19. package/dist/commonjs/utils/internal/shouldDisableIOSLocalVideoOnBackground.js.map +1 -0
  20. package/dist/commonjs/version.js +1 -1
  21. package/dist/module/components/Call/CallContent/CallContent.js +10 -5
  22. package/dist/module/components/Call/CallContent/CallContent.js.map +1 -1
  23. package/dist/module/components/Call/CallContent/RTCViewPipIOS.js +101 -0
  24. package/dist/module/components/Call/CallContent/RTCViewPipIOS.js.map +1 -0
  25. package/dist/module/components/Call/CallContent/index.js +1 -0
  26. package/dist/module/components/Call/CallContent/index.js.map +1 -1
  27. package/dist/module/components/Call/CallLayout/CallParticipantsGrid.js +4 -3
  28. package/dist/module/components/Call/CallLayout/CallParticipantsGrid.js.map +1 -1
  29. package/dist/module/components/Call/CallLayout/CallParticipantsSpotlight.js +4 -3
  30. package/dist/module/components/Call/CallLayout/CallParticipantsSpotlight.js.map +1 -1
  31. package/dist/module/hooks/useAutoEnterPiPEffect.js +3 -3
  32. package/dist/module/hooks/useAutoEnterPiPEffect.js.map +1 -1
  33. package/dist/module/hooks/useIsInPiPMode.js +4 -4
  34. package/dist/module/hooks/useIsInPiPMode.js.map +1 -1
  35. package/dist/module/providers/StreamCall.js +5 -2
  36. package/dist/module/providers/StreamCall.js.map +1 -1
  37. package/dist/module/utils/internal/shouldDisableIOSLocalVideoOnBackground.js +4 -0
  38. package/dist/module/utils/internal/shouldDisableIOSLocalVideoOnBackground.js.map +1 -0
  39. package/dist/module/version.js +1 -1
  40. package/dist/typescript/components/Call/CallContent/CallContent.d.ts +6 -1
  41. package/dist/typescript/components/Call/CallContent/CallContent.d.ts.map +1 -1
  42. package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts +7 -0
  43. package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -0
  44. package/dist/typescript/components/Call/CallContent/index.d.ts +1 -0
  45. package/dist/typescript/components/Call/CallContent/index.d.ts.map +1 -1
  46. package/dist/typescript/components/Call/CallLayout/CallParticipantsGrid.d.ts +2 -2
  47. package/dist/typescript/components/Call/CallLayout/CallParticipantsGrid.d.ts.map +1 -1
  48. package/dist/typescript/components/Call/CallLayout/CallParticipantsSpotlight.d.ts +2 -2
  49. package/dist/typescript/components/Call/CallLayout/CallParticipantsSpotlight.d.ts.map +1 -1
  50. package/dist/typescript/hooks/useAutoEnterPiPEffect.d.ts +1 -1
  51. package/dist/typescript/hooks/useAutoEnterPiPEffect.d.ts.map +1 -1
  52. package/dist/typescript/hooks/useIsInPiPMode.d.ts +1 -1
  53. package/dist/typescript/hooks/useIsInPiPMode.d.ts.map +1 -1
  54. package/dist/typescript/providers/StreamCall.d.ts.map +1 -1
  55. package/dist/typescript/utils/internal/shouldDisableIOSLocalVideoOnBackground.d.ts +4 -0
  56. package/dist/typescript/utils/internal/shouldDisableIOSLocalVideoOnBackground.d.ts.map +1 -0
  57. package/dist/typescript/version.d.ts +1 -1
  58. package/ios/PictureInPicture/SampleBufferVideoCallView.swift +52 -0
  59. package/ios/PictureInPicture/StreamAVPictureInPictureVideoCallViewController.swift +83 -0
  60. package/ios/PictureInPicture/StreamBufferTransformer.swift +96 -0
  61. package/ios/PictureInPicture/StreamPictureInPictureController.swift +185 -0
  62. package/ios/PictureInPicture/StreamPictureInPictureTrackStateAdapter.swift +68 -0
  63. package/ios/PictureInPicture/StreamPictureInPictureVideoRenderer.swift +250 -0
  64. package/ios/PictureInPicture/StreamPixelBufferPool.swift +118 -0
  65. package/ios/PictureInPicture/StreamPixelBufferRepository.swift +98 -0
  66. package/ios/PictureInPicture/StreamRTCYUVBuffer.swift +249 -0
  67. package/ios/PictureInPicture/StreamYUVToARGBConversion.swift +128 -0
  68. package/ios/PictureInPicture/WindowSizePolicy/StreamPictureInPictureAdaptiveWindowSizePolicy.swift +25 -0
  69. package/ios/PictureInPicture/WindowSizePolicy/StreamPictureInPictureFixedWindowSizePolicy.swift +29 -0
  70. package/ios/PictureInPicture/WindowSizePolicy/StreamPictureInPictureWindowSizePolicy.swift +14 -0
  71. package/ios/PictureInPicture/YpCbCrPixelRange+Default.swift +32 -0
  72. package/ios/RTCViewPip.swift +69 -0
  73. package/ios/RTCViewPipManager.mm +16 -0
  74. package/ios/RTCViewPipManager.swift +34 -0
  75. package/ios/StreamVideoReactNative-Bridging-Header.h +11 -0
  76. package/package.json +1 -1
  77. package/src/components/Call/CallContent/CallContent.tsx +58 -40
  78. package/src/components/Call/CallContent/RTCViewPipIOS.tsx +138 -0
  79. package/src/components/Call/CallContent/index.ts +1 -0
  80. package/src/components/Call/CallLayout/CallParticipantsGrid.tsx +7 -3
  81. package/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx +7 -3
  82. package/src/hooks/useAutoEnterPiPEffect.tsx +7 -3
  83. package/src/hooks/useIsInPiPMode.tsx +6 -4
  84. package/src/providers/StreamCall.tsx +5 -2
  85. package/src/utils/internal/shouldDisableIOSLocalVideoOnBackground.ts +3 -0
  86. package/src/version.ts +1 -1
  87. package/stream-video-react-native.podspec +27 -4
  88. package/ios/StreamVideoReactNative.xcodeproj/project.pbxproj +0 -274
  89. package/ios/StreamVideoReactNative.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  90. package/ios/StreamVideoReactNative.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
@@ -0,0 +1,32 @@
1
+ //
2
+ // Copyright © 2024 Stream.io Inc. All rights reserved.
3
+ //
4
+
5
+ import Accelerate
6
+ import Foundation
7
+
8
+ /// Extension of vImage_YpCbCrPixelRange to include a default configuration.
9
+ extension vImage_YpCbCrPixelRange {
10
+
11
+ /// Initializes a default pixel range for YpCbCr pixel format.
12
+ /// This default configuration is often used when converting YpCbCr to RGB.
13
+ /// - Yp_bias: The bias for the Y' (luma) component, typically 0.
14
+ /// - CbCr_bias: The bias for the Cb and Cr (chroma) components, usually set to 128 to center the chroma values.
15
+ /// - YpRangeMax: The maximum value for the Y' range, typically 255.
16
+ /// - CbCrRangeMax: The maximum value for the Cb and Cr range, also usually 255.
17
+ /// - YpMax: The maximum possible value for Y', generally 255.
18
+ /// - YpMin: The minimum possible value for Y', usually set to 1 for video ranges.
19
+ /// - CbCrMax: The maximum possible value for Cb and Cr, typically 255.
20
+ /// - CbCrMin: The minimum possible value for Cb and Cr, often 0.
21
+ /// Reference: [Apple's documentation on vImageConvert_YpCbCrToARGB](https://developer.apple.com/documentation/accelerate/1533189-vimageconvert_ypcbcrtoargb_gener)
22
+ static let `default` = vImage_YpCbCrPixelRange(
23
+ Yp_bias: 0, /// The bias applied to the Y' component.
24
+ CbCr_bias: 128, /// The bias applied to the Cb and Cr components.
25
+ YpRangeMax: 255, /// The maximum value of the Y' range.
26
+ CbCrRangeMax: 255, /// The maximum value of the Cb and Cr range.
27
+ YpMax: 255, /// The maximum value of Y'.
28
+ YpMin: 1, /// The minimum value of Y' (often used for setting video range).
29
+ CbCrMax: 255, /// The maximum value of Cb and Cr.
30
+ CbCrMin: 0 /// The minimum value of Cb and Cr.
31
+ )
32
+ }
@@ -0,0 +1,69 @@
1
+ //
2
+ // RTCViewPip.swift
3
+ // stream-video-react-native
4
+ //
5
+ // Created by santhosh vaiyapuri on 22/08/2024.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ @objc(RTCViewPip)
11
+ class RTCViewPip: UIView {
12
+
13
+ private lazy var pictureInPictureController = StreamPictureInPictureController()
14
+ private var webRtcModule: WebRTCModule?
15
+
16
+ override init(frame: CGRect) {
17
+ super.init(frame: frame)
18
+ setupView()
19
+ }
20
+
21
+ required init?(coder aDecoder: NSCoder) {
22
+ super.init(coder: aDecoder)
23
+ setupView()
24
+ }
25
+
26
+ private func setupView() {
27
+ let videoView = UIView()
28
+ self.addSubview(videoView)
29
+ pictureInPictureController?.sourceView = videoView
30
+ videoView.backgroundColor = UIColor.clear // make it transparent
31
+ }
32
+
33
+ func setWebRtcModule(_ module: WebRTCModule) {
34
+ webRtcModule = module
35
+ }
36
+
37
+ @objc func setStreamURL(_ streamReactTag: NSString) {
38
+ webRtcModule?.workerQueue.async {
39
+ let stream = self.webRtcModule?.stream(forReactTag: String(streamReactTag))
40
+ let videoTracks = stream?.videoTracks ?? []
41
+ let videoTrack = videoTracks.first
42
+ if videoTrack == nil {
43
+ NSLog("PiP - No video stream for react tag: -\(streamReactTag)")
44
+ } else {
45
+ DispatchQueue.main.async {
46
+ self.pictureInPictureController?.track = videoTrack
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ @objc
53
+ func onCallClosed() {
54
+ DispatchQueue.main.async {
55
+ self.pictureInPictureController?.cleanup()
56
+ self.pictureInPictureController = nil
57
+ }
58
+ }
59
+
60
+ override func didMoveToWindow() {
61
+ super.didMoveToWindow()
62
+ let isVisible = self.superview != nil && self.window != nil;
63
+ if (!isVisible) {
64
+ // view is detached so we cleanup the pip controller
65
+ // taken from: https://github.com/software-mansion/react-native-screens/blob/main/Example/ios/ScreensExample/RNSSampleLifecycleAwareView.m
66
+ onCallClosed()
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,16 @@
1
+ //
2
+ // RTCViewPipManager.m
3
+ // stream-video-react-native
4
+ //
5
+ // Created by santhosh vaiyapuri on 22/08/2024.
6
+ //
7
+ // tutorial used: https://teabreak.e-spres-oh.com/swift-in-react-native-the-ultimate-guide-part-2-ui-components-907767123d9e
8
+
9
+ #import <React/RCTViewManager.h>
10
+
11
+ @interface RCT_EXTERN_MODULE(RTCViewPipManager, RCTViewManager)
12
+
13
+ RCT_EXPORT_VIEW_PROPERTY(streamURL, NSString)
14
+ RCT_EXTERN_METHOD(onCallClosed:(nonnull NSNumber*) reactTag)
15
+
16
+ @end
@@ -0,0 +1,34 @@
1
+ //
2
+ // RTCViewPipManager.swift
3
+ // stream-video-react-native
4
+ //
5
+ // Created by santhosh vaiyapuri on 22/08/2024.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ @objc (RTCViewPipManager)
11
+ class RTCViewPipManager: RCTViewManager {
12
+
13
+ override func view() -> UIView! {
14
+ let view = RTCViewPip()
15
+ view.setWebRtcModule(self.bridge.module(forName: "WebRTCModule") as! WebRTCModule)
16
+ return view
17
+ }
18
+
19
+ override static func requiresMainQueueSetup() -> Bool {
20
+ return true
21
+ }
22
+
23
+ @objc func onCallClosed(_ reactTag: NSNumber) {
24
+ self.bridge!.uiManager.addUIBlock { (_: RCTUIManager?, viewRegistry: [NSNumber: UIView]?) in
25
+ guard let view = viewRegistry?[reactTag] as? RTCViewPip else {
26
+ if RCT_DEBUG == 1 {
27
+ print("Invalid view returned from registry, expecting RTCViewPip")
28
+ }
29
+ return
30
+ }
31
+ view.onCallClosed()
32
+ }
33
+ }
34
+ }
@@ -1,3 +1,14 @@
1
1
  #import <React/RCTBridgeModule.h>
2
2
  #import <React/RCTViewManager.h>
3
3
  #import <React/RCTEventEmitter.h>
4
+ #import <React/RCTLog.h>
5
+ #import <React/RCTUIManager.h>
6
+ #import <React/RCTView.h>
7
+ #import <React/RCTBridge.h>
8
+
9
+ #import <WebRTC/RTCCVPixelBuffer.h>
10
+ #import <WebRTC/RTCVideoFrame.h>
11
+ #import <WebRTC/RTCVideoTrack.h>
12
+ #import <WebRTC/RTCVideoRenderer.h>
13
+ #import <WebRTC/RTCVideoFrameBuffer.h>
14
+ #import "WebRTCModule.h"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-react-native-sdk",
3
- "version": "0.10.4",
3
+ "version": "0.10.6",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "dist/commonjs/index.js",
6
6
  "module": "dist/module/index.js",
@@ -37,6 +37,7 @@ import {
37
37
  ScreenShareOverlay as DefaultScreenShareOverlay,
38
38
  ScreenShareOverlayProps,
39
39
  } from '../../utility/ScreenShareOverlay';
40
+ import RTCViewPipIOS from './RTCViewPipIOS';
40
41
 
41
42
  export type StreamReactionType = StreamReaction & {
42
43
  icon: string;
@@ -88,6 +89,14 @@ export type CallContentProps = Pick<
88
89
  * This will apply the landscape mode styles to the component.
89
90
  */
90
91
  landscape?: boolean;
92
+ /*
93
+ * If true, includes the local participant video in the PiP mode for iOS
94
+ */
95
+ iOSPiPIncludeLocalParticipantVideo?: boolean;
96
+ /**
97
+ * If true, disables the Picture-in-Picture mode for iOS and Android
98
+ */
99
+ disablePictureInPicture?: boolean;
91
100
  };
92
101
 
93
102
  export const CallContent = ({
@@ -109,6 +118,8 @@ export const CallContent = ({
109
118
  layout = 'grid',
110
119
  landscape = false,
111
120
  supportedReactions,
121
+ iOSPiPIncludeLocalParticipantVideo,
122
+ disablePictureInPicture,
112
123
  }: CallContentProps) => {
113
124
  const [
114
125
  showRemoteParticipantInFloatingView,
@@ -124,7 +135,7 @@ export const CallContent = ({
124
135
  useLocalParticipant,
125
136
  } = useCallStateHooks();
126
137
 
127
- useAutoEnterPiPEffect();
138
+ useAutoEnterPiPEffect(disablePictureInPicture);
128
139
 
129
140
  const callSettings = useCallSettings();
130
141
  const isVideoEnabledInCall = callSettings?.video.enabled;
@@ -132,7 +143,7 @@ export const CallContent = ({
132
143
  const _remoteParticipants = useRemoteParticipants();
133
144
  const remoteParticipants = useDebouncedValue(_remoteParticipants, 300); // we debounce the remote participants to avoid unnecessary rerenders that happen when participant tracks are all subscribed simultaneously
134
145
  const localParticipant = useLocalParticipant();
135
- const isInPiPMode = useIsInPiPMode();
146
+ const isInPiPMode = useIsInPiPMode(disablePictureInPicture);
136
147
  const hasScreenShare = useHasOngoingScreenShare();
137
148
  const showSpotlightLayout = hasScreenShare || layout === 'spotlight';
138
149
 
@@ -195,48 +206,55 @@ export const CallContent = ({
195
206
  };
196
207
 
197
208
  return (
198
- <View style={[styles.container, landscapeStyles, callContent.container]}>
199
- <View style={[styles.container, callContent.callParticipantsContainer]}>
200
- <View
201
- style={[styles.view, callContent.topContainer]}
202
- // "box-none" disallows the container view to be not take up touches
203
- // and allows only the top and floating view (its child views) to take up the touches
204
- pointerEvents="box-none"
205
- >
206
- {!isInPiPMode && CallTopView && (
207
- <CallTopView
208
- onBackPressed={onBackPressed}
209
- onParticipantInfoPress={onParticipantInfoPress}
210
- ParticipantsInfoBadge={ParticipantsInfoBadge}
211
- />
212
- )}
213
- {showFloatingView && FloatingParticipantView && (
214
- <FloatingParticipantView
215
- participant={
216
- isRemoteParticipantInFloatingView
217
- ? remoteParticipants[0]
218
- : localParticipant
219
- }
220
- onPressHandler={handleFloatingViewParticipantSwitch}
221
- supportedReactions={supportedReactions}
222
- {...participantViewProps}
223
- />
209
+ <>
210
+ {!disablePictureInPicture && (
211
+ <RTCViewPipIOS
212
+ includeLocalParticipantVideo={iOSPiPIncludeLocalParticipantVideo}
213
+ />
214
+ )}
215
+ <View style={[styles.container, landscapeStyles, callContent.container]}>
216
+ <View style={[styles.container, callContent.callParticipantsContainer]}>
217
+ <View
218
+ style={[styles.view, callContent.topContainer]}
219
+ // "box-none" disallows the container view to be not take up touches
220
+ // and allows only the top and floating view (its child views) to take up the touches
221
+ pointerEvents="box-none"
222
+ >
223
+ {!isInPiPMode && CallTopView && (
224
+ <CallTopView
225
+ onBackPressed={onBackPressed}
226
+ onParticipantInfoPress={onParticipantInfoPress}
227
+ ParticipantsInfoBadge={ParticipantsInfoBadge}
228
+ />
229
+ )}
230
+ {showFloatingView && FloatingParticipantView && (
231
+ <FloatingParticipantView
232
+ participant={
233
+ isRemoteParticipantInFloatingView
234
+ ? remoteParticipants[0]
235
+ : localParticipant
236
+ }
237
+ onPressHandler={handleFloatingViewParticipantSwitch}
238
+ supportedReactions={supportedReactions}
239
+ {...participantViewProps}
240
+ />
241
+ )}
242
+ </View>
243
+ {showSpotlightLayout ? (
244
+ <CallParticipantsSpotlight {...callParticipantsSpotlightProps} />
245
+ ) : (
246
+ <CallParticipantsGrid {...callParticipantsGridProps} />
224
247
  )}
225
248
  </View>
226
- {showSpotlightLayout ? (
227
- <CallParticipantsSpotlight {...callParticipantsSpotlightProps} />
228
- ) : (
229
- <CallParticipantsGrid {...callParticipantsGridProps} />
249
+
250
+ {!isInPiPMode && CallControls && (
251
+ <CallControls
252
+ onHangupCallHandler={onHangupCallHandler}
253
+ landscape={landscape}
254
+ />
230
255
  )}
231
256
  </View>
232
-
233
- {!isInPiPMode && CallControls && (
234
- <CallControls
235
- onHangupCallHandler={onHangupCallHandler}
236
- landscape={landscape}
237
- />
238
- )}
239
- </View>
257
+ </>
240
258
  );
241
259
  };
242
260
 
@@ -0,0 +1,138 @@
1
+ import {
2
+ CallingState,
3
+ hasScreenShare,
4
+ speakerLayoutSortPreset,
5
+ } from '@stream-io/video-client';
6
+ import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
7
+ import type { MediaStream } from '@stream-io/react-native-webrtc';
8
+ import React, { useEffect } from 'react';
9
+ import {
10
+ findNodeHandle,
11
+ HostComponent,
12
+ Platform,
13
+ requireNativeComponent,
14
+ UIManager,
15
+ } from 'react-native';
16
+ import { useDebouncedValue } from '../../../utils/hooks/useDebouncedValue';
17
+ import { shouldDisableIOSLocalVideoOnBackgroundRef } from '../../../utils/internal/shouldDisableIOSLocalVideoOnBackground';
18
+
19
+ const COMPONENT_NAME = 'RTCViewPip';
20
+
21
+ type RTCViewPipNativeProps = {
22
+ streamURL?: string;
23
+ };
24
+
25
+ // workaround to support hot reloading
26
+ // https://medium.com/tribalscale/beyond-the-framework-using-react-native-with-swift-and-kotlin-cfccf4bb9a03
27
+ let RTCViewPipNative: HostComponent<RTCViewPipNativeProps>;
28
+
29
+ if (__DEV__) {
30
+ /* @ts-ignore */
31
+ const cachedView = global.RTCViewPipNative as
32
+ | HostComponent<RTCViewPipNativeProps>
33
+ | undefined;
34
+ if (!cachedView) {
35
+ RTCViewPipNative = requireNativeComponent(COMPONENT_NAME);
36
+ /* @ts-ignore */
37
+ global.RTCViewPipNative = RTCViewPipNative;
38
+ } else {
39
+ RTCViewPipNative = cachedView;
40
+ }
41
+ } else {
42
+ RTCViewPipNative = requireNativeComponent(COMPONENT_NAME);
43
+ }
44
+
45
+ /** Wrapper for the native view
46
+ * meant to stay private and not exposed */
47
+ const RTCViewPip = React.forwardRef<
48
+ React.Ref<any>,
49
+ {
50
+ streamURL?: string;
51
+ }
52
+ >((props, ref) => {
53
+ if (Platform.OS !== 'ios') return null;
54
+ // @ts-ignore
55
+ return <RTCViewPipNative streamURL={props.streamURL} ref={ref} />;
56
+ });
57
+
58
+ type Props = {
59
+ includeLocalParticipantVideo?: boolean;
60
+ };
61
+
62
+ const RTCViewPipIOS = ({ includeLocalParticipantVideo }: Props) => {
63
+ const call = useCall();
64
+ const { useParticipants } = useCallStateHooks();
65
+ const _allParticipants = useParticipants({
66
+ sortBy: speakerLayoutSortPreset,
67
+ });
68
+ const allParticipants = useDebouncedValue(_allParticipants, 300); // we debounce the participants to avoid unnecessary rerenders that happen when participant tracks are all subscribed simultaneously
69
+
70
+ const [participantInSpotlight] = allParticipants.filter((participant) =>
71
+ includeLocalParticipantVideo ? true : !participant.isLocalParticipant
72
+ );
73
+
74
+ useEffect(() => {
75
+ shouldDisableIOSLocalVideoOnBackgroundRef.current =
76
+ !includeLocalParticipantVideo;
77
+ }, [includeLocalParticipantVideo]);
78
+
79
+ const [videoStreamToRender, setVideoStreamToRender] =
80
+ React.useState<MediaStream>();
81
+
82
+ const nativeRef = React.useRef<any>(null);
83
+
84
+ React.useEffect(() => {
85
+ if (!participantInSpotlight) {
86
+ setVideoStreamToRender(undefined);
87
+ return;
88
+ }
89
+ const { videoStream, screenShareStream } = participantInSpotlight;
90
+
91
+ const isScreenSharing = hasScreenShare(participantInSpotlight);
92
+
93
+ const _videoStreamToRender = (isScreenSharing
94
+ ? screenShareStream
95
+ : videoStream) as unknown as MediaStream | undefined;
96
+
97
+ setVideoStreamToRender(_videoStreamToRender);
98
+ }, [participantInSpotlight]);
99
+
100
+ React.useEffect(() => {
101
+ let callClosedInvokedOnce = false;
102
+ const onCallClosed = () => {
103
+ if (callClosedInvokedOnce) {
104
+ return;
105
+ }
106
+ callClosedInvokedOnce = true;
107
+ const node = findNodeHandle(nativeRef.current);
108
+ if (node !== null) {
109
+ UIManager.dispatchViewManagerCommand(
110
+ node,
111
+ // @ts-ignore
112
+ UIManager.getViewManagerConfig(COMPONENT_NAME).Commands.onCallClosed,
113
+ []
114
+ );
115
+ }
116
+ shouldDisableIOSLocalVideoOnBackgroundRef.current = true;
117
+ };
118
+ const unsubFunc = call?.on('call.ended', () => {
119
+ onCallClosed();
120
+ });
121
+ const subscription = call?.state.callingState$.subscribe((state) => {
122
+ if (state === CallingState.LEFT || state === CallingState.IDLE) {
123
+ onCallClosed();
124
+ }
125
+ });
126
+ return () => {
127
+ onCallClosed();
128
+ unsubFunc?.();
129
+ subscription?.unsubscribe();
130
+ };
131
+ }, [call]);
132
+
133
+ return (
134
+ <RTCViewPip streamURL={videoStreamToRender?.toURL()} ref={nativeRef} />
135
+ );
136
+ };
137
+
138
+ export default RTCViewPipIOS;
@@ -1 +1,2 @@
1
1
  export * from './CallContent';
2
+ export * from './RTCViewPipIOS';
@@ -10,14 +10,17 @@ import { ComponentTestIds } from '../../../constants/TestIds';
10
10
  import { useTheme } from '../../../contexts/ThemeContext';
11
11
  import { CallContentProps } from '../CallContent';
12
12
  import { ParticipantViewComponentProps } from '../../Participant';
13
- import { useIsInPiPMode } from '../../../hooks';
13
+ import { useIsInPiPMode } from '../../../hooks/useIsInPiPMode';
14
14
  import { StreamVideoParticipant } from '@stream-io/video-client';
15
15
 
16
16
  /**
17
17
  * Props for the CallParticipantsGrid component.
18
18
  */
19
19
  export type CallParticipantsGridProps = ParticipantViewComponentProps &
20
- Pick<CallContentProps, 'supportedReactions' | 'CallParticipantsList'> &
20
+ Pick<
21
+ CallContentProps,
22
+ 'supportedReactions' | 'CallParticipantsList' | 'disablePictureInPicture'
23
+ > &
21
24
  Pick<CallParticipantsListComponentProps, 'ParticipantView'> & {
22
25
  /**
23
26
  * Boolean to decide if local participant will be visible in the grid when there is 1:1 call.
@@ -44,6 +47,7 @@ export const CallParticipantsGrid = ({
44
47
  showLocalParticipant = false,
45
48
  supportedReactions,
46
49
  landscape,
50
+ disablePictureInPicture,
47
51
  }: CallParticipantsGridProps) => {
48
52
  const {
49
53
  theme: { colors, callParticipantsGrid },
@@ -60,7 +64,7 @@ export const CallParticipantsGrid = ({
60
64
  flexDirection: landscape ? 'row' : 'column',
61
65
  };
62
66
 
63
- const isInPiPMode = useIsInPiPMode();
67
+ const isInPiPMode = useIsInPiPMode(disablePictureInPicture);
64
68
 
65
69
  const showFloatingView =
66
70
  !isInPiPMode &&
@@ -17,7 +17,7 @@ import {
17
17
  } from '../../Participant';
18
18
  import { useTheme } from '../../../contexts/ThemeContext';
19
19
  import { CallContentProps } from '../CallContent';
20
- import { useIsInPiPMode } from '../../../hooks';
20
+ import { useIsInPiPMode } from '../../../hooks/useIsInPiPMode';
21
21
 
22
22
  /**
23
23
  * Props for the CallParticipantsSpotlight component.
@@ -25,7 +25,10 @@ import { useIsInPiPMode } from '../../../hooks';
25
25
  export type CallParticipantsSpotlightProps = ParticipantViewComponentProps &
26
26
  Pick<
27
27
  CallContentProps,
28
- 'supportedReactions' | 'CallParticipantsList' | 'ScreenShareOverlay'
28
+ | 'supportedReactions'
29
+ | 'CallParticipantsList'
30
+ | 'ScreenShareOverlay'
31
+ | 'disablePictureInPicture'
29
32
  > &
30
33
  Pick<CallParticipantsListComponentProps, 'ParticipantView'> & {
31
34
  /**
@@ -50,6 +53,7 @@ export const CallParticipantsSpotlight = ({
50
53
  VideoRenderer,
51
54
  supportedReactions,
52
55
  landscape,
56
+ disablePictureInPicture,
53
57
  }: CallParticipantsSpotlightProps) => {
54
58
  const {
55
59
  theme: { colors, callParticipantsSpotlight },
@@ -64,7 +68,7 @@ export const CallParticipantsSpotlight = ({
64
68
  participantInSpotlight && hasScreenShare(participantInSpotlight);
65
69
  const isUserAloneInCall = _allParticipants?.length === 1;
66
70
 
67
- const isInPiP = useIsInPiPMode();
71
+ const isInPiP = useIsInPiPMode(disablePictureInPicture);
68
72
 
69
73
  const participantViewProps: ParticipantViewComponentProps = {
70
74
  ParticipantLabel,
@@ -1,16 +1,20 @@
1
1
  import { useEffect } from 'react';
2
2
  import { NativeModules, Platform } from 'react-native';
3
3
 
4
- export function useAutoEnterPiPEffect() {
4
+ export function useAutoEnterPiPEffect(
5
+ disablePictureInPicture: boolean | undefined
6
+ ) {
5
7
  useEffect(() => {
6
8
  if (Platform.OS !== 'android') {
7
9
  return;
8
10
  }
9
11
 
10
- NativeModules.StreamVideoReactNative.canAutoEnterPipMode(true);
12
+ NativeModules.StreamVideoReactNative.canAutoEnterPipMode(
13
+ !disablePictureInPicture
14
+ );
11
15
 
12
16
  return () => {
13
17
  NativeModules.StreamVideoReactNative.canAutoEnterPipMode(false);
14
18
  };
15
- }, []);
19
+ }, [disablePictureInPicture]);
16
20
  }
@@ -10,9 +10,11 @@ const PIP_CHANGE_EVENT = 'StreamVideoReactNative_PIP_CHANGE_EVENT';
10
10
 
11
11
  const isAndroid8OrAbove = Platform.OS === 'android' && Platform.Version >= 26;
12
12
 
13
- export function useIsInPiPMode() {
13
+ export function useIsInPiPMode(disablePictureInPicture: boolean | undefined) {
14
14
  const [isInPiPMode, setIsInPiPMode] = useState(
15
- isAndroid8OrAbove && AppState.currentState === 'background'
15
+ disablePictureInPicture &&
16
+ isAndroid8OrAbove &&
17
+ AppState.currentState === 'background'
16
18
  );
17
19
 
18
20
  useEffect(() => {
@@ -39,7 +41,7 @@ export function useIsInPiPMode() {
39
41
  'change',
40
42
  (nextAppState) => {
41
43
  if (nextAppState === 'background') {
42
- setIsInPiPMode(true); // set with an assumption that its enabled so that UI disabling happens faster
44
+ setIsInPiPMode(!disablePictureInPicture); // set with an assumption that its enabled so that UI disabling happens faster
43
45
  // if PiP was not enabled anyway, then in the next code we ll set it to false and UI wont be shown anyway
44
46
  }
45
47
  setFromNativeMethod();
@@ -52,7 +54,7 @@ export function useIsInPiPMode() {
52
54
  subscriptionPiPChange.remove();
53
55
  subscriptionAppState.remove();
54
56
  };
55
- }, []);
57
+ }, [disablePictureInPicture]);
56
58
 
57
59
  return isInPiPMode;
58
60
  }
@@ -8,6 +8,7 @@ import {
8
8
  } from '../utils/push/utils';
9
9
  import { useAndroidKeepCallAliveEffect } from '../hooks/useAndroidKeepCallAliveEffect';
10
10
  import { AppState, NativeModules, Platform } from 'react-native';
11
+ import { shouldDisableIOSLocalVideoOnBackgroundRef } from '../utils/internal/shouldDisableIOSLocalVideoOnBackground';
11
12
 
12
13
  export type StreamCallProps = {
13
14
  /**
@@ -77,7 +78,9 @@ const AppStateListener = () => {
77
78
  }
78
79
  );
79
80
  } else {
80
- call?.camera?.disable();
81
+ if (shouldDisableIOSLocalVideoOnBackgroundRef.current) {
82
+ call?.camera?.disable();
83
+ }
81
84
  }
82
85
  appState.current = nextAppState;
83
86
  }
@@ -92,7 +95,7 @@ const AppStateListener = () => {
92
95
  };
93
96
 
94
97
  /**
95
- * This is a renderless component is used to keep the call alive on Android device using useAndroidKeepCallAliveEffect.
98
+ * This is a renderless component to keep the call alive on Android device using useAndroidKeepCallAliveEffect.
96
99
  * useAndroidKeepCallAliveEffect needs to called inside a child of StreamCallProvider.
97
100
  */
98
101
  const AndroidKeepCallAlive = () => {
@@ -0,0 +1,3 @@
1
+ export const shouldDisableIOSLocalVideoOnBackgroundRef = {
2
+ current: true,
3
+ };
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '0.10.4';
1
+ export const version = '0.10.6';