Skip to content

Swift 6: Room and Track state #652

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Apr 1, 2025
1 change: 1 addition & 0 deletions .nanpa/swift-6-state.kdl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
patch type="fixed" "Swift 6: Fixed warnings for mutable state"
2 changes: 1 addition & 1 deletion Sources/LiveKit/Broadcast/BroadcastBundleInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import Foundation

enum BroadcastBundleInfo {
actor BroadcastBundleInfo {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also mostly for the static state (including "mutable" property wrappers below).

/// Identifier of the app group shared by the primary app and broadcast extension.
static var groupIdentifier: String? {
if let override = groupIdentifierOverride { return override }
Expand Down
2 changes: 1 addition & 1 deletion Sources/LiveKit/Broadcast/BroadcastScreenCapturer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal import LiveKitWebRTC
@_implementationOnly import LiveKitWebRTC
#endif

class BroadcastScreenCapturer: BufferCapturer {
class BroadcastScreenCapturer: BufferCapturer, @unchecked Sendable {
private let appAudio: Bool
private var receiver: BroadcastReceiver?

Expand Down
4 changes: 2 additions & 2 deletions Sources/LiveKit/Broadcast/IPC/BroadcastImageCodec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
#if os(iOS)

import AVFoundation
import CoreImage
@preconcurrency import CoreImage

/// Encode and decode image samples for transport.
struct BroadcastImageCodec {
struct BroadcastImageCodec: Sendable {
struct Metadata: Codable {
let width: Int
let height: Int
Expand Down
2 changes: 1 addition & 1 deletion Sources/LiveKit/Broadcast/LKSampleHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import LKObjCHelpers
#endif

@available(macCatalyst 13.1, *)
open class LKSampleHandler: RPBroadcastSampleHandler {
open class LKSampleHandler: RPBroadcastSampleHandler, @unchecked Sendable {
private var uploader: BroadcastUploader?
private var cancellable = Set<AnyCancellable>()

Expand Down
22 changes: 11 additions & 11 deletions Sources/LiveKit/Core/DataChannelPair.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
func dataChannel(_ dataChannelPair: DataChannelPair, didReceiveDataPacket dataPacket: Livekit_DataPacket)
}

class DataChannelPair: NSObject, Loggable {
class DataChannelPair: NSObject, @unchecked Sendable, Loggable {
// MARK: - Public

public let delegates = MulticastDelegate<DataChannelDelegate>(label: "DataChannelDelegate")
Expand All @@ -48,6 +48,8 @@
guard let lossy, let reliable else { return false }
return reliable.readyState == .open && lossy.readyState == .open
}

var eventContinuation: AsyncStream<ChannelEvent>.Continuation?
}

private let _state: StateSync<State>
Expand All @@ -61,24 +63,22 @@
var amount: UInt64 = 0
}

private struct PublishDataRequest {
private struct PublishDataRequest: Sendable {
let data: LKRTCDataBuffer
let continuation: CheckedContinuation<Void, any Error>?
}

private struct ChannelEvent {
private struct ChannelEvent: Sendable {
let channelKind: ChannelKind
let detail: Detail

enum Detail {
enum Detail: Sendable {
case publishData(PublishDataRequest)
case bufferedAmountChanged(UInt64)
}
}

private var eventContinuation: AsyncStream<ChannelEvent>.Continuation?

@Sendable private func handleEvents(
private func handleEvents(
events: AsyncStream<ChannelEvent>
) async {
var lossyBuffering = BufferingState()
Expand Down Expand Up @@ -175,7 +175,7 @@

Task {
let eventStream = AsyncStream<ChannelEvent> { continuation in
self.eventContinuation = continuation
_state.mutate { $0.eventContinuation = continuation }
}
await handleEvents(events: eventStream)
}
Expand Down Expand Up @@ -241,7 +241,7 @@
channelKind: ChannelKind(packet.kind), // TODO: field is deprecated
detail: .publishData(request)
)
eventContinuation?.yield(event)
_state.eventContinuation?.yield(event)
}
}

Expand All @@ -255,7 +255,7 @@
private static let lossyLowThreshold: UInt64 = reliableLowThreshold

deinit {
eventContinuation?.finish()
_state.eventContinuation?.finish()
}
}

Expand All @@ -267,7 +267,7 @@
channelKind: dataChannel.kind,
detail: .bufferedAmountChanged(amount)
)
eventContinuation?.yield(event)
_state.eventContinuation?.yield(event)
}

func dataChannelDidChangeState(_: LKRTCDataChannel) {
Expand All @@ -277,7 +277,7 @@
}

func dataChannel(_: LKRTCDataChannel, didReceiveMessageWith buffer: LKRTCDataBuffer) {
guard let dataPacket = try? Livekit_DataPacket(serializedData: buffer.data) else {

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS,variant=Mac Catalyst)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, iOS Simulator,OS=18.1,name=iPhone 16 Pro, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, iOS Simulator,OS=18.1,name=iPhone 16 Pro, true)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, macOS,variant=Mac Catalyst)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, iOS Simulator,OS=17.5,name=iPhone 15 Pro)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, tvOS Simulator,name=Apple TV)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, macOS)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'

Check warning on line 280 in Sources/LiveKit/Core/DataChannelPair.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, visionOS Simulator,name=Apple Vision Pro)

'init(serializedData:extensions:partial:options:)' is deprecated: replaced by 'init(serializedBytes:extensions:partial:options:)'
log("Could not decode data message", .error)
return
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/LiveKit/Core/RPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ let MAX_RPC_PAYLOAD_BYTES = 15360 // 15 KB
/// Throwing an `RpcError` will send the error back to the requester.
///
/// - SeeAlso: `LocalParticipant.registerRpcMethod`
public typealias RpcHandler = (RpcInvocationData) async throws -> String
public typealias RpcHandler = @Sendable (RpcInvocationData) async throws -> String

public struct RpcInvocationData {
/// A unique identifier for this RPC request
Expand All @@ -136,9 +136,9 @@ public struct RpcInvocationData {
public let responseTimeout: TimeInterval
}

struct PendingRpcResponse {
struct PendingRpcResponse: Sendable {
let participantIdentity: Participant.Identity
let onResolve: (_ payload: String?, _ error: RpcError?) -> Void
let onResolve: @Sendable (_ payload: String?, _ error: RpcError?) -> Void
}

actor RpcStateManager: Loggable {
Expand Down
4 changes: 2 additions & 2 deletions Sources/LiveKit/Core/Room+Engine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal import LiveKitWebRTC
extension Room {
// MARK: - Public

typealias ConditionEvalFunc = (_ newState: State, _ oldState: State?) -> Bool
typealias ConditionEvalFunc = @Sendable (_ newState: State, _ oldState: State?) -> Bool

// MARK: - Private

Expand Down Expand Up @@ -207,7 +207,7 @@ extension Room {
extension Room {
func execute(when condition: @escaping ConditionEvalFunc,
removeWhen removeCondition: @escaping ConditionEvalFunc,
_ block: @escaping () -> Void)
_ block: @Sendable @escaping () -> Void)
{
// already matches condition, execute immediately
if _state.read({ condition($0, nil) }) {
Expand Down
2 changes: 1 addition & 1 deletion Sources/LiveKit/Core/Room+EngineDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ extension Room {
}

// Notify change when engine's state mutates
Task.detached { @MainActor in
Task { @MainActor in
self.objectWillChange.send()
}
}
Expand Down
10 changes: 6 additions & 4 deletions Sources/LiveKit/Core/Room.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Network
#endif

@objc
public class Room: NSObject, ObservableObject, Loggable {
public class Room: NSObject, @unchecked Sendable, ObservableObject, Loggable {
// MARK: - MulticastDelegate

public let delegates = MulticastDelegate<RoomDelegate>(label: "RoomDelegate")
Expand Down Expand Up @@ -133,7 +133,7 @@ public class Room: NSObject, ObservableObject, Loggable {

// MARK: - State

struct State: Equatable {
struct State: Equatable, Sendable {
// Options
var connectOptions: ConnectOptions
var roomOptions: RoomOptions
Expand Down Expand Up @@ -228,7 +228,9 @@ public class Room: NSObject, ObservableObject, Loggable {
}

// listen to app states
AppStateListener.shared.delegates.add(delegate: self)
Task { @MainActor in
AppStateListener.shared.delegates.add(delegate: self)
}

// trigger events when state mutates
_state.onDidMutate = { [weak self] newState, oldState in
Expand Down Expand Up @@ -290,7 +292,7 @@ public class Room: NSObject, ObservableObject, Loggable {
}

// Notify Room when state mutates
Task.detached { @MainActor in
Task { @MainActor in
self.objectWillChange.send()
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/LiveKit/E2EE/E2EEManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal import LiveKitWebRTC
#endif

@objc
public class E2EEManager: NSObject, ObservableObject, Loggable {
public class E2EEManager: NSObject, @unchecked Sendable, ObservableObject, Loggable {
// Private delegate adapter to hide RTCFrameCryptorDelegate symbol
private class DelegateAdapter: NSObject, LKRTCFrameCryptorDelegate {
weak var target: E2EEManager?
Expand Down
36 changes: 29 additions & 7 deletions Sources/LiveKit/Extensions/Sendable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,36 @@ internal import LiveKitWebRTC
@_implementationOnly import LiveKitWebRTC
#endif

// Immutable classes.
// MARK: Immutable classes

extension LKRTCDataBuffer: @unchecked Swift.Sendable {}
extension LKRTCDataChannel: @unchecked Swift.Sendable {}
extension LKRTCFrameCryptorKeyProvider: @unchecked Swift.Sendable {}
extension LKRTCIceCandidate: @unchecked Swift.Sendable {}
extension LKRTCMediaConstraints: @unchecked Swift.Sendable {}
extension LKRTCMediaStream: @unchecked Swift.Sendable {}
extension LKRTCMediaStreamTrack: @unchecked Swift.Sendable {}
extension LKRTCPeerConnection: @unchecked Swift.Sendable {}
extension LKRTCPeerConnectionFactory: @unchecked Swift.Sendable {}
extension LKRTCRtpReceiver: @unchecked Swift.Sendable {}
extension LKRTCRtpSender: @unchecked Swift.Sendable {}
extension LKRTCRtpTransceiver: @unchecked Swift.Sendable {}
extension LKRTCRtpTransceiverInit: @unchecked Swift.Sendable {}
extension LKRTCSessionDescription: @unchecked Swift.Sendable {}
extension LKRTCVideoFrame: @unchecked Swift.Sendable {}
extension LKRTCStatisticsReport: @unchecked Swift.Sendable {}
extension LKRTCIceCandidate: @unchecked Swift.Sendable {}
extension LKRTCVideoCodecInfo: @unchecked Swift.Sendable {}
extension LKRTCFrameCryptorKeyProvider: @unchecked Swift.Sendable {}
extension LKRTCRtpTransceiver: @unchecked Swift.Sendable {}
extension LKRTCRtpTransceiverInit: @unchecked Swift.Sendable {}
extension LKRTCPeerConnectionFactory: @unchecked Swift.Sendable {}
extension LKRTCVideoFrame: @unchecked Swift.Sendable {}

// MARK: Mutable classes - to be validated

extension LKRTCConfiguration: @unchecked Swift.Sendable {}
extension LKRTCVideoCapturer: @unchecked Swift.Sendable {}

// MARK: Foundation

extension KeyPath: @unchecked Swift.Sendable where Root: Sendable, Value: Sendable {}

// MARK: Collections

extension NSHashTable: @unchecked Swift.Sendable {} // cannot specify Obj-C generics
extension Dictionary: Swift.Sendable where Key: Sendable, Value: Sendable {}
2 changes: 1 addition & 1 deletion Sources/LiveKit/Participant/Participant+Kind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

public extension Participant {
@objc
enum Kind: Int {
enum Kind: Int, Sendable {
case unknown
/// Standard participants, e.g. web clients.
case standard
Expand Down
2 changes: 1 addition & 1 deletion Sources/LiveKit/Participant/Participant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public class Participant: NSObject, @unchecked Sendable, ObservableObject, Logga

// MARK: - Internal

struct State: Equatable, Hashable {
struct State: Equatable, Hashable, Sendable {
var sid: Sid?
var identity: Identity?
var name: String?
Expand Down
4 changes: 2 additions & 2 deletions Sources/LiveKit/Support/AsyncCompleter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import Foundation

/// Manages a map of AsyncCompleters
actor CompleterMapActor<T> {
actor CompleterMapActor<T: Sendable> {
// MARK: - Public

public nonisolated let label: String
Expand Down Expand Up @@ -63,7 +63,7 @@ actor CompleterMapActor<T> {
}
}

class AsyncCompleter<T>: Loggable {
final class AsyncCompleter<T: Sendable>: @unchecked Sendable, Loggable {
//
struct WaitEntry {
let continuation: UnsafeContinuation<T, Error>
Expand Down
2 changes: 1 addition & 1 deletion Sources/LiveKit/Support/AsyncDebounce.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ actor Debounce {
_task?.cancel()
}

func schedule(_ action: @escaping () async throws -> Void) {
func schedule(_ action: @Sendable @escaping () async throws -> Void) {
_task?.cancel()
_task = Task.detached(priority: .utility) {
try? await Task.sleep(nanoseconds: UInt64(self._delay * 1_000_000_000))
Expand Down
2 changes: 1 addition & 1 deletion Sources/LiveKit/Support/AsyncRetry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ extension Task where Failure == Error {
priority: TaskPriority? = nil,
totalAttempts: Int = 3,
retryDelay: TimeInterval = 1,
@_implicitSelfCapture operation: @escaping (_ currentAttempt: Int, _ totalAttempts: Int) async throws -> Success
@_implicitSelfCapture operation: @Sendable @escaping (_ currentAttempt: Int, _ totalAttempts: Int) async throws -> Success
) -> Task {
Task(priority: priority) {
for currentAttempt in 1 ..< max(1, totalAttempts) {
Expand Down
6 changes: 3 additions & 3 deletions Sources/LiveKit/Support/AsyncTimer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@

import Foundation

class AsyncTimer: Loggable {
final class AsyncTimer: Sendable, Loggable {
// MARK: - Public types

typealias TimerBlock = () async throws -> Void
typealias TimerBlock = @Sendable () async throws -> Void

// MARK: - Private

struct State {
struct State: Sendable {
var isStarted: Bool = false
var interval: TimeInterval
var task: Task<Void, Never>?
Expand Down
4 changes: 2 additions & 2 deletions Sources/LiveKit/Support/AudioMixRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import Foundation
/// Audio sources can be added or removed at any time, including while recording is active.
/// When no audio is being provided by any source, the recorder will capture silence.

public class AudioMixRecorder: Loggable {
public class AudioMixRecorder: Loggable, @unchecked Sendable {
// MARK: - Public

/// The format used internally by engine & recorder.
Expand Down Expand Up @@ -243,7 +243,7 @@ public class AudioMixRecorder: Loggable {
}
}

public class AudioMixRecorderSource: Loggable, AudioRenderer {
public class AudioMixRecorderSource: Loggable, AudioRenderer, @unchecked Sendable {
public let processingFormat: AVAudioFormat
let playerNode = AVAudioPlayerNode()

Expand Down
8 changes: 2 additions & 6 deletions Sources/LiveKit/Support/DeviceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,13 @@
* limitations under the License.
*/

import AVFoundation
@preconcurrency import AVFoundation

// Internal-only for now
class DeviceManager: Loggable {
class DeviceManager: @unchecked Sendable, Loggable {
// MARK: - Public

#if compiler(>=6.0)
public nonisolated(unsafe) static let shared = DeviceManager()
#else
public static let shared = DeviceManager()
#endif

public static func prepare() {
// Instantiate shared instance
Expand Down
4 changes: 2 additions & 2 deletions Sources/LiveKit/Support/QueueActor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

import Foundation

actor QueueActor<T>: Loggable {
typealias OnProcess = (T) async -> Void
actor QueueActor<T: Sendable>: Loggable {
typealias OnProcess = @Sendable (T) async -> Void

// MARK: - Public

Expand Down
4 changes: 2 additions & 2 deletions Sources/LiveKit/Support/Stopwatch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

import Foundation

public struct Stopwatch {
public struct Entry: Equatable {
public struct Stopwatch: Sendable {
public struct Entry: Equatable, Sendable {
let label: String
let time: TimeInterval
}
Expand Down
Loading
Loading