diff --git a/.gitignore b/.gitignore
index 8bd3c2a36..f8e6f6f2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -124,3 +124,7 @@ Credentials.xcconfig
.vscode/settings.json
.vscode/launch.json
buildServer.json
+
+# xcode-build-server files
+buildServer.json
+.compile
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 962a32bf9..9f659bbe1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,14 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### 🔄 Changed
+# [1.36.0](https://github.com/GetStream/stream-video-swift/releases/tag/1.36.0)
+_November 19, 2025_
+
+### ✅ Added
+- Add support for ringing individual members. [#995](https://github.com/GetStream/stream-video-swift/pull/995)
+
+### 🐞 Fixed
+- Ensure SFU track and participant updates create missing participants. [#996](https://github.com/GetStream/stream-video-swift/pull/996)
+
# [1.35.0](https://github.com/GetStream/stream-video-swift/releases/tag/1.35.0)
_November 05, 2025_
### ✅ Added
- SwiftUI modifiers that surface moderation blur and warning events in `CallView`. [#987](https://github.com/GetStream/stream-video-swift/pull/987)
-### 🔄 Changed
-
# [1.34.2](https://github.com/GetStream/stream-video-swift/releases/tag/1.34.2)
_October 24, 2025_
diff --git a/DemoApp/Sources/Views/CallView/CallingView/DetailedCallingView.swift b/DemoApp/Sources/Views/CallView/CallingView/DetailedCallingView.swift
index b333f33d3..2d5c191f5 100644
--- a/DemoApp/Sources/Views/CallView/CallingView/DetailedCallingView.swift
+++ b/DemoApp/Sources/Views/CallView/CallingView/DetailedCallingView.swift
@@ -14,9 +14,10 @@ struct DetailedCallingView: View {
}
enum CallFlow: String, Equatable, CaseIterable {
- case joinImmediately = "Join immediately"
+ case joinImmediately = "Join now"
case ringEvents = "Ring events"
case lobby = "Lobby"
+ case joinAndRing = "Join and ring"
}
@Injected(\.streamVideo) var streamVideo
@@ -159,6 +160,13 @@ struct DetailedCallingView: View {
callId: text,
members: members
)
+ } else if callFlow == .joinAndRing {
+ viewModel.joinAndRingCall(
+ callType: callType,
+ callId: text,
+ members: members,
+ video: viewModel.callSettings.videoOn
+ )
} else {
viewModel.startCall(
callType: callType,
diff --git a/Gemfile.lock b/Gemfile.lock
index 0f12880b9..d7434a2ab 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -214,8 +214,8 @@ GEM
fastlane-plugin-stream_actions (0.3.101)
xctest_list (= 1.2.1)
fastlane-plugin-versioning (0.7.1)
- fastlane-plugin-xcsize (1.1.0)
- xcsize (= 1.1.0)
+ fastlane-plugin-xcsize (1.2.0)
+ xcsize (= 1.2.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
ffi (1.17.2)
@@ -418,7 +418,7 @@ GEM
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
- xcsize (1.1.0)
+ xcsize (1.2.0)
commander (>= 4.6, < 6.0)
xctest_list (1.2.1)
@@ -435,7 +435,7 @@ DEPENDENCIES
fastlane-plugin-lizard
fastlane-plugin-stream_actions (= 0.3.101)
fastlane-plugin-versioning
- fastlane-plugin-xcsize (= 1.1.0)
+ fastlane-plugin-xcsize (= 1.2.0)
json
lefthook
plist
diff --git a/README.md b/README.md
index fbdd71711..88b05e19b 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-
+
diff --git a/Sources/StreamVideo/Call.swift b/Sources/StreamVideo/Call.swift
index a2763ea43..74777880c 100644
--- a/Sources/StreamVideo/Call.swift
+++ b/Sources/StreamVideo/Call.swift
@@ -362,6 +362,20 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
}
return response.call
}
+
+ /// Initiates a ring action for the current call.
+ /// - Parameter request: The `RingCallRequest` containing ring configuration, such as member ids and whether it's a video call.
+ /// - Returns: A `RingCallResponse` with information about the ring operation.
+ /// - Throws: An error if the coordinator request fails or the call cannot be rung.
+ @discardableResult
+ public func ring(request: RingCallRequest) async throws -> RingCallResponse {
+ let response = try await coordinatorClient.ringCall(
+ type: callType,
+ id: callId,
+ ringCallRequest: request
+ )
+ return response
+ }
/// Updates an existing call with the specified parameters.
/// - Parameters:
diff --git a/Sources/StreamVideo/CallStateMachine/Stages/Call+RejectingStage.swift b/Sources/StreamVideo/CallStateMachine/Stages/Call+RejectingStage.swift
index c5b3aebc9..c52c908cd 100644
--- a/Sources/StreamVideo/CallStateMachine/Stages/Call+RejectingStage.swift
+++ b/Sources/StreamVideo/CallStateMachine/Stages/Call+RejectingStage.swift
@@ -53,7 +53,7 @@ extension Call.StateMachine.Stage {
from previousStage: Call.StateMachine.Stage
) -> Self? {
switch previousStage.id {
- case .idle:
+ case .idle, .joined:
execute()
return self
default:
diff --git a/Sources/StreamVideo/Generated/SystemEnvironment+Version.swift b/Sources/StreamVideo/Generated/SystemEnvironment+Version.swift
index 69e2f37c3..ea1845004 100644
--- a/Sources/StreamVideo/Generated/SystemEnvironment+Version.swift
+++ b/Sources/StreamVideo/Generated/SystemEnvironment+Version.swift
@@ -7,7 +7,7 @@ import Foundation
extension SystemEnvironment {
/// A Stream Video version.
- public static let version: String = "1.35.0"
+ public static let version: String = "1.36.0"
/// The WebRTC version.
public static let webRTCVersion: String = "137.0.43"
}
diff --git a/Sources/StreamVideo/Info.plist b/Sources/StreamVideo/Info.plist
index 81683f3c5..5c985b4ce 100644
--- a/Sources/StreamVideo/Info.plist
+++ b/Sources/StreamVideo/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.35.0
+ 1.36.0
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSHumanReadableCopyright
diff --git a/Sources/StreamVideo/OpenApi/generated/APIs/DefaultAPI.swift b/Sources/StreamVideo/OpenApi/generated/APIs/DefaultAPI.swift
index 93ff09541..a6ba2a62a 100644
--- a/Sources/StreamVideo/OpenApi/generated/APIs/DefaultAPI.swift
+++ b/Sources/StreamVideo/OpenApi/generated/APIs/DefaultAPI.swift
@@ -1170,6 +1170,26 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable {
try self.jsonDecoder.decode(EmptyResponse.self, from: $0)
}
}
+
+ open func ringCall(type: String, id: String, ringCallRequest: RingCallRequest) async throws -> RingCallResponse {
+ var path = "/video/call/{type}/{id}/ring"
+
+ let typePreEscape = "\(APIHelper.mapValueToPathItem(type))"
+ let typePostEscape = typePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
+ path = path.replacingOccurrences(of: String(format: "{%@}", "type"), with: typePostEscape, options: .literal, range: nil)
+ let idPreEscape = "\(APIHelper.mapValueToPathItem(id))"
+ let idPostEscape = idPreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
+ path = path.replacingOccurrences(of: String(format: "{%@}", "id"), with: idPostEscape, options: .literal, range: nil)
+
+ let urlRequest = try makeRequest(
+ uriPath: path,
+ httpMethod: "POST",
+ request: ringCallRequest
+ )
+ return try await send(request: urlRequest) {
+ try self.jsonDecoder.decode(RingCallResponse.self, from: $0)
+ }
+ }
}
protocol DefaultAPIEndpoints {
@@ -1286,4 +1306,6 @@ protocol DefaultAPIEndpoints {
func createGuest(createGuestRequest: CreateGuestRequest) async throws -> CreateGuestResponse
func videoConnect() async throws -> Void
+
+ func ringCall(type: String, id: String, ringCallRequest: RingCallRequest) async throws -> RingCallResponse
}
diff --git a/Sources/StreamVideo/OpenApi/generated/Models/RingCallRequest.swift b/Sources/StreamVideo/OpenApi/generated/Models/RingCallRequest.swift
new file mode 100644
index 000000000..11c1c19f0
--- /dev/null
+++ b/Sources/StreamVideo/OpenApi/generated/Models/RingCallRequest.swift
@@ -0,0 +1,30 @@
+//
+// Copyright © 2025 Stream.io Inc. All rights reserved.
+//
+
+import Foundation
+
+public final class RingCallRequest: @unchecked Sendable, Codable, JSONEncodable, Hashable {
+ public var membersIds: [String]?
+ public var video: Bool?
+
+ public init(membersIds: [String]? = nil, video: Bool? = nil) {
+ self.membersIds = membersIds
+ self.video = video
+ }
+
+public enum CodingKeys: String, CodingKey, CaseIterable {
+ case membersIds = "members_ids"
+ case video
+}
+
+ public static func == (lhs: RingCallRequest, rhs: RingCallRequest) -> Bool {
+ lhs.membersIds == rhs.membersIds &&
+ lhs.video == rhs.video
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(membersIds)
+ hasher.combine(video)
+ }
+}
diff --git a/Sources/StreamVideo/OpenApi/generated/Models/RingCallResponse.swift b/Sources/StreamVideo/OpenApi/generated/Models/RingCallResponse.swift
new file mode 100644
index 000000000..42dd635e1
--- /dev/null
+++ b/Sources/StreamVideo/OpenApi/generated/Models/RingCallResponse.swift
@@ -0,0 +1,30 @@
+//
+// Copyright © 2025 Stream.io Inc. All rights reserved.
+//
+
+import Foundation
+
+public final class RingCallResponse: @unchecked Sendable, Codable, JSONEncodable, Hashable {
+ public var duration: String
+ public var membersIds: [String]
+
+ public init(duration: String, membersIds: [String]) {
+ self.duration = duration
+ self.membersIds = membersIds
+ }
+
+public enum CodingKeys: String, CodingKey, CaseIterable {
+ case duration
+ case membersIds = "members_ids"
+}
+
+ public static func == (lhs: RingCallResponse, rhs: RingCallResponse) -> Bool {
+ lhs.duration == rhs.duration &&
+ lhs.membersIds == rhs.membersIds
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(duration)
+ hasher.combine(membersIds)
+ }
+}
diff --git a/Sources/StreamVideo/WebRTC/v2/SFU/SFUEventAdapter.swift b/Sources/StreamVideo/WebRTC/v2/SFU/SFUEventAdapter.swift
index c211d23f6..4b141083f 100644
--- a/Sources/StreamVideo/WebRTC/v2/SFU/SFUEventAdapter.swift
+++ b/Sources/StreamVideo/WebRTC/v2/SFU/SFUEventAdapter.swift
@@ -330,9 +330,7 @@ final class SFUEventAdapter: @unchecked Sendable {
await stateAdapter.enqueue { participants in
var updatedParticipants = participants
- guard let participant = updatedParticipants[sessionID] else {
- return participants
- }
+ let participant = updatedParticipants[sessionID] ?? event.participant.toCallParticipant()
switch event.type {
case .audio:
@@ -390,9 +388,7 @@ final class SFUEventAdapter: @unchecked Sendable {
await stateAdapter.enqueue { participants in
var updatedParticipants = participants
- guard let participant = updatedParticipants[sessionID] else {
- return participants
- }
+ let participant = updatedParticipants[sessionID] ?? event.participant.toCallParticipant()
switch event.type {
case .audio:
@@ -484,19 +480,20 @@ final class SFUEventAdapter: @unchecked Sendable {
await stateAdapter.enqueue { participants in
var updatedParticipants = participants
- guard
- let participant = updatedParticipants[event.participant.sessionID]
- else {
- return participants
+ if let participant = updatedParticipants[event.participant.sessionID] {
+ updatedParticipants[event.participant.sessionID] = event
+ .participant
+ .toCallParticipant()
+ .withUpdated(showTrack: participant.showTrack)
+ .withUpdated(pin: participant.pin)
+ .withUpdated(track: participant.track)
+ .withUpdated(screensharingTrack: participant.screenshareTrack)
+ } else {
+ updatedParticipants[event.participant.sessionID] = event
+ .participant
+ .toCallParticipant()
}
- updatedParticipants[event.participant.sessionID] = event
- .participant
- .toCallParticipant()
- .withUpdated(showTrack: participant.showTrack)
- .withUpdated(pin: participant.pin)
- .withUpdated(track: participant.track)
- .withUpdated(screensharingTrack: participant.screenshareTrack)
return updatedParticipants
}
}
diff --git a/Sources/StreamVideoSwiftUI/CallViewModel.swift b/Sources/StreamVideoSwiftUI/CallViewModel.swift
index 03da5f698..c72384d0c 100644
--- a/Sources/StreamVideoSwiftUI/CallViewModel.swift
+++ b/Sources/StreamVideoSwiftUI/CallViewModel.swift
@@ -84,7 +84,24 @@ open class CallViewModel: ObservableObject {
/// Tracks the current state of a call. It should be used to show different UI in your views.
@Published public var callingState: CallingState = .idle {
- didSet { handleRingingEvents() }
+ didSet {
+ // When we join a call and then ring, we need to disable the speaker.
+ // If the dashboard settings have the speaker on, we need to enable it
+ // again when we transition into a call.
+ if let temporaryCallSettings, oldValue == .outgoing && callingState == .inCall {
+ if temporaryCallSettings.speakerOn {
+ Task {
+ do {
+ try await call?.speaker.enableSpeakerPhone()
+ } catch {
+ log.error("Error enabling the speaker: \(error.localizedDescription)")
+ }
+ }
+ }
+ self.temporaryCallSettings = nil
+ }
+ handleRingingEvents()
+ }
}
/// Optional, has a value if there was an error. You can use it to display more detailed error messages to the users.
@@ -193,6 +210,8 @@ open class CallViewModel: ObservableObject {
private(set) var localCallSettingsChange = false
private var hasAcceptedCall = false
+ private var skipCallStateUpdates = false
+ private var temporaryCallSettings: CallSettings?
public var participants: [CallParticipant] {
let updateParticipants = call?.state.participants ?? []
@@ -431,7 +450,97 @@ open class CallViewModel: ObservableObject {
customData: customData
)
}
+
+ /// Joins a call and then rings the specified members.
+ /// - Parameters:
+ /// - callType: The type of the call to join (for example, "default").
+ /// - callId: The unique identifier of the call.
+ /// - members: The members who should be rung for this call.
+ /// - team: An optional team identifier to associate with the call.
+ /// - maxDuration: The maximum duration of the call in seconds.
+ /// - maxParticipants: The maximum number of participants allowed in the call.
+ /// - startsAt: An optional scheduled start time for the call.
+ /// - customData: Optional custom payload to associate with the call on creation.
+ /// - video: Optional flag indicating whether the ring should suggest a video call.
+ public func joinAndRingCall(
+ callType: String,
+ callId: String,
+ members: [Member],
+ team: String? = nil,
+ maxDuration: Int? = nil,
+ maxParticipants: Int? = nil,
+ startsAt: Date? = nil,
+ customData: [String: RawJSON]? = nil,
+ video: Bool? = nil
+ ) {
+ outgoingCallMembers = members
+ skipCallStateUpdates = true
+ setCallingState(.outgoing)
+ let membersRequest: [MemberRequest]? = members.isEmpty
+ ? nil
+ : members.map(\.toMemberRequest)
+
+ if enteringCallTask != nil || callingState == .inCall {
+ return
+ }
+ enteringCallTask = Task(disposableBag: disposableBag, priority: .userInitiated) { [weak self] in
+ guard let self else { return }
+ do {
+ log.debug("Starting call")
+ let call = call ?? streamVideo.call(
+ callType: callType,
+ callId: callId,
+ callSettings: callSettings
+ )
+ var settingsRequest: CallSettingsRequest?
+ var limits: LimitsSettingsRequest?
+ if maxDuration != nil || maxParticipants != nil {
+ limits = .init(maxDurationSeconds: maxDuration, maxParticipants: maxParticipants)
+ }
+ settingsRequest = .init(limits: limits)
+ let options = CreateCallOptions(
+ members: membersRequest,
+ custom: customData,
+ settings: settingsRequest,
+ startsAt: startsAt,
+ team: team
+ )
+ let settings = localCallSettingsChange ? callSettings : nil
+ call.updateParticipantsSorting(with: participantsSortComparators)
+
+ let joinResponse = try await call.join(
+ create: true,
+ options: options,
+ ring: false,
+ callSettings: settings
+ )
+
+ temporaryCallSettings = call.state.callSettings
+ try? await call.speaker.disableSpeakerPhone()
+
+ try await call.ring(
+ request: .init(membersIds: members.map(\.id).filter { $0 != self.streamVideo.user.id }, video: video)
+ )
+
+ let autoCancelTimeoutMs = call.state.settings?.ring.autoCancelTimeoutMs
+ ?? joinResponse.call.settings.ring.autoCancelTimeoutMs
+ let timeoutSeconds = TimeInterval(autoCancelTimeoutMs) / 1000
+ startTimer(timeout: timeoutSeconds)
+ save(call: call)
+ enteringCallTask = nil
+ hasAcceptedCall = false
+ } catch {
+ hasAcceptedCall = false
+ log.error("Error starting a call", error: error)
+ self.error = error
+ setCallingState(.idle)
+ audioRecorder.stopRecording()
+ enteringCallTask = nil
+ }
+ }
+ }
+
/// Enters into a lobby before joining a call.
/// - Parameters:
/// - callType: the type of the call.
@@ -593,7 +702,9 @@ open class CallViewModel: ObservableObject {
lineNumber: line
)
if let call, (callingState != .inCall || self.call?.cId != call.cId) {
- setCallingState(.inCall)
+ if !skipCallStateUpdates {
+ setCallingState(.inCall)
+ }
self.call = call
} else if call == nil, callingState != .idle {
setCallingState(.idle)
@@ -656,6 +767,8 @@ open class CallViewModel: ObservableObject {
screenSharingUpdates = nil
recordingUpdates?.cancel()
recordingUpdates = nil
+ skipCallStateUpdates = false
+ temporaryCallSettings = nil
call?.leave()
pictureInPictureAdapter.call = nil
@@ -769,6 +882,9 @@ open class CallViewModel: ObservableObject {
}
private func handleCallHangUp(ringTimeout: Bool = false) {
+ if skipCallStateUpdates {
+ skipCallStateUpdates = false
+ }
guard
let call,
callingState == .outgoing
@@ -887,6 +1003,7 @@ open class CallViewModel: ObservableObject {
setActiveCall(call)
}
case .outgoing where call?.cId == event.callCid:
+ skipCallStateUpdates = false
enterCall(
call: call,
callType: event.type,
@@ -929,6 +1046,10 @@ open class CallViewModel: ObservableObject {
}()
let accepted = outgoingCall.state.session?.acceptedBy.count ?? 0
if accepted == 0, rejections >= outgoingMembersCount {
+ if skipCallStateUpdates {
+ skipCallStateUpdates = false
+ setCallingState(.idle)
+ }
Task(disposableBag: disposableBag, priority: .userInitiated) { [weak self] in
_ = try? await outgoingCall.reject(
reason: "Call rejected by all \(outgoingMembersCount) outgoing call members."
@@ -942,6 +1063,7 @@ open class CallViewModel: ObservableObject {
}
private func updateCallStateIfNeeded() {
+ guard !skipCallStateUpdates else { return }
if callingState == .outgoing {
if !callParticipants.isEmpty {
setCallingState(.inCall)
diff --git a/Sources/StreamVideoSwiftUI/Info.plist b/Sources/StreamVideoSwiftUI/Info.plist
index 81683f3c5..5c985b4ce 100644
--- a/Sources/StreamVideoSwiftUI/Info.plist
+++ b/Sources/StreamVideoSwiftUI/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.35.0
+ 1.36.0
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSHumanReadableCopyright
diff --git a/Sources/StreamVideoUIKit/Info.plist b/Sources/StreamVideoUIKit/Info.plist
index 81683f3c5..5c985b4ce 100644
--- a/Sources/StreamVideoUIKit/Info.plist
+++ b/Sources/StreamVideoUIKit/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.35.0
+ 1.36.0
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSHumanReadableCopyright
diff --git a/StreamVideo-XCFramework.podspec b/StreamVideo-XCFramework.podspec
index 5261b15da..433096e55 100644
--- a/StreamVideo-XCFramework.podspec
+++ b/StreamVideo-XCFramework.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'StreamVideo-XCFramework'
- spec.version = '1.35.0'
+ spec.version = '1.36.0'
spec.summary = 'StreamVideo iOS Video Client'
spec.description = 'StreamVideo is the official Swift client for Stream Video, a service for building video applications.'
diff --git a/StreamVideo.podspec b/StreamVideo.podspec
index 829a86742..95083a646 100644
--- a/StreamVideo.podspec
+++ b/StreamVideo.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'StreamVideo'
- spec.version = '1.35.0'
+ spec.version = '1.36.0'
spec.summary = 'StreamVideo iOS Video Client'
spec.description = 'StreamVideo is the official Swift client for Stream Video, a service for building video applications.'
diff --git a/StreamVideo.xcodeproj/project.pbxproj b/StreamVideo.xcodeproj/project.pbxproj
index 801a3668f..fcf701949 100644
--- a/StreamVideo.xcodeproj/project.pbxproj
+++ b/StreamVideo.xcodeproj/project.pbxproj
@@ -1138,6 +1138,8 @@
8414081129F284A800FF2D7C /* AssertJSONEqual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8414081029F284A800FF2D7C /* AssertJSONEqual.swift */; };
8414081329F28B5700FF2D7C /* RTCConfiguration_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8414081229F28B5600FF2D7C /* RTCConfiguration_Tests.swift */; };
8414081529F28FFC00FF2D7C /* CallSettings_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8414081429F28FFC00FF2D7C /* CallSettings_Tests.swift */; };
+ 841457372EBE5BF100D0D034 /* RingCallResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841457362EBE5BF100D0D034 /* RingCallResponse.swift */; };
+ 841457382EBE5BF100D0D034 /* RingCallRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841457352EBE5BF100D0D034 /* RingCallRequest.swift */; };
8415D3E1290B2AF2006E53CB /* outgoing.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 8415D3E0290B2AF2006E53CB /* outgoing.m4a */; };
8415D3E3290BC882006E53CB /* Sounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8415D3E2290BC882006E53CB /* Sounds.swift */; };
841947982886D9CD0007B36E /* BundleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841947972886D9CD0007B36E /* BundleExtensions.swift */; };
@@ -2720,6 +2722,8 @@
8414081029F284A800FF2D7C /* AssertJSONEqual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssertJSONEqual.swift; sourceTree = ""; };
8414081229F28B5600FF2D7C /* RTCConfiguration_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTCConfiguration_Tests.swift; sourceTree = ""; };
8414081429F28FFC00FF2D7C /* CallSettings_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings_Tests.swift; sourceTree = ""; };
+ 841457352EBE5BF100D0D034 /* RingCallRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RingCallRequest.swift; sourceTree = ""; };
+ 841457362EBE5BF100D0D034 /* RingCallResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RingCallResponse.swift; sourceTree = ""; };
8415D3E0290B2AF2006E53CB /* outgoing.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = outgoing.m4a; sourceTree = ""; };
8415D3E2290BC882006E53CB /* Sounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sounds.swift; sourceTree = ""; };
841947972886D9CD0007B36E /* BundleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtensions.swift; sourceTree = ""; };
@@ -7182,6 +7186,8 @@
84DC383E29ADFCFC00946713 /* Models */ = {
isa = PBXGroup;
children = (
+ 841457352EBE5BF100D0D034 /* RingCallRequest.swift */,
+ 841457362EBE5BF100D0D034 /* RingCallResponse.swift */,
8464FBA72EB3832000933768 /* CallModerationBlurEvent.swift */,
8464FBA82EB3832000933768 /* CallModerationWarningEvent.swift */,
8438AB042E5F3A2900BA834F /* AppEventResponse.swift */,
@@ -8628,6 +8634,8 @@
84DC38DB29ADFCFD00946713 /* JSONDataEncoding.swift in Sources */,
40FB15112BF77D5800D5E580 /* StreamStateMachineStage.swift in Sources */,
8496A9A629CC500F00F15FF1 /* StreamVideoCaptureHandler.swift in Sources */,
+ 841457372EBE5BF100D0D034 /* RingCallResponse.swift in Sources */,
+ 841457382EBE5BF100D0D034 /* RingCallRequest.swift in Sources */,
406568872E0426FD00A67EAC /* IdleTimerAdapter.swift in Sources */,
84CD12162C73831000056640 /* CallRtmpBroadcastStartedEvent.swift in Sources */,
40944D232E4E3D7D00088AF0 /* StreamCallAudioRecorder+State.swift in Sources */,
diff --git a/StreamVideoArtifacts.json b/StreamVideoArtifacts.json
index 82e1c7f3f..672bba84d 100644
--- a/StreamVideoArtifacts.json
+++ b/StreamVideoArtifacts.json
@@ -1 +1 @@
-{"0.4.2":"https://github.com/GetStream/stream-video-swift/releases/download/0.4.2/StreamVideo-All.zip","0.5.0":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.0/StreamVideo-All.zip","0.5.1":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.1/StreamVideo-All.zip","0.5.2":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.2/StreamVideo-All.zip","0.5.3":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.3/StreamVideo-All.zip","1.0.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.0/StreamVideo-All.zip","1.0.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.1/StreamVideo-All.zip","1.0.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.2/StreamVideo-All.zip","1.0.3":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.3/StreamVideo-All.zip","1.0.4":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.4/StreamVideo-All.zip","1.0.5":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.5/StreamVideo-All.zip","1.0.6":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.6/StreamVideo-All.zip","1.0.7":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.7/StreamVideo-All.zip","1.0.8":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.8/StreamVideo-All.zip","1.0.9":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.9/StreamVideo-All.zip","1.10.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.10.0/StreamVideo-All.zip","1.11.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.11.0/StreamVideo-All.zip","1.12.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.12.0/StreamVideo-All.zip","1.13.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.13.0/StreamVideo-All.zip","1.14.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.14.0/StreamVideo-All.zip","1.14.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.14.1/StreamVideo-All.zip","1.15.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.15.0/StreamVideo-All.zip","1.16.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.16.0/StreamVideo-All.zip","1.17.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.17.0/StreamVideo-All.zip","1.18.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.18.0/StreamVideo-All.zip","1.19.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.19.0/StreamVideo-All.zip","1.19.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.19.1/StreamVideo-All.zip","1.19.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.19.2/StreamVideo-All.zip","1.20.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.20.0/StreamVideo-All.zip","1.21.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.21.0/StreamVideo-All.zip","1.21.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.21.1/StreamVideo-All.zip","1.22.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.22.0/StreamVideo-All.zip","1.22.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.22.1/StreamVideo-All.zip","1.22.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.22.2/StreamVideo-All.zip","1.24.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.24.0/StreamVideo-All.zip","1.25.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.25.0/StreamVideo-All.zip","1.26.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.26.0/StreamVideo-All.zip","1.27.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.27.0/StreamVideo-All.zip","1.27.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.27.1/StreamVideo-All.zip","1.27.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.27.2/StreamVideo-All.zip","1.28.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.28.0/StreamVideo-All.zip","1.28.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.28.1/StreamVideo-All.zip","1.29.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.29.0/StreamVideo-All.zip","1.29.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.29.1/StreamVideo-All.zip","1.30.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.30.0/StreamVideo-All.zip","1.31.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.31.0/StreamVideo-All.zip","1.32.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.32.0/StreamVideo-All.zip","1.33.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.33.0/StreamVideo-All.zip","1.34.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.34.0/StreamVideo-All.zip","1.34.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.34.1/StreamVideo-All.zip","1.34.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.34.2/StreamVideo-All.zip","1.35.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.35.0/StreamVideo-All.zip"}
\ No newline at end of file
+{"0.4.2":"https://github.com/GetStream/stream-video-swift/releases/download/0.4.2/StreamVideo-All.zip","0.5.0":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.0/StreamVideo-All.zip","0.5.1":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.1/StreamVideo-All.zip","0.5.2":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.2/StreamVideo-All.zip","0.5.3":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.3/StreamVideo-All.zip","1.0.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.0/StreamVideo-All.zip","1.0.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.1/StreamVideo-All.zip","1.0.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.2/StreamVideo-All.zip","1.0.3":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.3/StreamVideo-All.zip","1.0.4":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.4/StreamVideo-All.zip","1.0.5":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.5/StreamVideo-All.zip","1.0.6":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.6/StreamVideo-All.zip","1.0.7":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.7/StreamVideo-All.zip","1.0.8":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.8/StreamVideo-All.zip","1.0.9":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.9/StreamVideo-All.zip","1.10.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.10.0/StreamVideo-All.zip","1.11.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.11.0/StreamVideo-All.zip","1.12.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.12.0/StreamVideo-All.zip","1.13.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.13.0/StreamVideo-All.zip","1.14.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.14.0/StreamVideo-All.zip","1.14.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.14.1/StreamVideo-All.zip","1.15.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.15.0/StreamVideo-All.zip","1.16.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.16.0/StreamVideo-All.zip","1.17.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.17.0/StreamVideo-All.zip","1.18.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.18.0/StreamVideo-All.zip","1.19.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.19.0/StreamVideo-All.zip","1.19.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.19.1/StreamVideo-All.zip","1.19.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.19.2/StreamVideo-All.zip","1.20.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.20.0/StreamVideo-All.zip","1.21.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.21.0/StreamVideo-All.zip","1.21.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.21.1/StreamVideo-All.zip","1.22.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.22.0/StreamVideo-All.zip","1.22.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.22.1/StreamVideo-All.zip","1.22.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.22.2/StreamVideo-All.zip","1.24.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.24.0/StreamVideo-All.zip","1.25.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.25.0/StreamVideo-All.zip","1.26.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.26.0/StreamVideo-All.zip","1.27.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.27.0/StreamVideo-All.zip","1.27.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.27.1/StreamVideo-All.zip","1.27.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.27.2/StreamVideo-All.zip","1.28.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.28.0/StreamVideo-All.zip","1.28.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.28.1/StreamVideo-All.zip","1.29.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.29.0/StreamVideo-All.zip","1.29.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.29.1/StreamVideo-All.zip","1.30.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.30.0/StreamVideo-All.zip","1.31.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.31.0/StreamVideo-All.zip","1.32.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.32.0/StreamVideo-All.zip","1.33.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.33.0/StreamVideo-All.zip","1.34.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.34.0/StreamVideo-All.zip","1.34.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.34.1/StreamVideo-All.zip","1.34.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.34.2/StreamVideo-All.zip","1.35.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.35.0/StreamVideo-All.zip","1.36.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.36.0/StreamVideo-All.zip"}
\ No newline at end of file
diff --git a/StreamVideoSwiftUI-XCFramework.podspec b/StreamVideoSwiftUI-XCFramework.podspec
index 8efff7eaa..12c5818af 100644
--- a/StreamVideoSwiftUI-XCFramework.podspec
+++ b/StreamVideoSwiftUI-XCFramework.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'StreamVideoSwiftUI-XCFramework'
- spec.version = '1.35.0'
+ spec.version = '1.36.0'
spec.summary = 'StreamVideo SwiftUI Video Components'
spec.description = 'StreamVideoSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamVideo SDK.'
diff --git a/StreamVideoSwiftUI.podspec b/StreamVideoSwiftUI.podspec
index 1324446bc..139286613 100644
--- a/StreamVideoSwiftUI.podspec
+++ b/StreamVideoSwiftUI.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'StreamVideoSwiftUI'
- spec.version = '1.35.0'
+ spec.version = '1.36.0'
spec.summary = 'StreamVideo SwiftUI Video Components'
spec.description = 'StreamVideoSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamVideo SDK.'
diff --git a/StreamVideoSwiftUITests/CallViewModel_Tests.swift b/StreamVideoSwiftUITests/CallViewModel_Tests.swift
index 4b406e51b..0ed0df4aa 100644
--- a/StreamVideoSwiftUITests/CallViewModel_Tests.swift
+++ b/StreamVideoSwiftUITests/CallViewModel_Tests.swift
@@ -497,6 +497,110 @@ final class CallViewModel_Tests: XCTestCase, @unchecked Sendable {
await assertCallingState(.inCall)
}
+ func test_joinAndRingCall_joinsAndRingsMembers() async throws {
+ // Given
+ await prepare()
+ mockCall.resetRecords(for: .join)
+ mockCall.resetRecords(for: .ring)
+ let thirdParticipant = try XCTUnwrap(thirdUser)
+ let recipients: [Member] = participants + [thirdParticipant]
+ let team = "test-team"
+ let startsAt = Date(timeIntervalSince1970: 1_700_000_000)
+ let maxDuration = 600
+ let maxParticipants = 8
+ let customData: [String: RawJSON] = ["topic": .string("demo")]
+ let expectedOptions = CreateCallOptions(
+ members: recipients.map(\.toMemberRequest),
+ custom: customData,
+ settings: CallSettingsRequest(
+ limits: LimitsSettingsRequest(
+ maxDurationSeconds: maxDuration,
+ maxParticipants: maxParticipants
+ )
+ ),
+ startsAt: startsAt,
+ team: team
+ )
+
+ // When
+ subject.joinAndRingCall(
+ callType: callType,
+ callId: callId,
+ members: recipients,
+ team: team,
+ maxDuration: maxDuration,
+ maxParticipants: maxParticipants,
+ startsAt: startsAt,
+ customData: customData,
+ video: true
+ )
+
+ // Then
+ XCTAssertEqual(subject.callingState, .outgoing)
+ XCTAssertEqual(subject.outgoingCallMembers, recipients)
+
+ await fulfilmentInMainActor { self.mockCall.timesCalled(.join) == 1 }
+ let joinPayload = try XCTUnwrap(
+ mockCall
+ .recordedInputPayload((Bool, CreateCallOptions?, Bool, Bool, CallSettings?).self, for: .join)?
+ .last
+ )
+ let (createFlag, options, ringFlag, notifyFlag, forwardedCallSettings) = joinPayload
+ XCTAssertTrue(createFlag)
+ XCTAssertEqual(options, expectedOptions)
+ XCTAssertFalse(ringFlag)
+ XCTAssertFalse(notifyFlag)
+ XCTAssertNil(forwardedCallSettings)
+
+ await fulfilmentInMainActor { self.mockCall.timesCalled(.ring) == 1 }
+ let ringRequest = try XCTUnwrap(
+ mockCall
+ .recordedInputPayload(RingCallRequest.self, for: .ring)?
+ .last
+ )
+ XCTAssertEqual(
+ ringRequest,
+ RingCallRequest(
+ membersIds: [secondUser.id, thirdParticipant.id],
+ video: true
+ )
+ )
+ }
+
+ func test_joinAndRingCall_usesLocalCallSettingsOverrides() async throws {
+ // Given
+ await prepare()
+ mockCall.resetRecords(for: .join)
+ mockCall.resetRecords(for: .ring)
+ subject.toggleMicrophoneEnabled()
+ await fulfilmentInMainActor { self.subject.callSettings.audioOn == false }
+ let expectedCallSettings = subject.callSettings
+
+ // When
+ subject.joinAndRingCall(
+ callType: callType,
+ callId: callId,
+ members: participants
+ )
+
+ // Then
+ XCTAssertEqual(subject.callingState, .outgoing)
+ await fulfilmentInMainActor { self.mockCall.timesCalled(.join) == 1 }
+ let joinPayload = try XCTUnwrap(
+ mockCall
+ .recordedInputPayload((Bool, CreateCallOptions?, Bool, Bool, CallSettings?).self, for: .join)?
+ .last
+ )
+ let (_, _, _, _, forwardedCallSettings) = joinPayload
+ XCTAssertEqual(forwardedCallSettings, expectedCallSettings)
+ XCTAssertEqual(
+ joinPayload.1?.members,
+ participants.map(\.toMemberRequest)
+ )
+
+ await fulfilmentInMainActor { self.mockCall.timesCalled(.ring) == 1 }
+ }
+
// MARK: - EnterLobby
func test_enterLobby_joinCall() async throws {
@@ -1059,6 +1163,7 @@ final class CallViewModel_Tests: XCTestCase, @unchecked Sendable {
)
)
mockCall.stub(for: .reject, with: RejectCallResponse(duration: "0"))
+ mockCall.stub(for: .ring, with: RingCallResponse(duration: "0", membersIds: participants.map(\.id)))
streamVideo = .init(stubbedProperty: [:], stubbedFunction: [
.call: mockCall!
diff --git a/StreamVideoTests/CallKit/CallKitServiceTests.swift b/StreamVideoTests/CallKit/CallKitServiceTests.swift
index a3a34cca8..3a9e5ea04 100644
--- a/StreamVideoTests/CallKit/CallKitServiceTests.swift
+++ b/StreamVideoTests/CallKit/CallKitServiceTests.swift
@@ -461,6 +461,8 @@ final class CallKitServiceTests: XCTestCase, @unchecked Sendable {
XCTFail()
case .reject:
XCTFail()
+ case .ring:
+ XCTFail()
}
}
@@ -559,6 +561,8 @@ final class CallKitServiceTests: XCTestCase, @unchecked Sendable {
XCTFail()
case .reject:
XCTFail()
+ case .ring:
+ XCTFail()
}
XCTAssertEqual(call.microphone.status, .enabled)
diff --git a/StreamVideoTests/CallStateMachine/CallStateMachine/Stages/CallStateMachine_RejectingStageTests.swift b/StreamVideoTests/CallStateMachine/CallStateMachine/Stages/CallStateMachine_RejectingStageTests.swift
index 0d631efd8..672e61f7d 100644
--- a/StreamVideoTests/CallStateMachine/CallStateMachine/Stages/CallStateMachine_RejectingStageTests.swift
+++ b/StreamVideoTests/CallStateMachine/CallStateMachine/Stages/CallStateMachine_RejectingStageTests.swift
@@ -19,7 +19,7 @@ final class CallStateMachineStageRejectingStage_Tests: StreamVideoTestCase, @unc
.filter { $0 != subject.id }
.map { Call.StateMachine.Stage(id: $0, context: .init(call: call)) }
private lazy var validOtherStages: Set! = [
- .idle
+ .idle, .joined
]
private lazy var response: RejectCallResponse! = .init(duration: "10")
private lazy var subject: Call.StateMachine.Stage! = .rejecting(
diff --git a/StreamVideoTests/Mock/MockCall.swift b/StreamVideoTests/Mock/MockCall.swift
index 5bb962841..59bfbfc4b 100644
--- a/StreamVideoTests/Mock/MockCall.swift
+++ b/StreamVideoTests/Mock/MockCall.swift
@@ -18,6 +18,7 @@ final class MockCall: Call, Mockable, @unchecked Sendable {
case join
case updateTrackSize
case callKitActivated
+ case ring
}
enum MockCallFunctionInputKey: Payloadable {
@@ -35,6 +36,8 @@ final class MockCall: Call, Mockable, @unchecked Sendable {
case reject(reason: String?)
+ case ring(request: RingCallRequest)
+
var payload: Any {
switch self {
case let .join(create, options, ring, notify, callSettings):
@@ -48,6 +51,9 @@ final class MockCall: Call, Mockable, @unchecked Sendable {
case let .reject(reason):
return reason ?? ""
+
+ case let .ring(request):
+ return request
}
}
}
@@ -176,4 +182,16 @@ final class MockCall: Call, Mockable, @unchecked Sendable {
.updateTrackSize(trackSize: trackSize, participant: participant)
)
}
+
+ override func ring(request: RingCallRequest) async throws -> RingCallResponse {
+ stubbedFunctionInput[.ring]?.append(.ring(request: request))
+ if let response = stubbedFunction[.ring] as? RingCallResponse {
+ return response
+ } else if let error = stubbedFunction[.ring] as? Error {
+ throw error
+ } else {
+ // Default to a benign response if not stubbed to simplify tests
+ return RingCallResponse(duration: "0", membersIds: request.membersIds ?? [])
+ }
+ }
}
diff --git a/StreamVideoTests/Mock/MockDefaultAPIEndpoints.swift b/StreamVideoTests/Mock/MockDefaultAPIEndpoints.swift
index 3a35c7846..e18bcf818 100644
--- a/StreamVideoTests/Mock/MockDefaultAPIEndpoints.swift
+++ b/StreamVideoTests/Mock/MockDefaultAPIEndpoints.swift
@@ -74,6 +74,7 @@ final class MockDefaultAPIEndpoints: DefaultAPIEndpoints, Mockable, @unchecked S
case getEdges
case createGuest
case videoConnect
+ case ringCall
}
enum MockFunctionInputKey: Payloadable {
@@ -124,6 +125,7 @@ final class MockDefaultAPIEndpoints: DefaultAPIEndpoints, Mockable, @unchecked S
case getEdges
case createGuest(request: CreateGuestRequest)
case videoConnect
+ case ringCall(request: RingCallRequest)
var payload: Any {
switch self {
@@ -221,6 +223,8 @@ final class MockDefaultAPIEndpoints: DefaultAPIEndpoints, Mockable, @unchecked S
return request
case .videoConnect:
return ()
+ case let .ringCall(request: request):
+ return request
}
}
}
@@ -577,4 +581,9 @@ final class MockDefaultAPIEndpoints: DefaultAPIEndpoints, Mockable, @unchecked S
guard stubbedFunction[.videoConnect] != nil else { throw ClientError("Not stubbed function.") }
return ()
}
+
+ func ringCall(type: String, id: String, ringCallRequest: RingCallRequest) async throws -> RingCallResponse {
+ stubbedFunctionInput[.ringCall]?.append(.ringCall(request: ringCallRequest))
+ return try stubbedResult(for: .ringCall)
+ }
}
diff --git a/StreamVideoTests/WebRTC/SFU/SFUEventAdapter_Tests.swift b/StreamVideoTests/WebRTC/SFU/SFUEventAdapter_Tests.swift
index 14fa74c4c..1c3c48fad 100644
--- a/StreamVideoTests/WebRTC/SFU/SFUEventAdapter_Tests.swift
+++ b/StreamVideoTests/WebRTC/SFU/SFUEventAdapter_Tests.swift
@@ -406,6 +406,22 @@ final class SFUEventAdapter_Tests: XCTestCase, @unchecked Sendable {
}
}
+ func test_handleTrackPublished_givenAudioEvent_participantDoesNotExist_whenPublished_thenAddsAndUpdatesParticipantAudioStatus(
+ ) async throws {
+ let participant = CallParticipant.dummy()
+ var event = Stream_Video_Sfu_Event_TrackPublished()
+ event.sessionID = participant.sessionId
+ event.type = .audio
+
+ try await assert(
+ event,
+ wrappedEvent: .sfuEvent(.trackPublished(event)),
+ initialState: [:]
+ ) {
+ $0.count == 1 && $0[participant.sessionId]?.hasAudio == true
+ }
+ }
+
// MARK: trackUnpublished
func test_handleTrackUnpublished_givenAudioEvent_whenPublished_thenUpdatesParticipantAudioStatus() async throws {
@@ -507,6 +523,22 @@ final class SFUEventAdapter_Tests: XCTestCase, @unchecked Sendable {
}
}
+ func test_handleTrackUnpublished_givenAudioEvent_participantDoesNotExist_whenPublished_thenAddsAndUpdatesParticipantAudioStatus(
+ ) async throws {
+ let participant = CallParticipant.dummy(hasAudio: true)
+ var event = Stream_Video_Sfu_Event_TrackUnpublished()
+ event.sessionID = participant.sessionId
+ event.type = .audio
+
+ try await assert(
+ event,
+ wrappedEvent: .sfuEvent(.trackUnpublished(event)),
+ initialState: [:]
+ ) {
+ $0.count == 1 && $0[participant.sessionId]?.hasAudio == false
+ }
+ }
+
// MARK: pinsChanged
func test_handlePinsChanged_givenEvent_whenPublished_thenUpdatesPinnedParticipants() async throws {
@@ -587,6 +619,23 @@ final class SFUEventAdapter_Tests: XCTestCase, @unchecked Sendable {
}
}
+ func test_handleParticipantUpdated_givenEvent_participantDoesNotExist_whenPublished_thenUAddsAndpdatesParticipant(
+ ) async throws {
+ var event = Stream_Video_Sfu_Event_ParticipantUpdated()
+ // We add the showTrack and audioLevels to match what the `event.participant.toCallParticipant()`
+ // does (defaults to showTrack:True and uses the audioLevel to create an array for the audioLevels.)
+ let expectedParticipant = CallParticipant.dummy(showTrack: true, audioLevels: [0])
+ event.participant = .init(expectedParticipant)
+
+ try await assert(
+ event,
+ wrappedEvent: .sfuEvent(.participantUpdated(event)),
+ initialState: [:]
+ ) {
+ $0.count == 1 && $0[expectedParticipant.sessionId] == expectedParticipant
+ }
+ }
+
// MARK: publishOptionsChanged
func test_handleChangePublishOptions_givenEvent_whenPublished_thenUpdatesPublishOptions() async throws {
diff --git a/StreamVideoUIKit-XCFramework.podspec b/StreamVideoUIKit-XCFramework.podspec
index 7bb494585..5a2229416 100644
--- a/StreamVideoUIKit-XCFramework.podspec
+++ b/StreamVideoUIKit-XCFramework.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'StreamVideoUIKit-XCFramework'
- spec.version = '1.35.0'
+ spec.version = '1.36.0'
spec.summary = 'StreamVideo UIKit Video Components'
spec.description = 'StreamVideoUIKit SDK offers flexible UIKit components able to display data provided by StreamVideo SDK.'
diff --git a/StreamVideoUIKit.podspec b/StreamVideoUIKit.podspec
index 5ab3ad443..0ec80fc68 100644
--- a/StreamVideoUIKit.podspec
+++ b/StreamVideoUIKit.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'StreamVideoUIKit'
- spec.version = '1.35.0'
+ spec.version = '1.36.0'
spec.summary = 'StreamVideo UIKit Video Components'
spec.description = 'StreamVideoUIKit SDK offers flexible UIKit components able to display data provided by StreamVideo SDK.'
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 353d43803..b21d60abf 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -812,7 +812,7 @@ private_lane :frameworks_sizes do
}
end
-desc "Updates WebRTC dependency locally. Usage: `bundle exec fastlane update_stream_chat version:125.6422.070`"
+desc "Updates WebRTC dependency locally. Usage: `bundle exec fastlane update_stream_chat version:125.6422.070 skip_pr:true`"
lane :update_webrtc do |options|
raise UI.user_error!('Provide a version.') unless options[:version]
@@ -834,12 +834,18 @@ lane :update_webrtc do |options|
end
end
- pr_create(
- title: "Update WebRTC dependency to #{options[:version]}",
- base_branch: 'develop',
- head_branch: "ci/update-webrtc-dependency-#{Time.now.to_i}",
- github_repo: github_repo
- )
+ skip_pr = %w[true 1].include?(options[:skip_pr].to_s.downcase)
+
+ if skip_pr
+ UI.message('Skipping PR creation as requested.')
+ else
+ pr_create(
+ title: "Update WebRTC dependency to #{options[:version]}",
+ base_branch: 'develop',
+ head_branch: "ci/update-webrtc-dependency-#{Time.now.to_i}",
+ github_repo: github_repo
+ )
+ end
end
lane :size_analyze do
diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile
index a3db622f0..21dba2f41 100644
--- a/fastlane/Pluginfile
+++ b/fastlane/Pluginfile
@@ -1,4 +1,4 @@
gem 'fastlane-plugin-versioning'
gem 'fastlane-plugin-create_xcframework'
gem 'fastlane-plugin-stream_actions', '0.3.101'
-gem 'fastlane-plugin-xcsize', '1.1.0'
+gem 'fastlane-plugin-xcsize', '1.2.0'