Skip to content

Commit f78066b

Browse files
committed
[Enhancement]Picture-in-Picture improvements
1 parent 60bdee9 commit f78066b

20 files changed

+1177
-474
lines changed

Sources/StreamVideo/HTTPClient/InternetConnection.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ extension Notification {
2626
///
2727
/// Basically, it's a wrapper over legacy monitor based on `Reachability` (iOS 11 only)
2828
/// and default monitor based on `Network`.`NWPathMonitor` (iOS 12+).
29-
class InternetConnection: @unchecked Sendable {
29+
public final class InternetConnection: @unchecked Sendable {
3030
/// The current Internet connection status.
3131
@Published private(set) var status: InternetConnection.Status {
3232
didSet {
@@ -110,7 +110,7 @@ protocol InternetConnectionMonitor: AnyObject {
110110

111111
extension InternetConnection {
112112
/// The Internet connectivity status.
113-
enum Status: Equatable {
113+
public enum Status: Equatable {
114114
/// Notification of an Internet connection has not begun.
115115
case unknown
116116

@@ -122,7 +122,7 @@ extension InternetConnection {
122122
}
123123

124124
/// The Internet connectivity status quality.
125-
enum Quality: Equatable {
125+
public enum Quality: Equatable {
126126
/// The Internet connection is great (like Wi-Fi).
127127
case great
128128

@@ -138,7 +138,7 @@ extension InternetConnection {
138138

139139
extension InternetConnection.Status {
140140
/// Returns `true` if the internet connection is available, ignoring the quality of the connection.
141-
var isAvailable: Bool {
141+
public var isAvailable: Bool {
142142
if case .available = self {
143143
return true
144144
} else {
@@ -212,7 +212,7 @@ extension InternetConnection {
212212
}
213213

214214
/// A protocol defining the interface for internet connection monitoring.
215-
protocol InternetConnectionProtocol {
215+
public protocol InternetConnectionProtocol {
216216
/// A publisher that emits the current internet connection status.
217217
///
218218
/// This publisher never fails and continuously updates with the latest
@@ -227,7 +227,7 @@ extension InternetConnection: InternetConnectionProtocol {
227227
/// type to `AnyPublisher`.
228228
///
229229
/// - Note: The publisher won't publish any duplicates.
230-
var statusPublisher: AnyPublisher<InternetConnection.Status, Never> {
230+
public var statusPublisher: AnyPublisher<InternetConnection.Status, Never> {
231231
$status.removeDuplicates().eraseToAnyPublisher()
232232
}
233233
}
@@ -237,7 +237,7 @@ extension InternetConnection: InjectionKey {
237237
///
238238
/// This property provides a default implementation of the
239239
/// `InternetConnection` with a default monitor.
240-
nonisolated(unsafe) static var currentValue: InternetConnectionProtocol = InternetConnection(
240+
nonisolated(unsafe) public static var currentValue: InternetConnectionProtocol = InternetConnection(
241241
monitor: InternetConnection
242242
.Monitor()
243243
)
@@ -248,7 +248,7 @@ extension InjectedValues {
248248
///
249249
/// This property allows for dependency injection using the protocol type,
250250
/// providing more flexibility in testing and modular design.
251-
var internetConnectionObserver: InternetConnectionProtocol {
251+
public var internetConnectionObserver: InternetConnectionProtocol {
252252
get { Self[InternetConnection.self] }
253253
set { Self[InternetConnection.self] = newValue }
254254
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import StreamVideo
6+
import StreamWebRTC
7+
import SwiftUI
8+
9+
public final class AnyViewFactory: ViewFactory {
10+
11+
private let _makeCallControlsView: (CallViewModel) -> AnyView
12+
private let _makeOutgoingCallView: (CallViewModel) -> AnyView
13+
private let _makeJoiningCallView: (CallViewModel) -> AnyView
14+
private let _makeIncomingCallView: (CallViewModel, IncomingCall) -> AnyView
15+
private let _makeWaitingLocalUserView: (CallViewModel) -> AnyView
16+
private let _makeVideoParticipantsView: (
17+
CallViewModel,
18+
CGRect,
19+
@escaping @MainActor(CallParticipant, Bool) -> Void
20+
) -> AnyView
21+
private let _makeVideoParticipantView: (
22+
CallParticipant,
23+
String,
24+
CGRect,
25+
UIView.ContentMode,
26+
[String: RawJSON],
27+
Call?
28+
) -> AnyView
29+
private let _makeVideoCallParticipantModifier: (
30+
CallParticipant,
31+
Call?,
32+
CGRect,
33+
CGFloat,
34+
Bool
35+
) -> any ViewModifier
36+
private let _makeCallView: (CallViewModel) -> AnyView
37+
private let _makeMinimizedCallView: (CallViewModel) -> AnyView
38+
private let _makeCallTopView: (CallViewModel) -> AnyView
39+
private let _makeParticipantsListView: (CallViewModel) -> AnyView
40+
private let _makeScreenSharingView: (CallViewModel, ScreenSharingSession, CGRect) -> AnyView
41+
private let _makeLobbyView: (CallViewModel, LobbyInfo, Binding<CallSettings>) -> AnyView
42+
private let _makeReconnectionView: (CallViewModel) -> AnyView
43+
private let _makeLocalParticipantViewModifier: (
44+
CallParticipant,
45+
Binding<CallSettings>,
46+
Call?
47+
) -> any ViewModifier
48+
private let _makeUserAvatar: (User, UserAvatarViewOptions) -> AnyView
49+
50+
public init<T: ViewFactory>(_ factory: T) {
51+
_makeCallControlsView = { AnyView(factory.makeCallControlsView(viewModel: $0)) }
52+
_makeOutgoingCallView = { AnyView(factory.makeOutgoingCallView(viewModel: $0)) }
53+
_makeJoiningCallView = { AnyView(factory.makeJoiningCallView(viewModel: $0)) }
54+
_makeIncomingCallView = { AnyView(factory.makeIncomingCallView(viewModel: $0, callInfo: $1)) }
55+
_makeWaitingLocalUserView = { AnyView(factory.makeWaitingLocalUserView(viewModel: $0)) }
56+
_makeVideoParticipantsView = {
57+
AnyView(factory.makeVideoParticipantsView(viewModel: $0, availableFrame: $1, onChangeTrackVisibility: $2))
58+
}
59+
_makeVideoParticipantView = {
60+
AnyView(
61+
factory
62+
.makeVideoParticipantView(
63+
participant: $0,
64+
id: $1,
65+
availableFrame: $2,
66+
contentMode: $3,
67+
customData: $4,
68+
call: $5
69+
)
70+
)
71+
}
72+
_makeVideoCallParticipantModifier = {
73+
factory.makeVideoCallParticipantModifier(
74+
participant: $0,
75+
call: $1,
76+
availableFrame: $2,
77+
ratio: $3,
78+
showAllInfo: $4
79+
)
80+
}
81+
_makeCallView = { AnyView(factory.makeCallView(viewModel: $0)) }
82+
_makeMinimizedCallView = { AnyView(factory.makeMinimizedCallView(viewModel: $0)) }
83+
_makeCallTopView = { AnyView(factory.makeCallTopView(viewModel: $0)) }
84+
_makeParticipantsListView = { AnyView(factory.makeParticipantsListView(viewModel: $0)) }
85+
_makeScreenSharingView = {
86+
AnyView(factory.makeScreenSharingView(viewModel: $0, screensharingSession: $1, availableFrame: $2))
87+
}
88+
_makeLobbyView = { AnyView(factory.makeLobbyView(viewModel: $0, lobbyInfo: $1, callSettings: $2)) }
89+
_makeReconnectionView = { AnyView(factory.makeReconnectionView(viewModel: $0)) }
90+
_makeLocalParticipantViewModifier = {
91+
factory.makeLocalParticipantViewModifier(localParticipant: $0, callSettings: $1, call: $2)
92+
}
93+
_makeUserAvatar = { AnyView(factory.makeUserAvatar($0, with: $1)) }
94+
}
95+
96+
public func makeCallControlsView(viewModel: CallViewModel) -> some View {
97+
_makeCallControlsView(viewModel)
98+
}
99+
100+
public func makeOutgoingCallView(viewModel: CallViewModel) -> some View {
101+
_makeOutgoingCallView(viewModel)
102+
}
103+
104+
public func makeJoiningCallView(viewModel: CallViewModel) -> some View {
105+
_makeJoiningCallView(viewModel)
106+
}
107+
108+
public func makeIncomingCallView(viewModel: CallViewModel, callInfo: IncomingCall) -> some View {
109+
_makeIncomingCallView(viewModel, callInfo)
110+
}
111+
112+
public func makeWaitingLocalUserView(viewModel: CallViewModel) -> some View {
113+
_makeWaitingLocalUserView(viewModel)
114+
}
115+
116+
public func makeVideoParticipantsView(
117+
viewModel: CallViewModel,
118+
availableFrame: CGRect,
119+
onChangeTrackVisibility: @escaping @MainActor(CallParticipant, Bool) -> Void
120+
) -> some View {
121+
_makeVideoParticipantsView(viewModel, availableFrame, onChangeTrackVisibility)
122+
}
123+
124+
public func makeVideoParticipantView(
125+
participant: CallParticipant,
126+
id: String,
127+
availableFrame: CGRect,
128+
contentMode: UIView.ContentMode,
129+
customData: [String: RawJSON],
130+
call: Call?
131+
) -> some View {
132+
_makeVideoParticipantView(participant, id, availableFrame, contentMode, customData, call)
133+
}
134+
135+
public func makeVideoCallParticipantModifier(
136+
participant: CallParticipant,
137+
call: Call?,
138+
availableFrame: CGRect,
139+
ratio: CGFloat,
140+
showAllInfo: Bool
141+
) -> any ViewModifier {
142+
_makeVideoCallParticipantModifier(participant, call, availableFrame, ratio, showAllInfo)
143+
}
144+
145+
public func makeCallView(viewModel: CallViewModel) -> some View {
146+
_makeCallView(viewModel)
147+
}
148+
149+
public func makeMinimizedCallView(viewModel: CallViewModel) -> some View {
150+
_makeMinimizedCallView(viewModel)
151+
}
152+
153+
public func makeCallTopView(viewModel: CallViewModel) -> some View {
154+
_makeCallTopView(viewModel)
155+
}
156+
157+
public func makeParticipantsListView(viewModel: CallViewModel) -> some View {
158+
_makeParticipantsListView(viewModel)
159+
}
160+
161+
public func makeScreenSharingView(
162+
viewModel: CallViewModel,
163+
screensharingSession: ScreenSharingSession,
164+
availableFrame: CGRect
165+
) -> some View {
166+
_makeScreenSharingView(viewModel, screensharingSession, availableFrame)
167+
}
168+
169+
public func makeLobbyView(
170+
viewModel: CallViewModel,
171+
lobbyInfo: LobbyInfo,
172+
callSettings: Binding<CallSettings>
173+
) -> some View {
174+
_makeLobbyView(viewModel, lobbyInfo, callSettings)
175+
}
176+
177+
public func makeReconnectionView(viewModel: CallViewModel) -> some View {
178+
_makeReconnectionView(viewModel)
179+
}
180+
181+
public func makeLocalParticipantViewModifier(
182+
localParticipant: CallParticipant,
183+
callSettings: Binding<CallSettings>,
184+
call: Call?
185+
) -> any ViewModifier {
186+
_makeLocalParticipantViewModifier(localParticipant, callSettings, call)
187+
}
188+
189+
public func makeUserAvatar(
190+
_ user: User,
191+
with options: UserAvatarViewOptions
192+
) -> some View {
193+
_makeUserAvatar(user, options)
194+
}
195+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import StreamVideo
6+
import SwiftUI
7+
8+
private struct PictureInPictureParticipantModifier: ViewModifier {
9+
10+
var participant: CallParticipant
11+
var call: Call?
12+
var showAllInfo: Bool
13+
var decorations: Set<VideoCallParticipantDecoration>
14+
15+
init(
16+
participant: CallParticipant,
17+
call: Call?,
18+
showAllInfo: Bool,
19+
decorations: [VideoCallParticipantDecoration]
20+
) {
21+
self.participant = participant
22+
self.call = call
23+
self.showAllInfo = showAllInfo
24+
self.decorations = .init(decorations)
25+
}
26+
27+
func body(content: Content) -> some View {
28+
content
29+
.overlay(
30+
BottomView(content: {
31+
HStack {
32+
ParticipantInfoView(
33+
participant: participant,
34+
isPinned: participant.isPinned
35+
)
36+
37+
Spacer()
38+
39+
if showAllInfo {
40+
ConnectionQualityIndicator(
41+
connectionQuality: participant.connectionQuality
42+
)
43+
}
44+
}
45+
})
46+
)
47+
.applyDecorationModifierIfRequired(
48+
VideoCallParticipantSpeakingModifier(participant: participant, participantCount: participantCount),
49+
decoration: .speaking,
50+
availableDecorations: decorations
51+
)
52+
}
53+
54+
private var participantCount: Int {
55+
call?.state.participants.count ?? 0
56+
}
57+
}
58+
59+
extension View {
60+
61+
@ViewBuilder
62+
func pictureInPictureParticipant(
63+
participant: CallParticipant,
64+
call: Call?,
65+
showAllInfo: Bool = true
66+
) -> some View {
67+
modifier(
68+
PictureInPictureParticipantModifier(
69+
participant: participant,
70+
call: call,
71+
showAllInfo: showAllInfo,
72+
decorations: [VideoCallParticipantDecoration.speaking]
73+
)
74+
)
75+
}
76+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import StreamVideo
6+
import SwiftUI
7+
8+
struct PictureInPictureReconnectionView: View {
9+
10+
@Injected(\.colors) private var colors
11+
12+
var body: some View {
13+
VStack {
14+
Text(L10n.Call.Current.reconnecting)
15+
.applyCallingStyle()
16+
.padding()
17+
.accessibility(identifier: "reconnectingMessage")
18+
CallingIndicator()
19+
}
20+
.padding()
21+
.background(
22+
Color(colors.callBackground).opacity(0.7).edgesIgnoringSafeArea(.all)
23+
)
24+
.cornerRadius(16)
25+
}
26+
}

0 commit comments

Comments
 (0)