expo-realtime-ivs-broadcast 0.2.2 → 0.2.4
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.
|
@@ -103,9 +103,17 @@ class ExpoIVSRemoteStreamView: ExpoView {
|
|
|
103
103
|
print("✅ [REMOTE VIEW] Manager commanded me to render URN: \(deviceUrn)")
|
|
104
104
|
|
|
105
105
|
// Notify the stage manager that a stream started rendering (for PiP)
|
|
106
|
-
// Use a
|
|
107
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.
|
|
108
|
-
self
|
|
106
|
+
// Use a longer delay to ensure the view hierarchy is fully set up
|
|
107
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
|
108
|
+
guard let self = self else { return }
|
|
109
|
+
print("✅ [REMOTE VIEW] Notifying PiP system - view in window: \(self.window != nil), isRenderingVideo: \(self.isRenderingVideo)")
|
|
110
|
+
self.stageManager?.notifyRemoteStreamRendered()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Also notify immediately for faster setup if view is already ready
|
|
114
|
+
if self.window != nil {
|
|
115
|
+
print("✅ [REMOTE VIEW] View already in window, notifying PiP immediately")
|
|
116
|
+
self.stageManager?.notifyRemoteStreamRendered()
|
|
109
117
|
}
|
|
110
118
|
} catch {
|
|
111
119
|
print("❌ [REMOTE VIEW] Failed to create preview for URN \(deviceUrn): \(error)")
|
|
@@ -304,6 +304,8 @@ class IVSStageManager: NSObject, IVSStageStreamDelegate, IVSStageStrategy, IVSSt
|
|
|
304
304
|
private var currentPiPDevice: IVSImageDevice?
|
|
305
305
|
// Store the local preview view for broadcaster PiP
|
|
306
306
|
private weak var localPreviewView: UIView?
|
|
307
|
+
// Track whether we have a valid visible source view (not just device.previewView())
|
|
308
|
+
private var pipHasValidSourceView: Bool = false
|
|
307
309
|
|
|
308
310
|
@available(iOS 15.0, *)
|
|
309
311
|
private var pipController: IVSPictureInPictureController {
|
|
@@ -1026,19 +1028,22 @@ class IVSStageManager: NSObject, IVSStageStreamDelegate, IVSStageStrategy, IVSSt
|
|
|
1026
1028
|
currentPiPDevice = nil
|
|
1027
1029
|
currentPiPSourceDeviceUrn = nil
|
|
1028
1030
|
pipTargetView = nil
|
|
1031
|
+
pipHasValidSourceView = false
|
|
1029
1032
|
}
|
|
1030
1033
|
|
|
1031
1034
|
/// Attach frame callback to an IVS Image Device
|
|
1032
1035
|
@available(iOS 15.0, *)
|
|
1033
1036
|
private func attachToDevice(_ device: IVSImageDevice, sourceView: UIView? = nil) {
|
|
1034
|
-
// If we are already attached to this device, do nothing
|
|
1035
|
-
if currentPiPDevice === device {
|
|
1037
|
+
// If we are already attached to this device AND have a valid source view, do nothing
|
|
1038
|
+
if currentPiPDevice === device && pipHasValidSourceView {
|
|
1039
|
+
print("🖼️ [PiP] Already attached to device with valid source view, skipping")
|
|
1036
1040
|
return
|
|
1037
1041
|
}
|
|
1038
1042
|
|
|
1039
1043
|
// If attached to another device, detach first
|
|
1040
|
-
if let oldDevice = currentPiPDevice {
|
|
1044
|
+
if let oldDevice = currentPiPDevice, oldDevice !== device {
|
|
1041
1045
|
oldDevice.setOnFrameCallback(nil)
|
|
1046
|
+
print("🖼️ [PiP] Detached from previous device")
|
|
1042
1047
|
}
|
|
1043
1048
|
|
|
1044
1049
|
currentPiPDevice = device
|
|
@@ -1051,36 +1056,44 @@ class IVSStageManager: NSObject, IVSStageStreamDelegate, IVSStageStrategy, IVSSt
|
|
|
1051
1056
|
if let view = sourceView {
|
|
1052
1057
|
pipController.setupWithSourceView(view)
|
|
1053
1058
|
pipTargetView = view
|
|
1059
|
+
pipHasValidSourceView = true
|
|
1060
|
+
print("🖼️ [PiP] Set up with provided source view (VALID)")
|
|
1054
1061
|
} else {
|
|
1055
|
-
//
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
pipController.setupWithSourceView(
|
|
1059
|
-
pipTargetView =
|
|
1060
|
-
|
|
1061
|
-
print("🖼️ [PiP]
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1062
|
+
// For remote streams, device.previewView() returns an internal view that's not visible
|
|
1063
|
+
// We should prefer finding a registered remote view first
|
|
1064
|
+
if let remoteView = remoteViews.compactMap({ $0.value }).first(where: { $0.currentRenderedDeviceUrn == device.descriptor().urn }) {
|
|
1065
|
+
pipController.setupWithSourceView(remoteView as UIView)
|
|
1066
|
+
pipTargetView = remoteView.previewViewForPiP ?? remoteView
|
|
1067
|
+
pipHasValidSourceView = true
|
|
1068
|
+
print("🖼️ [PiP] Set up with matching remote view container (VALID)")
|
|
1069
|
+
} else if let anyRenderingView = remoteViews.compactMap({ $0.value }).first(where: { $0.isRenderingVideo }) {
|
|
1070
|
+
pipController.setupWithSourceView(anyRenderingView as UIView)
|
|
1071
|
+
pipTargetView = anyRenderingView.previewViewForPiP ?? anyRenderingView
|
|
1072
|
+
pipHasValidSourceView = true
|
|
1073
|
+
print("🖼️ [PiP] Set up with fallback rendering view (VALID)")
|
|
1074
|
+
} else if let anyView = remoteViews.compactMap({ $0.value }).first {
|
|
1075
|
+
pipController.setupWithSourceView(anyView as UIView)
|
|
1076
|
+
pipTargetView = anyView
|
|
1077
|
+
pipHasValidSourceView = true
|
|
1078
|
+
print("🖼️ [PiP] Set up with any available view (VALID)")
|
|
1079
|
+
} else {
|
|
1080
|
+
// Last resort: try device.previewView() - but mark as NOT valid for remote streams
|
|
1081
|
+
// This allows frame capture to start, but we'll re-setup when a remote view becomes available
|
|
1082
|
+
do {
|
|
1083
|
+
let previewView = try device.previewView()
|
|
1084
|
+
pipController.setupWithSourceView(previewView)
|
|
1085
|
+
pipTargetView = previewView
|
|
1086
|
+
// Mark as NOT valid - we need to re-setup when remote view is available
|
|
1087
|
+
pipHasValidSourceView = false
|
|
1088
|
+
print("🖼️ [PiP] Set up with device preview view (NOT VALID - will re-setup when remote view available)")
|
|
1089
|
+
} catch {
|
|
1090
|
+
print("🖼️ [PiP] ERROR: Could not get any preview view: \(error)")
|
|
1091
|
+
pipHasValidSourceView = false
|
|
1078
1092
|
}
|
|
1079
1093
|
}
|
|
1080
1094
|
}
|
|
1081
1095
|
|
|
1082
|
-
// Set frame callback to receive CVPixelBuffers
|
|
1083
|
-
// Using a dedicated queue for better performance
|
|
1096
|
+
// Set frame callback to receive CVPixelBuffers (only if not already set on this device)
|
|
1084
1097
|
let frameQueue = DispatchQueue(label: "com.ivs.pip.frameCallback", qos: .userInteractive)
|
|
1085
1098
|
device.setOnFrameCallbackQueue(frameQueue, includePixelBuffer: true) { [weak self] frame in
|
|
1086
1099
|
guard let self = self else { return }
|
|
@@ -1118,30 +1131,55 @@ class IVSStageManager: NSObject, IVSStageStreamDelegate, IVSStageStrategy, IVSSt
|
|
|
1118
1131
|
}
|
|
1119
1132
|
|
|
1120
1133
|
if let stream = candidateStream, let imageDevice = stream.device as? IVSImageDevice {
|
|
1121
|
-
// Found a valid video stream
|
|
1122
|
-
if
|
|
1123
|
-
|
|
1134
|
+
// Found a valid video stream
|
|
1135
|
+
// Re-attach if: 1) different device, OR 2) same device but we don't have a valid source view yet
|
|
1136
|
+
let needsSetup = currentPiPSourceDeviceUrn != imageDevice.descriptor().urn || !pipHasValidSourceView
|
|
1137
|
+
|
|
1138
|
+
if needsSetup {
|
|
1139
|
+
let isNewDevice = currentPiPSourceDeviceUrn != imageDevice.descriptor().urn
|
|
1140
|
+
let reason = isNewDevice ? "new device" : "need valid source view"
|
|
1141
|
+
print("🖼️ [PiP] Setting up PiP for remote video stream (\(reason)): \(imageDevice.descriptor().urn)")
|
|
1142
|
+
print("🖼️ [PiP] pipHasValidSourceView: \(pipHasValidSourceView)")
|
|
1143
|
+
|
|
1144
|
+
// Debug: Log all registered remote views and their URNs
|
|
1145
|
+
print("🖼️ [PiP] Registered remote views: \(remoteViews.count)")
|
|
1146
|
+
for (index, viewWrapper) in remoteViews.enumerated() {
|
|
1147
|
+
if let view = viewWrapper.value {
|
|
1148
|
+
print("🖼️ [PiP] View \(index): URN=\(view.currentRenderedDeviceUrn ?? "nil"), isRendering=\(view.isRenderingVideo)")
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1124
1151
|
|
|
1125
1152
|
// Try to find the remote view that's rendering this stream
|
|
1126
1153
|
if let remoteView = remoteViews.compactMap({ $0.value }).first(where: { $0.currentRenderedDeviceUrn == imageDevice.descriptor().urn }) {
|
|
1127
|
-
// Use the remote view (container) as the source view for Video Call API
|
|
1128
|
-
// Cast to UIView explicitly since ExpoIVSRemoteStreamView extends ExpoView -> UIView
|
|
1129
1154
|
candidateSourceView = remoteView as UIView
|
|
1130
1155
|
print("🖼️ [PiP] Found matching remote view for source")
|
|
1131
1156
|
} else {
|
|
1132
|
-
// Fallback:
|
|
1157
|
+
// Fallback: Use ANY remote view that's rendering video
|
|
1133
1158
|
if let anyRenderingView = remoteViews.compactMap({ $0.value }).first(where: { $0.isRenderingVideo }) {
|
|
1134
1159
|
candidateSourceView = anyRenderingView as UIView
|
|
1135
|
-
print("🖼️ [PiP] Using fallback remote view
|
|
1160
|
+
print("🖼️ [PiP] Using fallback remote view (URN didn't match but view is rendering)")
|
|
1161
|
+
} else if let anyView = remoteViews.compactMap({ $0.value }).first {
|
|
1162
|
+
// Last resort: use any registered view
|
|
1163
|
+
candidateSourceView = anyView as UIView
|
|
1164
|
+
print("🖼️ [PiP] Using any available remote view as last resort")
|
|
1165
|
+
} else {
|
|
1166
|
+
print("🖼️ [PiP] No remote views available yet - will retry when view registers")
|
|
1136
1167
|
}
|
|
1137
1168
|
}
|
|
1138
1169
|
|
|
1139
1170
|
attachToDevice(imageDevice, sourceView: candidateSourceView)
|
|
1140
1171
|
}
|
|
1141
1172
|
} else {
|
|
1142
|
-
// No candidate stream found
|
|
1173
|
+
// No candidate stream found - log why
|
|
1143
1174
|
if currentPiPDevice == nil {
|
|
1144
1175
|
print("🖼️ [PiP] No active remote video stream found yet")
|
|
1176
|
+
print("🖼️ [PiP] Total participants: \(participants.count)")
|
|
1177
|
+
for p in participants {
|
|
1178
|
+
print("🖼️ [PiP] Participant \(p.info.participantId ?? "nil"): \(p.streams.count) streams")
|
|
1179
|
+
for s in p.streams {
|
|
1180
|
+
print("🖼️ [PiP] Stream type: \(s.device.descriptor().type.rawValue), URN: \(s.device.descriptor().urn)")
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1145
1183
|
}
|
|
1146
1184
|
}
|
|
1147
1185
|
}
|