@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.
- package/CHANGELOG.md +14 -0
- package/dist/commonjs/components/Call/CallContent/CallContent.js +10 -5
- package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +109 -0
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -0
- package/dist/commonjs/components/Call/CallContent/index.js +11 -0
- package/dist/commonjs/components/Call/CallContent/index.js.map +1 -1
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsGrid.js +4 -3
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsGrid.js.map +1 -1
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsSpotlight.js +4 -3
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsSpotlight.js.map +1 -1
- package/dist/commonjs/hooks/useAutoEnterPiPEffect.js +3 -3
- package/dist/commonjs/hooks/useAutoEnterPiPEffect.js.map +1 -1
- package/dist/commonjs/hooks/useIsInPiPMode.js +4 -4
- package/dist/commonjs/hooks/useIsInPiPMode.js.map +1 -1
- package/dist/commonjs/providers/StreamCall.js +5 -2
- package/dist/commonjs/providers/StreamCall.js.map +1 -1
- package/dist/commonjs/utils/internal/shouldDisableIOSLocalVideoOnBackground.js +10 -0
- package/dist/commonjs/utils/internal/shouldDisableIOSLocalVideoOnBackground.js.map +1 -0
- package/dist/commonjs/version.js +1 -1
- package/dist/module/components/Call/CallContent/CallContent.js +10 -5
- package/dist/module/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js +101 -0
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js.map +1 -0
- package/dist/module/components/Call/CallContent/index.js +1 -0
- package/dist/module/components/Call/CallContent/index.js.map +1 -1
- package/dist/module/components/Call/CallLayout/CallParticipantsGrid.js +4 -3
- package/dist/module/components/Call/CallLayout/CallParticipantsGrid.js.map +1 -1
- package/dist/module/components/Call/CallLayout/CallParticipantsSpotlight.js +4 -3
- package/dist/module/components/Call/CallLayout/CallParticipantsSpotlight.js.map +1 -1
- package/dist/module/hooks/useAutoEnterPiPEffect.js +3 -3
- package/dist/module/hooks/useAutoEnterPiPEffect.js.map +1 -1
- package/dist/module/hooks/useIsInPiPMode.js +4 -4
- package/dist/module/hooks/useIsInPiPMode.js.map +1 -1
- package/dist/module/providers/StreamCall.js +5 -2
- package/dist/module/providers/StreamCall.js.map +1 -1
- package/dist/module/utils/internal/shouldDisableIOSLocalVideoOnBackground.js +4 -0
- package/dist/module/utils/internal/shouldDisableIOSLocalVideoOnBackground.js.map +1 -0
- package/dist/module/version.js +1 -1
- package/dist/typescript/components/Call/CallContent/CallContent.d.ts +6 -1
- package/dist/typescript/components/Call/CallContent/CallContent.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts +7 -0
- package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -0
- package/dist/typescript/components/Call/CallContent/index.d.ts +1 -0
- package/dist/typescript/components/Call/CallContent/index.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallLayout/CallParticipantsGrid.d.ts +2 -2
- package/dist/typescript/components/Call/CallLayout/CallParticipantsGrid.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallLayout/CallParticipantsSpotlight.d.ts +2 -2
- package/dist/typescript/components/Call/CallLayout/CallParticipantsSpotlight.d.ts.map +1 -1
- package/dist/typescript/hooks/useAutoEnterPiPEffect.d.ts +1 -1
- package/dist/typescript/hooks/useAutoEnterPiPEffect.d.ts.map +1 -1
- package/dist/typescript/hooks/useIsInPiPMode.d.ts +1 -1
- package/dist/typescript/hooks/useIsInPiPMode.d.ts.map +1 -1
- package/dist/typescript/providers/StreamCall.d.ts.map +1 -1
- package/dist/typescript/utils/internal/shouldDisableIOSLocalVideoOnBackground.d.ts +4 -0
- package/dist/typescript/utils/internal/shouldDisableIOSLocalVideoOnBackground.d.ts.map +1 -0
- package/dist/typescript/version.d.ts +1 -1
- package/ios/PictureInPicture/SampleBufferVideoCallView.swift +52 -0
- package/ios/PictureInPicture/StreamAVPictureInPictureVideoCallViewController.swift +83 -0
- package/ios/PictureInPicture/StreamBufferTransformer.swift +96 -0
- package/ios/PictureInPicture/StreamPictureInPictureController.swift +185 -0
- package/ios/PictureInPicture/StreamPictureInPictureTrackStateAdapter.swift +68 -0
- package/ios/PictureInPicture/StreamPictureInPictureVideoRenderer.swift +250 -0
- package/ios/PictureInPicture/StreamPixelBufferPool.swift +118 -0
- package/ios/PictureInPicture/StreamPixelBufferRepository.swift +98 -0
- package/ios/PictureInPicture/StreamRTCYUVBuffer.swift +249 -0
- package/ios/PictureInPicture/StreamYUVToARGBConversion.swift +128 -0
- package/ios/PictureInPicture/WindowSizePolicy/StreamPictureInPictureAdaptiveWindowSizePolicy.swift +25 -0
- package/ios/PictureInPicture/WindowSizePolicy/StreamPictureInPictureFixedWindowSizePolicy.swift +29 -0
- package/ios/PictureInPicture/WindowSizePolicy/StreamPictureInPictureWindowSizePolicy.swift +14 -0
- package/ios/PictureInPicture/YpCbCrPixelRange+Default.swift +32 -0
- package/ios/RTCViewPip.swift +69 -0
- package/ios/RTCViewPipManager.mm +16 -0
- package/ios/RTCViewPipManager.swift +34 -0
- package/ios/StreamVideoReactNative-Bridging-Header.h +11 -0
- package/package.json +1 -1
- package/src/components/Call/CallContent/CallContent.tsx +58 -40
- package/src/components/Call/CallContent/RTCViewPipIOS.tsx +138 -0
- package/src/components/Call/CallContent/index.ts +1 -0
- package/src/components/Call/CallLayout/CallParticipantsGrid.tsx +7 -3
- package/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx +7 -3
- package/src/hooks/useAutoEnterPiPEffect.tsx +7 -3
- package/src/hooks/useIsInPiPMode.tsx +6 -4
- package/src/providers/StreamCall.tsx +5 -2
- package/src/utils/internal/shouldDisableIOSLocalVideoOnBackground.ts +3 -0
- package/src/version.ts +1 -1
- package/stream-video-react-native.podspec +27 -4
- package/ios/StreamVideoReactNative.xcodeproj/project.pbxproj +0 -274
- package/ios/StreamVideoReactNative.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- 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
|
@@ -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
|
-
|
|
199
|
-
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
>
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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;
|
|
@@ -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<
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 = () => {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.10.
|
|
1
|
+
export const version = '0.10.6';
|