@stream-io/video-react-native-sdk 1.37.1-beta.0 → 1.38.1
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 +33 -0
- package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt +0 -81
- package/dist/commonjs/hooks/index.js +0 -11
- package/dist/commonjs/hooks/index.js.map +1 -1
- package/dist/commonjs/modules/call-manager/CallManager.js +13 -0
- package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -1
- package/dist/commonjs/providers/StreamCall/AudioInterruptionTracer.js +37 -0
- package/dist/commonjs/providers/StreamCall/AudioInterruptionTracer.js.map +1 -0
- package/dist/commonjs/providers/StreamCall/index.js +2 -1
- package/dist/commonjs/providers/StreamCall/index.js.map +1 -1
- package/dist/commonjs/utils/internal/callingx/callingx.js +2 -2
- package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/commonjs/utils/internal/registerSDKGlobals.js +16 -0
- package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/commonjs/utils/push/internal/ios.js +5 -0
- package/dist/commonjs/utils/push/internal/ios.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/commonjs/version.js.map +1 -1
- package/dist/module/hooks/index.js +0 -1
- package/dist/module/hooks/index.js.map +1 -1
- package/dist/module/modules/call-manager/CallManager.js +13 -0
- package/dist/module/modules/call-manager/CallManager.js.map +1 -1
- package/dist/module/providers/StreamCall/AudioInterruptionTracer.js +30 -0
- package/dist/module/providers/StreamCall/AudioInterruptionTracer.js.map +1 -0
- package/dist/module/providers/StreamCall/index.js +2 -1
- package/dist/module/providers/StreamCall/index.js.map +1 -1
- package/dist/module/utils/internal/callingx/callingx.js +2 -2
- package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/module/utils/internal/registerSDKGlobals.js +17 -1
- package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/module/utils/push/internal/ios.js +5 -0
- package/dist/module/utils/push/internal/ios.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/module/version.js.map +1 -1
- package/dist/typescript/hooks/index.d.ts +0 -1
- package/dist/typescript/hooks/index.d.ts.map +1 -1
- package/dist/typescript/modules/call-manager/CallManager.d.ts +8 -1
- package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
- package/dist/typescript/modules/call-manager/types.d.ts +6 -0
- package/dist/typescript/modules/call-manager/types.d.ts.map +1 -1
- package/dist/typescript/providers/StreamCall/AudioInterruptionTracer.d.ts +5 -0
- package/dist/typescript/providers/StreamCall/AudioInterruptionTracer.d.ts.map +1 -0
- package/dist/typescript/providers/StreamCall/index.d.ts.map +1 -1
- package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
- package/dist/typescript/utils/push/internal/ios.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/dist/typescript/version.d.ts.map +1 -1
- package/ios/PictureInPicture/PictureInPictureAvatarView.swift +3 -1
- package/ios/PictureInPicture/StreamBufferTransformer.swift +13 -4
- package/ios/PictureInPicture/StreamPictureInPictureVideoRenderer.swift +79 -71
- package/ios/PictureInPicture/StreamRTCYUVBuffer.swift +20 -16
- package/ios/StreamInCallManager.swift +256 -81
- package/ios/StreamVideoReactNative-Bridging-Header.h +0 -2
- package/ios/StreamVideoReactNative.m +0 -81
- package/package.json +11 -11
- package/src/hooks/index.ts +0 -1
- package/src/modules/call-manager/CallManager.ts +25 -1
- package/src/modules/call-manager/types.ts +7 -0
- package/src/providers/StreamCall/AudioInterruptionTracer.tsx +51 -0
- package/src/providers/StreamCall/index.tsx +2 -0
- package/src/utils/internal/callingx/callingx.ts +2 -2
- package/src/utils/internal/registerSDKGlobals.ts +23 -1
- package/src/utils/push/internal/ios.ts +5 -0
- package/src/version.ts +1 -1
- package/android/src/main/java/com/streamvideo/reactnative/recorder/AudioPipeline.kt +0 -436
- package/android/src/main/java/com/streamvideo/reactnative/recorder/EncoderConstants.kt +0 -17
- package/android/src/main/java/com/streamvideo/reactnative/recorder/PipelineHost.kt +0 -36
- package/android/src/main/java/com/streamvideo/reactnative/recorder/RecorderPlaybackSamplesSink.kt +0 -60
- package/android/src/main/java/com/streamvideo/reactnative/recorder/RecorderVideoSink.kt +0 -31
- package/android/src/main/java/com/streamvideo/reactnative/recorder/TracksRecorderManager.kt +0 -329
- package/android/src/main/java/com/streamvideo/reactnative/recorder/VideoPipeline.kt +0 -472
- package/dist/commonjs/hooks/useLoopbackRecording.js +0 -243
- package/dist/commonjs/hooks/useLoopbackRecording.js.map +0 -1
- package/dist/module/hooks/useLoopbackRecording.js +0 -238
- package/dist/module/hooks/useLoopbackRecording.js.map +0 -1
- package/dist/typescript/hooks/useLoopbackRecording.d.ts +0 -85
- package/dist/typescript/hooks/useLoopbackRecording.d.ts.map +0 -1
- package/ios/TracksRecorder/AudioPipeline.swift +0 -270
- package/ios/TracksRecorder/PipelineHost.swift +0 -56
- package/ios/TracksRecorder/RecorderAudioRenderTap.swift +0 -154
- package/ios/TracksRecorder/RecorderVideoSink.swift +0 -137
- package/ios/TracksRecorder/TracksRecorderManager.swift +0 -327
- package/ios/TracksRecorder/VideoPipeline.swift +0 -297
- package/src/hooks/useLoopbackRecording.ts +0 -438
|
@@ -17,9 +17,9 @@ import UIKit
|
|
|
17
17
|
/// The content can be managed either through individual properties (legacy approach)
|
|
18
18
|
/// or through the unified `content` property using `PictureInPictureContent` enum.
|
|
19
19
|
final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
// MARK: - Content State (New unified approach)
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
/// The current content being displayed, using the unified content enum.
|
|
24
24
|
/// Setting this property automatically updates all overlay views and the video track.
|
|
25
25
|
var content: PictureInPictureContent = .inactive {
|
|
@@ -28,7 +28,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
28
28
|
applyContent(content)
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
/// The content state manager for reactive state updates.
|
|
33
33
|
/// When set, the renderer subscribes to content changes automatically.
|
|
34
34
|
var contentState: PictureInPictureContentState? {
|
|
@@ -36,13 +36,13 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
36
36
|
subscribeToContentState()
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
/// Cancellable for content state subscription
|
|
41
41
|
private var contentStateCancellable: AnyCancellable?
|
|
42
42
|
private var isApplyingContentBatch = false
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
// MARK: - Individual Properties (Legacy approach - still supported)
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
/// The rendering track.
|
|
47
47
|
var track: RTCVideoTrack? {
|
|
48
48
|
didSet {
|
|
@@ -60,25 +60,25 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
/// The layer that renders the track's frames.
|
|
65
65
|
var displayLayer: CALayer { contentView.layer }
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
/// Indicates whether the rendered video should be mirrored.
|
|
68
68
|
var isMirrored: Bool = false {
|
|
69
69
|
didSet {
|
|
70
70
|
contentView.transform = isMirrored
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
? CGAffineTransform(scaleX: -1, y: 1)
|
|
72
|
+
: .identity
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
/// A policy defining how the Picture in Picture window should be resized in order to better fit
|
|
77
77
|
/// the rendering frame size.
|
|
78
78
|
var pictureInPictureWindowSizePolicy: PictureInPictureWindowSizePolicy
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
// MARK: - Avatar Placeholder Properties
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
/// The participant's name for the avatar and overlay
|
|
83
83
|
var participantName: String? {
|
|
84
84
|
didSet {
|
|
@@ -87,14 +87,14 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
87
87
|
participantOverlayView.participantName = participantName
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
/// The URL string for the participant's profile image
|
|
92
92
|
var participantImageURL: String? {
|
|
93
93
|
didSet {
|
|
94
94
|
avatarView.imageURL = participantImageURL
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
/// Whether video is enabled - when false, shows avatar placeholder
|
|
99
99
|
var isVideoEnabled: Bool = true {
|
|
100
100
|
didSet {
|
|
@@ -104,7 +104,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
/// Whether the call is reconnecting - when true, shows reconnection view
|
|
109
109
|
var isReconnecting: Bool = false {
|
|
110
110
|
didSet {
|
|
@@ -114,52 +114,52 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
/// Whether screen sharing is active (used for content state tracking)
|
|
119
119
|
var isScreenSharing: Bool = false
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
/// Whether the participant has audio enabled (shown in participant overlay)
|
|
122
122
|
var hasAudio: Bool = true {
|
|
123
123
|
didSet {
|
|
124
124
|
participantOverlayView.hasAudio = hasAudio
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
/// Whether the video track is paused (shown in participant overlay)
|
|
129
129
|
var isTrackPaused: Bool = false {
|
|
130
130
|
didSet {
|
|
131
131
|
participantOverlayView.isTrackPaused = isTrackPaused
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
/// Whether the participant is pinned (shown in participant overlay)
|
|
136
136
|
var isPinned: Bool = false {
|
|
137
137
|
didSet {
|
|
138
138
|
participantOverlayView.isPinned = isPinned
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
/// Whether the participant is currently speaking (shows border highlight)
|
|
143
143
|
var isSpeaking: Bool = false {
|
|
144
144
|
didSet {
|
|
145
145
|
updateSpeakingIndicator()
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
/// The connection quality level (0: unknown, 1: poor, 2: good, 3: excellent)
|
|
150
150
|
var connectionQuality: Int = 0 {
|
|
151
151
|
didSet {
|
|
152
152
|
connectionQualityIndicator.connectionQuality = PictureInPictureConnectionQualityIndicator.ConnectionQuality(rawValue: connectionQuality) ?? .unspecified
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
|
|
155
|
+
|
|
156
156
|
/// Whether the participant overlay is enabled
|
|
157
157
|
var isParticipantOverlayEnabled: Bool = true {
|
|
158
158
|
didSet {
|
|
159
159
|
participantOverlayView.isOverlayEnabled = isParticipantOverlayEnabled
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
/// The publisher which is used to streamline the frames received from the track.
|
|
164
164
|
private let bufferPublisher: PassthroughSubject<CMSampleBuffer, Never> = .init()
|
|
165
165
|
|
|
@@ -191,7 +191,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
191
191
|
didUpdateTrackSize()
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
/// A property that defines if the RTCVideoFrame instances that will be rendered need to be resized
|
|
196
196
|
/// to fid the view's contentSize.
|
|
197
197
|
private var requiresResize = false {
|
|
@@ -208,8 +208,8 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
208
208
|
/// ``renderFrame(_:)``.
|
|
209
209
|
private var skippedFrames = 0
|
|
210
210
|
|
|
211
|
-
///
|
|
212
|
-
private var shouldRenderFrame: Bool { skippedFrames == 0 && trackSize != .zero }
|
|
211
|
+
/// Render only when not skipping and both trackSize and contentSize are non-zero.
|
|
212
|
+
private var shouldRenderFrame: Bool { skippedFrames == 0 && trackSize != .zero && contentSize != .zero }
|
|
213
213
|
|
|
214
214
|
/// A size ratio threshold used to determine if resizing is required.
|
|
215
215
|
/// - Note: It seems that Picture-in-Picture doesn't like rendering frames that are bigger than its
|
|
@@ -218,7 +218,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
218
218
|
|
|
219
219
|
/// A size ratio threshold used to determine if skipping frames is required.
|
|
220
220
|
private let sizeRatioThreshold: CGFloat = 15
|
|
221
|
-
|
|
221
|
+
|
|
222
222
|
/// The avatar view shown when video is disabled
|
|
223
223
|
/// Note: Uses alpha=0 for visibility instead of isHidden to match upstream SwiftUI behavior
|
|
224
224
|
/// and ensure layoutSubviews is always called for proper constraint layout.
|
|
@@ -228,7 +228,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
228
228
|
view.alpha = 0 // Initially invisible (video enabled by default)
|
|
229
229
|
return view
|
|
230
230
|
}()
|
|
231
|
-
|
|
231
|
+
|
|
232
232
|
/// The reconnection view shown when connection is being recovered
|
|
233
233
|
private lazy var reconnectionView: PictureInPictureReconnectionView = {
|
|
234
234
|
let view = PictureInPictureReconnectionView()
|
|
@@ -236,22 +236,22 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
236
236
|
view.isHidden = true // Initially hidden (not reconnecting by default)
|
|
237
237
|
return view
|
|
238
238
|
}()
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
|
|
240
|
+
|
|
241
241
|
/// The participant overlay view showing name and mute status
|
|
242
242
|
private lazy var participantOverlayView: PictureInPictureParticipantOverlayView = {
|
|
243
243
|
let view = PictureInPictureParticipantOverlayView()
|
|
244
244
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
245
245
|
return view
|
|
246
246
|
}()
|
|
247
|
-
|
|
247
|
+
|
|
248
248
|
/// Connection quality indicator view (bottom-right)
|
|
249
249
|
private lazy var connectionQualityIndicator: PictureInPictureConnectionQualityIndicator = {
|
|
250
250
|
let view = PictureInPictureConnectionQualityIndicator()
|
|
251
251
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
252
252
|
return view
|
|
253
253
|
}()
|
|
254
|
-
|
|
254
|
+
|
|
255
255
|
/// Speaking indicator border layer
|
|
256
256
|
private lazy var speakingBorderLayer: CAShapeLayer = {
|
|
257
257
|
let layer = CAShapeLayer()
|
|
@@ -261,7 +261,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
261
261
|
layer.isHidden = true
|
|
262
262
|
return layer
|
|
263
263
|
}()
|
|
264
|
-
|
|
264
|
+
|
|
265
265
|
/// The speaking indicator corner radius (matches upstream)
|
|
266
266
|
private var speakingCornerRadius: CGFloat {
|
|
267
267
|
if #available(iOS 26.0, *) {
|
|
@@ -270,7 +270,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
270
270
|
return 16
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
|
-
|
|
273
|
+
|
|
274
274
|
// MARK: - Lifecycle
|
|
275
275
|
|
|
276
276
|
@available(*, unavailable)
|
|
@@ -301,8 +301,14 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
301
301
|
|
|
302
302
|
override func layoutSubviews() {
|
|
303
303
|
super.layoutSubviews()
|
|
304
|
+
let previousContentSize = contentSize
|
|
304
305
|
contentSize = frame.size
|
|
305
|
-
|
|
306
|
+
|
|
307
|
+
// Recompute resize params once the real size is known; trackSize alone won't trigger it.
|
|
308
|
+
if contentSize != previousContentSize, contentSize != .zero, trackSize != .zero {
|
|
309
|
+
didUpdateTrackSize()
|
|
310
|
+
}
|
|
311
|
+
|
|
306
312
|
// Update speaking border frame
|
|
307
313
|
CATransaction.begin()
|
|
308
314
|
CATransaction.setDisableActions(true)
|
|
@@ -322,12 +328,12 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
322
328
|
guard let frame = frame else {
|
|
323
329
|
return
|
|
324
330
|
}
|
|
325
|
-
|
|
331
|
+
|
|
326
332
|
// Ignore empty frames
|
|
327
333
|
if frame.width <= 0 || frame.height <= 0 {
|
|
328
334
|
return
|
|
329
335
|
}
|
|
330
|
-
|
|
336
|
+
|
|
331
337
|
// Update the trackSize and re-calculate rendering properties if the size
|
|
332
338
|
// has changed.
|
|
333
339
|
trackSize = .init(width: Int(frame.width), height: Int(frame.height))
|
|
@@ -339,13 +345,13 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
339
345
|
guard shouldRenderFrame else {
|
|
340
346
|
return
|
|
341
347
|
}
|
|
342
|
-
|
|
348
|
+
|
|
343
349
|
if
|
|
344
350
|
let yuvBuffer = bufferTransformer.transformAndResizeIfRequired(frame, targetSize: contentSize)?
|
|
345
|
-
|
|
351
|
+
.buffer as? StreamRTCYUVBuffer,
|
|
346
352
|
let sampleBuffer = yuvBuffer.sampleBuffer {
|
|
347
353
|
bufferPublisher.send(sampleBuffer)
|
|
348
|
-
}
|
|
354
|
+
}
|
|
349
355
|
}
|
|
350
356
|
|
|
351
357
|
// MARK: - Private helpers
|
|
@@ -354,35 +360,35 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
354
360
|
private func setUp() {
|
|
355
361
|
// Add speaking border layer first (behind everything else)
|
|
356
362
|
layer.addSublayer(speakingBorderLayer)
|
|
357
|
-
|
|
363
|
+
|
|
358
364
|
addSubview(contentView)
|
|
359
365
|
addSubview(avatarView)
|
|
360
366
|
addSubview(reconnectionView)
|
|
361
367
|
addSubview(participantOverlayView)
|
|
362
368
|
addSubview(connectionQualityIndicator)
|
|
363
|
-
|
|
369
|
+
|
|
364
370
|
NSLayoutConstraint.activate([
|
|
365
371
|
contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
366
372
|
contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
367
373
|
contentView.topAnchor.constraint(equalTo: topAnchor),
|
|
368
374
|
contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
369
|
-
|
|
375
|
+
|
|
370
376
|
avatarView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
371
377
|
avatarView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
372
378
|
avatarView.topAnchor.constraint(equalTo: topAnchor),
|
|
373
379
|
avatarView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
374
|
-
|
|
380
|
+
|
|
375
381
|
reconnectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
376
382
|
reconnectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
377
383
|
reconnectionView.topAnchor.constraint(equalTo: topAnchor),
|
|
378
384
|
reconnectionView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
379
|
-
|
|
385
|
+
|
|
380
386
|
// Participant overlay positioned at bottom
|
|
381
387
|
participantOverlayView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
382
388
|
participantOverlayView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
383
389
|
participantOverlayView.topAnchor.constraint(equalTo: topAnchor),
|
|
384
390
|
participantOverlayView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
385
|
-
|
|
391
|
+
|
|
386
392
|
// Connection quality indicator at bottom-right
|
|
387
393
|
connectionQualityIndicator.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
388
394
|
connectionQualityIndicator.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
@@ -390,7 +396,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
390
396
|
connectionQualityIndicator.heightAnchor.constraint(equalToConstant: 28)
|
|
391
397
|
])
|
|
392
398
|
}
|
|
393
|
-
|
|
399
|
+
|
|
394
400
|
/// Updates the visibility of overlay views based on current state.
|
|
395
401
|
/// Priority: reconnection view > avatar view > video content
|
|
396
402
|
///
|
|
@@ -416,24 +422,26 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
416
422
|
let shouldShowVideo = isVideoEnabled && track != nil
|
|
417
423
|
let shouldShowAvatar = !shouldShowVideo
|
|
418
424
|
PictureInPictureLogger.log("updateOverlayVisibility: isVideoEnabled=\(isVideoEnabled), track=\(track?.trackId ?? "nil"), shouldShowAvatar=\(shouldShowAvatar)")
|
|
419
|
-
|
|
425
|
+
|
|
420
426
|
// Update avatar visibility - setting isVideoEnabled triggers internal layout
|
|
421
427
|
avatarView.isVideoEnabled = !shouldShowAvatar
|
|
422
428
|
avatarView.alpha = shouldShowAvatar ? 1 : 0
|
|
423
|
-
|
|
429
|
+
|
|
424
430
|
// Force layout when avatar becomes visible to ensure proper sizing
|
|
425
431
|
if shouldShowAvatar {
|
|
426
432
|
PictureInPictureLogger.log("updateOverlayVisibility: showing avatar, forcing layout. participantName=\(participantName ?? "nil"), avatarView.participantName='\(avatarView.participantName ?? "nil")'")
|
|
427
433
|
avatarView.setNeedsLayout()
|
|
428
|
-
|
|
434
|
+
if bounds.width > 0, bounds.height > 0 {
|
|
435
|
+
avatarView.layoutIfNeeded()
|
|
436
|
+
}
|
|
429
437
|
}
|
|
430
|
-
|
|
438
|
+
|
|
431
439
|
// Participant overlay shows on BOTH video and avatar (matches upstream)
|
|
432
440
|
// Only hide during reconnection
|
|
433
441
|
participantOverlayView.isOverlayEnabled = true
|
|
434
442
|
}
|
|
435
443
|
}
|
|
436
|
-
|
|
444
|
+
|
|
437
445
|
/// Updates the speaking indicator border visibility based on isSpeaking state.
|
|
438
446
|
/// The border is shown when the participant is speaking, on BOTH video and avatar views
|
|
439
447
|
/// (matching upstream behavior). Only hidden during reconnection.
|
|
@@ -441,7 +449,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
441
449
|
let shouldShowBorder = isSpeaking && !isReconnecting
|
|
442
450
|
speakingBorderLayer.isHidden = !shouldShowBorder
|
|
443
451
|
}
|
|
444
|
-
|
|
452
|
+
|
|
445
453
|
/// A method used to process the frame's buffer and enqueue on the rendering view.
|
|
446
454
|
private func process(_ buffer: CMSampleBuffer) {
|
|
447
455
|
guard
|
|
@@ -472,14 +480,14 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
472
480
|
on window: UIWindow?
|
|
473
481
|
) {
|
|
474
482
|
guard window != nil, let track else { return }
|
|
475
|
-
|
|
483
|
+
|
|
476
484
|
bufferUpdatesCancellable = bufferPublisher
|
|
477
485
|
.receive(on: DispatchQueue.main)
|
|
478
486
|
.sink { [weak self] in self?.process($0) }
|
|
479
|
-
|
|
487
|
+
|
|
480
488
|
track.add(self)
|
|
481
489
|
}
|
|
482
|
-
|
|
490
|
+
|
|
483
491
|
/// A method that stops the frame consumption from the track. Used automatically when the rendering
|
|
484
492
|
/// view move's away from the window or when the track changes.
|
|
485
493
|
private func stopFrameStreaming(for track: RTCVideoTrack?) {
|
|
@@ -504,8 +512,8 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
504
512
|
noOfFramesToSkipAfterRendering = requiresFramesSkipping ? max(Int(max(Int(widthDiffRatio), Int(heightDiffRatio)) / 2), 1) :
|
|
505
513
|
0
|
|
506
514
|
skippedFrames = 0
|
|
507
|
-
|
|
508
|
-
|
|
515
|
+
|
|
516
|
+
/// We update the provided windowSizePolicy with the size of the track we received, transformed
|
|
509
517
|
/// to the value that fits.
|
|
510
518
|
pictureInPictureWindowSizePolicy.trackSize = trackSize
|
|
511
519
|
}
|
|
@@ -531,7 +539,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
531
539
|
requiresResize = false
|
|
532
540
|
startFrameStreaming(for: track, on: window)
|
|
533
541
|
}
|
|
534
|
-
|
|
542
|
+
|
|
535
543
|
private func isSameTrackInstance(_ lhs: RTCVideoTrack?, _ rhs: RTCVideoTrack?) -> Bool {
|
|
536
544
|
switch (lhs, rhs) {
|
|
537
545
|
case (nil, nil):
|
|
@@ -542,23 +550,23 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
542
550
|
return false
|
|
543
551
|
}
|
|
544
552
|
}
|
|
545
|
-
|
|
553
|
+
|
|
546
554
|
// MARK: - Content State System
|
|
547
|
-
|
|
555
|
+
|
|
548
556
|
/// Subscribes to the content state manager for reactive updates.
|
|
549
557
|
private func subscribeToContentState() {
|
|
550
558
|
contentStateCancellable?.cancel()
|
|
551
559
|
contentStateCancellable = nil
|
|
552
|
-
|
|
560
|
+
|
|
553
561
|
guard let contentState = contentState else { return }
|
|
554
|
-
|
|
562
|
+
|
|
555
563
|
contentStateCancellable = contentState.contentPublisher
|
|
556
564
|
.receive(on: DispatchQueue.main)
|
|
557
565
|
.sink { [weak self] newContent in
|
|
558
566
|
self?.content = newContent
|
|
559
567
|
}
|
|
560
568
|
}
|
|
561
|
-
|
|
569
|
+
|
|
562
570
|
/// Applies the given content state to update all view components.
|
|
563
571
|
/// This method synchronizes the unified content enum with the individual properties
|
|
564
572
|
/// for backward compatibility while providing a cleaner API.
|
|
@@ -568,7 +576,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
568
576
|
isApplyingContentBatch = false
|
|
569
577
|
updateOverlayVisibility()
|
|
570
578
|
}
|
|
571
|
-
|
|
579
|
+
|
|
572
580
|
switch content {
|
|
573
581
|
case .inactive:
|
|
574
582
|
// Clear everything
|
|
@@ -578,7 +586,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
578
586
|
isVideoEnabled = true
|
|
579
587
|
isReconnecting = false
|
|
580
588
|
isScreenSharing = false
|
|
581
|
-
|
|
589
|
+
|
|
582
590
|
case let .video(newTrack, name, imageURL):
|
|
583
591
|
// Show video content
|
|
584
592
|
track = newTrack
|
|
@@ -587,7 +595,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
587
595
|
isVideoEnabled = true
|
|
588
596
|
isReconnecting = false
|
|
589
597
|
isScreenSharing = false
|
|
590
|
-
|
|
598
|
+
|
|
591
599
|
case let .screenSharing(newTrack, name):
|
|
592
600
|
// Show screen sharing content with indicator
|
|
593
601
|
track = newTrack
|
|
@@ -596,7 +604,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
596
604
|
isVideoEnabled = true
|
|
597
605
|
isReconnecting = false
|
|
598
606
|
isScreenSharing = true
|
|
599
|
-
|
|
607
|
+
|
|
600
608
|
case let .avatar(name, imageURL):
|
|
601
609
|
// Show avatar placeholder (video disabled)
|
|
602
610
|
// Keep existing track for potential quick re-enable
|
|
@@ -605,7 +613,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
605
613
|
isVideoEnabled = false
|
|
606
614
|
isReconnecting = false
|
|
607
615
|
isScreenSharing = false
|
|
608
|
-
|
|
616
|
+
|
|
609
617
|
case .reconnecting:
|
|
610
618
|
// Show reconnection view
|
|
611
619
|
// Keep existing track and participant info for recovery
|
|
@@ -613,7 +621,7 @@ final class StreamPictureInPictureVideoRenderer: UIView, RTCVideoRenderer {
|
|
|
613
621
|
isScreenSharing = false
|
|
614
622
|
}
|
|
615
623
|
}
|
|
616
|
-
|
|
624
|
+
|
|
617
625
|
/// Returns the current content as a `PictureInPictureContent` enum value.
|
|
618
626
|
/// This is useful for reading the current state in a unified way.
|
|
619
627
|
func getCurrentContent() -> PictureInPictureContent {
|
|
@@ -65,17 +65,21 @@ final class StreamRTCYUVBuffer: NSObject, RTCVideoFrameBuffer {
|
|
|
65
65
|
scaleHeight: Int32(targetSize.height)
|
|
66
66
|
)
|
|
67
67
|
return .init(source: resizedSource, conversion: conversion)
|
|
68
|
-
} else if
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
} else if let pixelBuffer = source as? RTCCVPixelBuffer {
|
|
69
|
+
do {
|
|
70
|
+
let dequeuedPixelBuffer = try pixelBufferRepository.dequeuePixelBuffer(
|
|
71
|
+
of: targetSize,
|
|
72
|
+
pixelFormat: CVPixelBufferGetPixelFormatType(pixelBuffer.pixelBuffer)
|
|
73
|
+
)
|
|
74
|
+
let count = pixelBuffer.bufferSizeForCroppingAndScaling(toWidth: Int32(targetSize.width), height: Int32(targetSize.height))
|
|
75
|
+
let tempBuffer: UnsafeMutableRawPointer? = malloc(Int(count))
|
|
76
|
+
pixelBuffer.cropAndScale(to: dequeuedPixelBuffer, withTempBuffer: tempBuffer)
|
|
77
|
+
tempBuffer?.deallocate()
|
|
78
|
+
return .init(source: RTCCVPixelBuffer(pixelBuffer: dequeuedPixelBuffer))
|
|
79
|
+
} catch {
|
|
80
|
+
// No pixel buffer available for this size; drop the frame.
|
|
81
|
+
return nil
|
|
82
|
+
}
|
|
79
83
|
} else {
|
|
80
84
|
return nil
|
|
81
85
|
}
|
|
@@ -97,9 +101,9 @@ final class StreamRTCYUVBuffer: NSObject, RTCVideoFrameBuffer {
|
|
|
97
101
|
guard let pixelBuffer else {
|
|
98
102
|
return nil
|
|
99
103
|
}
|
|
100
|
-
|
|
104
|
+
|
|
101
105
|
var sampleBuffer: CMSampleBuffer?
|
|
102
|
-
|
|
106
|
+
|
|
103
107
|
var timingInfo = CMSampleTimingInfo()
|
|
104
108
|
var formatDescription: CMFormatDescription?
|
|
105
109
|
CMVideoFormatDescriptionCreateForImageBuffer(
|
|
@@ -107,12 +111,12 @@ final class StreamRTCYUVBuffer: NSObject, RTCVideoFrameBuffer {
|
|
|
107
111
|
imageBuffer: pixelBuffer,
|
|
108
112
|
formatDescriptionOut: &formatDescription
|
|
109
113
|
)
|
|
110
|
-
|
|
114
|
+
|
|
111
115
|
guard let formatDescription = formatDescription else {
|
|
112
116
|
// log.error("Cannot create sample buffer formatDescription.")
|
|
113
117
|
return nil
|
|
114
118
|
}
|
|
115
|
-
|
|
119
|
+
|
|
116
120
|
_ = CMSampleBufferCreateReadyWithImageBuffer(
|
|
117
121
|
allocator: kCFAllocatorDefault,
|
|
118
122
|
imageBuffer: pixelBuffer,
|
|
@@ -120,7 +124,7 @@ final class StreamRTCYUVBuffer: NSObject, RTCVideoFrameBuffer {
|
|
|
120
124
|
sampleTiming: &timingInfo,
|
|
121
125
|
sampleBufferOut: &sampleBuffer
|
|
122
126
|
)
|
|
123
|
-
|
|
127
|
+
|
|
124
128
|
guard let buffer = sampleBuffer else {
|
|
125
129
|
// log.error("Cannot create sample buffer")
|
|
126
130
|
return nil
|