Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .github/workflows/release-start.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ jobs:

- uses: ./.github/actions/xcode-cache

- name: Integration Test
run: gh workflow run test.yml --repo GetStream/apple-internal-testing-pipeline # reports to @support on failure
env:
GITHUB_TOKEN: ${{ secrets.CI_BOT_GITHUB_TOKEN }}

- name: Create Release PR
run: bundle exec fastlane release version:"${{ github.event.inputs.version }}" --verbose
env:
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

# Upcoming

### 🔄 Changed
- Improve reconnection logic. [#1013](https://github.com/GetStream/stream-video-swift/pull/1013)

# [1.38.2](https://github.com/GetStream/stream-video-swift/releases/tag/1.38.2)
_December 22, 2025_

### 🔄 Changed

# [1.38.1](https://github.com/GetStream/stream-video-swift/releases/tag/1.38.1)
Expand Down
18 changes: 9 additions & 9 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ GEM
ast (2.4.3)
atomos (0.1.3)
aws-eventstream (1.4.0)
aws-partitions (1.1139.0)
aws-sdk-core (3.228.0)
aws-partitions (1.1197.0)
aws-sdk-core (3.240.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
bigdecimal
jmespath (~> 1, >= 1.6.1)
logger
aws-sdk-kms (1.109.0)
aws-sdk-core (~> 3, >= 3.228.0)
aws-sdk-kms (1.118.0)
aws-sdk-core (~> 3, >= 3.239.1)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.195.0)
aws-sdk-core (~> 3, >= 3.228.0)
aws-sdk-s3 (1.208.0)
aws-sdk-core (~> 3, >= 3.234.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
Expand All @@ -51,7 +51,7 @@ GEM
mini_magick (>= 4.9.4, < 5.0.0)
base64 (0.3.0)
benchmark (0.4.1)
bigdecimal (3.2.2)
bigdecimal (4.0.1)
claide (1.1.0)
claide-plugins (0.9.2)
cork
Expand Down Expand Up @@ -211,7 +211,7 @@ GEM
bundler
fastlane
pry
fastlane-plugin-stream_actions (0.3.101)
fastlane-plugin-stream_actions (0.3.102)
xctest_list (= 1.2.1)
fastlane-plugin-versioning (0.7.1)
fastlane-plugin-xcsize (1.2.0)
Expand Down Expand Up @@ -433,7 +433,7 @@ DEPENDENCIES
fastlane
fastlane-plugin-create_xcframework
fastlane-plugin-lizard
fastlane-plugin-stream_actions (= 0.3.101)
fastlane-plugin-stream_actions (= 0.3.102)
fastlane-plugin-versioning
fastlane-plugin-xcsize (= 1.2.0)
json
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.38.1"
public static let version: String = "1.38.2"
/// The WebRTC version.
public static let webRTCVersion: String = "137.0.54"
}
24 changes: 17 additions & 7 deletions Sources/StreamVideo/HTTPClient/InternetConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ final class InternetConnection: @unchecked Sendable {
/// The current Internet connection status.
@Published private(set) var status: InternetConnectionStatus {
didSet {
guard oldValue != status else { return }

log.info("Internet Connection: \(status)", subsystems: .httpRequests)

postNotification(.internetConnectionStatusDidChange, with: status)

guard oldValue.isAvailable != status.isAvailable else { return }
Expand All @@ -42,6 +38,9 @@ final class InternetConnection: @unchecked Sendable {
}
}

private let subject: PassthroughSubject<InternetConnectionStatus, Never> = .init()
private var processingCancellable: AnyCancellable?

/// The notification center that posts notifications when connection state changes..
let notificationCenter: NotificationCenter

Expand All @@ -56,8 +55,14 @@ final class InternetConnection: @unchecked Sendable {
) {
self.notificationCenter = notificationCenter
self.monitor = monitor
self.status = monitor.status

processingCancellable = subject
.removeDuplicates()
.log(.debug) { "Internet Connection: \($0)" }
.receive(on: DispatchQueue.main)
.assign(to: \.status, onWeak: self)

status = monitor.status
monitor.delegate = self
monitor.start()
}
Expand All @@ -69,7 +74,7 @@ final class InternetConnection: @unchecked Sendable {

extension InternetConnection: InternetConnectionDelegate {
func internetConnectionStatusDidChange(status: InternetConnectionStatus) {
self.status = status
subject.send(status)
}
}

Expand Down Expand Up @@ -211,6 +216,8 @@ extension InternetConnection {

/// A protocol defining the interface for internet connection monitoring.
public protocol InternetConnectionProtocol {
var status: InternetConnectionStatus { get }

/// A publisher that emits the current internet connection status.
///
/// This publisher never fails and continuously updates with the latest
Expand All @@ -226,7 +233,10 @@ extension InternetConnection: InternetConnectionProtocol {
///
/// - Note: The publisher won't publish any duplicates.
public var statusPublisher: AnyPublisher<InternetConnectionStatus, Never> {
$status.removeDuplicates().eraseToAnyPublisher()
$status
.debounce(for: .seconds(0.1), scheduler: DispatchQueue.main)
.removeDuplicates()
.eraseToAnyPublisher()
}
}

Expand Down
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.38.1</string>
<string>1.38.2</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ final class RTCAudioSessionPublisher: NSObject, RTCAudioSessionDelegate, @unchec
from: AVAudioSessionRouteDescription,
to: AVAudioSessionRouteDescription
)

static func == (lhs: Event, rhs: Event) -> Bool {
switch (lhs, rhs) {
case (.didBeginInterruption, .didBeginInterruption):
return true

case (let .didEndInterruption(lhsValue), let .didEndInterruption(rhsValue)):
return lhsValue == rhsValue

case (let .didChangeRoute(lReason, lFrom, lTo), let .didChangeRoute(rReason, rFrom, rTo)):
return lReason == rReason
&& RTCAudioStore.StoreState.AudioRoute(lFrom) == RTCAudioStore.StoreState.AudioRoute(rFrom)
&& RTCAudioStore.StoreState.AudioRoute(lTo) == RTCAudioStore.StoreState.AudioRoute(rTo)

default:
return false
}
}
}

/// The Combine publisher that emits session events.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ extension RTCAudioStore {
self.channels = channels
self.source = nil
}

static func == (lhs: Port, rhs: Port) -> Bool {
lhs.type == rhs.type
&& lhs.name == rhs.name
&& lhs.id == rhs.id
&& lhs.isExternal == rhs.isExternal
&& lhs.isSpeaker == rhs.isSpeaker
&& lhs.isReceiver == rhs.isReceiver
&& lhs.channels == rhs.channels
}
}

let inputs: [Port]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public extension ReflectiveStringConvertible {
/// The default separator used to join different parts of the string representation.
///
/// By default, this is set to a newline character ("\n").
var separator: String { "\n" }
var separator: String { ", " }

/// The default set of properties to be excluded from the string representation.
///
Expand Down Expand Up @@ -169,11 +169,16 @@ public extension ReflectiveStringConvertible {
///
/// - Returns: A string representation of the object.
var description: String {
#if STREAM_TESTS
// During tests we allow full error logging.
#else
guard LogConfig.level == .debug else {
return "\(type(of: self))"
}
#endif
let mirror = Mirror(reflecting: self)
var output: [String] = ["Type: \(type(of: self))"]
var result = "\(type(of: self))"
var components: [String] = []

let excludedProperties = self.excludedProperties
mirror
Expand All @@ -188,9 +193,14 @@ public extension ReflectiveStringConvertible {
}
}
.forEach { (child: (label: String, value: Any)) in
output.append(" - \(child.label): \(child.value)")
components.append("\(child.label):\(child.value)")
}

return output.joined(separator: separator)
if !components.isEmpty {
result += " { "
result += components.joined(separator: separator)
result += " }"
}
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
private var internetObservationCancellable: AnyCancellable?
private var timeInStageCancellable: AnyCancellable?
private var disposableBag = DisposableBag()
private let processingQueue = OperationQueue(maxConcurrentOperationCount: 1)

/// Initializes a new instance of `DisconnectedStage`.
/// - Parameter context: The context for the disconnected stage.
Expand Down Expand Up @@ -180,10 +181,11 @@ extension WebRTCCoordinator.StateMachine.Stage {
internetObservationCancellable?.cancel()
internetObservationCancellable = internetConnectionObserver
.statusPublisher
.receive(on: DispatchQueue.main)
.filter { $0 != .unknown }
.log(.debug, subsystems: .webRTC) { "Internet connection status updated to \($0)" }
.debounce(for: 1, scheduler: processingQueue)
.removeDuplicates()
.receive(on: processingQueue)
.sinkTask(storeIn: disposableBag) { [weak self] in
/// Trace internet connection changes
await self?
Expand Down Expand Up @@ -223,6 +225,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
}
timeInStageCancellable = DefaultTimer
.publish(every: context.disconnectionTimeout)
.receive(on: processingQueue)
.sink { [weak self] _ in self?.didTimeInStageExpired() }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ extension WebRTCCoordinator.StateMachine.Stage {

private let disposableBag = DisposableBag()
private var updateSubscriptionsAdapter: WebRTCUpdateSubscriptionsAdapter?
private let processingQueue = OperationQueue(maxConcurrentOperationCount: 1)

/// Initializes a new instance of `JoinedStage`.
/// - Parameter context: The context for the joined stage.
Expand Down Expand Up @@ -209,6 +210,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
return nil
}
}
.receive(on: processingQueue)
.sink { [weak self] (source: WebSocketConnectionState.DisconnectionSource) in
guard let self else { return }
context.disconnectionSource = source
Expand Down Expand Up @@ -248,6 +250,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
sfuAdapter?
.publisher(eventType: Stream_Video_Sfu_Event_CallEnded.self)
.log(.debug, subsystems: .sfu) { "Call ended with reason: \($0.reason)." }
.receive(on: processingQueue)
.sink { [weak self] _ in
guard let self else { return }
transitionOrError(.leaving(context))
Expand All @@ -264,6 +267,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
sfuAdapter?
.publisher(eventType: Stream_Video_Sfu_Event_Error.self)
.filter { $0.reconnectStrategy == .migrate }
.receive(on: processingQueue)
.sink { [weak self] _ in
guard let self else { return }
context.reconnectionStrategy = .migrate
Expand All @@ -277,6 +281,7 @@ extension WebRTCCoordinator.StateMachine.Stage {

sfuAdapter?
.publisher(eventType: Stream_Video_Sfu_Event_GoAway.self)
.receive(on: processingQueue)
.sink { [weak self] _ in
guard let self else { return }
context.reconnectionStrategy = .migrate
Expand All @@ -300,6 +305,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
sfuAdapter?
.publisher(eventType: Stream_Video_Sfu_Event_Error.self)
.filter { $0.reconnectStrategy == .disconnect }
.receive(on: processingQueue)
.sink { [weak self] _ in
guard let self else { return }
transitionOrError(.leaving(context))
Expand All @@ -314,6 +320,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
sfuAdapter?
.publisher(eventType: Stream_Video_Sfu_Event_Error.self)
.filter { $0.error.code == .participantSignalLost }
.receive(on: processingQueue)
.sink { [weak self] _ in
guard let self else { return }
context.reconnectionStrategy = .fast(
Expand All @@ -332,6 +339,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
let sfuAdapter = await context.coordinator?.stateAdapter.sfuAdapter
sfuAdapter?
.publisher(eventType: Stream_Video_Sfu_Event_HealthCheckResponse.self)
.receive(on: processingQueue)
.sink { [weak self] _ in
self?.context.lastHealthCheckReceivedAt = .init()
}
Expand All @@ -348,6 +356,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
}
return abs($0.timeIntervalSinceNow) > timeout
}
.receive(on: processingQueue)
.sink { [weak self] lastHealthCheckReceivedAt in
guard let self else {
return
Expand Down Expand Up @@ -377,6 +386,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
)
}
.log(.debug, subsystems: .webRTC) { "Reconnection strategy updated to \($0)." }
.receive(on: processingQueue)
.sink { [weak self] in self?.context.reconnectionStrategy = $0 }
.store(in: disposableBag)
}
Expand All @@ -396,6 +406,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
publisher
.disconnectedPublisher
.log(.debug, subsystems: .webRTC) { "PeerConnection of type: .publisher was disconnected. Will attempt rejoin." }
.receive(on: processingQueue)
.sink { [weak self] in
guard let self else { return }
context.reconnectionStrategy = .rejoin
Expand All @@ -406,6 +417,7 @@ extension WebRTCCoordinator.StateMachine.Stage {
subscriber
.disconnectedPublisher
.log(.debug, subsystems: .webRTC) { "PeerConnection of type: .subscriber was disconnected. Will attempt rejoin." }
.receive(on: processingQueue)
.sink { [weak self] in
guard let self else { return }
context.reconnectionStrategy = .rejoin
Expand Down Expand Up @@ -467,11 +479,11 @@ extension WebRTCCoordinator.StateMachine.Stage {
private func observeInternetConnection() {
internetConnectionObserver
.statusPublisher
.receive(on: DispatchQueue.main)
.filter { $0 != .unknown }
.log(.debug, subsystems: .webRTC) { "Internet connection status updated to \($0)" }
.filter { !$0.isAvailable }
.removeDuplicates()
.receive(on: processingQueue)
.sinkTask(storeIn: disposableBag) { [weak self] in
guard let self else { return }

Expand Down
2 changes: 1 addition & 1 deletion Sources/StreamVideoSwiftUI/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.38.1</string>
<string>1.38.2</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
Expand Down
Loading
Loading