Skip to content

Commit bb694ae

Browse files
authored
[Enhancement]Implement static frame pipeline for PiP (#724)
1 parent e5bd154 commit bb694ae

File tree

72 files changed

+2371
-939
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+2371
-939
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010
- You can now access the `custom-data` attached on a Call object you received as incoming. [#766](https://github.com/GetStream/stream-video-swift/pull/766)
1111

1212
### 🔄 Changed
13+
- `CallViewModel.callingState` transition to `.idle` just before moving to `.inCall` after the user has accepted the call. [#759](https://github.com/GetStream/stream-video-swift/pull/759)
14+
- `AudioSession` mode wasn't configured correctly for audio-only calls. [#762](https://github.com/GetStream/stream-video-swift/pull/762)
1315
- Updated WebRTC version to 125.6422.070 [#760](https://github.com/GetStream/stream-video-swift/pull/760)
16+
- Picture-in-Picture improved UI and stability fixes. [#724](https://github.com/GetStream/stream-video-swift/pull/724)
1417

1518
### 🐞 Fixed
1619
- Sound resources weren't loaded correctly when the SDK was linked via SPM. [#757](https://github.com/GetStream/stream-video-swift/pull/757)
1720
- Redefined the priorities by which dashboard audio settings will be applied. [#758](https://github.com/GetStream/stream-video-swift/pull/758)
18-
- `CallViewModel.callingState` transition to `.idle` just before moving to `.inCall` after the user has accepted the call. [#759](https://github.com/GetStream/stream-video-swift/pull/759)
19-
- `AudioSession` mode wasn't configured correctly for audio-only calls. [#762](https://github.com/GetStream/stream-video-swift/pull/762)
2021

2122
# [1.20.0](https://github.com/GetStream/stream-video-swift/releases/tag/1.20.0)
2223
_April 07, 2025_

Sources/StreamVideo/Controllers/CallController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ class CallController: @unchecked Sendable {
479479
private func handleParticipantsUpdated() {
480480
webRTCParticipantsObserver = participants?
481481
.$value
482+
.removeDuplicates() // Avoid unnecessary updates when participants haven't changed.
482483
.sinkTask { @MainActor [weak self] participants in
483484
self?.call?.state.participantsMap = participants
484485
}

Sources/StreamVideo/HTTPClient/InternetConnection.swift

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ extension Notification.Name {
1717
extension Notification {
1818
static let internetConnectionStatusUserInfoKey = "internetConnectionStatus"
1919

20-
var internetConnectionStatus: InternetConnection.Status? {
21-
userInfo?[Self.internetConnectionStatusUserInfoKey] as? InternetConnection.Status
20+
var internetConnectionStatus: InternetConnectionStatus? {
21+
userInfo?[Self.internetConnectionStatusUserInfoKey] as? InternetConnectionStatus
2222
}
2323
}
2424

2525
/// An Internet Connection monitor.
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+
final class InternetConnection: @unchecked Sendable {
3030
/// The current Internet connection status.
31-
@Published private(set) var status: InternetConnection.Status {
31+
@Published private(set) var status: InternetConnectionStatus {
3232
didSet {
3333
guard oldValue != status else { return }
3434

@@ -68,13 +68,13 @@ class InternetConnection: @unchecked Sendable {
6868
}
6969

7070
extension InternetConnection: InternetConnectionDelegate {
71-
func internetConnectionStatusDidChange(status: Status) {
71+
func internetConnectionStatusDidChange(status: InternetConnectionStatus) {
7272
self.status = status
7373
}
7474
}
7575

7676
private extension InternetConnection {
77-
func postNotification(_ name: Notification.Name, with status: Status) {
77+
func postNotification(_ name: Notification.Name, with status: InternetConnectionStatus) {
7878
notificationCenter.post(
7979
name: name,
8080
object: self,
@@ -89,7 +89,7 @@ private extension InternetConnection {
8989
protocol InternetConnectionDelegate: AnyObject {
9090
/// Calls when the Internet connection status did change.
9191
/// - Parameter status: an Internet connection status.
92-
func internetConnectionStatusDidChange(status: InternetConnection.Status)
92+
func internetConnectionStatusDidChange(status: InternetConnectionStatus)
9393
}
9494

9595
/// A protocol for Internet connection monitors.
@@ -98,7 +98,7 @@ protocol InternetConnectionMonitor: AnyObject {
9898
var delegate: InternetConnectionDelegate? { get set }
9999

100100
/// The current status of Internet connection.
101-
var status: InternetConnection.Status { get }
101+
var status: InternetConnectionStatus { get }
102102

103103
/// Start Internet connection monitoring.
104104
func start()
@@ -108,37 +108,35 @@ protocol InternetConnectionMonitor: AnyObject {
108108

109109
// MARK: Internet Connection Subtypes
110110

111-
extension InternetConnection {
112-
/// The Internet connectivity status.
113-
enum Status: Equatable {
114-
/// Notification of an Internet connection has not begun.
115-
case unknown
111+
/// The Internet connectivity status.
112+
public enum InternetConnectionStatus: Equatable, Sendable {
113+
/// Notification of an Internet connection has not begun.
114+
case unknown
116115

117-
/// The Internet is available with a specific `Quality` level.
118-
case available(Quality)
116+
/// The Internet is available with a specific `Quality` level.
117+
case available(InternetConnectionQuality)
119118

120-
/// The Internet is unavailable.
121-
case unavailable
122-
}
119+
/// The Internet is unavailable.
120+
case unavailable
121+
}
123122

124-
/// The Internet connectivity status quality.
125-
enum Quality: Equatable {
126-
/// The Internet connection is great (like Wi-Fi).
127-
case great
123+
/// The Internet connectivity status quality.
124+
public enum InternetConnectionQuality: Equatable, Sendable {
125+
/// The Internet connection is great (like Wi-Fi).
126+
case great
128127

129-
/// Internet connection uses an interface that is considered expensive, such as Cellular or a Personal Hotspot.
130-
case expensive
128+
/// Internet connection uses an interface that is considered expensive, such as Cellular or a Personal Hotspot.
129+
case expensive
131130

132-
/// Internet connection uses Low Data Mode.
133-
/// Recommendations for Low Data Mode: don't autoplay video, music (high-quality) or gifs (big files).
134-
/// Supports only by iOS 13+
135-
case constrained
136-
}
131+
/// Internet connection uses Low Data Mode.
132+
/// Recommendations for Low Data Mode: don't autoplay video, music (high-quality) or gifs (big files).
133+
/// Supports only by iOS 13+
134+
case constrained
137135
}
138136

139-
extension InternetConnection.Status {
137+
extension InternetConnectionStatus {
140138
/// Returns `true` if the internet connection is available, ignoring the quality of the connection.
141-
var isAvailable: Bool {
139+
public var isAvailable: Bool {
142140
if case .available = self {
143141
return true
144142
} else {
@@ -158,7 +156,7 @@ extension InternetConnection {
158156

159157
weak var delegate: InternetConnectionDelegate?
160158

161-
var status: InternetConnection.Status {
159+
var status: InternetConnectionStatus {
162160
if let path = monitor?.currentPath {
163161
return status(from: path)
164162
}
@@ -194,12 +192,12 @@ extension InternetConnection {
194192
delegate?.internetConnectionStatusDidChange(status: status(from: path))
195193
}
196194

197-
private func status(from path: NWPath) -> InternetConnection.Status {
195+
private func status(from path: NWPath) -> InternetConnectionStatus {
198196
guard path.status == .satisfied else {
199197
return .unavailable
200198
}
201199

202-
let quality: InternetConnection.Quality
200+
let quality: InternetConnectionQuality
203201
quality = path.isConstrained ? .constrained : (path.isExpensive ? .expensive : .great)
204202

205203
return .available(quality)
@@ -212,12 +210,12 @@ extension InternetConnection {
212210
}
213211

214212
/// A protocol defining the interface for internet connection monitoring.
215-
protocol InternetConnectionProtocol {
213+
public protocol InternetConnectionProtocol {
216214
/// A publisher that emits the current internet connection status.
217215
///
218216
/// This publisher never fails and continuously updates with the latest
219217
/// connection status.
220-
var statusPublisher: AnyPublisher<InternetConnection.Status, Never> { get }
218+
var statusPublisher: AnyPublisher<InternetConnectionStatus, Never> { get }
221219
}
222220

223221
extension InternetConnection: InternetConnectionProtocol {
@@ -227,7 +225,7 @@ extension InternetConnection: InternetConnectionProtocol {
227225
/// type to `AnyPublisher`.
228226
///
229227
/// - Note: The publisher won't publish any duplicates.
230-
var statusPublisher: AnyPublisher<InternetConnection.Status, Never> {
228+
public var statusPublisher: AnyPublisher<InternetConnectionStatus, Never> {
231229
$status.removeDuplicates().eraseToAnyPublisher()
232230
}
233231
}
@@ -237,9 +235,8 @@ extension InternetConnection: InjectionKey {
237235
///
238236
/// This property provides a default implementation of the
239237
/// `InternetConnection` with a default monitor.
240-
nonisolated(unsafe) static var currentValue: InternetConnectionProtocol = InternetConnection(
241-
monitor: InternetConnection
242-
.Monitor()
238+
nonisolated(unsafe) public static var currentValue: InternetConnectionProtocol = InternetConnection(
239+
monitor: InternetConnection.Monitor()
243240
)
244241
}
245242

@@ -248,7 +245,7 @@ extension InjectedValues {
248245
///
249246
/// This property allows for dependency injection using the protocol type,
250247
/// providing more flexibility in testing and modular design.
251-
var internetConnectionObserver: InternetConnectionProtocol {
248+
public var internetConnectionObserver: InternetConnectionProtocol {
252249
get { Self[InternetConnection.self] }
253250
set { Self[InternetConnection.self] = newValue }
254251
}

Sources/StreamVideo/Utils/Swift6Migration/Sendable+Extensions.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ extension RTCStatisticsReport: @retroactive @unchecked Sendable {}
2727
extension WritableKeyPath: @retroactive @unchecked Sendable {}
2828
extension Published.Publisher: @retroactive @unchecked Sendable {}
2929
extension RTCVideoFrame: @retroactive @unchecked Sendable {}
30+
extension AnyPublisher: @retroactive @unchecked Sendable {}
31+
extension Publishers.Filter: @retroactive @unchecked Sendable {}
3032
#else
3133
extension AnyCancellable: @unchecked Sendable {}
3234
extension AVCaptureDevice: @unchecked Sendable {}
@@ -46,4 +48,6 @@ extension RTCStatisticsReport: @unchecked Sendable {}
4648
extension WritableKeyPath: @unchecked Sendable {}
4749
extension Published.Publisher: @unchecked Sendable {}
4850
extension RTCVideoFrame: @unchecked Sendable {}
51+
extension AnyPublisher: @unchecked Sendable {}
52+
extension Publishers.Filter: @unchecked Sendable {}
4953
#endif

Sources/StreamVideoSwiftUI/Utils/PictureInPicture/StreamBufferTransformer.swift renamed to Sources/StreamVideoSwiftUI/Utils/PictureInPicture/PictureInPictureBufferTransformer.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import Foundation
66
import StreamVideo
77
import StreamWebRTC
88

9-
/// `StreamBufferTransformer` is a struct that provides methods for transforming RTCI420Buffer to
9+
/// `PictureInPictureBufferTransformer` is a struct that provides methods for transforming RTCI420Buffer to
1010
/// CVPixelBuffer, while performing downsampling when necessary.
11-
struct StreamBufferTransformer {
11+
struct PictureInPictureBufferTransformer {
1212

1313
var requiresResize = false
1414

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import StreamVideo
6+
import StreamWebRTC
7+
8+
/// Content state for Picture-in-Picture window during video calls.
9+
enum PictureInPictureContent: Equatable, CustomStringConvertible {
10+
/// Picture-in-Picture window is not active.
11+
case inactive
12+
13+
/// Shows a participant's video feed in Picture-in-Picture.
14+
///
15+
/// - Parameters:
16+
/// - call: Current call instance
17+
/// - participant: Participant being displayed
18+
/// - track: Video track to render
19+
case participant(Call?, CallParticipant, RTCVideoTrack?)
20+
21+
/// Shows a participant's screen share in Picture-in-Picture.
22+
///
23+
/// - Parameters:
24+
/// - call: Current call instance
25+
/// - participant: Participant sharing screen
26+
/// - track: Screen sharing track
27+
case screenSharing(Call?, CallParticipant, RTCVideoTrack)
28+
29+
/// Picture-in-Picture window is reconnecting to the call.
30+
case reconnecting
31+
32+
var description: String {
33+
switch self {
34+
case .inactive:
35+
return ".inactive"
36+
case let .participant(call, participant, track):
37+
return ".participant(cId:\(call?.cId ?? "-"), name:\(participant.name), track:\(track?.trackId ?? "-"))"
38+
case let .screenSharing(call, participant, track):
39+
return ".screenSharing(cId:\(call?.cId ?? "-"), name:\(participant.name), track:\(track.trackId))"
40+
case .reconnecting:
41+
return ".reconnecting"
42+
}
43+
}
44+
45+
static func == (
46+
lhs: PictureInPictureContent,
47+
rhs: PictureInPictureContent
48+
) -> Bool {
49+
switch (lhs, rhs) {
50+
case (.inactive, .inactive):
51+
return true
52+
53+
case (let .participant(lhsCall, lhsParticipant, lhsTrack), let .participant(rhsCall, rhsParticipant, rhsTrack)):
54+
return lhsCall?.cId == rhsCall?.cId
55+
&& lhsParticipant == rhsParticipant
56+
&& lhsTrack == rhsTrack
57+
58+
case (let .screenSharing(lhsCall, lhsParticipant, lhsTrack), let .screenSharing(rhsCall, rhsParticipant, rhsTrack)):
59+
return lhsCall?.cId == rhsCall?.cId
60+
&& lhsParticipant == rhsParticipant
61+
&& lhsTrack == rhsTrack
62+
63+
case (.reconnecting, .reconnecting):
64+
return true
65+
66+
default:
67+
return false
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)