@stream-io/video-react-native-sdk 1.29.4-beta.0 → 1.30.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 +3162 -0
- package/android/src/main/AndroidManifest.xml +1 -8
- package/android/src/main/AndroidManifestNew.xml +0 -11
- package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt +5 -42
- package/android/src/main/java/com/streamvideo/reactnative/audio/utils/WebRtcAudioUtils.kt +6 -70
- package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +4 -6
- package/android/src/main/java/com/streamvideo/reactnative/util/CallAliveServiceChecker.kt +95 -0
- package/dist/commonjs/components/Call/CallContent/CallContent.js +13 -7
- package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +50 -14
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
- package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js +27 -0
- package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsGrid.js +19 -10
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsGrid.js.map +1 -1
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsSpotlight.js +12 -9
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsSpotlight.js.map +1 -1
- package/dist/commonjs/components/Call/CallParticipantsList/CallParticipantsList.js +19 -4
- package/dist/commonjs/components/Call/CallParticipantsList/CallParticipantsList.js.map +1 -1
- package/dist/commonjs/hooks/push/index.js +2 -0
- package/dist/commonjs/hooks/push/index.js.map +1 -1
- package/dist/commonjs/hooks/push/useIosCallkeepWithCallingStateEffect.js +160 -0
- package/dist/commonjs/hooks/push/useIosCallkeepWithCallingStateEffect.js.map +1 -0
- package/dist/commonjs/hooks/push/useIosVoipPushEventsSetupEffect.js +31 -18
- package/dist/commonjs/hooks/push/useIosVoipPushEventsSetupEffect.js.map +1 -1
- package/dist/commonjs/hooks/push/useProcessPushCallEffect.js +67 -0
- package/dist/commonjs/hooks/push/useProcessPushCallEffect.js.map +1 -0
- package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js +97 -64
- package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
- package/dist/commonjs/index.js +0 -1
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/modules/call-manager/CallManager.js +0 -26
- package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -1
- package/dist/commonjs/providers/StreamCall/index.js +6 -6
- package/dist/commonjs/providers/StreamCall/index.js.map +1 -1
- package/dist/commonjs/utils/StreamVideoRN/index.js +21 -33
- package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/commonjs/utils/hooks/index.js +0 -11
- package/dist/commonjs/utils/hooks/index.js.map +1 -1
- package/dist/commonjs/utils/internal/registerSDKGlobals.js +3 -52
- package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/commonjs/utils/push/android.js +202 -151
- package/dist/commonjs/utils/push/android.js.map +1 -1
- package/dist/commonjs/utils/push/internal/ios.js +34 -17
- package/dist/commonjs/utils/push/internal/ios.js.map +1 -1
- package/dist/commonjs/utils/push/internal/rxSubjects.js +45 -1
- package/dist/commonjs/utils/push/internal/rxSubjects.js.map +1 -1
- package/dist/commonjs/utils/push/internal/utils.js +20 -32
- package/dist/commonjs/utils/push/internal/utils.js.map +1 -1
- package/dist/commonjs/utils/push/ios.js.map +1 -1
- package/dist/commonjs/utils/push/libs/callkeep.js +17 -0
- package/dist/commonjs/utils/push/libs/callkeep.js.map +1 -0
- package/dist/commonjs/utils/push/libs/index.js +19 -8
- package/dist/commonjs/utils/push/libs/index.js.map +1 -1
- package/dist/commonjs/utils/push/libs/notifee/index.js +19 -0
- package/dist/commonjs/utils/push/libs/notifee/index.js.map +1 -1
- package/dist/commonjs/utils/push/libs/voipPushNotification.js +17 -0
- package/dist/commonjs/utils/push/libs/voipPushNotification.js.map +1 -0
- package/dist/commonjs/utils/push/setupIosCallKeepEvents.js +205 -0
- package/dist/commonjs/utils/push/setupIosCallKeepEvents.js.map +1 -0
- package/dist/commonjs/utils/push/setupIosVoipPushEvents.js +6 -7
- package/dist/commonjs/utils/push/setupIosVoipPushEvents.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/commonjs/version.js.map +1 -1
- package/dist/module/components/Call/CallContent/CallContent.js +10 -4
- package/dist/module/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js +52 -16
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
- package/dist/module/components/Call/CallContent/RTCViewPipNative.js +27 -0
- package/dist/module/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
- package/dist/module/components/Call/CallLayout/CallParticipantsGrid.js +19 -10
- package/dist/module/components/Call/CallLayout/CallParticipantsGrid.js.map +1 -1
- package/dist/module/components/Call/CallLayout/CallParticipantsSpotlight.js +15 -12
- package/dist/module/components/Call/CallLayout/CallParticipantsSpotlight.js.map +1 -1
- package/dist/module/components/Call/CallParticipantsList/CallParticipantsList.js +20 -5
- package/dist/module/components/Call/CallParticipantsList/CallParticipantsList.js.map +1 -1
- package/dist/module/hooks/push/index.js +2 -0
- package/dist/module/hooks/push/index.js.map +1 -1
- package/dist/module/hooks/push/useIosCallkeepWithCallingStateEffect.js +153 -0
- package/dist/module/hooks/push/useIosCallkeepWithCallingStateEffect.js.map +1 -0
- package/dist/module/hooks/push/useIosVoipPushEventsSetupEffect.js +31 -18
- package/dist/module/hooks/push/useIosVoipPushEventsSetupEffect.js.map +1 -1
- package/dist/module/hooks/push/useProcessPushCallEffect.js +60 -0
- package/dist/module/hooks/push/useProcessPushCallEffect.js.map +1 -0
- package/dist/module/hooks/useAndroidKeepCallAliveEffect.js +99 -66
- package/dist/module/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
- package/dist/module/index.js +0 -1
- package/dist/module/index.js.map +1 -1
- package/dist/module/modules/call-manager/CallManager.js +0 -26
- package/dist/module/modules/call-manager/CallManager.js.map +1 -1
- package/dist/module/providers/StreamCall/index.js +6 -6
- package/dist/module/providers/StreamCall/index.js.map +1 -1
- package/dist/module/utils/StreamVideoRN/index.js +21 -33
- package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/module/utils/hooks/index.js +0 -1
- package/dist/module/utils/hooks/index.js.map +1 -1
- package/dist/module/utils/internal/registerSDKGlobals.js +3 -52
- package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/module/utils/push/android.js +204 -153
- package/dist/module/utils/push/android.js.map +1 -1
- package/dist/module/utils/push/internal/ios.js +34 -17
- package/dist/module/utils/push/internal/ios.js.map +1 -1
- package/dist/module/utils/push/internal/rxSubjects.js +44 -0
- package/dist/module/utils/push/internal/rxSubjects.js.map +1 -1
- package/dist/module/utils/push/internal/utils.js +19 -29
- package/dist/module/utils/push/internal/utils.js.map +1 -1
- package/dist/module/utils/push/ios.js.map +1 -1
- package/dist/module/utils/push/libs/callkeep.js +11 -0
- package/dist/module/utils/push/libs/callkeep.js.map +1 -0
- package/dist/module/utils/push/libs/index.js +2 -1
- package/dist/module/utils/push/libs/index.js.map +1 -1
- package/dist/module/utils/push/libs/notifee/index.js +18 -0
- package/dist/module/utils/push/libs/notifee/index.js.map +1 -1
- package/dist/module/utils/push/libs/voipPushNotification.js +11 -0
- package/dist/module/utils/push/libs/voipPushNotification.js.map +1 -0
- package/dist/module/utils/push/setupIosCallKeepEvents.js +199 -0
- package/dist/module/utils/push/setupIosCallKeepEvents.js.map +1 -0
- package/dist/module/utils/push/setupIosVoipPushEvents.js +6 -7
- package/dist/module/utils/push/setupIosVoipPushEvents.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/module/version.js.map +1 -1
- package/dist/typescript/components/Call/CallContent/CallContent.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts +18 -0
- package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallLayout/CallParticipantsGrid.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallLayout/CallParticipantsSpotlight.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallParticipantsList/CallParticipantsList.d.ts.map +1 -1
- package/dist/typescript/hooks/push/index.d.ts.map +1 -1
- package/dist/typescript/hooks/push/useIosCallkeepWithCallingStateEffect.d.ts +5 -0
- package/dist/typescript/hooks/push/useIosCallkeepWithCallingStateEffect.d.ts.map +1 -0
- package/dist/typescript/hooks/push/useIosVoipPushEventsSetupEffect.d.ts.map +1 -1
- package/dist/typescript/hooks/push/useProcessPushCallEffect.d.ts +8 -0
- package/dist/typescript/hooks/push/useProcessPushCallEffect.d.ts.map +1 -0
- package/dist/typescript/hooks/useAndroidKeepCallAliveEffect.d.ts.map +1 -1
- package/dist/typescript/index.d.ts +0 -1
- package/dist/typescript/index.d.ts.map +1 -1
- package/dist/typescript/modules/call-manager/CallManager.d.ts +0 -5
- package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/index.d.ts +2 -20
- package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/types.d.ts +29 -54
- package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
- package/dist/typescript/utils/hooks/index.d.ts +0 -1
- package/dist/typescript/utils/hooks/index.d.ts.map +1 -1
- package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
- package/dist/typescript/utils/push/android.d.ts +2 -1
- package/dist/typescript/utils/push/android.d.ts.map +1 -1
- package/dist/typescript/utils/push/internal/ios.d.ts.map +1 -1
- package/dist/typescript/utils/push/internal/rxSubjects.d.ts +33 -0
- package/dist/typescript/utils/push/internal/rxSubjects.d.ts.map +1 -1
- package/dist/typescript/utils/push/internal/utils.d.ts +1 -8
- package/dist/typescript/utils/push/internal/utils.d.ts.map +1 -1
- package/dist/typescript/utils/push/ios.d.ts +2 -1
- package/dist/typescript/utils/push/ios.d.ts.map +1 -1
- package/dist/typescript/utils/push/libs/callkeep.d.ts +3 -0
- package/dist/typescript/utils/push/libs/callkeep.d.ts.map +1 -0
- package/dist/typescript/utils/push/libs/index.d.ts +2 -1
- package/dist/typescript/utils/push/libs/index.d.ts.map +1 -1
- package/dist/typescript/utils/push/libs/notifee/index.d.ts +1 -0
- package/dist/typescript/utils/push/libs/notifee/index.d.ts.map +1 -1
- package/dist/typescript/utils/push/libs/voipPushNotification.d.ts +3 -0
- package/dist/typescript/utils/push/libs/voipPushNotification.d.ts.map +1 -0
- package/dist/typescript/utils/push/setupIosCallKeepEvents.d.ts +6 -0
- package/dist/typescript/utils/push/setupIosCallKeepEvents.d.ts.map +1 -0
- package/dist/typescript/utils/push/setupIosVoipPushEvents.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/dist/typescript/version.d.ts.map +1 -1
- package/expo-config-plugin/dist/withAndroidManifest.js +33 -1
- package/expo-config-plugin/dist/withAndroidPermissions.js +7 -2
- package/expo-config-plugin/dist/withAppDelegate.js +197 -19
- package/expo-config-plugin/dist/withMainActivity.js +1 -1
- package/expo-config-plugin/dist/withiOSInfoPlist.js +3 -2
- package/ios/PictureInPicture/PictureInPictureAvatarView.swift +273 -0
- package/ios/PictureInPicture/PictureInPictureConnectionQualityIndicator.swift +162 -0
- package/ios/PictureInPicture/PictureInPictureContent.swift +173 -0
- package/ios/PictureInPicture/PictureInPictureContentState.swift +123 -0
- package/ios/PictureInPicture/PictureInPictureDelegateProxy.swift +89 -0
- package/ios/PictureInPicture/PictureInPictureEnforcedStopAdapter.swift +166 -0
- package/ios/PictureInPicture/PictureInPictureLogger.swift +16 -0
- package/ios/PictureInPicture/PictureInPictureParticipantOverlayView.swift +217 -0
- package/ios/PictureInPicture/PictureInPictureReconnectionView.swift +193 -0
- package/ios/PictureInPicture/StreamAVPictureInPictureVideoCallViewController.swift +125 -7
- package/ios/PictureInPicture/StreamPictureInPictureController.swift +237 -63
- package/ios/PictureInPicture/StreamPictureInPictureControllerProtocol.swift +30 -0
- package/ios/PictureInPicture/StreamPictureInPictureVideoRenderer.swift +384 -12
- package/ios/RTCViewPip.swift +187 -21
- package/ios/RTCViewPipManager.mm +9 -0
- package/ios/RTCViewPipManager.swift +3 -3
- package/ios/StreamInCallManager.m +0 -2
- package/ios/StreamInCallManager.swift +7 -19
- package/ios/StreamVideoReactNative.h +4 -7
- package/ios/StreamVideoReactNative.m +82 -189
- package/package.json +19 -14
- package/src/components/Call/CallContent/CallContent.tsx +16 -8
- package/src/components/Call/CallContent/RTCViewPipIOS.tsx +81 -15
- package/src/components/Call/CallContent/RTCViewPipNative.tsx +36 -0
- package/src/components/Call/CallLayout/CallParticipantsGrid.tsx +28 -14
- package/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx +19 -10
- package/src/components/Call/CallParticipantsList/CallParticipantsList.tsx +20 -5
- package/src/hooks/push/index.ts +2 -0
- package/src/hooks/push/useIosCallkeepWithCallingStateEffect.ts +235 -0
- package/src/hooks/push/useIosVoipPushEventsSetupEffect.ts +34 -21
- package/src/hooks/push/useProcessPushCallEffect.ts +108 -0
- package/src/hooks/useAndroidKeepCallAliveEffect.ts +120 -94
- package/src/index.ts +0 -1
- package/src/modules/call-manager/CallManager.ts +0 -36
- package/src/modules/call-manager/native-module.d.ts +0 -7
- package/src/providers/StreamCall/index.tsx +6 -6
- package/src/utils/StreamVideoRN/index.ts +30 -40
- package/src/utils/StreamVideoRN/types.ts +29 -56
- package/src/utils/hooks/index.ts +0 -1
- package/src/utils/internal/registerSDKGlobals.ts +4 -47
- package/src/utils/push/android.ts +309 -227
- package/src/utils/push/internal/ios.ts +44 -28
- package/src/utils/push/internal/rxSubjects.ts +61 -0
- package/src/utils/push/internal/utils.ts +26 -45
- package/src/utils/push/ios.ts +6 -1
- package/src/utils/push/libs/callkeep.ts +16 -0
- package/src/utils/push/libs/index.ts +2 -1
- package/src/utils/push/libs/notifee/index.ts +27 -0
- package/src/utils/push/libs/voipPushNotification.ts +17 -0
- package/src/utils/push/setupIosCallKeepEvents.ts +252 -0
- package/src/utils/push/setupIosVoipPushEvents.ts +7 -11
- package/src/version.ts +1 -1
- package/android/src/main/java/com/streamvideo/reactnative/keepalive/KeepAliveNotification.kt +0 -83
- package/android/src/main/java/com/streamvideo/reactnative/keepalive/StreamCallKeepAliveHeadlessService.kt +0 -149
- package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js +0 -121
- package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js.map +0 -1
- package/dist/commonjs/utils/hooks/useDebouncedValue.js +0 -24
- package/dist/commonjs/utils/hooks/useDebouncedValue.js.map +0 -1
- package/dist/commonjs/utils/internal/callingx/audioSessionPromise.js +0 -58
- package/dist/commonjs/utils/internal/callingx/audioSessionPromise.js.map +0 -1
- package/dist/commonjs/utils/internal/callingx/callingx.js +0 -109
- package/dist/commonjs/utils/internal/callingx/callingx.js.map +0 -1
- package/dist/commonjs/utils/keepCallAliveHeadlessTask.js +0 -48
- package/dist/commonjs/utils/keepCallAliveHeadlessTask.js.map +0 -1
- package/dist/commonjs/utils/push/libs/callingx.js +0 -75
- package/dist/commonjs/utils/push/libs/callingx.js.map +0 -1
- package/dist/commonjs/utils/push/setupCallingExpEvents.js +0 -108
- package/dist/commonjs/utils/push/setupCallingExpEvents.js.map +0 -1
- package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js +0 -114
- package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js.map +0 -1
- package/dist/module/utils/hooks/useDebouncedValue.js +0 -19
- package/dist/module/utils/hooks/useDebouncedValue.js.map +0 -1
- package/dist/module/utils/internal/callingx/audioSessionPromise.js +0 -51
- package/dist/module/utils/internal/callingx/audioSessionPromise.js.map +0 -1
- package/dist/module/utils/internal/callingx/callingx.js +0 -100
- package/dist/module/utils/internal/callingx/callingx.js.map +0 -1
- package/dist/module/utils/keepCallAliveHeadlessTask.js +0 -42
- package/dist/module/utils/keepCallAliveHeadlessTask.js.map +0 -1
- package/dist/module/utils/push/libs/callingx.js +0 -67
- package/dist/module/utils/push/libs/callingx.js.map +0 -1
- package/dist/module/utils/push/setupCallingExpEvents.js +0 -102
- package/dist/module/utils/push/setupCallingExpEvents.js.map +0 -1
- package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts +0 -5
- package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts.map +0 -1
- package/dist/typescript/utils/hooks/useDebouncedValue.d.ts +0 -8
- package/dist/typescript/utils/hooks/useDebouncedValue.d.ts.map +0 -1
- package/dist/typescript/utils/internal/callingx/audioSessionPromise.d.ts +0 -16
- package/dist/typescript/utils/internal/callingx/audioSessionPromise.d.ts.map +0 -1
- package/dist/typescript/utils/internal/callingx/callingx.d.ts +0 -14
- package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +0 -1
- package/dist/typescript/utils/keepCallAliveHeadlessTask.d.ts +0 -10
- package/dist/typescript/utils/keepCallAliveHeadlessTask.d.ts.map +0 -1
- package/dist/typescript/utils/push/libs/callingx.d.ts +0 -9
- package/dist/typescript/utils/push/libs/callingx.d.ts.map +0 -1
- package/dist/typescript/utils/push/setupCallingExpEvents.d.ts +0 -8
- package/dist/typescript/utils/push/setupCallingExpEvents.d.ts.map +0 -1
- package/src/hooks/push/useCallingExpWithCallingStateEffect.ts +0 -147
- package/src/utils/hooks/useDebouncedValue.ts +0 -21
- package/src/utils/internal/callingx/audioSessionPromise.ts +0 -53
- package/src/utils/internal/callingx/callingx.ts +0 -146
- package/src/utils/keepCallAliveHeadlessTask.ts +0 -54
- package/src/utils/push/libs/callingx.ts +0 -90
- package/src/utils/push/setupCallingExpEvents.ts +0 -130
|
@@ -7,13 +7,13 @@ import Foundation
|
|
|
7
7
|
|
|
8
8
|
/// Describes an object that can be used to present picture-in-picture content.
|
|
9
9
|
protocol StreamAVPictureInPictureViewControlling: AnyObject {
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
/// The closure to call whenever the picture-in-picture window size changes.
|
|
12
12
|
var onSizeUpdate: ((CGSize) -> Void)? { get set }
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
/// The track that will be rendered on picture-in-picture window.
|
|
15
15
|
var track: RTCVideoTrack? { get set }
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
/// The preferred size for the picture-in-picture window.
|
|
18
18
|
/// - Important: This should **always** be greater to ``CGSize.zero``. If not, iOS throws
|
|
19
19
|
/// a cryptic error with content `PGPegasus code:-1003`
|
|
@@ -24,17 +24,65 @@ protocol StreamAVPictureInPictureViewControlling: AnyObject {
|
|
|
24
24
|
|
|
25
25
|
/// The layer that renders the incoming frames from WebRTC.
|
|
26
26
|
var displayLayer: CALayer { get }
|
|
27
|
+
|
|
28
|
+
// MARK: - Avatar Placeholder Properties
|
|
29
|
+
|
|
30
|
+
/// The participant's name for the avatar placeholder
|
|
31
|
+
var participantName: String? { get set }
|
|
32
|
+
|
|
33
|
+
/// The URL string for the participant's profile image
|
|
34
|
+
var participantImageURL: String? { get set }
|
|
35
|
+
|
|
36
|
+
/// Whether video is enabled - when false, shows avatar placeholder
|
|
37
|
+
var isVideoEnabled: Bool { get set }
|
|
38
|
+
|
|
39
|
+
// MARK: - Reconnection Properties
|
|
40
|
+
|
|
41
|
+
/// Whether the call is reconnecting - when true, shows reconnection view
|
|
42
|
+
var isReconnecting: Bool { get set }
|
|
43
|
+
|
|
44
|
+
// MARK: - Screen Sharing Properties
|
|
45
|
+
|
|
46
|
+
/// Whether screen sharing is active (used for content state tracking)
|
|
47
|
+
var isScreenSharing: Bool { get set }
|
|
48
|
+
|
|
49
|
+
// MARK: - Participant Overlay Properties
|
|
50
|
+
|
|
51
|
+
/// Whether the participant has audio enabled (shown in participant overlay)
|
|
52
|
+
var hasAudio: Bool { get set }
|
|
53
|
+
|
|
54
|
+
/// Whether the video track is paused (shown in participant overlay)
|
|
55
|
+
var isTrackPaused: Bool { get set }
|
|
56
|
+
|
|
57
|
+
/// Whether the participant is pinned (shown in participant overlay)
|
|
58
|
+
var isPinned: Bool { get set }
|
|
59
|
+
|
|
60
|
+
/// Whether the participant is currently speaking (shows border highlight)
|
|
61
|
+
var isSpeaking: Bool { get set }
|
|
62
|
+
|
|
63
|
+
/// The connection quality level (0: unknown, 1: poor, 2: good, 3: excellent)
|
|
64
|
+
var connectionQuality: Int { get set }
|
|
65
|
+
|
|
66
|
+
// MARK: - Content State System
|
|
67
|
+
|
|
68
|
+
/// The content state manager for unified state handling.
|
|
69
|
+
/// When set, the view controller subscribes to content changes automatically.
|
|
70
|
+
var contentState: PictureInPictureContentState? { get set }
|
|
71
|
+
|
|
72
|
+
/// The current content being displayed.
|
|
73
|
+
/// Can be set directly for one-off updates or managed via contentState for reactive updates.
|
|
74
|
+
var content: PictureInPictureContent { get set }
|
|
27
75
|
}
|
|
28
76
|
|
|
29
77
|
@available(iOS 15.0, *)
|
|
30
78
|
final class StreamAVPictureInPictureVideoCallViewController: AVPictureInPictureVideoCallViewController,
|
|
31
79
|
StreamAVPictureInPictureViewControlling {
|
|
32
|
-
|
|
80
|
+
|
|
33
81
|
private let contentView: StreamPictureInPictureVideoRenderer =
|
|
34
82
|
.init(windowSizePolicy: StreamPictureInPictureAdaptiveWindowSizePolicy())
|
|
35
|
-
|
|
83
|
+
|
|
36
84
|
var onSizeUpdate: ((CGSize) -> Void)?
|
|
37
|
-
|
|
85
|
+
|
|
38
86
|
var track: RTCVideoTrack? {
|
|
39
87
|
get { contentView.track }
|
|
40
88
|
set { contentView.track = newValue }
|
|
@@ -46,7 +94,77 @@ final class StreamAVPictureInPictureVideoCallViewController: AVPictureInPictureV
|
|
|
46
94
|
}
|
|
47
95
|
|
|
48
96
|
var displayLayer: CALayer { contentView.displayLayer }
|
|
49
|
-
|
|
97
|
+
|
|
98
|
+
// MARK: - Avatar Placeholder Properties
|
|
99
|
+
|
|
100
|
+
var participantName: String? {
|
|
101
|
+
get { contentView.participantName }
|
|
102
|
+
set { contentView.participantName = newValue }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
var participantImageURL: String? {
|
|
106
|
+
get { contentView.participantImageURL }
|
|
107
|
+
set { contentView.participantImageURL = newValue }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var isVideoEnabled: Bool {
|
|
111
|
+
get { contentView.isVideoEnabled }
|
|
112
|
+
set { contentView.isVideoEnabled = newValue }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// MARK: - Reconnection Properties
|
|
116
|
+
|
|
117
|
+
var isReconnecting: Bool {
|
|
118
|
+
get { contentView.isReconnecting }
|
|
119
|
+
set { contentView.isReconnecting = newValue }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// MARK: - Screen Sharing Properties
|
|
123
|
+
|
|
124
|
+
var isScreenSharing: Bool {
|
|
125
|
+
get { contentView.isScreenSharing }
|
|
126
|
+
set { contentView.isScreenSharing = newValue }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// MARK: - Participant Overlay Properties
|
|
130
|
+
|
|
131
|
+
var hasAudio: Bool {
|
|
132
|
+
get { contentView.hasAudio }
|
|
133
|
+
set { contentView.hasAudio = newValue }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var isTrackPaused: Bool {
|
|
137
|
+
get { contentView.isTrackPaused }
|
|
138
|
+
set { contentView.isTrackPaused = newValue }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
var isPinned: Bool {
|
|
142
|
+
get { contentView.isPinned }
|
|
143
|
+
set { contentView.isPinned = newValue }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
var isSpeaking: Bool {
|
|
147
|
+
get { contentView.isSpeaking }
|
|
148
|
+
set { contentView.isSpeaking = newValue }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
var connectionQuality: Int {
|
|
152
|
+
get { contentView.connectionQuality }
|
|
153
|
+
set { contentView.connectionQuality = newValue }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// MARK: - Content State System
|
|
157
|
+
|
|
158
|
+
var contentState: PictureInPictureContentState? {
|
|
159
|
+
get { contentView.contentState }
|
|
160
|
+
set { contentView.contentState = newValue }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
var content: PictureInPictureContent {
|
|
164
|
+
get { contentView.content }
|
|
165
|
+
set { contentView.content = newValue }
|
|
166
|
+
}
|
|
167
|
+
|
|
50
168
|
// MARK: - Lifecycle
|
|
51
169
|
|
|
52
170
|
@available(*, unavailable)
|
|
@@ -7,31 +7,36 @@ import Combine
|
|
|
7
7
|
import Foundation
|
|
8
8
|
|
|
9
9
|
/// A controller class for picture-in-picture whenever that is possible.
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
///
|
|
11
|
+
/// This controller manages the Picture-in-Picture window state and handles transitions
|
|
12
|
+
/// between foreground and background states. It uses the `PictureInPictureContentState`
|
|
13
|
+
/// for centralized state management and a delegate proxy pattern to enable reactive
|
|
14
|
+
/// handling of PiP lifecycle events.
|
|
15
|
+
@objc final class StreamPictureInPictureController: NSObject {
|
|
16
|
+
|
|
12
17
|
// MARK: - Properties
|
|
13
|
-
|
|
18
|
+
|
|
14
19
|
/// The RTCVideoTrack for which the picture-in-picture session is created.
|
|
15
20
|
@objc public var track: RTCVideoTrack? {
|
|
16
21
|
didSet {
|
|
17
22
|
didUpdate(track) // Called when the `track` property changes
|
|
18
23
|
}
|
|
19
24
|
}
|
|
20
|
-
|
|
25
|
+
|
|
21
26
|
/// The UIView that contains the video content.
|
|
22
27
|
@objc public var sourceView: UIView? {
|
|
23
28
|
didSet {
|
|
24
29
|
didUpdate(sourceView) // Called when the `sourceView` property changes
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
|
-
|
|
32
|
+
|
|
28
33
|
/// A closure called when the picture-in-picture view's size changes.
|
|
29
34
|
public var onSizeUpdate: ((CGSize) -> Void)? {
|
|
30
35
|
didSet {
|
|
31
36
|
contentViewController?.onSizeUpdate = onSizeUpdate // Updates the onSizeUpdate closure of the content view controller
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
|
-
|
|
39
|
+
|
|
35
40
|
/// A closure called when the picture-in-picture state changes.
|
|
36
41
|
public var onPiPStateChange: ((Bool) -> Void)?
|
|
37
42
|
|
|
@@ -44,28 +49,118 @@ import Foundation
|
|
|
44
49
|
|
|
45
50
|
/// A boolean value indicating whether the picture-in-picture session should start automatically when the app enters background.
|
|
46
51
|
public var canStartPictureInPictureAutomaticallyFromInline: Bool
|
|
47
|
-
|
|
52
|
+
|
|
53
|
+
// MARK: - Content State Properties
|
|
54
|
+
// These properties update the centralized content state, which manages view switching
|
|
55
|
+
|
|
56
|
+
/// The participant's name for the avatar placeholder
|
|
57
|
+
@objc public var participantName: String? {
|
|
58
|
+
didSet {
|
|
59
|
+
syncContentStateIfNeeded()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// The URL string for the participant's profile image
|
|
64
|
+
@objc public var participantImageURL: String? {
|
|
65
|
+
didSet {
|
|
66
|
+
syncContentStateIfNeeded()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Whether video is enabled - when false, shows avatar placeholder
|
|
71
|
+
@objc public var isVideoEnabled: Bool = true {
|
|
72
|
+
didSet {
|
|
73
|
+
syncContentStateIfNeeded()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Whether the call is reconnecting - when true, shows reconnection view
|
|
78
|
+
@objc public var isReconnecting: Bool = false {
|
|
79
|
+
didSet {
|
|
80
|
+
syncContentStateIfNeeded()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Whether screen sharing is active (used for content state tracking)
|
|
85
|
+
@objc public var isScreenSharing: Bool = false {
|
|
86
|
+
didSet {
|
|
87
|
+
syncContentStateIfNeeded()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Whether the participant has audio enabled (shown in participant overlay)
|
|
92
|
+
@objc public var hasAudio: Bool = true {
|
|
93
|
+
didSet {
|
|
94
|
+
contentViewController?.hasAudio = hasAudio
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Whether the video track is paused (shown in participant overlay)
|
|
99
|
+
@objc public var isTrackPaused: Bool = false {
|
|
100
|
+
didSet {
|
|
101
|
+
contentViewController?.isTrackPaused = isTrackPaused
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Whether the participant is pinned (shown in participant overlay)
|
|
106
|
+
@objc public var isPinned: Bool = false {
|
|
107
|
+
didSet {
|
|
108
|
+
contentViewController?.isPinned = isPinned
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Whether the participant is currently speaking (shows border highlight)
|
|
113
|
+
@objc public var isSpeaking: Bool = false {
|
|
114
|
+
didSet {
|
|
115
|
+
contentViewController?.isSpeaking = isSpeaking
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// The connection quality level (0: unknown, 1: poor, 2: good, 3: excellent)
|
|
120
|
+
@objc public var connectionQuality: Int = 0 {
|
|
121
|
+
didSet {
|
|
122
|
+
contentViewController?.connectionQuality = connectionQuality
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
48
126
|
// MARK: - Private Properties
|
|
49
|
-
|
|
127
|
+
|
|
50
128
|
/// The AVPictureInPictureController object.
|
|
51
129
|
private var pictureInPictureController: AVPictureInPictureController?
|
|
52
|
-
|
|
130
|
+
|
|
53
131
|
/// The StreamAVPictureInPictureViewControlling object that manages the picture-in-picture view.
|
|
54
132
|
private var contentViewController: StreamAVPictureInPictureViewControlling?
|
|
55
|
-
|
|
133
|
+
|
|
134
|
+
/// Centralized content state manager for unified state handling.
|
|
135
|
+
/// This manages the content switching between video, avatar, reconnection, and screen share views.
|
|
136
|
+
private let contentState = PictureInPictureContentState()
|
|
137
|
+
|
|
56
138
|
/// A set of `AnyCancellable` objects used to manage subscriptions.
|
|
57
139
|
private var cancellableBag: Set<AnyCancellable> = []
|
|
58
|
-
|
|
59
|
-
///
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
140
|
+
|
|
141
|
+
/// Delegate proxy that publishes PiP lifecycle events via Combine.
|
|
142
|
+
private let delegateProxy = PictureInPictureDelegateProxy()
|
|
143
|
+
|
|
144
|
+
/// Adapter responsible for enforcing the stop of PiP when the app returns to foreground.
|
|
145
|
+
private var enforcedStopAdapter: PictureInPictureEnforcedStopAdapter?
|
|
146
|
+
|
|
63
147
|
/// A `StreamPictureInPictureTrackStateAdapter` object that manages the state of the
|
|
64
148
|
/// active track.
|
|
65
149
|
private let trackStateAdapter: StreamPictureInPictureTrackStateAdapter = .init()
|
|
150
|
+
|
|
151
|
+
/// When true, multiple content fields are being updated as one transition.
|
|
152
|
+
private var isApplyingContentSnapshot = false
|
|
66
153
|
|
|
154
|
+
// MARK: - Content State Access
|
|
155
|
+
|
|
156
|
+
/// Returns the current content being displayed in the PiP window.
|
|
157
|
+
/// This is useful for debugging and logging purposes.
|
|
158
|
+
var currentContent: PictureInPictureContent {
|
|
159
|
+
contentState.content
|
|
160
|
+
}
|
|
161
|
+
|
|
67
162
|
// MARK: - Lifecycle
|
|
68
|
-
|
|
163
|
+
|
|
69
164
|
/// Initializes the controller and creates the content view
|
|
70
165
|
///
|
|
71
166
|
/// - Parameter canStartPictureInPictureAutomaticallyFromInline A boolean value
|
|
@@ -77,7 +172,7 @@ import Foundation
|
|
|
77
172
|
guard AVPictureInPictureController.isPictureInPictureSupported() else {
|
|
78
173
|
return nil
|
|
79
174
|
}
|
|
80
|
-
|
|
175
|
+
|
|
81
176
|
let contentViewController: StreamAVPictureInPictureViewControlling? = {
|
|
82
177
|
if #available(iOS 15.0, *) {
|
|
83
178
|
return StreamAVPictureInPictureVideoCallViewController()
|
|
@@ -85,60 +180,121 @@ import Foundation
|
|
|
85
180
|
return nil
|
|
86
181
|
}
|
|
87
182
|
}()
|
|
88
|
-
//
|
|
183
|
+
// Set a default preferred content size to avoid iOS PGPegasus code:-1003 error
|
|
184
|
+
// This will be updated later when track dimensions become available
|
|
185
|
+
contentViewController?.preferredContentSize = .init(width: 640, height: 480)
|
|
89
186
|
self.contentViewController = contentViewController
|
|
90
187
|
self.contentViewController?.isMirrored = isMirrored
|
|
91
188
|
self.canStartPictureInPictureAutomaticallyFromInline = canStartPictureInPictureAutomaticallyFromInline
|
|
92
189
|
super.init()
|
|
190
|
+
|
|
191
|
+
// Wire up the content state to the view controller for reactive updates (US-008)
|
|
192
|
+
// This enables the unified content view system where contentState changes
|
|
193
|
+
// automatically drive view switching in the renderer
|
|
194
|
+
contentViewController?.contentState = contentState
|
|
195
|
+
syncContentState()
|
|
196
|
+
|
|
197
|
+
// Subscribe to delegate proxy events for reactive PiP state handling
|
|
198
|
+
setupDelegateProxySubscriptions()
|
|
199
|
+
|
|
200
|
+
// Subscribe to content state changes for logging
|
|
201
|
+
setupContentStateSubscriptions()
|
|
93
202
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
) {
|
|
105
|
-
completionHandler(true)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
public func pictureInPictureControllerWillStartPictureInPicture(
|
|
109
|
-
_ pictureInPictureController: AVPictureInPictureController
|
|
110
|
-
) {
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
public func pictureInPictureControllerDidStartPictureInPicture(
|
|
114
|
-
_ pictureInPictureController: AVPictureInPictureController
|
|
115
|
-
) {
|
|
116
|
-
onPiPStateChange?(true)
|
|
203
|
+
|
|
204
|
+
// MARK: - Private Setup
|
|
205
|
+
|
|
206
|
+
/// Sets up subscriptions to the delegate proxy's event publisher.
|
|
207
|
+
private func setupDelegateProxySubscriptions() {
|
|
208
|
+
delegateProxy.publisher
|
|
209
|
+
.sink { [weak self] event in
|
|
210
|
+
self?.handleDelegateEvent(event)
|
|
211
|
+
}
|
|
212
|
+
.store(in: &cancellableBag)
|
|
117
213
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
214
|
+
|
|
215
|
+
/// Sets up subscriptions to content state changes for logging and debugging.
|
|
216
|
+
private func setupContentStateSubscriptions() {
|
|
217
|
+
contentState.contentPublisher
|
|
218
|
+
.removeDuplicates()
|
|
219
|
+
.sink { content in
|
|
220
|
+
PictureInPictureLogger.log("Content state changed to: \(content)")
|
|
221
|
+
}
|
|
222
|
+
.store(in: &cancellableBag)
|
|
124
223
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
224
|
+
|
|
225
|
+
/// Handles events from the delegate proxy.
|
|
226
|
+
private func handleDelegateEvent(_ event: PictureInPictureDelegateProxy.Event) {
|
|
227
|
+
switch event {
|
|
228
|
+
case .didStart:
|
|
229
|
+
onPiPStateChange?(true)
|
|
230
|
+
case .didStop:
|
|
231
|
+
onPiPStateChange?(false)
|
|
232
|
+
case let .failedToStart(_, error):
|
|
233
|
+
PictureInPictureLogger.log("failedToStartPictureInPictureWithError: \(error.localizedDescription)")
|
|
234
|
+
// Notify JS that PiP failed to start so it can update its state accordingly
|
|
235
|
+
onPiPStateChange?(false)
|
|
236
|
+
case let .restoreUI(_, completionHandler):
|
|
237
|
+
completionHandler(true)
|
|
238
|
+
case .willStart, .willStop:
|
|
239
|
+
// No action needed for will start/stop events
|
|
240
|
+
break
|
|
241
|
+
}
|
|
129
242
|
}
|
|
130
243
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
244
|
+
func setPreferredContentSize(_ size: CGSize) {
|
|
245
|
+
// Guard against setting zero size to avoid iOS PGPegasus code:-1003 error
|
|
246
|
+
guard size.width > 0, size.height > 0 else {
|
|
247
|
+
PictureInPictureLogger.log("Ignoring setPreferredContentSize with zero dimensions: \(size)")
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
contentViewController?.preferredContentSize = size
|
|
135
251
|
}
|
|
136
|
-
|
|
252
|
+
|
|
137
253
|
// MARK: - Private helpers
|
|
138
|
-
|
|
254
|
+
|
|
139
255
|
private func didUpdate(_ track: RTCVideoTrack?) {
|
|
140
|
-
contentViewController?.track = track
|
|
141
256
|
trackStateAdapter.activeTrack = track
|
|
257
|
+
syncContentStateIfNeeded()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private func syncContentStateIfNeeded() {
|
|
261
|
+
guard !isApplyingContentSnapshot else { return }
|
|
262
|
+
syncContentState()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private func syncContentState() {
|
|
266
|
+
let snapshot = PictureInPictureContentState.Snapshot(
|
|
267
|
+
track: track,
|
|
268
|
+
participantName: participantName,
|
|
269
|
+
participantImageURL: participantImageURL,
|
|
270
|
+
isVideoEnabled: isVideoEnabled,
|
|
271
|
+
isScreenSharing: isScreenSharing,
|
|
272
|
+
isReconnecting: isReconnecting
|
|
273
|
+
)
|
|
274
|
+
contentState.apply(snapshot)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/// Applies all content-driving fields as a single state transition.
|
|
278
|
+
func applyContentSnapshot(
|
|
279
|
+
track: RTCVideoTrack?,
|
|
280
|
+
participantName: String?,
|
|
281
|
+
participantImageURL: String?,
|
|
282
|
+
isVideoEnabled: Bool,
|
|
283
|
+
isScreenSharing: Bool,
|
|
284
|
+
isReconnecting: Bool
|
|
285
|
+
) {
|
|
286
|
+
isApplyingContentSnapshot = true
|
|
287
|
+
defer {
|
|
288
|
+
isApplyingContentSnapshot = false
|
|
289
|
+
syncContentState()
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
self.track = track
|
|
293
|
+
self.participantName = participantName
|
|
294
|
+
self.participantImageURL = participantImageURL
|
|
295
|
+
self.isVideoEnabled = isVideoEnabled
|
|
296
|
+
self.isScreenSharing = isScreenSharing
|
|
297
|
+
self.isReconnecting = isReconnecting
|
|
142
298
|
}
|
|
143
299
|
|
|
144
300
|
@objc private func didUpdate(_ sourceView: UIView?) {
|
|
@@ -149,7 +305,7 @@ import Foundation
|
|
|
149
305
|
|
|
150
306
|
pictureInPictureController?
|
|
151
307
|
.publisher(for: \.isPictureInPicturePossible)
|
|
152
|
-
.sink {
|
|
308
|
+
.sink { PictureInPictureLogger.log("isPictureInPicturePossible:\($0)") }
|
|
153
309
|
.store(in: &cancellableBag)
|
|
154
310
|
|
|
155
311
|
pictureInPictureController?
|
|
@@ -169,6 +325,19 @@ import Foundation
|
|
|
169
325
|
}
|
|
170
326
|
|
|
171
327
|
@objc func cleanup() {
|
|
328
|
+
// Cancel all Combine subscriptions
|
|
329
|
+
cancellableBag.removeAll()
|
|
330
|
+
|
|
331
|
+
// Reset the content state to inactive
|
|
332
|
+
contentState.reset()
|
|
333
|
+
|
|
334
|
+
// Disable the track state adapter to stop its timer
|
|
335
|
+
trackStateAdapter.isEnabled = false
|
|
336
|
+
trackStateAdapter.activeTrack = nil
|
|
337
|
+
|
|
338
|
+
// Release the enforced stop adapter
|
|
339
|
+
enforcedStopAdapter = nil
|
|
340
|
+
|
|
172
341
|
sourceView = nil
|
|
173
342
|
contentViewController?.track = nil
|
|
174
343
|
contentViewController = nil
|
|
@@ -177,7 +346,6 @@ import Foundation
|
|
|
177
346
|
}
|
|
178
347
|
pictureInPictureController?.delegate = nil
|
|
179
348
|
pictureInPictureController = nil
|
|
180
|
-
|
|
181
349
|
}
|
|
182
350
|
|
|
183
351
|
private func makePictureInPictureController(with sourceView: UIView) {
|
|
@@ -190,13 +358,19 @@ import Foundation
|
|
|
190
358
|
)
|
|
191
359
|
)
|
|
192
360
|
}
|
|
193
|
-
|
|
361
|
+
|
|
194
362
|
if #available(iOS 14.2, *) {
|
|
195
363
|
pictureInPictureController?
|
|
196
364
|
.canStartPictureInPictureAutomaticallyFromInline = canStartPictureInPictureAutomaticallyFromInline
|
|
197
365
|
}
|
|
198
|
-
|
|
199
|
-
|
|
366
|
+
|
|
367
|
+
// Use the delegate proxy for reactive event handling
|
|
368
|
+
pictureInPictureController?.delegate = delegateProxy
|
|
369
|
+
|
|
370
|
+
// Create the enforced stop adapter to handle app foreground transitions
|
|
371
|
+
if let pipController = pictureInPictureController {
|
|
372
|
+
enforcedStopAdapter = PictureInPictureEnforcedStopAdapter(pipController)
|
|
373
|
+
}
|
|
200
374
|
}
|
|
201
375
|
|
|
202
376
|
private func didUpdatePictureInPictureActiveState(_ isActive: Bool) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright © 2024 Stream.io Inc. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import AVKit
|
|
6
|
+
import Combine
|
|
7
|
+
import Foundation
|
|
8
|
+
|
|
9
|
+
/// Protocol defining the interface for Picture-in-Picture controller functionality.
|
|
10
|
+
///
|
|
11
|
+
/// This abstraction allows for easier testing and decouples components from the
|
|
12
|
+
/// concrete `AVPictureInPictureController` implementation.
|
|
13
|
+
protocol StreamPictureInPictureControllerProtocol: AnyObject {
|
|
14
|
+
/// Publisher that emits whenever the Picture-in-Picture active state changes.
|
|
15
|
+
/// Consumers should rely on this stream instead of synchronous snapshots so
|
|
16
|
+
/// lifecycle adapters can react to state transitions deterministically.
|
|
17
|
+
var isPictureInPictureActivePublisher: AnyPublisher<Bool, Never> { get }
|
|
18
|
+
|
|
19
|
+
/// Stops the Picture-in-Picture playback if it is currently active.
|
|
20
|
+
func stopPictureInPicture()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Extends `AVPictureInPictureController` to conform to `StreamPictureInPictureControllerProtocol`.
|
|
24
|
+
///
|
|
25
|
+
/// This extension provides a Combine publisher for observing the `isPictureInPictureActive` property.
|
|
26
|
+
extension AVPictureInPictureController: StreamPictureInPictureControllerProtocol {
|
|
27
|
+
var isPictureInPictureActivePublisher: AnyPublisher<Bool, Never> {
|
|
28
|
+
publisher(for: \.isPictureInPictureActive).eraseToAnyPublisher()
|
|
29
|
+
}
|
|
30
|
+
}
|