diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 37463d73c..7c339aff7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -176,7 +176,12 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v4 + - name: Install SwiftLint + run: brew install swiftlint + - name: SwiftLint + run: swiftlint lint --strict --reporter github-actions-logging - name: SwiftFormat Lint + if: always() run: swiftformat --lint . --reporter github-actions-log # Comes pre-installed on macOS runners build-docs: diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 000000000..5569d7e10 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,17 @@ +excluded: + - Sources/LiveKit/Protos + +disabled_rules: + - todo + - nesting + - identifier_name + - multiple_closures_with_trailing_closure + # SwiftFormat conflicts + - trailing_comma + - line_length + - opening_brace + +type_name: + excluded: + - ID + - OS diff --git a/Sources/LiveKit/Audio/AudioDeviceModuleDelegateAdapter.swift b/Sources/LiveKit/Audio/AudioDeviceModuleDelegateAdapter.swift index 37c4c887f..3c431c46e 100644 --- a/Sources/LiveKit/Audio/AudioDeviceModuleDelegateAdapter.swift +++ b/Sources/LiveKit/Audio/AudioDeviceModuleDelegateAdapter.swift @@ -70,12 +70,14 @@ class AudioDeviceModuleDelegateAdapter: NSObject, LKRTCAudioDeviceModuleDelegate return entryPoint?.engineWillRelease(engine) ?? 0 } + // swiftlint:disable:next function_parameter_count func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureInputFromSource src: AVAudioNode?, toDestination dst: AVAudioNode, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int { guard let audioManager else { return 0 } let entryPoint = audioManager.buildEngineObserverChain() return entryPoint?.engineWillConnectInput(engine, src: src, dst: dst, format: format, context: context) ?? 0 } + // swiftlint:disable:next function_parameter_count func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureOutputFromSource src: AVAudioNode, toDestination dst: AVAudioNode?, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int { guard let audioManager else { return 0 } let entryPoint = audioManager.buildEngineObserverChain() diff --git a/Sources/LiveKit/Audio/Manager/AudioManager.swift b/Sources/LiveKit/Audio/Manager/AudioManager.swift index 0970dc32c..ead30da89 100644 --- a/Sources/LiveKit/Audio/Manager/AudioManager.swift +++ b/Sources/LiveKit/Audio/Manager/AudioManager.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + import Accelerate import AVFoundation import Combine diff --git a/Sources/LiveKit/Broadcast/IPC/IPCChannel.swift b/Sources/LiveKit/Broadcast/IPC/IPCChannel.swift index d30b5b108..72b698504 100644 --- a/Sources/LiveKit/Broadcast/IPC/IPCChannel.swift +++ b/Sources/LiveKit/Broadcast/IPC/IPCChannel.swift @@ -114,7 +114,7 @@ final class IPCChannel: Sendable { guard let rawMessage = try await upstream.next() else { return nil } - let (data, context, isComplete) = rawMessage + let (data, context, isComplete) = (rawMessage.data, rawMessage.context, rawMessage.isComplete) guard let data, isComplete else { return nil } guard let payloadSize = context?.ipcMessagePayloadSize, @@ -223,7 +223,11 @@ private extension NWConnection { AsyncMessageSequence(connection: self) } - typealias IncomingMessage = (Data?, NWConnection.ContentContext?, Bool) + struct IncomingMessage { + let data: Data? + let context: NWConnection.ContentContext? + let isComplete: Bool + } struct AsyncMessageSequence: AsyncSequence, AsyncIteratorProtocol { let connection: NWConnection @@ -243,7 +247,7 @@ private extension NWConnection { try await withCheckedThrowingContinuation { [weak self] continuation in self?.receiveMessage { data, context, isComplete, error in guard let error else { - continuation.resume(returning: (data, context, isComplete)) + continuation.resume(returning: IncomingMessage(data: data, context: context, isComplete: isComplete)) return } continuation.resume(throwing: error) diff --git a/Sources/LiveKit/Broadcast/IPC/IPCProtocol.swift b/Sources/LiveKit/Broadcast/IPC/IPCProtocol.swift index ddced0d9f..1586be5a6 100644 --- a/Sources/LiveKit/Broadcast/IPC/IPCProtocol.swift +++ b/Sources/LiveKit/Broadcast/IPC/IPCProtocol.swift @@ -47,7 +47,7 @@ final class IPCProtocol: NWProtocolFramerImplementation, Loggable { func handleInput(framer: NWProtocolFramer.Instance) -> Int { while true { - var tempHeader: Header? = nil + var tempHeader: Header? let parsed = framer.parseInput( minimumIncompleteLength: Header.encodedSize, maximumLength: Header.encodedSize diff --git a/Sources/LiveKit/Core/DataChannelPair.swift b/Sources/LiveKit/Core/DataChannelPair.swift index 1c3ad01d9..d335bfde7 100644 --- a/Sources/LiveKit/Core/DataChannelPair.swift +++ b/Sources/LiveKit/Core/DataChannelPair.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + import DequeModule import Foundation @@ -21,11 +23,12 @@ internal import LiveKitWebRTC // MARK: - Internal delegate -protocol DataChannelDelegate: Sendable { +protocol DataChannelDelegate: AnyObject, Sendable { func dataChannel(_ dataChannelPair: DataChannelPair, didReceiveDataPacket dataPacket: Livekit_DataPacket) func dataChannel(_ dataChannelPair: DataChannelPair, didFailToDecryptDataPacket dataPacket: Livekit_DataPacket, error: LiveKitError) } +// swiftlint:disable:next type_body_length class DataChannelPair: NSObject, @unchecked Sendable, Loggable { // MARK: - Public @@ -133,6 +136,7 @@ class DataChannelPair: NSObject, @unchecked Sendable, Loggable { // MARK: - Event handling + // swiftlint:disable:next cyclomatic_complexity private func handleEvents( events: AsyncStream ) async { @@ -476,7 +480,7 @@ private extension DataChannelPair.ChannelKind { private extension LKRTCDataChannel { var kind: DataChannelPair.ChannelKind { - guard label == LKRTCDataChannel.labels.lossy else { + guard label == LKRTCDataChannel.Labels.lossy else { return .reliable } return .lossy diff --git a/Sources/LiveKit/Core/Room+DataStream.swift b/Sources/LiveKit/Core/Room+DataStream.swift index 1bc3abea2..d5dd62fc5 100644 --- a/Sources/LiveKit/Core/Room+DataStream.swift +++ b/Sources/LiveKit/Core/Room+DataStream.swift @@ -69,8 +69,7 @@ public extension Room { onError: (@Sendable (Error) -> Void)? ) { Task { - do { try await registerByteStreamHandler(for: topic, onNewStream: onNewStream) } - catch { onError?(error) } + do { try await registerByteStreamHandler(for: topic, onNewStream: onNewStream) } catch { onError?(error) } } } @@ -82,8 +81,7 @@ public extension Room { onError: (@Sendable (Error) -> Void)? ) { Task { - do { try await registerTextStreamHandler(for: topic, onNewStream: onNewStream) } - catch { onError?(error) } + do { try await registerTextStreamHandler(for: topic, onNewStream: onNewStream) } catch { onError?(error) } } } } diff --git a/Sources/LiveKit/Core/Room+Engine.swift b/Sources/LiveKit/Core/Room+Engine.swift index 286cdf3a7..eec036cee 100644 --- a/Sources/LiveKit/Core/Room+Engine.swift +++ b/Sources/LiveKit/Core/Room+Engine.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + import Foundation #if canImport(Network) @@ -113,6 +115,7 @@ extension Room { // MARK: - Internal extension Room { + // swiftlint:disable:next function_body_length func configureTransports(connectResponse: SignalClient.ConnectResponse) async throws { func makeConfiguration() -> LKRTCConfiguration { let connectOptions = _state.connectOptions @@ -169,10 +172,10 @@ extension Room { // data over pub channel for backwards compatibility - let reliableDataChannel = await publisher.dataChannel(for: LKRTCDataChannel.labels.reliable, + let reliableDataChannel = await publisher.dataChannel(for: LKRTCDataChannel.Labels.reliable, configuration: RTC.createDataChannelConfiguration()) - let lossyDataChannel = await publisher.dataChannel(for: LKRTCDataChannel.labels.lossy, + let lossyDataChannel = await publisher.dataChannel(for: LKRTCDataChannel.Labels.lossy, configuration: RTC.createDataChannelConfiguration(ordered: false, maxRetransmits: 0)) publisherDataChannel.set(reliable: reliableDataChannel) @@ -268,6 +271,7 @@ extension Room { log("\(_state.connectStopwatch)") } + // swiftlint:disable:next cyclomatic_complexity function_body_length func startReconnect(reason: StartReconnectReason, nextReconnectMode: ReconnectMode? = nil) async throws { log("[Connect] Starting, reason: \(reason)") diff --git a/Sources/LiveKit/Core/Room+EngineDelegate.swift b/Sources/LiveKit/Core/Room+EngineDelegate.swift index 3c30b0863..2e5da1487 100644 --- a/Sources/LiveKit/Core/Room+EngineDelegate.swift +++ b/Sources/LiveKit/Core/Room+EngineDelegate.swift @@ -19,6 +19,7 @@ import Foundation internal import LiveKitWebRTC extension Room { + // swiftlint:disable:next cyclomatic_complexity function_body_length func engine(_: Room, didMutateState state: Room.State, oldState: Room.State) { if state.connectionState != oldState.connectionState { // connectionState did update diff --git a/Sources/LiveKit/Core/Room+TransportDelegate.swift b/Sources/LiveKit/Core/Room+TransportDelegate.swift index 515210061..9df9e9b70 100644 --- a/Sources/LiveKit/Core/Room+TransportDelegate.swift +++ b/Sources/LiveKit/Core/Room+TransportDelegate.swift @@ -108,8 +108,8 @@ extension Room: TransportDelegate { if _state.isSubscriberPrimary, transport.target == .subscriber { switch dataChannel.label { - case LKRTCDataChannel.labels.reliable: subscriberDataChannel.set(reliable: dataChannel) - case LKRTCDataChannel.labels.lossy: subscriberDataChannel.set(lossy: dataChannel) + case LKRTCDataChannel.Labels.reliable: subscriberDataChannel.set(reliable: dataChannel) + case LKRTCDataChannel.Labels.lossy: subscriberDataChannel.set(lossy: dataChannel) default: log("Unknown data channel label \(dataChannel.label)", .warning) } } diff --git a/Sources/LiveKit/Core/Room.swift b/Sources/LiveKit/Core/Room.swift index 2f133eb7b..9bf663cfa 100644 --- a/Sources/LiveKit/Core/Room.swift +++ b/Sources/LiveKit/Core/Room.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + import Combine import Foundation @@ -22,6 +24,7 @@ import Network #endif @objc +// swiftlint:disable:next type_body_length public class Room: NSObject, @unchecked Sendable, ObservableObject, Loggable { // MARK: - MulticastDelegate @@ -213,6 +216,7 @@ public class Room: NSObject, @unchecked Sendable, ObservableObject, Loggable { } @objc + // swiftlint:disable:next cyclomatic_complexity function_body_length public init(delegate: RoomDelegate? = nil, connectOptions: ConnectOptions? = nil, roomOptions: RoomOptions? = nil) @@ -312,6 +316,7 @@ public class Room: NSObject, @unchecked Sendable, ObservableObject, Loggable { } @objc + // swiftlint:disable:next function_body_length public func connect(url: String, token: String, connectOptions: ConnectOptions? = nil, diff --git a/Sources/LiveKit/Core/SignalClient.swift b/Sources/LiveKit/Core/SignalClient.swift index a9faf8689..26013a3b8 100644 --- a/Sources/LiveKit/Core/SignalClient.swift +++ b/Sources/LiveKit/Core/SignalClient.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + import Foundation internal import LiveKitWebRTC @@ -107,6 +109,7 @@ actor SignalClient: Loggable { } @discardableResult + // swiftlint:disable:next function_body_length func connect(_ url: URL, _ token: String, connectOptions: ConnectOptions? = nil, @@ -269,6 +272,7 @@ private extension SignalClient { } } + // swiftlint:disable:next cyclomatic_complexity function_body_length func _process(signalResponse: Livekit_SignalResponse) async { guard connectionState != .disconnected else { log("connectionState is .disconnected", .error) diff --git a/Sources/LiveKit/DataStream/Outgoing/ByteStreamWriter.swift b/Sources/LiveKit/DataStream/Outgoing/ByteStreamWriter.swift index edeb39103..0079710a1 100644 --- a/Sources/LiveKit/DataStream/Outgoing/ByteStreamWriter.swift +++ b/Sources/LiveKit/DataStream/Outgoing/ByteStreamWriter.swift @@ -79,8 +79,7 @@ public extension ByteStreamWriter { @available(*, unavailable, message: "Use async write(_:) method instead.") func write(_ data: Data, completion: @Sendable @escaping (Error?) -> Void) { Task { - do { try await write(data) } - catch { completion(error) } + do { try await write(data) } catch { completion(error) } } } @@ -88,8 +87,7 @@ public extension ByteStreamWriter { @available(*, unavailable, message: "Use async close(reason:) method instead.") func close(reason: String?, completion: @Sendable @escaping (Error?) -> Void) { Task { - do { try await close(reason: reason) } - catch { completion(error) } + do { try await close(reason: reason) } catch { completion(error) } } } } diff --git a/Sources/LiveKit/DataStream/Outgoing/TextStreamWriter.swift b/Sources/LiveKit/DataStream/Outgoing/TextStreamWriter.swift index 46e496f21..474ed7191 100644 --- a/Sources/LiveKit/DataStream/Outgoing/TextStreamWriter.swift +++ b/Sources/LiveKit/DataStream/Outgoing/TextStreamWriter.swift @@ -64,8 +64,7 @@ public extension TextStreamWriter { @available(*, unavailable, message: "Use async write(_:) method instead.") func write(_ text: String, onCompletion: @Sendable @escaping (Error?) -> Void) { Task { - do { try await write(text) } - catch { onCompletion(error) } + do { try await write(text) } catch { onCompletion(error) } } } @@ -73,8 +72,7 @@ public extension TextStreamWriter { @available(*, unavailable, message: "Use async close(reason:) method instead.") func close(reason: String?, onCompletion: @Sendable @escaping (Error?) -> Void) { Task { - do { try await close(reason: reason) } - catch { onCompletion(error) } + do { try await close(reason: reason) } catch { onCompletion(error) } } } } diff --git a/Sources/LiveKit/Extensions/LKRTCRtpSender.swift b/Sources/LiveKit/Extensions/LKRTCRtpSender.swift index ceba0f64f..8d4fa7cb7 100644 --- a/Sources/LiveKit/Extensions/LKRTCRtpSender.swift +++ b/Sources/LiveKit/Extensions/LKRTCRtpSender.swift @@ -28,7 +28,7 @@ extension LKRTCRtpSender: Loggable { // For SVC mode... if let firstEncoding = encodings.first, - let _ = ScalabilityMode.fromString(firstEncoding.scalabilityMode) + ScalabilityMode.fromString(firstEncoding.scalabilityMode) != nil { let _enabled = qualities.highest != .off if firstEncoding.isActive != _enabled { diff --git a/Sources/LiveKit/Extensions/PixelBuffer.swift b/Sources/LiveKit/Extensions/PixelBuffer.swift index 5c65c0a0f..30bb87f69 100644 --- a/Sources/LiveKit/Extensions/PixelBuffer.swift +++ b/Sources/LiveKit/Extensions/PixelBuffer.swift @@ -60,6 +60,7 @@ public extension CVPixelBuffer { } public extension CMSampleBuffer { + // swiftlint:disable:next cyclomatic_complexity function_body_length static func from(_ pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? { var sampleBuffer: CMSampleBuffer? @@ -179,6 +180,7 @@ public extension Data { extension OSType { // Get string representation of CVPixelFormatType + // swiftlint:disable:next function_body_length func toString() -> String { let types = [ kCVPixelFormatType_TwoComponent8: "kCVPixelFormatType_TwoComponent8", diff --git a/Sources/LiveKit/Extensions/RTCDataChannel+Util.swift b/Sources/LiveKit/Extensions/RTCDataChannel+Util.swift index 7acaba58b..8c0ab8ba1 100644 --- a/Sources/LiveKit/Extensions/RTCDataChannel+Util.swift +++ b/Sources/LiveKit/Extensions/RTCDataChannel+Util.swift @@ -19,7 +19,7 @@ import Foundation internal import LiveKitWebRTC extension LKRTCDataChannel { - enum labels { + enum Labels { static let reliable = "_reliable" static let lossy = "_lossy" } diff --git a/Sources/LiveKit/Extensions/RTCI420Buffer.swift b/Sources/LiveKit/Extensions/RTCI420Buffer.swift index 02604d948..77fac3a2d 100644 --- a/Sources/LiveKit/Extensions/RTCI420Buffer.swift +++ b/Sources/LiveKit/Extensions/RTCI420Buffer.swift @@ -19,6 +19,7 @@ import Foundation internal import LiveKitWebRTC extension LKRTCI420Buffer { + // swiftlint:disable:next function_body_length func toPixelBuffer() -> CVPixelBuffer? { // default options let options = [ diff --git a/Sources/LiveKit/LiveKit+DeviceHelpers.swift b/Sources/LiveKit/LiveKit+DeviceHelpers.swift index d20ad7788..64ddf0883 100644 --- a/Sources/LiveKit/LiveKit+DeviceHelpers.swift +++ b/Sources/LiveKit/LiveKit+DeviceHelpers.swift @@ -44,7 +44,11 @@ public extension LiveKitSDK { /// Blocking version of ensureDeviceAccess that uses DispatchGroup to wait for permissions. static func ensureDeviceAccessSync(for types: Set) -> Bool { let group = DispatchGroup() - var result = true + #if swift(>=6.0) + nonisolated(unsafe) var result = false + #else + var result = false + #endif for type in types { if ![.video, .audio].contains(type) { diff --git a/Sources/LiveKit/Participant/LocalParticipant+DataStream.swift b/Sources/LiveKit/Participant/LocalParticipant+DataStream.swift index 27fc2d387..5a8f0a44a 100644 --- a/Sources/LiveKit/Participant/LocalParticipant+DataStream.swift +++ b/Sources/LiveKit/Participant/LocalParticipant+DataStream.swift @@ -125,8 +125,7 @@ public extension LocalParticipant { onError: (@Sendable (Error) -> Void)? ) { Task { - do { try await onCompletion(sendText(text, options: options)) } - catch { onError?(error) } + do { try await onCompletion(sendText(text, options: options)) } catch { onError?(error) } } } @@ -139,8 +138,7 @@ public extension LocalParticipant { onError: (@Sendable (Error) -> Void)? ) { Task { - do { try await onCompletion(sendFile(fileURL, options: options)) } - catch { onError?(error) } + do { try await onCompletion(sendFile(fileURL, options: options)) } catch { onError?(error) } } } @@ -152,8 +150,7 @@ public extension LocalParticipant { onError: (@Sendable (Error) -> Void)? ) { Task { - do { try await streamHandler(streamText(options: options)) } - catch { onError?(error) } + do { try await streamHandler(streamText(options: options)) } catch { onError?(error) } } } @@ -165,8 +162,7 @@ public extension LocalParticipant { onError: (@Sendable (Error) -> Void)? ) { Task { - do { try await streamHandler(streamBytes(options: options)) } - catch { onError?(error) } + do { try await streamHandler(streamBytes(options: options)) } catch { onError?(error) } } } } diff --git a/Sources/LiveKit/Participant/LocalParticipant+RPC.swift b/Sources/LiveKit/Participant/LocalParticipant+RPC.swift index b3a3286a9..b45cef27b 100644 --- a/Sources/LiveKit/Participant/LocalParticipant+RPC.swift +++ b/Sources/LiveKit/Participant/LocalParticipant+RPC.swift @@ -185,6 +185,7 @@ extension LocalParticipant { try await room.send(dataPacket: dataPacket) } + // swiftlint:disable:next function_body_length function_parameter_count func handleIncomingRpcRequest(callerIdentity: Identity, requestId: String, method: String, diff --git a/Sources/LiveKit/Participant/LocalParticipant.swift b/Sources/LiveKit/Participant/LocalParticipant.swift index 5ebda4a63..8159bd9d5 100644 --- a/Sources/LiveKit/Participant/LocalParticipant.swift +++ b/Sources/LiveKit/Participant/LocalParticipant.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + import Combine import Foundation @@ -341,6 +343,7 @@ public extension LocalParticipant { @objc @discardableResult + // swiftlint:disable:next cyclomatic_complexity function_body_length func set(source: Track.Source, enabled: Bool, captureOptions: CaptureOptions? = nil, @@ -505,6 +508,7 @@ extension [Livekit_SubscribedQuality] { extension LocalParticipant { @discardableResult + // swiftlint:disable:next cyclomatic_complexity function_body_length func _publish(track: LocalTrack, options: TrackPublishOptions? = nil) async throws -> LocalTrackPublication { log("[publish] \(track) options: \(String(describing: options ?? nil))...", .info) @@ -529,7 +533,7 @@ extension LocalParticipant { do { var dimensions: Dimensions? // Only for Video - var publishName: String? = nil + var publishName: String? var sendEncodings: [LKRTCRtpEncodingParameters]? var populatorFunc: SignalClient.AddTrackRequestPopulator? diff --git a/Sources/LiveKit/Participant/Participant.swift b/Sources/LiveKit/Participant/Participant.swift index 310c7654f..c6b577f38 100644 --- a/Sources/LiveKit/Participant/Participant.swift +++ b/Sources/LiveKit/Participant/Participant.swift @@ -114,6 +114,7 @@ public class Participant: NSObject, @unchecked Sendable, ObservableObject, Logga let _publishSerialRunner = SerialRunnerActor() + // swiftlint:disable:next cyclomatic_complexity function_body_length init(room: Room, sid: Sid? = nil, identity: Identity? = nil) { _room = room diff --git a/Sources/LiveKit/Support/Logger.swift b/Sources/LiveKit/Support/Logger.swift index 7ed8223da..9549e39c9 100644 --- a/Sources/LiveKit/Support/Logger.swift +++ b/Sources/LiveKit/Support/Logger.swift @@ -24,6 +24,7 @@ public typealias ScopedMetadata = CustomStringConvertible public typealias ScopedMetadataContainer = [String: ScopedMetadata] public protocol Logger: Sendable { + // swiftlint:disable:next function_parameter_count func log( _ message: @autoclosure () -> CustomStringConvertible, _ level: LogLevel, @@ -55,6 +56,7 @@ public extension Logger { /// A no-op logger public struct DisabledLogger: Logger { @inlinable + // swiftlint:disable:next function_parameter_count public func log( _: @autoclosure () -> CustomStringConvertible, _: LogLevel, @@ -77,6 +79,7 @@ public struct PrintLogger: Logger { self.colors = colors } + // swiftlint:disable:next function_parameter_count public func log( _ message: @autoclosure () -> CustomStringConvertible, _ level: LogLevel, @@ -136,6 +139,7 @@ open class OSLogger: Logger, @unchecked Sendable { rtcLogger.stop() } + // swiftlint:disable:next function_parameter_count public func log( _ message: @autoclosure () -> CustomStringConvertible, _ level: LogLevel, diff --git a/Sources/LiveKit/Support/MapTable.swift b/Sources/LiveKit/Support/MapTable.swift index 737cea08b..ac8fe043b 100644 --- a/Sources/LiveKit/Support/MapTable.swift +++ b/Sources/LiveKit/Support/MapTable.swift @@ -22,7 +22,7 @@ final class MapTable: @unchecked Sendable where KeyType: An self.mapTable = mapTable } - class func weakToStrongObjects() -> MapTable { + static func weakToStrongObjects() -> MapTable { .init(.weakToStrongObjects()) } diff --git a/Sources/LiveKit/Support/Utils.swift b/Sources/LiveKit/Support/Utils.swift index 327b162fe..666f6bb28 100644 --- a/Sources/LiveKit/Support/Utils.swift +++ b/Sources/LiveKit/Support/Utils.swift @@ -280,10 +280,8 @@ func computeAttributesDiff(oldValues: [String: String], newValues: [String: Stri let allKeys = Set(oldValues.keys).union(newValues.keys) var diff = [String: String]() - for key in allKeys { - if oldValues[key] != newValues[key] { - diff[key] = newValues[key] ?? "" - } + for key in allKeys where oldValues[key] != newValues[key] { + diff[key] = newValues[key] ?? "" } return diff diff --git a/Sources/LiveKit/Track/Capturers/CameraCapturer.swift b/Sources/LiveKit/Track/Capturers/CameraCapturer.swift index b65e90652..716ac312a 100644 --- a/Sources/LiveKit/Track/Capturers/CameraCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/CameraCapturer.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + @preconcurrency import AVFoundation import Foundation @@ -149,6 +151,7 @@ public class CameraCapturer: VideoCapturer, @unchecked Sendable { return try await restartCapture() } + // swiftlint:disable:next cyclomatic_complexity function_body_length override public func startCapture() async throws -> Bool { let didStart = try await super.startCapture() @@ -199,7 +202,7 @@ public class CameraCapturer: VideoCapturer, @unchecked Sendable { // default to the largest supported dimensions (backup) var selectedFormat = sortedFormats.last - var selectedPredicateName: String? = nil + var selectedPredicateName: String? if let preferredFormat = options.preferredFormat, let foundFormat = sortedFormats.first(where: { $0.format == preferredFormat }) diff --git a/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift b/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift index 548f20521..32aebe3d8 100644 --- a/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + import AVFoundation import Foundation @@ -214,6 +216,7 @@ extension MacOSScreenCapturer: SCStreamDelegate { @available(macOS 12.3, *) extension MacOSScreenCapturer: SCStreamOutput { + // swiftlint:disable:next cyclomatic_complexity public func stream(_: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of outputType: SCStreamOutputType) { @@ -241,7 +244,7 @@ extension MacOSScreenCapturer: SCStreamOutput { // Retrieve the content rectangle, scale, and scale factor. guard let contentRectDict = attachments[.contentRect], - let contentRect = CGRect(dictionaryRepresentation: contentRectDict as! CFDictionary), + let contentRect = CGRect(dictionaryRepresentation: contentRectDict as! CFDictionary), // swiftlint:disable:this force_cast // let contentScale = attachments[.contentScale] as? CGFloat, let scaleFactor = attachments[.scaleFactor] as? CGFloat else { return } diff --git a/Sources/LiveKit/Track/Track.swift b/Sources/LiveKit/Track/Track.swift index 017605945..33d7ba986 100644 --- a/Sources/LiveKit/Track/Track.swift +++ b/Sources/LiveKit/Track/Track.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + import Foundation internal import LiveKitWebRTC diff --git a/Sources/LiveKit/Track/VideoTrack.swift b/Sources/LiveKit/Track/VideoTrack.swift index 6598567cf..d3da770c6 100644 --- a/Sources/LiveKit/Track/VideoTrack.swift +++ b/Sources/LiveKit/Track/VideoTrack.swift @@ -30,7 +30,7 @@ public protocol VideoTrackProtocol: AnyObject, Sendable { public typealias VideoTrack = Track & VideoTrackProtocol // Directly add/remove renderers for better performance -protocol VideoTrack_Internal where Self: Track { +protocol VideoTrackInternal where Self: Track { func add(rtcVideoRenderer: LKRTCVideoRenderer) func remove(rtcVideoRenderer: LKRTCVideoRenderer) diff --git a/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift b/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift index d218ff007..10e134453 100644 --- a/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift +++ b/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift @@ -167,8 +167,8 @@ public class RemoteTrackPublication: TrackPublication, @unchecked Sendable { // start adaptiveStream timer only if it's a video track if isAdaptiveStreamEnabled { - _asTimer.setTimerBlock { - [weak self] in await self?.onAdaptiveStreamTimer() + _asTimer.setTimerBlock { [weak self] in + await self?.onAdaptiveStreamTimer() } _asTimer.restart() } diff --git a/Sources/LiveKit/Types/Attributes/AttributeTypings.swift b/Sources/LiveKit/Types/Attributes/AttributeTypings.swift index 9fc56e609..3dce1667c 100644 --- a/Sources/LiveKit/Types/Attributes/AttributeTypings.swift +++ b/Sources/LiveKit/Types/Attributes/AttributeTypings.swift @@ -84,10 +84,9 @@ public enum AgentState: String, Codable, Sendable { case thinking } -/// Schema for transcription-related attributes - // MARK: - TranscriptionAttributes +/// Schema for transcription-related attributes struct TranscriptionAttributes: Codable, Sendable { /// The segment id of the transcription let lkSegmentID: String? diff --git a/Sources/LiveKit/Types/Statistics.swift b/Sources/LiveKit/Types/Statistics.swift index 640eb70bb..2954d8117 100644 --- a/Sources/LiveKit/Types/Statistics.swift +++ b/Sources/LiveKit/Types/Statistics.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + import Foundation /// Stats spec defined at https://www.w3.org/TR/webrtc-stats/ @@ -565,6 +567,7 @@ public class InboundRtpStreamStatistics: ReceivedRtpStreamStatistics { // Weak reference to previous stat so we can compare later. public weak var previous: InboundRtpStreamStatistics? + // swiftlint:disable:next function_body_length init?(id: String, timestamp: Double, rawValues: [String: NSObject], diff --git a/Sources/LiveKit/Types/TrackStatistics.swift b/Sources/LiveKit/Types/TrackStatistics.swift index 359957171..1c36e7efc 100644 --- a/Sources/LiveKit/Types/TrackStatistics.swift +++ b/Sources/LiveKit/Types/TrackStatistics.swift @@ -74,6 +74,7 @@ public class TrackStatistics: NSObject, @unchecked Sendable, Loggable { } extension LKRTCStatistics { + // swiftlint:disable:next cyclomatic_complexity func toLKType(prevStatistics: TrackStatistics?) -> Statistics? { switch type { case "codec": return CodecStatistics(id: id, timestamp: timestamp_us, rawValues: values) diff --git a/Sources/LiveKit/Views/VideoView.swift b/Sources/LiveKit/Views/VideoView.swift index 4f3c114b0..bbfe5eb0f 100644 --- a/Sources/LiveKit/Views/VideoView.swift +++ b/Sources/LiveKit/Views/VideoView.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + @preconcurrency import AVFoundation import MetalKit @@ -23,6 +25,7 @@ internal import LiveKitWebRTC typealias NativeRendererView = LKRTCVideoRenderer & Mirrorable & NativeViewType @objc +// swiftlint:disable:next type_body_length public class VideoView: NativeView, Loggable { // MARK: - MulticastDelegate @@ -142,13 +145,29 @@ public class VideoView: NativeView, Loggable { @objc public nonisolated var isPinchToZoomEnabled: Bool { get { _state.pinchToZoomOptions.isEnabled } - set { _state.mutate { $0.pinchToZoomOptions.insert(.zoomIn) } } + set { + _state.mutate { + if newValue { + $0.pinchToZoomOptions.insert(.zoomIn) + } else { + $0.pinchToZoomOptions.remove(.zoomIn) + } + } + } } @objc public nonisolated var isAutoZoomResetEnabled: Bool { get { _state.pinchToZoomOptions.contains(.resetOnRelease) } - set { _state.mutate { $0.pinchToZoomOptions.insert(.resetOnRelease) } } + set { + _state.mutate { + if newValue { + $0.pinchToZoomOptions.insert(.resetOnRelease) + } else { + $0.pinchToZoomOptions.remove(.resetOnRelease) + } + } + } } public nonisolated var pinchToZoomOptions: PinchToZoomOptions { @@ -249,6 +268,7 @@ public class VideoView: NativeView, Loggable { var _pinchStartZoomFactor: CGFloat = 0.0 #endif + // swiftlint:disable:next cyclomatic_complexity function_body_length override public init(frame: CGRect = .zero) { // initial state _state = StateSync(State(viewSize: frame.size)) @@ -408,6 +428,7 @@ public class VideoView: NativeView, Loggable { fatalError("init(coder:) has not been implemented") } + // swiftlint:disable:next cyclomatic_complexity function_body_length override public func performLayout() { super.performLayout() @@ -596,6 +617,7 @@ extension VideoView: VideoRenderer { } } + // swiftlint:disable:next function_body_length public func render(frame: VideoFrame, captureDevice: AVCaptureDevice?, captureOptions: VideoCaptureOptions?) { let state = _state.copy() diff --git a/Tests/LiveKitAudioTests/AudioEngineTests.swift b/Tests/LiveKitAudioTests/AudioEngineTests.swift index cfee93077..8620670f6 100644 --- a/Tests/LiveKitAudioTests/AudioEngineTests.swift +++ b/Tests/LiveKitAudioTests/AudioEngineTests.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +// swiftlint:disable file_length + @preconcurrency import AVFoundation @testable import LiveKit #if canImport(LiveKitTestSupport) diff --git a/Tests/LiveKitAudioTests/PublishDeviceOptimization.swift b/Tests/LiveKitAudioTests/PublishDeviceOptimization.swift index 895658177..5c8126f2b 100644 --- a/Tests/LiveKitAudioTests/PublishDeviceOptimization.swift +++ b/Tests/LiveKitAudioTests/PublishDeviceOptimization.swift @@ -45,7 +45,7 @@ class PublishDeviceOptimizationTests: LKTestCase { // No-VP publish flow func testNoVpMicPublish() async throws { // Turn off Apple's VP - try! AudioManager.shared.setVoiceProcessingEnabled(false) + try AudioManager.shared.setVoiceProcessingEnabled(false) var sw = Stopwatch(label: "Test: No-VP publish sequence") diff --git a/Tests/LiveKitCoreTests/AVFoundation/AVAudioPCMRingBufferTests.swift b/Tests/LiveKitCoreTests/AVFoundation/AVAudioPCMRingBufferTests.swift index 599076ca3..a3cfca2ff 100644 --- a/Tests/LiveKitCoreTests/AVFoundation/AVAudioPCMRingBufferTests.swift +++ b/Tests/LiveKitCoreTests/AVFoundation/AVAudioPCMRingBufferTests.swift @@ -97,7 +97,7 @@ final class AVAudioPCMRingBufferTests: LKTestCase { ringBuffer.append(audioBuffer: halfBuffer) // Read a quarter of the buffer - guard let _ = ringBuffer.read(frames: capacity / 4) else { + guard ringBuffer.read(frames: capacity / 4) != nil else { XCTFail("Failed to read quarter buffer") return } @@ -133,7 +133,7 @@ final class AVAudioPCMRingBufferTests: LKTestCase { // Fill buffer with test data let bufferList = UnsafeMutableAudioBufferListPointer(buffer.mutableAudioBufferList) - for audioBuffer in bufferList { + for audioBuffer in bufferList where audioBuffer.mData != nil { guard let data = audioBuffer.mData else { continue } // Fill with simple pattern (ramp from 0 to 1) @@ -159,10 +159,8 @@ final class AVAudioPCMRingBufferTests: LKTestCase { let floatData1 = data1.assumingMemoryBound(to: Float.self) let floatData2 = data2.assumingMemoryBound(to: Float.self) - for i in 0 ..< Int(buffer1.frameLength) { - if floatData1[i] != floatData2[i] { - return false - } + for i in 0 ..< Int(buffer1.frameLength) where floatData1[i] != floatData2[i] { + return false } } diff --git a/Tests/LiveKitCoreTests/AVFoundation/AudioMixRecorderTests.swift b/Tests/LiveKitCoreTests/AVFoundation/AudioMixRecorderTests.swift index 8e008bc74..bc6a7ba8a 100644 --- a/Tests/LiveKitCoreTests/AVFoundation/AudioMixRecorderTests.swift +++ b/Tests/LiveKitCoreTests/AVFoundation/AudioMixRecorderTests.swift @@ -41,6 +41,7 @@ final class AudioMixRecorderTests: LKTestCase { AVLinearPCMIsBigEndianKey: false, ] + // swiftlint:disable:next function_body_length func testRecord() async throws { // Sample audio 1 let audio1Url = URL(string: "https://github.com/audio-samples/audio-samples.github.io/raw/refs/heads/master/samples/mp3/music/sample-3.mp3")! diff --git a/Tests/LiveKitCoreTests/Broadcast/BroadcastAudioCodecTests.swift b/Tests/LiveKitCoreTests/Broadcast/BroadcastAudioCodecTests.swift index 6e9b89b1e..d7f05826a 100644 --- a/Tests/LiveKitCoreTests/Broadcast/BroadcastAudioCodecTests.swift +++ b/Tests/LiveKitCoreTests/Broadcast/BroadcastAudioCodecTests.swift @@ -53,6 +53,7 @@ final class BroadcastAudioCodecTests: XCTestCase { } } + // swiftlint:disable:next function_body_length private func createTestAudioBuffer() -> CMSampleBuffer? { let frames = 1024 let sampleRate: Float64 = 44100.0 diff --git a/Tests/LiveKitCoreTests/Proto/ProtoConverterTests.swift b/Tests/LiveKitCoreTests/Proto/ProtoConverterTests.swift index 6d88c1365..3898480fb 100644 --- a/Tests/LiveKitCoreTests/Proto/ProtoConverterTests.swift +++ b/Tests/LiveKitCoreTests/Proto/ProtoConverterTests.swift @@ -140,10 +140,8 @@ enum Comparator { } } - for sdkField in sdkFields { - if protoFieldMap[sdkField.name] == nil { - errors.append(.extraField(sdkField.name)) - } + for sdkField in sdkFields where protoFieldMap[sdkField.name] == nil { + errors.append(.extraField(sdkField.name)) } return errors diff --git a/Tests/LiveKitCoreTests/PublishBufferCapturerTests.swift b/Tests/LiveKitCoreTests/PublishBufferCapturerTests.swift index 40eef943e..536a55691 100644 --- a/Tests/LiveKitCoreTests/PublishBufferCapturerTests.swift +++ b/Tests/LiveKitCoreTests/PublishBufferCapturerTests.swift @@ -37,6 +37,7 @@ class PublishBufferCapturerTests: LKTestCase { } extension PublishBufferCapturerTests { + // swiftlint:disable:next function_body_length func testWith(publishOptions: VideoPublishOptions) async throws { try await withRooms([RoomTestingOptions(canPublish: true), RoomTestingOptions(canSubscribe: true)]) { rooms in // Alias to Rooms @@ -51,7 +52,10 @@ extension PublishBufferCapturerTests { options: captureOptions ) - let bufferCapturer = bufferTrack.capturer as! BufferCapturer + guard let bufferCapturer = bufferTrack.capturer as? BufferCapturer else { + XCTFail("Expected BufferCapturer") + return + } let captureTask = try await self.createSampleVideoTrack { buffer in bufferCapturer.capture(buffer) diff --git a/Tests/LiveKitCoreTests/TTLDictionaryTests.swift b/Tests/LiveKitCoreTests/TTLDictionaryTests.swift index d2b9390c7..ed2be775c 100644 --- a/Tests/LiveKitCoreTests/TTLDictionaryTests.swift +++ b/Tests/LiveKitCoreTests/TTLDictionaryTests.swift @@ -48,7 +48,7 @@ class TTLDictionaryTests: LKTestCase { XCTAssertTrue(dictionary.keys.isEmpty) XCTAssertTrue(dictionary.values.isEmpty) - dictionary.forEach { _, _ in XCTFail() } - _ = dictionary.map { _, _ in XCTFail() } + dictionary.forEach { _, _ in XCTFail("Dictionary should be empty") } + _ = dictionary.map { _, _ in XCTFail("Dictionary should be empty") } } } diff --git a/Tests/LiveKitCoreTests/TimeIntervalTests.swift b/Tests/LiveKitCoreTests/TimeIntervalTests.swift index ebeba6a3b..d81ab0eb1 100644 --- a/Tests/LiveKitCoreTests/TimeIntervalTests.swift +++ b/Tests/LiveKitCoreTests/TimeIntervalTests.swift @@ -23,7 +23,7 @@ class TimeIntervalTests: LKTestCase { /// Tests that the reconnection delay computation follows the expected easeOutCirc pattern: /// - All attempts (0 through n-2): easeOutCirc curve from baseDelay to maxDelay for dramatic early growth /// - Last attempt (n-1): exactly maxDelay - func testComputeReconnectDelay() { + func testComputeReconnectDelay() { // swiftlint:disable:this function_body_length // Default values: baseDelay=0.3, maxDelay=7.0, totalAttempts=10 let totalAttempts = 10 let baseDelay = TimeInterval.defaultReconnectDelay // 0.3 @@ -184,7 +184,7 @@ class TimeIntervalTests: LKTestCase { } /// Tests that jitter is properly applied to attempts - func testReconnectDelayJitter() { + func testReconnectDelayJitter() { // swiftlint:disable:this function_body_length // Set up test values let baseDelay = TimeInterval.defaultReconnectDelay let maxDelay = TimeInterval.defaultReconnectMaxDelay diff --git a/Tests/LiveKitCoreTests/Token/TokenSourceTests.swift b/Tests/LiveKitCoreTests/Token/TokenSourceTests.swift index 8b6400d96..854a25213 100644 --- a/Tests/LiveKitCoreTests/Token/TokenSourceTests.swift +++ b/Tests/LiveKitCoreTests/Token/TokenSourceTests.swift @@ -180,6 +180,7 @@ class TokenSourceTests: LKTestCase { XCTAssertEqual(expiredCallCount2, 2) } + // swiftlint:disable:next function_body_length func testCustomValidator() async throws { let mockSource = MockValidJWTSource(participantName: "charlie") diff --git a/Tests/LiveKitTestSupport/Room.swift b/Tests/LiveKitTestSupport/Room.swift index bedc94867..9b8ef3747 100644 --- a/Tests/LiveKitTestSupport/Room.swift +++ b/Tests/LiveKitTestSupport/Room.swift @@ -65,6 +65,7 @@ public extension LKTestCase { readEnvironmentString(for: "LIVEKIT_TESTING_URL", defaultValue: "ws://localhost:7880") } + // swiftlint:disable:next function_parameter_count func liveKitServerToken(for room: String, identity: String, canPublish: Bool, @@ -101,6 +102,7 @@ public extension LKTestCase { } // Set up variable number of Rooms + // swiftlint:disable:next function_body_length func withRooms(_ options: [RoomTestingOptions] = [], _ block: @escaping ([Room]) async throws -> Void) async throws { diff --git a/Tests/LiveKitTestSupport/Tracks.swift b/Tests/LiveKitTestSupport/Tracks.swift index f631e339f..bf48f3b07 100644 --- a/Tests/LiveKitTestSupport/Tracks.swift +++ b/Tests/LiveKitTestSupport/Tracks.swift @@ -26,6 +26,7 @@ public extension LKTestCase { #endif // Creates a LocalVideoTrack with BufferCapturer, generates frames for approx 30 seconds + // swiftlint:disable:next function_body_length func createSampleVideoTrack(targetFps: Int = 30, _ onCapture: @Sendable @escaping (CMSampleBuffer) -> Void) async throws -> (Task) { // Sample video let url = URL(string: "https://storage.unxpected.co.jp/public/sample-videos/ocean-1080p.mp4")! @@ -174,10 +175,8 @@ public class VideoTrackWatcher: TrackDelegate, VideoRenderer, @unchecked Sendabl onDidRenderFirstFrame?(id) } - for (key, value) in $0.expectationsForDimensions { - if frame.dimensions.area >= key.area { - value.fulfill() - } + for (key, value) in $0.expectationsForDimensions where frame.dimensions.area >= key.area { + value.fulfill() } } } @@ -201,10 +200,8 @@ public class VideoTrackWatcher: TrackDelegate, VideoRenderer, @unchecked Sendabl $0.detectedCodecs.insert(codecName) // Check if any codec expectations match - for (expectedCodec, expectation) in $0.expectationsForCodecs { - if expectedCodec.name.lowercased() == codecName { - expectation.fulfill() - } + for (expectedCodec, expectation) in $0.expectationsForCodecs where expectedCodec.name.lowercased() == codecName { + expectation.fulfill() } } } diff --git a/scripts/create_version.swift b/scripts/create_version.swift index 50ceb92d1..200f7b562 100755 --- a/scripts/create_version.swift +++ b/scripts/create_version.swift @@ -153,6 +153,7 @@ func getCurrentVersion() -> SemanticVersion { } } +// swiftlint:disable:next cyclomatic_complexity func parseChanges() -> [Change] { let fileManager = FileManager.default