Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### 🔄 Changed

# [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)
_December 15, 2025_

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