Skip to content

Commit a994138

Browse files
committed
[Enhancement]Incoming video pause
1 parent dcadd88 commit a994138

File tree

36 files changed

+704
-111
lines changed

36 files changed

+704
-111
lines changed

DemoApp/Sources/Components/AppEnvironment.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,20 @@ extension AppEnvironment {
620620
}()
621621
}
622622

623+
extension AppEnvironment {
624+
625+
static var clientCapabilities: Set<ClientCapability> = []
626+
}
627+
628+
extension ClientCapability: Debuggable {
629+
var title: String {
630+
switch self {
631+
case .subscriberVideoPause:
632+
"Subscriber video pause"
633+
}
634+
}
635+
}
636+
623637
extension String: Debuggable {
624638
var title: String {
625639
self

DemoApp/Sources/Views/CallView/CallingView/SimpleCallingView.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,15 @@ struct SimpleCallingView: View {
197197
try policies.forEach { try call.addProximityPolicy($0) }
198198
}
199199

200+
private func setClientCapabilities(for callId: String) async {
201+
let clientCapabilities = AppEnvironment.clientCapabilities
202+
guard !clientCapabilities.isEmpty else {
203+
return
204+
}
205+
let call = streamVideo.call(callType: callType, callId: callId)
206+
await call.updateClientCapabilities(clientCapabilities)
207+
}
208+
200209
private func parseURLIfRequired(_ text: String) {
201210
let adapter = DeeplinkAdapter()
202211
guard
@@ -234,6 +243,7 @@ struct SimpleCallingView: View {
234243
await setPreferredVideoCodec(for: text)
235244
try? await setAudioSessionPolicyOverride(for: text)
236245
try? setProximityPolicies(for: text)
246+
await setClientCapabilities(for: text)
237247
viewModel.enterLobby(
238248
callType: callType,
239249
callId: text,
@@ -243,11 +253,13 @@ struct SimpleCallingView: View {
243253
await setPreferredVideoCodec(for: text)
244254
try? await setAudioSessionPolicyOverride(for: text)
245255
try? setProximityPolicies(for: text)
256+
await setClientCapabilities(for: text)
246257
viewModel.joinCall(callType: callType, callId: text)
247258
case let .start(callId):
248259
await setPreferredVideoCodec(for: callId)
249260
try? await setAudioSessionPolicyOverride(for: callId)
250261
try? setProximityPolicies(for: callId)
262+
await setClientCapabilities(for: callId)
251263
viewModel.startCall(
252264
callType: callType,
253265
callId: callId,

DemoApp/Sources/Views/Login/DebugMenu.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ struct DebugMenu: View {
129129
didSet { AppEnvironment.proximityPolicies = proximityPolicies }
130130
}
131131

132+
@State private var clientCapabilities = AppEnvironment.clientCapabilities {
133+
didSet { AppEnvironment.clientCapabilities = clientCapabilities }
134+
}
135+
132136
var body: some View {
133137
Menu {
134138
makeMenu(
@@ -188,6 +192,18 @@ struct DebugMenu: View {
188192
label: "ClosedCaptions Integration"
189193
) { self.closedCaptionsIntegration = $0 }
190194

195+
makeMultipleSelectMenu(
196+
for: ClientCapability.allCases,
197+
currentValues: clientCapabilities,
198+
label: "Client Capabilities"
199+
) { item, isSelected in
200+
if isSelected {
201+
clientCapabilities = clientCapabilities.filter { item != $0 }
202+
} else {
203+
clientCapabilities.insert(item)
204+
}
205+
}
206+
191207
makeMenu(
192208
for: [.default, .ownCapabilities],
193209
currentValue: audioSessionPolicy,

DemoApp/Sources/Views/Reactions/ReactionOverlayView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ struct ReactionOverlayView_Previews: PreviewProvider {
5757
joinedAt: Date(),
5858
audioLevel: 0,
5959
audioLevels: [],
60-
pin: nil
60+
pin: nil,
61+
pausedTracks: []
6162
)
6263
)
6364
}

DemoApp/Sources/Views/Reactions/ReactionsViewModifier.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ struct ReactionsViewModifier_Previews: PreviewProvider {
5555
joinedAt: Date(),
5656
audioLevel: 0,
5757
audioLevels: [],
58-
pin: nil
58+
pin: nil,
59+
pausedTracks: []
5960
)
6061
)
6162
)

Sources/StreamVideo/Call.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,6 +1389,16 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
13891389
proximity.remove(policy)
13901390
}
13911391

1392+
// MARK: - ClientCapabilities
1393+
1394+
/// Updates the set of client capabilities for the call.
1395+
///
1396+
/// - Parameter clientCapabilities: A set of client capabilities that influence
1397+
/// subscription logic (e.g., support for paused tracks).
1398+
public func updateClientCapabilities(_ clientCapabilities: Set<ClientCapability>) async {
1399+
await callController.updateClientCapabilities(clientCapabilities)
1400+
}
1401+
13921402
// MARK: - Internal
13931403

13941404
internal func update(reconnectionStatus: ReconnectionStatus) {

Sources/StreamVideo/Controllers/CallController.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,10 @@ class CallController: @unchecked Sendable {
505505
try webRTCCoordinator.callKitActivated(audioSession)
506506
}
507507

508+
func updateClientCapabilities(_ capabilities: Set<ClientCapability>) async {
509+
await webRTCCoordinator.updateClientCapabilities(capabilities)
510+
}
511+
508512
// MARK: - private
509513

510514
private func handleParticipantsUpdated() {

Sources/StreamVideo/Models/CallParticipant.swift

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,18 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
4343
public var audioLevel: Float
4444
/// List of the last 10 audio levels.
4545
public var audioLevels: [Float]
46+
/// Pinning metadata used to keep this participant visible across layouts.
47+
///
48+
/// If set, the participant is considered pinned either locally or remotely.
49+
/// SDK integrators can use this to reflect UI state (e.g., always visible).
4650
public var pin: PinInfo?
4751

52+
/// The set of media track types currently paused for this participant.
53+
///
54+
/// This is used to control bandwidth or presentation. SDK integrators can
55+
/// rely on it to know when a participant's track has been paused remotely.
56+
public var pausedTracks: Set<TrackType>
57+
4858
/// The user's id. This is not necessarily unique, since a user can join from multiple devices.
4959
public var userId: String {
5060
user.id
@@ -81,7 +91,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
8191
joinedAt: Date,
8292
audioLevel: Float,
8393
audioLevels: [Float],
84-
pin: PinInfo?
94+
pin: PinInfo?,
95+
pausedTracks: Set<TrackType>
8596
) {
8697
user = User(
8798
id: userId,
@@ -106,6 +117,7 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
106117
self.audioLevel = audioLevel
107118
self.audioLevels = audioLevels
108119
self.pin = pin
120+
self.pausedTracks = pausedTracks
109121
}
110122

111123
public static func == (lhs: CallParticipant, rhs: CallParticipant) -> Bool {
@@ -127,7 +139,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
127139
lhs.audioLevels == rhs.audioLevels &&
128140
lhs.pin == rhs.pin &&
129141
lhs.track === rhs.track &&
130-
lhs.screenshareTrack === rhs.screenshareTrack
142+
lhs.screenshareTrack === rhs.screenshareTrack &&
143+
lhs.pausedTracks == rhs.pausedTracks
131144
}
132145

133146
public var isPinned: Bool {
@@ -141,7 +154,7 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
141154

142155
/// Determines whether the track of the participant should be displayed.
143156
public var shouldDisplayTrack: Bool {
144-
hasVideo && showTrack && track != nil
157+
hasVideo && showTrack && track != nil && pausedTracks.contains(.video) == false
145158
}
146159

147160
public func withUpdated(trackSize: CGSize) -> CallParticipant {
@@ -166,7 +179,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
166179
joinedAt: joinedAt,
167180
audioLevel: audioLevel,
168181
audioLevels: audioLevels,
169-
pin: pin
182+
pin: pin,
183+
pausedTracks: pausedTracks
170184
)
171185
}
172186

@@ -192,7 +206,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
192206
joinedAt: joinedAt,
193207
audioLevel: audioLevel,
194208
audioLevels: audioLevels,
195-
pin: pin
209+
pin: pin,
210+
pausedTracks: pausedTracks
196211
)
197212
}
198213

@@ -218,7 +233,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
218233
joinedAt: joinedAt,
219234
audioLevel: audioLevel,
220235
audioLevels: audioLevels,
221-
pin: pin
236+
pin: pin,
237+
pausedTracks: pausedTracks
222238
)
223239
}
224240

@@ -244,7 +260,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
244260
joinedAt: joinedAt,
245261
audioLevel: audioLevel,
246262
audioLevels: audioLevels,
247-
pin: pin
263+
pin: pin,
264+
pausedTracks: pausedTracks
248265
)
249266
}
250267

@@ -270,7 +287,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
270287
joinedAt: joinedAt,
271288
audioLevel: audioLevel,
272289
audioLevels: audioLevels,
273-
pin: pin
290+
pin: pin,
291+
pausedTracks: pausedTracks
274292
)
275293
}
276294

@@ -296,7 +314,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
296314
joinedAt: joinedAt,
297315
audioLevel: audioLevel,
298316
audioLevels: audioLevels,
299-
pin: pin
317+
pin: pin,
318+
pausedTracks: pausedTracks
300319
)
301320
}
302321

@@ -322,7 +341,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
322341
joinedAt: joinedAt,
323342
audioLevel: audioLevel,
324343
audioLevels: audioLevels,
325-
pin: pin
344+
pin: pin,
345+
pausedTracks: pausedTracks
326346
)
327347
}
328348

@@ -348,7 +368,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
348368
joinedAt: joinedAt,
349369
audioLevel: audioLevel,
350370
audioLevels: audioLevels,
351-
pin: pin
371+
pin: pin,
372+
pausedTracks: pausedTracks
352373
)
353374
}
354375

@@ -383,7 +404,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
383404
joinedAt: joinedAt,
384405
audioLevel: audioLevel,
385406
audioLevels: levels,
386-
pin: pin
407+
pin: pin,
408+
pausedTracks: pausedTracks
387409
)
388410
}
389411

@@ -409,7 +431,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
409431
joinedAt: joinedAt,
410432
audioLevel: audioLevel,
411433
audioLevels: audioLevels,
412-
pin: pin
434+
pin: pin,
435+
pausedTracks: pausedTracks
413436
)
414437
}
415438

@@ -435,7 +458,8 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
435458
joinedAt: joinedAt,
436459
audioLevel: audioLevel,
437460
audioLevels: audioLevels,
438-
pin: pin
461+
pin: pin,
462+
pausedTracks: pausedTracks
439463
)
440464
}
441465

@@ -461,7 +485,66 @@ public struct CallParticipant: Identifiable, Sendable, Hashable {
461485
joinedAt: joinedAt,
462486
audioLevel: audioLevel,
463487
audioLevels: audioLevels,
464-
pin: pin
488+
pin: pin,
489+
pausedTracks: pausedTracks
490+
)
491+
}
492+
493+
public func withPausedTrack(_ trackType: TrackType) -> CallParticipant {
494+
var updatedPausedTracks = pausedTracks
495+
updatedPausedTracks.insert(trackType)
496+
return CallParticipant(
497+
id: id,
498+
userId: userId,
499+
roles: roles,
500+
name: name,
501+
profileImageURL: profileImageURL,
502+
trackLookupPrefix: trackLookupPrefix,
503+
hasVideo: hasVideo,
504+
hasAudio: hasAudio,
505+
isScreenSharing: isScreensharing,
506+
showTrack: showTrack,
507+
track: track,
508+
trackSize: trackSize,
509+
screenshareTrack: screenshareTrack,
510+
isSpeaking: isSpeaking,
511+
isDominantSpeaker: isDominantSpeaker,
512+
sessionId: sessionId,
513+
connectionQuality: connectionQuality,
514+
joinedAt: joinedAt,
515+
audioLevel: audioLevel,
516+
audioLevels: audioLevels,
517+
pin: pin,
518+
pausedTracks: updatedPausedTracks
519+
)
520+
}
521+
522+
public func withUnpausedTrack(_ trackType: TrackType) -> CallParticipant {
523+
var updatedPausedTracks = pausedTracks
524+
updatedPausedTracks.remove(trackType)
525+
return CallParticipant(
526+
id: id,
527+
userId: userId,
528+
roles: roles,
529+
name: name,
530+
profileImageURL: profileImageURL,
531+
trackLookupPrefix: trackLookupPrefix,
532+
hasVideo: hasVideo,
533+
hasAudio: hasAudio,
534+
isScreenSharing: isScreensharing,
535+
showTrack: showTrack,
536+
track: track,
537+
trackSize: trackSize,
538+
screenshareTrack: screenshareTrack,
539+
isSpeaking: isSpeaking,
540+
isDominantSpeaker: isDominantSpeaker,
541+
sessionId: sessionId,
542+
connectionQuality: connectionQuality,
543+
joinedAt: joinedAt,
544+
audioLevel: audioLevel,
545+
audioLevels: audioLevels,
546+
pin: pin,
547+
pausedTracks: updatedPausedTracks
465548
)
466549
}
467550
}

0 commit comments

Comments
 (0)