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 @@

- StreamVideo + StreamVideo StreamVideoSwiftUI StreamVideoUIKit StreamWebRTC 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'