@stream-io/video-react-native-sdk 1.43.0 → 1.45.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.
- package/CHANGELOG.md +44 -1
- package/dist/commonjs/components/Call/CallControls/internal/ReactionsPicker.js.map +1 -1
- package/dist/commonjs/components/Call/Lobby/Lobby.js +4 -9
- package/dist/commonjs/components/Call/Lobby/Lobby.js.map +1 -1
- package/dist/commonjs/components/Call/Lobby/LobbyCameraPreview.js +93 -0
- package/dist/commonjs/components/Call/Lobby/LobbyCameraPreview.js.map +1 -0
- package/dist/commonjs/components/Call/Lobby/index.js +11 -0
- package/dist/commonjs/components/Call/Lobby/index.js.map +1 -1
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +4 -15
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/index.js +10 -4
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/index.js.map +1 -1
- package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js +9 -5
- package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
- package/dist/commonjs/hooks/useLoopbackRecording.js +10 -127
- package/dist/commonjs/hooks/useLoopbackRecording.js.map +1 -1
- package/dist/commonjs/modules/call-manager/CallManager.js +37 -54
- package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -1
- package/dist/commonjs/utils/StreamVideoRN/index.js +2 -0
- package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/commonjs/utils/internal/callingx/callingx.js +28 -39
- package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/commonjs/utils/internal/registerMediaEngine.js +41 -0
- package/dist/commonjs/utils/internal/registerMediaEngine.js.map +1 -0
- package/dist/commonjs/utils/internal/registerSDKGlobals.js +93 -35
- package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/commonjs/utils/push/android.js +13 -175
- package/dist/commonjs/utils/push/android.js.map +1 -1
- package/dist/commonjs/utils/push/internal/android.js +186 -0
- package/dist/commonjs/utils/push/internal/android.js.map +1 -0
- package/dist/commonjs/utils/push/libs/firebaseMessaging/index.js +8 -9
- package/dist/commonjs/utils/push/libs/firebaseMessaging/index.js.map +1 -1
- package/dist/commonjs/utils/push/libs/firebaseMessaging/lib.js +1 -1
- package/dist/commonjs/utils/push/libs/firebaseMessaging/lib.js.map +1 -1
- package/dist/commonjs/utils/push/setupAndroidPushEvents.js +25 -0
- package/dist/commonjs/utils/push/setupAndroidPushEvents.js.map +1 -0
- package/dist/commonjs/utils/push/setupCallingExpEvents.js +6 -2
- package/dist/commonjs/utils/push/setupCallingExpEvents.js.map +1 -1
- package/dist/commonjs/utils/push/setupIosVoipPushEvents.js +6 -7
- package/dist/commonjs/utils/push/setupIosVoipPushEvents.js.map +1 -1
- package/dist/commonjs/utils/push/utils.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/module/components/Call/CallControls/internal/ReactionsPicker.js.map +1 -1
- package/dist/module/components/Call/Lobby/Lobby.js +4 -9
- package/dist/module/components/Call/Lobby/Lobby.js.map +1 -1
- package/dist/module/components/Call/Lobby/LobbyCameraPreview.js +87 -0
- package/dist/module/components/Call/Lobby/LobbyCameraPreview.js.map +1 -0
- package/dist/module/components/Call/Lobby/index.js +1 -0
- package/dist/module/components/Call/Lobby/index.js.map +1 -1
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +6 -17
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/index.js +11 -5
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/index.js.map +1 -1
- package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js +9 -5
- package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
- package/dist/module/hooks/useLoopbackRecording.js +10 -127
- package/dist/module/hooks/useLoopbackRecording.js.map +1 -1
- package/dist/module/modules/call-manager/CallManager.js +36 -53
- package/dist/module/modules/call-manager/CallManager.js.map +1 -1
- package/dist/module/utils/StreamVideoRN/index.js +2 -0
- package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/module/utils/internal/callingx/callingx.js +27 -40
- package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/module/utils/internal/registerMediaEngine.js +37 -0
- package/dist/module/utils/internal/registerMediaEngine.js.map +1 -0
- package/dist/module/utils/internal/registerSDKGlobals.js +95 -37
- package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/module/utils/push/android.js +16 -178
- package/dist/module/utils/push/android.js.map +1 -1
- package/dist/module/utils/push/internal/android.js +182 -0
- package/dist/module/utils/push/internal/android.js.map +1 -0
- package/dist/module/utils/push/libs/firebaseMessaging/index.js +8 -8
- package/dist/module/utils/push/libs/firebaseMessaging/index.js.map +1 -1
- package/dist/module/utils/push/libs/firebaseMessaging/lib.js +1 -1
- package/dist/module/utils/push/libs/firebaseMessaging/lib.js.map +1 -1
- package/dist/module/utils/push/setupAndroidPushEvents.js +21 -0
- package/dist/module/utils/push/setupAndroidPushEvents.js.map +1 -0
- package/dist/module/utils/push/setupCallingExpEvents.js +6 -2
- package/dist/module/utils/push/setupCallingExpEvents.js.map +1 -1
- package/dist/module/utils/push/setupIosVoipPushEvents.js +6 -7
- package/dist/module/utils/push/setupIosVoipPushEvents.js.map +1 -1
- package/dist/module/utils/push/utils.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/typescript/components/Call/Lobby/Lobby.d.ts.map +1 -1
- package/dist/typescript/components/Call/Lobby/LobbyCameraPreview.d.ts +28 -0
- package/dist/typescript/components/Call/Lobby/LobbyCameraPreview.d.ts.map +1 -0
- package/dist/typescript/components/Call/Lobby/index.d.ts +1 -0
- package/dist/typescript/components/Call/Lobby/index.d.ts.map +1 -1
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts +11 -6
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts.map +1 -1
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/index.d.ts.map +1 -1
- package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts.map +1 -1
- package/dist/typescript/hooks/useLoopbackRecording.d.ts +12 -69
- package/dist/typescript/hooks/useLoopbackRecording.d.ts.map +1 -1
- package/dist/typescript/modules/call-manager/CallManager.d.ts +28 -6
- package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
- package/dist/typescript/modules/call-manager/types.d.ts +0 -1
- package/dist/typescript/modules/call-manager/types.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/types.d.ts +3 -0
- package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
- package/dist/typescript/utils/internal/callingx/callingx.d.ts +2 -0
- package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
- package/dist/typescript/utils/internal/registerMediaEngine.d.ts +15 -0
- package/dist/typescript/utils/internal/registerMediaEngine.d.ts.map +1 -0
- package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
- package/dist/typescript/utils/push/android.d.ts +3 -4
- package/dist/typescript/utils/push/android.d.ts.map +1 -1
- package/dist/typescript/utils/push/internal/android.d.ts +8 -0
- package/dist/typescript/utils/push/internal/android.d.ts.map +1 -0
- package/dist/typescript/utils/push/libs/firebaseMessaging/index.d.ts +6 -5
- package/dist/typescript/utils/push/libs/firebaseMessaging/index.d.ts.map +1 -1
- package/dist/typescript/utils/push/libs/firebaseMessaging/lib.d.ts +2 -2
- package/dist/typescript/utils/push/libs/firebaseMessaging/lib.d.ts.map +1 -1
- package/dist/typescript/utils/push/setupAndroidPushEvents.d.ts +3 -0
- package/dist/typescript/utils/push/setupAndroidPushEvents.d.ts.map +1 -0
- package/dist/typescript/utils/push/setupCallingExpEvents.d.ts.map +1 -1
- package/dist/typescript/utils/push/setupIosVoipPushEvents.d.ts.map +1 -1
- package/dist/typescript/utils/push/utils.d.ts +2 -2
- package/dist/typescript/utils/push/utils.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/ios/StreamInCallManager.m +4 -0
- package/ios/StreamInCallManager.swift +42 -10
- package/ios/TracksRecorder/RecorderVideoSink.swift +92 -5
- package/package.json +8 -8
- package/src/components/Call/CallControls/internal/ReactionsPicker.tsx +2 -2
- package/src/components/Call/Lobby/Lobby.tsx +4 -11
- package/src/components/Call/Lobby/LobbyCameraPreview.tsx +99 -0
- package/src/components/Call/Lobby/index.ts +1 -0
- package/src/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.tsx +56 -75
- package/src/components/Participant/ParticipantView/VideoRenderer/index.tsx +18 -7
- package/src/hooks/push/useCallingExpWithCallingStateEffect.ts +12 -5
- package/src/hooks/useLoopbackRecording.ts +36 -248
- package/src/modules/call-manager/CallManager.ts +40 -71
- package/src/modules/call-manager/native-module.d.ts +13 -0
- package/src/modules/call-manager/types.ts +0 -1
- package/src/utils/StreamVideoRN/index.ts +2 -0
- package/src/utils/StreamVideoRN/types.ts +3 -0
- package/src/utils/internal/callingx/callingx.ts +37 -46
- package/src/utils/internal/registerMediaEngine.ts +42 -0
- package/src/utils/internal/registerSDKGlobals.ts +115 -42
- package/src/utils/push/android.ts +20 -253
- package/src/utils/push/internal/android.ts +252 -0
- package/src/utils/push/libs/firebaseMessaging/index.ts +14 -22
- package/src/utils/push/libs/firebaseMessaging/lib.ts +3 -3
- package/src/utils/push/setupAndroidPushEvents.ts +25 -0
- package/src/utils/push/setupCallingExpEvents.ts +6 -3
- package/src/utils/push/setupIosVoipPushEvents.ts +8 -7
- package/src/utils/push/utils.ts +2 -4
- package/src/version.ts +1 -1
|
@@ -124,7 +124,30 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
124
124
|
self.enableStereo = enabled
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
|
|
128
|
+
@objc(setMuteMode:)
|
|
129
|
+
func setMuteMode(mode: NSInteger) {
|
|
130
|
+
audioSessionQueue.async { [self] in
|
|
131
|
+
guard let adm = getAudioDeviceModule() else {
|
|
132
|
+
log("setMuteMode(\(mode)) skipped: no live call ADM")
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
let muteMode = RTCAudioEngineMuteMode(rawValue: mode) ?? .voiceProcessing
|
|
136
|
+
_ = adm.setMuteMode(muteMode)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@objc(setRecordingAlwaysPreparedMode:)
|
|
141
|
+
func setRecordingAlwaysPreparedMode(enabled: Bool) {
|
|
142
|
+
audioSessionQueue.async { [self] in
|
|
143
|
+
guard let adm = getAudioDeviceModule() else {
|
|
144
|
+
log("setRecordingAlwaysPreparedMode(\(enabled)) skipped: no live call ADM")
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
_ = adm.setRecordingAlwaysPreparedMode(enabled)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
128
151
|
/// Builds the audio config for the current role/device and sets it as WebRTC's default.
|
|
129
152
|
private func makeAudioConfiguration(for routing: OutputRouting?) -> RTCAudioSessionConfiguration {
|
|
130
153
|
let category: AVAudioSession.Category
|
|
@@ -199,11 +222,11 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
199
222
|
// This path (no CallKit) owns audio-session activation itself, so the
|
|
200
223
|
// engine must be allowed to start. Restores availability in case a
|
|
201
224
|
// prior CallKit call had gated it off.
|
|
202
|
-
_ = adm
|
|
225
|
+
_ = adm?.setEngineAvailability(.default)
|
|
203
226
|
|
|
204
227
|
// Stereo is listener-only and applies live. Cleared by stop()'s reset().
|
|
205
228
|
if callAudioRole == .listener && enableStereo {
|
|
206
|
-
adm
|
|
229
|
+
adm?.setStereoPlayoutPreference(true)
|
|
207
230
|
}
|
|
208
231
|
|
|
209
232
|
let rtcConfig = makeAudioConfiguration(for: selectedOutput)
|
|
@@ -228,7 +251,7 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
228
251
|
#if DEBUG
|
|
229
252
|
NSLog("%@","[StreamInCallManager][wireEngineSubscription]")
|
|
230
253
|
#endif
|
|
231
|
-
engineSubscription = adm
|
|
254
|
+
engineSubscription = adm?.publisher.sink { [weak self] event in
|
|
232
255
|
guard let self else { return }
|
|
233
256
|
self.audioSessionQueue.async {
|
|
234
257
|
switch event {
|
|
@@ -339,7 +362,7 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
339
362
|
// session.setActive(true) here.
|
|
340
363
|
do {
|
|
341
364
|
let adm = getAudioDeviceModule()
|
|
342
|
-
try adm
|
|
365
|
+
try adm?.setPlayout(true)
|
|
343
366
|
self.log("adm.setPlayout(true) done")
|
|
344
367
|
} catch {
|
|
345
368
|
// String(describing:) surfaces the real error code (see setup()).
|
|
@@ -357,7 +380,7 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
357
380
|
return
|
|
358
381
|
}
|
|
359
382
|
let adm = getAudioDeviceModule()
|
|
360
|
-
adm
|
|
383
|
+
adm?.reset()
|
|
361
384
|
clearOutputRouting()
|
|
362
385
|
// Deactivate directly: the .didDisableAudioEngine sink is async and we cancel it below.
|
|
363
386
|
applyConfigForEngineDisable()
|
|
@@ -603,7 +626,10 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
603
626
|
@objc(getAudioStateLog)
|
|
604
627
|
func getAudioStateLog() -> String {
|
|
605
628
|
let session = AVAudioSession.sharedInstance()
|
|
606
|
-
|
|
629
|
+
|
|
630
|
+
guard let adm = getAudioDeviceModule() else {
|
|
631
|
+
return "No audio device module found"
|
|
632
|
+
}
|
|
607
633
|
|
|
608
634
|
// WebRTC wraps AVAudioSession with RTCAudioSession; log its state as well.
|
|
609
635
|
let rtcSession = RTCAudioSession.sharedInstance()
|
|
@@ -798,7 +824,11 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
798
824
|
stereoRefreshWorkItem?.cancel()
|
|
799
825
|
// Create a new debounced work item
|
|
800
826
|
let workItem = DispatchWorkItem { [weak self] in
|
|
801
|
-
self?.getAudioDeviceModule()
|
|
827
|
+
guard let adm = self?.getAudioDeviceModule() else {
|
|
828
|
+
self?.log("Audio device module is not ready")
|
|
829
|
+
return
|
|
830
|
+
}
|
|
831
|
+
adm.refreshStereoPlayoutState()
|
|
802
832
|
self?.log("Executed debounced refreshStereoPlayoutState")
|
|
803
833
|
}
|
|
804
834
|
stereoRefreshWorkItem = workItem
|
|
@@ -863,12 +893,14 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
863
893
|
}
|
|
864
894
|
|
|
865
895
|
// MARK: - Helper Methods
|
|
866
|
-
private func getAudioDeviceModule() -> AudioDeviceModule {
|
|
896
|
+
private func getAudioDeviceModule() -> AudioDeviceModule? {
|
|
867
897
|
guard let webrtcModule = moduleRegistry?.module(forName: "WebRTCModule") as? WebRTCModule else {
|
|
868
898
|
fatalError("WebRTCModule is required but not registered with the module registry")
|
|
869
899
|
}
|
|
870
900
|
|
|
871
|
-
|
|
901
|
+
// Follow the live call's ADM; fall back to the default only when no call factory is
|
|
902
|
+
// active (bare-fork, or an in-call-manager op firing outside the join↔leave window).
|
|
903
|
+
return webrtcModule.currentAudioDeviceModuleOrNil()
|
|
872
904
|
}
|
|
873
905
|
|
|
874
906
|
private func getCurrentWindow() -> UIWindow? {
|
|
@@ -12,11 +12,21 @@ import WebRTC
|
|
|
12
12
|
///
|
|
13
13
|
/// Each delivered `RTCVideoFrame` is normalised to a CVPixelBuffer in the
|
|
14
14
|
/// hardware H.264 encoder's native format — **NV12**
|
|
15
|
-
/// (`kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`).
|
|
16
|
-
/// `
|
|
17
|
-
///
|
|
18
|
-
///
|
|
19
|
-
///
|
|
15
|
+
/// (`kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`). One branch per
|
|
16
|
+
/// buffer type `renderFrame` can be handed:
|
|
17
|
+
///
|
|
18
|
+
/// - `RTCCVPixelBuffer` (camera passthrough) already wraps a CVPixelBuffer,
|
|
19
|
+
/// so the underlying buffer is forwarded unchanged.
|
|
20
|
+
/// - `RTCNV12Buffer` already holds NV12 planes, but as raw pointers rather
|
|
21
|
+
/// than a CVPixelBuffer, so the planes are copied row-wise into a fresh
|
|
22
|
+
/// one. No format conversion. This is the buffer type decoded remote
|
|
23
|
+
/// frames arrive as, so it is the hot path for loopback recording — do
|
|
24
|
+
/// not remove it. Without it these frames fall through to `toI420()`
|
|
25
|
+
/// below, which converts NV12 → I420 → NV12 and caps throughput at
|
|
26
|
+
/// roughly half the source frame rate.
|
|
27
|
+
/// - `RTCI420Buffer` (and anything else, via `toI420()`) is converted into
|
|
28
|
+
/// a fresh IOSurface-backed NV12 buffer by interleaving the U and V
|
|
29
|
+
/// planes — a plane reorder, no colour-space conversion.
|
|
20
30
|
///
|
|
21
31
|
/// **Why NV12 and not BGRA?** AVAssetWriter's hardware encoder accepts
|
|
22
32
|
/// both, but BGRA requires an internal colour-space conversion that
|
|
@@ -51,6 +61,8 @@ import WebRTC
|
|
|
51
61
|
if let cvBuffer = frame.buffer as? RTCCVPixelBuffer {
|
|
52
62
|
// Camera passthrough — already a CVPixelBuffer (typically NV12 on iOS).
|
|
53
63
|
pixelBuffer = cvBuffer.pixelBuffer
|
|
64
|
+
} else if let nv12 = frame.buffer as? RTCNV12Buffer {
|
|
65
|
+
pixelBuffer = Self.makeNV12PixelBuffer(fromNV12: nv12)
|
|
54
66
|
} else if let i420 = frame.buffer as? RTCI420Buffer {
|
|
55
67
|
pixelBuffer = Self.makeNV12PixelBuffer(fromI420: i420)
|
|
56
68
|
} else {
|
|
@@ -67,6 +79,81 @@ import WebRTC
|
|
|
67
79
|
frameHandler(outputBuffer, frame.width, frame.height, frame.timeStampNs)
|
|
68
80
|
}
|
|
69
81
|
|
|
82
|
+
private static func makeNV12PixelBuffer(fromNV12 nv12: RTCNV12Buffer) -> CVPixelBuffer? {
|
|
83
|
+
let width = Int(nv12.width)
|
|
84
|
+
let height = Int(nv12.height)
|
|
85
|
+
let chromaWidth = Int(nv12.chromaWidth)
|
|
86
|
+
let chromaHeight = Int(nv12.chromaHeight)
|
|
87
|
+
|
|
88
|
+
guard let buffer = makePixelBuffer(width: width, height: height) else { return nil }
|
|
89
|
+
|
|
90
|
+
CVPixelBufferLockBaseAddress(buffer, [])
|
|
91
|
+
defer { CVPixelBufferUnlockBaseAddress(buffer, []) }
|
|
92
|
+
|
|
93
|
+
guard let yDest = CVPixelBufferGetBaseAddressOfPlane(buffer, 0) else { return nil }
|
|
94
|
+
copyPlane(
|
|
95
|
+
source: UnsafeRawPointer(nv12.dataY),
|
|
96
|
+
sourceStride: Int(nv12.strideY),
|
|
97
|
+
destination: yDest,
|
|
98
|
+
destinationStride: CVPixelBufferGetBytesPerRowOfPlane(buffer, 0),
|
|
99
|
+
rowBytes: width,
|
|
100
|
+
rows: height
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// Plane 1: UV — already interleaved, so two bytes per chroma sample.
|
|
104
|
+
guard let uvDest = CVPixelBufferGetBaseAddressOfPlane(buffer, 1) else { return nil }
|
|
105
|
+
copyPlane(
|
|
106
|
+
source: UnsafeRawPointer(nv12.dataUV),
|
|
107
|
+
sourceStride: Int(nv12.strideUV),
|
|
108
|
+
destination: uvDest,
|
|
109
|
+
destinationStride: CVPixelBufferGetBytesPerRowOfPlane(buffer, 1),
|
|
110
|
+
rowBytes: chromaWidth * 2,
|
|
111
|
+
rows: chromaHeight
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return buffer
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private static func makePixelBuffer(width: Int, height: Int) -> CVPixelBuffer? {
|
|
118
|
+
var pixelBuffer: CVPixelBuffer?
|
|
119
|
+
let attrs: [String: Any] = [
|
|
120
|
+
kCVPixelBufferIOSurfacePropertiesKey as String: [:] as [String: Any],
|
|
121
|
+
]
|
|
122
|
+
let status = CVPixelBufferCreate(
|
|
123
|
+
kCFAllocatorDefault,
|
|
124
|
+
width,
|
|
125
|
+
height,
|
|
126
|
+
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
|
|
127
|
+
attrs as CFDictionary,
|
|
128
|
+
&pixelBuffer
|
|
129
|
+
)
|
|
130
|
+
guard status == kCVReturnSuccess else { return nil }
|
|
131
|
+
return pixelBuffer
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Row-wise copy, collapsing to a single `memcpy` when the strides match.
|
|
135
|
+
private static func copyPlane(
|
|
136
|
+
source: UnsafeRawPointer,
|
|
137
|
+
sourceStride: Int,
|
|
138
|
+
destination: UnsafeMutableRawPointer,
|
|
139
|
+
destinationStride: Int,
|
|
140
|
+
rowBytes: Int,
|
|
141
|
+
rows: Int
|
|
142
|
+
) {
|
|
143
|
+
if sourceStride == destinationStride && sourceStride == rowBytes {
|
|
144
|
+
memcpy(destination, source, rowBytes * rows)
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
let copyBytes = min(rowBytes, min(sourceStride, destinationStride))
|
|
148
|
+
for row in 0..<rows {
|
|
149
|
+
memcpy(
|
|
150
|
+
destination.advanced(by: row * destinationStride),
|
|
151
|
+
source.advanced(by: row * sourceStride),
|
|
152
|
+
copyBytes
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
70
157
|
// MARK: - I420 → NV12
|
|
71
158
|
|
|
72
159
|
/// Allocates a fresh IOSurface-backed NV12 `CVPixelBuffer` and copies
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-react-native-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.45.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.
|
|
56
|
-
"@stream-io/video-react-bindings": "1.
|
|
55
|
+
"@stream-io/video-client": "1.59.0",
|
|
56
|
+
"@stream-io/video-react-bindings": "1.20.1",
|
|
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.
|
|
68
|
+
"@stream-io/react-native-webrtc": "^145.3.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.
|
|
121
|
-
"@stream-io/react-native-callingx": "^0.
|
|
122
|
-
"@stream-io/react-native-webrtc": "145.
|
|
120
|
+
"@stream-io/noise-cancellation-react-native": "^0.11.0",
|
|
121
|
+
"@stream-io/react-native-callingx": "^0.11.0",
|
|
122
|
+
"@stream-io/react-native-webrtc": "145.3.1",
|
|
123
123
|
"@stream-io/typescript-config": "^0.0.0",
|
|
124
|
-
"@stream-io/video-filters-react-native": "^0.
|
|
124
|
+
"@stream-io/video-filters-react-native": "^0.16.0",
|
|
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",
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from 'react-native';
|
|
9
9
|
import { useCall } from '@stream-io/video-react-bindings';
|
|
10
10
|
import {
|
|
11
|
-
type
|
|
11
|
+
type SendVideoReactionRequest,
|
|
12
12
|
videoLoggerSystem,
|
|
13
13
|
} from '@stream-io/video-client';
|
|
14
14
|
import { ComponentTestIds } from '../../../../constants/TestIds';
|
|
@@ -82,7 +82,7 @@ export const ReactionsPicker = ({
|
|
|
82
82
|
left: reactionsButtonLayoutRectangle?.x,
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
const onClose = (reaction?:
|
|
85
|
+
const onClose = (reaction?: SendVideoReactionRequest) => {
|
|
86
86
|
if (reaction) {
|
|
87
87
|
call?.sendReaction(reaction).catch((e) => {
|
|
88
88
|
const logger = videoLoggerSystem.getLogger('ReactionsPicker');
|
|
@@ -7,9 +7,8 @@ import {
|
|
|
7
7
|
} from '@stream-io/video-react-bindings';
|
|
8
8
|
import { Avatar } from '../../utility/Avatar';
|
|
9
9
|
import type { StreamVideoParticipant } from '@stream-io/video-client';
|
|
10
|
-
import type { MediaStream } from '@stream-io/react-native-webrtc';
|
|
11
|
-
import { RTCView } from '@stream-io/react-native-webrtc';
|
|
12
10
|
import { LobbyControls as DefaultLobbyControls } from '../CallControls/LobbyControls';
|
|
11
|
+
import { LobbyCameraPreview } from './LobbyCameraPreview';
|
|
13
12
|
import {
|
|
14
13
|
JoinCallButton as DefaultJoinCallButton,
|
|
15
14
|
type JoinCallButtonProps,
|
|
@@ -65,9 +64,8 @@ export const Lobby = ({
|
|
|
65
64
|
const { useCameraState, useCallSettings } = useCallStateHooks();
|
|
66
65
|
const callSettings = useCallSettings();
|
|
67
66
|
const isVideoEnabledInCall = callSettings?.video.enabled;
|
|
68
|
-
const {
|
|
67
|
+
const { optimisticIsMute: cameraIsMuted } = useCameraState();
|
|
69
68
|
const { t } = useI18n();
|
|
70
|
-
const localVideoStream = mediaStream as unknown as MediaStream | undefined;
|
|
71
69
|
|
|
72
70
|
const connectedUserAsParticipant = useMemo(
|
|
73
71
|
() =>
|
|
@@ -98,13 +96,8 @@ export const Lobby = ({
|
|
|
98
96
|
]}
|
|
99
97
|
>
|
|
100
98
|
<View style={styles.topView} />
|
|
101
|
-
{!cameraIsMuted
|
|
102
|
-
<
|
|
103
|
-
mirror={true}
|
|
104
|
-
streamURL={localVideoStream.toURL()}
|
|
105
|
-
objectFit="cover"
|
|
106
|
-
style={StyleSheet.absoluteFill}
|
|
107
|
-
/>
|
|
99
|
+
{!cameraIsMuted ? (
|
|
100
|
+
<LobbyCameraPreview objectFit="cover" />
|
|
108
101
|
) : (
|
|
109
102
|
<View style={[styles.avatarContainer, lobby.avatarContainer]}>
|
|
110
103
|
<Avatar participant={connectedUserAsParticipant} />
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Platform, StyleSheet, ViewStyle } from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
permissions,
|
|
5
|
+
RTCCameraPreviewView,
|
|
6
|
+
} from '@stream-io/react-native-webrtc';
|
|
7
|
+
import { useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Props for the {@link LobbyCameraPreview} component.
|
|
11
|
+
*/
|
|
12
|
+
export type LobbyCameraPreviewProps = {
|
|
13
|
+
/**
|
|
14
|
+
* Resembles the CSS style object-fit.
|
|
15
|
+
*
|
|
16
|
+
* @default 'cover'
|
|
17
|
+
*/
|
|
18
|
+
objectFit?: 'contain' | 'cover';
|
|
19
|
+
/**
|
|
20
|
+
* Style applied to the preview view.
|
|
21
|
+
*/
|
|
22
|
+
style?: ViewStyle;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The lobby runs before the call is joined. This component drives the native
|
|
27
|
+
* camera capturer directly via {@link RTCCameraPreviewView} which requires
|
|
28
|
+
* no track/peer connection factory to be created.
|
|
29
|
+
*
|
|
30
|
+
* It is driven by the *intended* camera state (optimistic): the lobby camera
|
|
31
|
+
* toggle only updates `optimisticStatus`; the real WebRTC track is acquired and
|
|
32
|
+
* published at join.
|
|
33
|
+
*/
|
|
34
|
+
export const LobbyCameraPreview = ({
|
|
35
|
+
style,
|
|
36
|
+
objectFit = 'cover',
|
|
37
|
+
}: LobbyCameraPreviewProps) => {
|
|
38
|
+
const { useCameraState, useCallSettings } = useCallStateHooks();
|
|
39
|
+
|
|
40
|
+
const { optimisticIsMute, direction, selectedDevice } = useCameraState();
|
|
41
|
+
const settings = useCallSettings();
|
|
42
|
+
|
|
43
|
+
const wantsCamera = !optimisticIsMute;
|
|
44
|
+
const hasCameraPermission = useEnsureCameraPermission(wantsCamera);
|
|
45
|
+
|
|
46
|
+
const facing = direction === 'back' ? 'back' : 'front';
|
|
47
|
+
|
|
48
|
+
// Capture at the call's target resolution so the running preview capturer matches
|
|
49
|
+
// what the track publishes and can be adopted at join without reconfiguring.
|
|
50
|
+
// Normalize to landscape, matching CameraManager.selectTargetResolution.
|
|
51
|
+
const targetResolution = settings?.video.target_resolution;
|
|
52
|
+
let captureWidth = targetResolution?.width ?? 1280;
|
|
53
|
+
let captureHeight = targetResolution?.height ?? 720;
|
|
54
|
+
if (captureWidth < captureHeight) {
|
|
55
|
+
[captureWidth, captureHeight] = [captureHeight, captureWidth];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<RTCCameraPreviewView
|
|
60
|
+
style={[StyleSheet.absoluteFill, style as any]}
|
|
61
|
+
isActive={wantsCamera && hasCameraPermission}
|
|
62
|
+
facing={facing}
|
|
63
|
+
deviceId={selectedDevice}
|
|
64
|
+
mirror={Platform.OS === 'ios' ? true : facing === 'front'}
|
|
65
|
+
objectFit={objectFit}
|
|
66
|
+
captureWidth={captureWidth}
|
|
67
|
+
captureHeight={captureHeight}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const useEnsureCameraPermission = (enabled: boolean): boolean => {
|
|
73
|
+
const [granted, setGranted] = useState(false);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!enabled || granted) return;
|
|
77
|
+
|
|
78
|
+
let cancelled = false;
|
|
79
|
+
(async () => {
|
|
80
|
+
try {
|
|
81
|
+
const status = await permissions.query({ name: 'camera' });
|
|
82
|
+
if (status === 'granted') {
|
|
83
|
+
if (!cancelled) setGranted(true);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const result = await permissions.request({ name: 'camera' });
|
|
87
|
+
if (!cancelled) setGranted(!!result);
|
|
88
|
+
} catch {
|
|
89
|
+
if (!cancelled) setGranted(false);
|
|
90
|
+
}
|
|
91
|
+
})();
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
cancelled = true;
|
|
95
|
+
};
|
|
96
|
+
}, [enabled, granted]);
|
|
97
|
+
|
|
98
|
+
return granted;
|
|
99
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LayoutChangeEvent } from 'react-native';
|
|
1
|
+
import { useEffect } from 'react';
|
|
3
2
|
import {
|
|
4
3
|
Call,
|
|
5
4
|
CallingState,
|
|
@@ -18,15 +17,17 @@ import {
|
|
|
18
17
|
filter,
|
|
19
18
|
map,
|
|
20
19
|
} from 'rxjs';
|
|
21
|
-
export type TrackSubscriberHandle = {
|
|
22
|
-
onLayoutUpdate: (event: LayoutChangeEvent) => void;
|
|
23
|
-
};
|
|
24
20
|
|
|
25
21
|
type TrackSubscriberProps = {
|
|
26
22
|
participantSessionId: string;
|
|
27
23
|
call: Call;
|
|
28
24
|
trackType: VideoTrackType;
|
|
29
25
|
isVisible: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The dimensions of the view rendering the track, owned by the parent so that
|
|
28
|
+
* the last reported layout survives a remount of this component.
|
|
29
|
+
*/
|
|
30
|
+
dimensions$: BehaviorSubject<SfuModels.VideoDimension | undefined>;
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -40,81 +41,61 @@ type TrackSubscriberProps = {
|
|
|
40
41
|
* 1. When the participant stops publishing the video track
|
|
41
42
|
* 2. When the participant becomes invisible
|
|
42
43
|
*/
|
|
43
|
-
const TrackSubscriber =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const dimensions$ = useMemo(() => {
|
|
47
|
-
return new BehaviorSubject<SfuModels.VideoDimension | undefined>(
|
|
48
|
-
undefined,
|
|
49
|
-
);
|
|
50
|
-
}, []);
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
const requestTrackWithDimensions = (
|
|
54
|
-
debounceType: DebounceType,
|
|
55
|
-
dimension: SfuModels.VideoDimension | undefined,
|
|
56
|
-
) => {
|
|
57
|
-
if (dimension && (dimension.width === 0 || dimension.height === 0)) {
|
|
58
|
-
// ignore 0x0 dimensions. this can happen when the video element
|
|
59
|
-
// is not visible (e.g., has display: none).
|
|
60
|
-
// we treat this as "unsubscription" as we don't want to keep
|
|
61
|
-
// consuming bandwidth for a video that is not visible on the screen.
|
|
62
|
-
dimension = undefined;
|
|
63
|
-
}
|
|
64
|
-
call.state.updateParticipantTracks(trackType, {
|
|
65
|
-
[participantSessionId]: { dimension },
|
|
66
|
-
});
|
|
67
|
-
call.trackSubscriptionManager.apply(debounceType);
|
|
68
|
-
};
|
|
69
|
-
const isPublishingTrack$ = call.state.participants$.pipe(
|
|
70
|
-
map((ps) => ps.find((p) => p.sessionId === participantSessionId)),
|
|
71
|
-
filter((p): p is StreamVideoParticipant => !!p),
|
|
72
|
-
distinctUntilKeyChanged('publishedTracks'),
|
|
73
|
-
map((p) =>
|
|
74
|
-
trackType === 'videoTrack' ? hasVideo(p) : hasScreenShare(p),
|
|
75
|
-
),
|
|
76
|
-
distinctUntilChanged(),
|
|
77
|
-
);
|
|
78
|
-
const isJoinedState$ = call.state.callingState$.pipe(
|
|
79
|
-
map((callingState) => callingState === CallingState.JOINED),
|
|
80
|
-
);
|
|
44
|
+
const TrackSubscriber = (props: TrackSubscriberProps) => {
|
|
45
|
+
const { call, participantSessionId, trackType, isVisible, dimensions$ } =
|
|
46
|
+
props;
|
|
81
47
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const requestTrackWithDimensions = (
|
|
50
|
+
debounceType: DebounceType,
|
|
51
|
+
dimension: SfuModels.VideoDimension | undefined,
|
|
52
|
+
) => {
|
|
53
|
+
if (dimension && (dimension.width === 0 || dimension.height === 0)) {
|
|
54
|
+
// ignore 0x0 dimensions. this can happen when the video element
|
|
55
|
+
// is not visible (e.g., has display: none).
|
|
56
|
+
// we treat this as "unsubscription" as we don't want to keep
|
|
57
|
+
// consuming bandwidth for a video that is not visible on the screen.
|
|
58
|
+
dimension = undefined;
|
|
59
|
+
}
|
|
60
|
+
call.state.updateParticipantTracks(trackType, {
|
|
61
|
+
[participantSessionId]: { dimension },
|
|
94
62
|
});
|
|
63
|
+
call.trackSubscriptionManager.apply(debounceType);
|
|
64
|
+
};
|
|
65
|
+
const isPublishingTrack$ = call.state.participants$.pipe(
|
|
66
|
+
map((ps) => ps.find((p) => p.sessionId === participantSessionId)),
|
|
67
|
+
filter((p): p is StreamVideoParticipant => !!p),
|
|
68
|
+
distinctUntilKeyChanged('publishedTracks'),
|
|
69
|
+
map((p) =>
|
|
70
|
+
trackType === 'videoTrack' ? hasVideo(p) : hasScreenShare(p),
|
|
71
|
+
),
|
|
72
|
+
distinctUntilChanged(),
|
|
73
|
+
);
|
|
74
|
+
const isJoinedState$ = call.state.callingState$.pipe(
|
|
75
|
+
map((callingState) => callingState === CallingState.JOINED),
|
|
76
|
+
);
|
|
95
77
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
78
|
+
const subscription = combineLatest([
|
|
79
|
+
dimensions$,
|
|
80
|
+
isPublishingTrack$,
|
|
81
|
+
isJoinedState$,
|
|
82
|
+
]).subscribe(([dimension, isPublishing, isJoined]) => {
|
|
83
|
+
if (isJoined) {
|
|
84
|
+
if (!isVisible || !isPublishing) {
|
|
85
|
+
requestTrackWithDimensions(DebounceType.MEDIUM, undefined);
|
|
86
|
+
} else if (dimension) {
|
|
87
|
+
requestTrackWithDimensions(DebounceType.IMMEDIATE, dimension);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
100
91
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const dimension = {
|
|
106
|
-
width: Math.trunc(event.nativeEvent.layout.width),
|
|
107
|
-
height: Math.trunc(event.nativeEvent.layout.height),
|
|
108
|
-
};
|
|
109
|
-
dimensions$.next(dimension);
|
|
110
|
-
},
|
|
111
|
-
}),
|
|
112
|
-
[dimensions$],
|
|
113
|
-
);
|
|
92
|
+
return () => {
|
|
93
|
+
subscription.unsubscribe();
|
|
94
|
+
};
|
|
95
|
+
}, [call, participantSessionId, trackType, isVisible, dimensions$]);
|
|
114
96
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
);
|
|
97
|
+
return null;
|
|
98
|
+
};
|
|
118
99
|
|
|
119
100
|
TrackSubscriber.displayName = 'TrackSubscriber';
|
|
120
101
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react';
|
|
2
|
-
import { Platform, StyleSheet, View } from 'react-native';
|
|
1
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { LayoutChangeEvent, Platform, StyleSheet, View } from 'react-native';
|
|
3
3
|
import type { MediaStream } from '@stream-io/react-native-webrtc';
|
|
4
4
|
import { RTCView } from '@stream-io/react-native-webrtc';
|
|
5
5
|
import type { ParticipantViewProps } from '../ParticipantView';
|
|
@@ -7,15 +7,17 @@ import {
|
|
|
7
7
|
hasPausedTrack,
|
|
8
8
|
hasScreenShare,
|
|
9
9
|
hasVideo,
|
|
10
|
+
SfuModels,
|
|
10
11
|
type VideoTrackType,
|
|
11
12
|
VisibilityState,
|
|
12
13
|
} from '@stream-io/video-client';
|
|
13
14
|
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
15
|
+
import { BehaviorSubject } from 'rxjs';
|
|
14
16
|
import { ParticipantVideoFallback as DefaultParticipantVideoFallback } from '../ParticipantVideoFallback';
|
|
15
17
|
import { useTheme } from '../../../../contexts/ThemeContext';
|
|
16
18
|
import { useTrackDimensions } from '../../../../hooks/useTrackDimensions';
|
|
17
19
|
import { useScreenshotIosContext } from '../../../../contexts/internal/ScreenshotIosContext';
|
|
18
|
-
import TrackSubscriber
|
|
20
|
+
import TrackSubscriber from './TrackSubscriber';
|
|
19
21
|
|
|
20
22
|
const DEFAULT_VIEWPORT_VISIBILITY_STATE: Record<
|
|
21
23
|
VideoTrackType,
|
|
@@ -59,7 +61,13 @@ export const VideoRenderer = React.memo(
|
|
|
59
61
|
} = useTheme();
|
|
60
62
|
const call = useCall();
|
|
61
63
|
const { useCameraState, useIncomingVideoSettings } = useCallStateHooks();
|
|
62
|
-
|
|
64
|
+
// a sticky note of the dimensions of the RTCView from onLayout
|
|
65
|
+
// to reuse for a new participant even if dimensions dont change
|
|
66
|
+
const dimensions$ = useMemo(
|
|
67
|
+
() =>
|
|
68
|
+
new BehaviorSubject<SfuModels.VideoDimension | undefined>(undefined),
|
|
69
|
+
[],
|
|
70
|
+
);
|
|
63
71
|
const { isParticipantVideoEnabled } = useIncomingVideoSettings();
|
|
64
72
|
const { direction } = useCameraState();
|
|
65
73
|
const viewRef = useRef(null);
|
|
@@ -199,9 +207,12 @@ export const VideoRenderer = React.memo(
|
|
|
199
207
|
]);
|
|
200
208
|
|
|
201
209
|
const onLayout: React.ComponentProps<typeof RTCView>['onLayout'] = (
|
|
202
|
-
event,
|
|
210
|
+
event: LayoutChangeEvent,
|
|
203
211
|
) => {
|
|
204
|
-
|
|
212
|
+
dimensions$.next({
|
|
213
|
+
width: Math.trunc(event.nativeEvent.layout.width),
|
|
214
|
+
height: Math.trunc(event.nativeEvent.layout.height),
|
|
215
|
+
});
|
|
205
216
|
};
|
|
206
217
|
|
|
207
218
|
return (
|
|
@@ -211,11 +222,11 @@ export const VideoRenderer = React.memo(
|
|
|
211
222
|
>
|
|
212
223
|
{call && !isLocalParticipant && (
|
|
213
224
|
<TrackSubscriber
|
|
214
|
-
ref={trackSubscriberRef}
|
|
215
225
|
call={call}
|
|
216
226
|
participantSessionId={sessionId}
|
|
217
227
|
trackType={trackType}
|
|
218
228
|
isVisible={isVisible}
|
|
229
|
+
dimensions$={dimensions$}
|
|
219
230
|
/>
|
|
220
231
|
)}
|
|
221
232
|
{canShowVideo &&
|