Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,7 @@ Credentials.xcconfig
.vscode/settings.json
.vscode/launch.json
buildServer.json

# xcode-build-server files
buildServer.json
.compile
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ struct DetailedCallingView<Factory: ViewFactory>: 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
Expand Down Expand Up @@ -159,6 +160,13 @@ struct DetailedCallingView<Factory: ViewFactory>: 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,
Expand Down
8 changes: 4 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<a href="https://swift.org"><img src="https://img.shields.io/badge/Swift-5.9%2B-orange.svg" /></a>
</p>
<p align="center">
<img id="stream-video-label" alt="StreamVideo" src="https://img.shields.io/badge/StreamVideo-8.77%20MB-blue"/>
<img id="stream-video-label" alt="StreamVideo" src="https://img.shields.io/badge/StreamVideo-8.79%20MB-blue"/>
<img id="stream-video-swiftui-label" alt="StreamVideoSwiftUI" src="https://img.shields.io/badge/StreamVideoSwiftUI-2.4%20MB-blue"/>
<img id="stream-video-uikit-label" alt="StreamVideoUIKit" src="https://img.shields.io/badge/StreamVideoUIKit-2.52%20MB-blue"/>
<img id="stream-web-rtc-label" alt="StreamWebRTC" src="https://img.shields.io/badge/StreamWebRTC-11.01%20MB-blue"/>
Expand Down
14 changes: 14 additions & 0 deletions Sources/StreamVideo/Call.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
2 changes: 1 addition & 1 deletion Sources/StreamVideo/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.35.0</string>
<string>1.36.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
Expand Down
22 changes: 22 additions & 0 deletions Sources/StreamVideo/OpenApi/generated/APIs/DefaultAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
31 changes: 14 additions & 17 deletions Sources/StreamVideo/WebRTC/v2/SFU/SFUEventAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
}
}
Expand Down
Loading
Loading