From 4a026c370e029eba32ed36b6ba356772450024a2 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Fri, 3 Nov 2023 23:56:55 +0800 Subject: [PATCH] apply swiftformat --- .swift-version | 1 + .swiftformat | 2 + Package.swift | 8 +- .../Broadcast/BroadcastScreenCapturer.swift | 149 ++-- .../BroadcastServerSocketConnection.swift | 30 +- .../SocketConnectionFrameReader.swift | 51 +- .../LiveKit/Broadcast/Uploader/Atomic.swift | 23 +- .../BroadcastUploadSocketConnection.swift | 28 +- .../Uploader/DarwinNotificationCenter.swift | 23 +- .../Broadcast/Uploader/LKSampleHandler.swift | 164 ++-- .../Broadcast/Uploader/SampleUploader.swift | 218 +++--- Sources/LiveKit/Core/DataChannelPair.swift | 37 +- .../Core/Engine+SignalClientDelegate.swift | 50 +- .../Core/Engine+TransportDelegate.swift | 13 +- Sources/LiveKit/Core/Engine+WebRTC.swift | 31 +- Sources/LiveKit/Core/Engine.swift | 124 ++- Sources/LiveKit/Core/Room+Convenience.swift | 7 +- .../LiveKit/Core/Room+EngineDelegate.swift | 36 +- .../LiveKit/Core/Room+MulticastDelegate.swift | 1 - .../Core/Room+SignalClientDelegate.swift | 68 +- Sources/LiveKit/Core/Room.swift | 91 +-- Sources/LiveKit/Core/SignalClient.swift | 142 ++-- Sources/LiveKit/Core/Transport.swift | 91 +-- Sources/LiveKit/E2EE/E2EEManager.swift | 81 +- Sources/LiveKit/E2EE/KeyProvider.swift | 41 +- Sources/LiveKit/E2EE/Options.swift | 3 +- Sources/LiveKit/E2EE/State.swift | 6 +- Sources/LiveKit/Errors.swift | 57 +- .../Extensions/CustomStringConvertible.swift | 31 +- .../LiveKit/Extensions/DispatchQueue.swift | 7 +- Sources/LiveKit/Extensions/Logger.swift | 22 +- Sources/LiveKit/Extensions/PixelBuffer.swift | 38 +- Sources/LiveKit/Extensions/Primitives.swift | 17 +- .../LiveKit/Extensions/RTCConfiguration.swift | 6 +- .../Extensions/RTCDataChannel+Util.swift | 5 +- .../LiveKit/Extensions/RTCI420Buffer.swift | 13 +- .../Extensions/RTCMediaConstraints.swift | 3 +- .../RTCVideoCapturerDelegate+Buffer.swift | 37 +- Sources/LiveKit/Extensions/String.swift | 7 +- Sources/LiveKit/Extensions/TimeInterval.swift | 2 +- Sources/LiveKit/LiveKit+DeviceHelpers.swift | 9 +- Sources/LiveKit/LiveKit.swift | 9 +- .../Participant/LocalParticipant.swift | 101 ++- .../Participant/Participant+Convenience.swift | 17 +- .../Participant/Participant+Equatable.swift | 5 +- .../Participant+Identifiable.swift | 1 - .../Participant+MulticastDelegate.swift | 1 - Sources/LiveKit/Participant/Participant.swift | 53 +- .../Participant/RemoteParticipant.swift | 35 +- Sources/LiveKit/Protocols/AudioRenderer.swift | 7 +- .../LiveKit/Protocols/EngineDelegate.swift | 4 +- Sources/LiveKit/Protocols/MediaEncoding.swift | 2 +- .../Protocols/ParticipantDelegate.swift | 3 +- Sources/LiveKit/Protocols/RoomDelegate.swift | 6 +- .../Protocols/SignalClientDelegate.swift | 5 +- Sources/LiveKit/Protocols/TrackDelegate.swift | 4 +- .../LiveKit/Protocols/TransportDelegate.swift | 4 +- Sources/LiveKit/Protocols/VideoRenderer.swift | 9 +- .../LiveKit/Protocols/VideoViewDelegate.swift | 2 +- .../LiveKit/Support/AppStateListener.swift | 48 +- Sources/LiveKit/Support/AsyncCompleter.swift | 14 +- Sources/LiveKit/Support/AsyncQueueActor.swift | 3 +- Sources/LiveKit/Support/AsyncRetry.swift | 4 +- .../Support/ConnectivityListener.swift | 60 +- .../LiveKit/Support/DispatchQueueTimer.swift | 9 +- Sources/LiveKit/Support/DisplayLink.swift | 142 ++-- Sources/LiveKit/Support/Global.swift | 6 +- Sources/LiveKit/Support/HTTP.swift | 11 +- .../LiveKit/Support/MulticastDelegate.swift | 22 +- Sources/LiveKit/Support/NativeView.swift | 44 +- .../Support/NativeViewRepresentable.swift | 48 +- Sources/LiveKit/Support/StateSync.swift | 8 +- Sources/LiveKit/Support/Stopwatch.swift | 12 +- Sources/LiveKit/Support/TextView.swift | 66 +- Sources/LiveKit/Support/UnfairLock.swift | 5 +- Sources/LiveKit/Support/Utils.swift | 117 ++- Sources/LiveKit/Support/WebSocket.swift | 31 +- .../SwiftUIAudioRoutePickerButton.swift | 16 +- .../LiveKit/SwiftUI/SwiftUIVideoView.swift | 41 +- Sources/LiveKit/Track/AudioManager.swift | 186 +++-- Sources/LiveKit/Track/AudioTrack.swift | 5 +- .../Track/Capturers/BufferCapturer.swift | 23 +- .../Track/Capturers/CameraCapturer.swift | 115 ++- .../Track/Capturers/InAppCapturer.swift | 20 +- .../Track/Capturers/MacOSScreenCapturer.swift | 724 +++++++++--------- .../VideoCapturer+MulticastDelegate.swift | 3 +- .../Track/Capturers/VideoCapturer.swift | 31 +- .../LiveKit/Track/Local/LocalAudioTrack.swift | 28 +- Sources/LiveKit/Track/Local/LocalTrack.swift | 3 +- .../LiveKit/Track/Local/LocalVideoTrack.swift | 28 +- .../Track/Remote/RemoteAudioTrack.swift | 9 +- .../LiveKit/Track/Remote/RemoteTrack.swift | 6 +- .../Track/Remote/RemoteVideoTrack.swift | 8 +- .../LiveKit/Track/Support/Extensions.swift | 58 +- Sources/LiveKit/Track/Track+Equatable.swift | 3 +- .../Track/Track+MulticastDelegate.swift | 1 - Sources/LiveKit/Track/Track.swift | 103 ++- .../Track/VideoCaptureInterceptor.swift | 15 +- Sources/LiveKit/Track/VideoTrack.swift | 6 +- .../LocalTrackPublication.swift | 25 +- .../RemoteTrackPublication.swift | 64 +- .../TrackPublication+Equatable.swift | 3 +- .../TrackPublication+Identifiable.swift | 1 - .../TrackPublications/TrackPublication.swift | 52 +- Sources/LiveKit/Types/AudioDevice.swift | 8 +- .../Types/AudioEncoding+Comparable.swift | 3 +- Sources/LiveKit/Types/AudioEncoding.swift | 26 +- Sources/LiveKit/Types/ConnectionQuality.swift | 3 +- .../LiveKit/Types/ConnectionState+ObjC.swift | 2 +- Sources/LiveKit/Types/ConnectionState.swift | 9 +- Sources/LiveKit/Types/Dimensions.swift | 47 +- Sources/LiveKit/Types/DisconnectReason.swift | 8 +- Sources/LiveKit/Types/IceCandidate.swift | 3 +- Sources/LiveKit/Types/IceServer.swift | 10 +- Sources/LiveKit/Types/MediaDevice.swift | 1 - .../Types/Options/AudioCaptureOptions.swift | 25 +- .../Types/Options/AudioPublishOptions.swift | 17 +- .../Types/Options/BufferCaptureOptions.swift | 18 +- .../Types/Options/CameraCaptureOptions.swift | 35 +- .../Types/Options/CaptureOptions.swift | 6 +- .../Types/Options/ConnectOptions+Copy.swift | 7 +- .../Types/Options/ConnectOptions.swift | 37 +- .../Types/Options/DataPublishOptions.swift | 17 +- .../Types/Options/PublishOptions.swift | 2 +- .../LiveKit/Types/Options/RoomOptions.swift | 31 +- .../Options/ScreenShareCaptureOptions.swift | 18 +- .../Types/Options/VideoCaptureOptions.swift | 2 +- .../Types/Options/VideoPublishOptions.swift | 23 +- Sources/LiveKit/Types/Other.swift | 5 +- .../Types/ParticipantPermissions.swift | 30 +- .../Types/ParticipantTrackPermission.swift | 9 +- Sources/LiveKit/Types/ProtocolVersion.swift | 6 +- .../LiveKit/Types/SessionDescription.swift | 4 +- Sources/LiveKit/Types/Statistics.swift | 581 +++++++------- Sources/LiveKit/Types/TrackPublishState.swift | 2 +- Sources/LiveKit/Types/TrackSettings.swift | 12 +- Sources/LiveKit/Types/TrackSource.swift | 4 +- Sources/LiveKit/Types/TrackStatistics.swift | 40 +- Sources/LiveKit/Types/TrackStreamState.swift | 3 +- Sources/LiveKit/Types/TrackType.swift | 4 +- .../Types/VideoEncoding+Comparable.swift | 4 +- Sources/LiveKit/Types/VideoEncoding.swift | 11 +- Sources/LiveKit/Types/VideoFrame.swift | 32 +- .../Types/VideoParameters+Comparable.swift | 4 +- Sources/LiveKit/Types/VideoParameters.swift | 96 ++- Sources/LiveKit/Types/VideoQuality.swift | 21 +- Sources/LiveKit/Types/VideoRotation.swift | 8 +- .../InternalSampleBufferVideoRenderer.swift | 29 +- .../Views/VideoView+MulticastDelegate.swift | 1 - Sources/LiveKit/Views/VideoView.swift | 170 ++-- Tests/LiveKitTests/AsyncRetryTests.swift | 10 +- Tests/LiveKitTests/CompleterTests.swift | 15 +- Tests/LiveKitTests/FunctionTests.swift | 9 +- Tests/LiveKitTests/LiveKitTests.swift | 5 +- Tests/LiveKitTests/ThreadSafetyTests.swift | 41 +- Tests/LiveKitTests/TimerTests.swift | 16 +- Tests/LiveKitTests/WebSocketTests.swift | 11 +- 157 files changed, 2703 insertions(+), 3106 deletions(-) create mode 100644 .swift-version create mode 100644 .swiftformat diff --git a/.swift-version b/.swift-version new file mode 100644 index 000000000..15df682a2 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +5.7 # Xcode 14 diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 000000000..54ffea421 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,2 @@ +--exclude Sources/LiveKit/Protos +--header "/*\n * Copyright {year} LiveKit\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */" diff --git a/Package.swift b/Package.swift index 53be5ccd4..abb706dd3 100644 --- a/Package.swift +++ b/Package.swift @@ -7,19 +7,19 @@ let package = Package( name: "LiveKit", platforms: [ .iOS(.v13), - .macOS(.v10_15) + .macOS(.v10_15), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( name: "LiveKit", targets: ["LiveKit"] - ) + ), ], dependencies: [ .package(name: "WebRTC", url: "https://github.com/livekit/webrtc-xcframework-static.git", .exact("114.5735.09")), .package(name: "SwiftProtobuf", url: "https://github.com/apple/swift-protobuf.git", .upToNextMajor(from: "1.21.0")), - .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.5.2")) + .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.5.2")), ], targets: [ .systemLibrary(name: "CHeaders"), @@ -36,6 +36,6 @@ let package = Package( .testTarget( name: "LiveKitTests", dependencies: ["LiveKit"] - ) + ), ] ) diff --git a/Sources/LiveKit/Broadcast/BroadcastScreenCapturer.swift b/Sources/LiveKit/Broadcast/BroadcastScreenCapturer.swift index 94f0b7afc..f660071e2 100644 --- a/Sources/LiveKit/Broadcast/BroadcastScreenCapturer.swift +++ b/Sources/LiveKit/Broadcast/BroadcastScreenCapturer.swift @@ -1,102 +1,107 @@ -// -// BroadcastScreenCapturer.m -// RCTWebRTC -// -// Created by Alex-Dan Bumbu on 06/01/2021. -// +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import Foundation #if canImport(UIKit) -import UIKit + import UIKit #endif @_implementationOnly import WebRTC #if os(iOS) -class BroadcastScreenCapturer: BufferCapturer { + class BroadcastScreenCapturer: BufferCapturer { + static let kRTCScreensharingSocketFD = "rtc_SSFD" + static let kAppGroupIdentifierKey = "RTCAppGroupIdentifier" + static let kRTCScreenSharingExtension = "RTCScreenSharingExtension" - static let kRTCScreensharingSocketFD = "rtc_SSFD" - static let kAppGroupIdentifierKey = "RTCAppGroupIdentifier" - static let kRTCScreenSharingExtension = "RTCScreenSharingExtension" + var frameReader: SocketConnectionFrameReader? - var frameReader: SocketConnectionFrameReader? + override func startCapture() async throws -> Bool { + let didStart = try await super.startCapture() - override func startCapture() async throws -> Bool { + guard didStart else { return false } - let didStart = try await super.startCapture() + guard let identifier = lookUpAppGroupIdentifier(), + let filePath = filePathForIdentifier(identifier) else { return false } - guard didStart else { return false } + let bounds = await UIScreen.main.bounds + let width = bounds.size.width + let height = bounds.size.height + let screenDimension = Dimensions(width: Int32(width), height: Int32(height)) - guard let identifier = lookUpAppGroupIdentifier(), - let filePath = filePathForIdentifier(identifier) else { return false } + // pre fill dimensions, so that we don't have to wait for the broadcast to start to get actual dimensions. + // should be able to safely predict using actual screen dimensions. + let targetDimensions = screenDimension + .aspectFit(size: options.dimensions.max) + .toEncodeSafeDimensions() - let bounds = await UIScreen.main.bounds - let width = bounds.size.width - let height = bounds.size.height - let screenDimension = Dimensions(width: Int32(width), height: Int32(height)) - - // pre fill dimensions, so that we don't have to wait for the broadcast to start to get actual dimensions. - // should be able to safely predict using actual screen dimensions. - let targetDimensions = screenDimension - .aspectFit(size: self.options.dimensions.max) - .toEncodeSafeDimensions() - - defer { self.dimensions = targetDimensions } - let frameReader = SocketConnectionFrameReader() - guard let socketConnection = BroadcastServerSocketConnection(filePath: filePath, streamDelegate: frameReader) - else { return false } - frameReader.didCapture = { pixelBuffer, rotation in - self.capture(pixelBuffer, rotation: rotation.toLKType()) + defer { self.dimensions = targetDimensions } + let frameReader = SocketConnectionFrameReader() + guard let socketConnection = BroadcastServerSocketConnection(filePath: filePath, streamDelegate: frameReader) + else { return false } + frameReader.didCapture = { pixelBuffer, rotation in + self.capture(pixelBuffer, rotation: rotation.toLKType()) + } + frameReader.startCapture(with: socketConnection) + self.frameReader = frameReader + return true } - frameReader.startCapture(with: socketConnection) - self.frameReader = frameReader - - return true - } - - override func stopCapture() async throws -> Bool { - let didStop = try await super.stopCapture() + override func stopCapture() async throws -> Bool { + let didStop = try await super.stopCapture() - // Already stopped - guard didStop else { return false } + // Already stopped + guard didStop else { return false } - self.frameReader?.stopCapture() - self.frameReader = nil - return true - } + frameReader?.stopCapture() + frameReader = nil + return true + } - private func lookUpAppGroupIdentifier() -> String? { - Bundle.main.infoDictionary?[BroadcastScreenCapturer.kAppGroupIdentifierKey] as? String - } + private func lookUpAppGroupIdentifier() -> String? { + Bundle.main.infoDictionary?[BroadcastScreenCapturer.kAppGroupIdentifierKey] as? String + } - private func filePathForIdentifier(_ identifier: String) -> String? { - guard let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: identifier) - else { return nil } + private func filePathForIdentifier(_ identifier: String) -> String? { + guard let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: identifier) + else { return nil } - let filePath = sharedContainer.appendingPathComponent(BroadcastScreenCapturer.kRTCScreensharingSocketFD).path - return filePath + let filePath = sharedContainer.appendingPathComponent(BroadcastScreenCapturer.kRTCScreensharingSocketFD).path + return filePath + } } -} - -extension LocalVideoTrack { - /// Creates a track that captures screen capture from a broadcast upload extension - public static func createBroadcastScreenCapturerTrack(name: String = Track.screenShareVideoName, - source: VideoTrack.Source = .screenShareVideo, - options: ScreenShareCaptureOptions = ScreenShareCaptureOptions()) -> LocalVideoTrack { - let videoSource = Engine.createVideoSource(forScreenShare: true) - let capturer = BroadcastScreenCapturer(delegate: videoSource, options: BufferCaptureOptions(from: options)) - return LocalVideoTrack( - name: name, - source: source, - capturer: capturer, - videoSource: videoSource - ) + public extension LocalVideoTrack { + /// Creates a track that captures screen capture from a broadcast upload extension + static func createBroadcastScreenCapturerTrack(name: String = Track.screenShareVideoName, + source: VideoTrack.Source = .screenShareVideo, + options: ScreenShareCaptureOptions = ScreenShareCaptureOptions()) -> LocalVideoTrack + { + let videoSource = Engine.createVideoSource(forScreenShare: true) + let capturer = BroadcastScreenCapturer(delegate: videoSource, options: BufferCaptureOptions(from: options)) + return LocalVideoTrack( + name: name, + source: source, + capturer: capturer, + videoSource: videoSource + ) + } } -} #endif diff --git a/Sources/LiveKit/Broadcast/BroadcastServerSocketConnection.swift b/Sources/LiveKit/Broadcast/BroadcastServerSocketConnection.swift index 35ee438e7..a22a06f0a 100644 --- a/Sources/LiveKit/Broadcast/BroadcastServerSocketConnection.swift +++ b/Sources/LiveKit/Broadcast/BroadcastServerSocketConnection.swift @@ -1,16 +1,24 @@ -// -// SocketConnection.swift -// Broadcast Extension -// -// Created by Alex-Dan Bumbu on 22/03/2021. -// Copyright © 2021 Atlassian Inc. All rights reserved. -// +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -import Foundation import Darwin +import Foundation #if canImport(CHeaders) -import CHeaders + import CHeaders #endif class BroadcastServerSocketConnection: NSObject { @@ -152,7 +160,7 @@ class BroadcastServerSocketConnection: NSObject { CFStreamCreatePairWithSocket(kCFAllocatorDefault, clientSocket, &readStream, &writeStream) inputStream = readStream?.takeRetainedValue() - inputStream?.delegate = self.streamDelegate + inputStream?.delegate = streamDelegate inputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String)) outputStream = writeStream?.takeRetainedValue() @@ -174,7 +182,7 @@ class BroadcastServerSocketConnection: NSObject { repeat { isRunning = self?.shouldKeepRunning ?? false && RunLoop.current.run(mode: .default, before: .distantFuture) - } while (isRunning) + } while isRunning logger.log(level: .debug, "streams stopped") } diff --git a/Sources/LiveKit/Broadcast/SocketConnectionFrameReader.swift b/Sources/LiveKit/Broadcast/SocketConnectionFrameReader.swift index 78911981e..61963f95a 100644 --- a/Sources/LiveKit/Broadcast/SocketConnectionFrameReader.swift +++ b/Sources/LiveKit/Broadcast/SocketConnectionFrameReader.swift @@ -1,13 +1,22 @@ -// -// SocketConnectionFrameReader.swift -// RCTWebRTC -// -// Created by Alex-Dan Bumbu on 06/01/2021. -// +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -import Foundation -import CoreVideo import CoreImage +import CoreVideo +import Foundation @_implementationOnly import WebRTC @@ -23,15 +32,14 @@ private class Message { var imageOrientation: CGImagePropertyOrientation = .up private var framedMessage: CFHTTPMessage? - init() { - } + init() {} func appendBytes(buffer: [UInt8], length: Int) -> Int { if framedMessage == nil { framedMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue() } - guard let framedMessage = framedMessage else { + guard let framedMessage else { return -1 } @@ -52,7 +60,7 @@ private class Message { let missingBytesCount = contentLength - bodyLength if missingBytesCount == 0 { let success = unwrapMessage(framedMessage) - self.didComplete?(success, self) + didComplete?(success, self) self.framedMessage = nil } @@ -61,7 +69,7 @@ private class Message { } private func imageContext() -> CIContext? { - return Message.imageContextVar + Message.imageContextVar } private func unwrapMessage(_ framedMessage: CFHTTPMessage) -> Bool { @@ -75,7 +83,7 @@ private class Message { let width = Int(CFStringGetIntValue(widthStr)) let height = Int(CFStringGetIntValue(heightStr)) - self.imageOrientation = CGImagePropertyOrientation(rawValue: UInt32(CFStringGetIntValue(imageOrientationStr))) ?? .up + imageOrientation = CGImagePropertyOrientation(rawValue: UInt32(CFStringGetIntValue(imageOrientationStr))) ?? .up // Copy the pixel buffer let status = CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, nil, &imageBuffer) @@ -90,19 +98,19 @@ private class Message { } private func copyImageData(_ data: Data?, to pixelBuffer: CVPixelBuffer?) { - if let pixelBuffer = pixelBuffer { + if let pixelBuffer { CVPixelBufferLockBaseAddress(pixelBuffer, []) } var image: CIImage? - if let data = data { + if let data { image = CIImage(data: data) } - if let image = image, let pixelBuffer = pixelBuffer { + if let image, let pixelBuffer { imageContext()?.render(image, to: pixelBuffer) } - if let pixelBuffer = pixelBuffer { + if let pixelBuffer { CVPixelBufferUnlockBaseAddress(pixelBuffer, []) } } @@ -126,8 +134,7 @@ class SocketConnectionFrameReader: NSObject { private var message: Message? var didCapture: ((CVPixelBuffer, RTCVideoRotation) -> Void)? - override init() { - } + override init() {} func startCapture(with connection: BroadcastServerSocketConnection) { self.connection = connection @@ -144,6 +151,7 @@ class SocketConnectionFrameReader: NSObject { } // MARK: Private Methods + func readBytes(from stream: InputStream) { if !(stream.hasBytesAvailable) { return @@ -185,7 +193,7 @@ class SocketConnectionFrameReader: NSObject { _ pixelBuffer: CVPixelBuffer?, with orientation: CGImagePropertyOrientation ) { - guard let pixelBuffer = pixelBuffer else { + guard let pixelBuffer else { return } @@ -203,7 +211,6 @@ class SocketConnectionFrameReader: NSObject { didCapture?(pixelBuffer, rotation) } - } extension SocketConnectionFrameReader: StreamDelegate { diff --git a/Sources/LiveKit/Broadcast/Uploader/Atomic.swift b/Sources/LiveKit/Broadcast/Uploader/Atomic.swift index 248410f65..2fb0b7fcd 100644 --- a/Sources/LiveKit/Broadcast/Uploader/Atomic.swift +++ b/Sources/LiveKit/Broadcast/Uploader/Atomic.swift @@ -1,16 +1,23 @@ -// -// Atomic.swift -// Broadcast Extension -// -// Created by Maksym Shcheglov. -// https://www.onswiftwings.com/posts/atomic-property-wrapper/ -// +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import Foundation @propertyWrapper struct Atomic { - private var value: Value private let lock = NSLock() diff --git a/Sources/LiveKit/Broadcast/Uploader/BroadcastUploadSocketConnection.swift b/Sources/LiveKit/Broadcast/Uploader/BroadcastUploadSocketConnection.swift index b7bf40842..7bbf71334 100644 --- a/Sources/LiveKit/Broadcast/Uploader/BroadcastUploadSocketConnection.swift +++ b/Sources/LiveKit/Broadcast/Uploader/BroadcastUploadSocketConnection.swift @@ -1,10 +1,18 @@ -// -// UploadSocketConnection.swift -// Broadcast Extension -// -// Created by Alex-Dan Bumbu on 22/03/2021. -// Copyright © 2021 Atlassian Inc. All rights reserved. -// +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import Foundation @@ -77,7 +85,6 @@ class BroadcastUploadSocketConnection: NSObject { } extension BroadcastUploadSocketConnection: StreamDelegate { - func stream(_ aStream: Stream, handle eventCode: Stream.Event) { switch eventCode { case .openCompleted: @@ -90,7 +97,7 @@ extension BroadcastUploadSocketConnection: StreamDelegate { var buffer: UInt8 = 0 logger.log(level: .debug, "client stream hasBytesAvailable") let numberOfBytesRead = inputStream?.read(&buffer, maxLength: 1) - if numberOfBytesRead == 0 && aStream.streamStatus == .atEnd { + if numberOfBytesRead == 0, aStream.streamStatus == .atEnd { logger.log(level: .debug, "server socket closed") close() notifyDidClose(error: nil) @@ -113,7 +120,6 @@ extension BroadcastUploadSocketConnection: StreamDelegate { } private extension BroadcastUploadSocketConnection { - func setupAddress() -> Bool { var addr = sockaddr_un() addr.sun_family = sa_family_t(AF_UNIX) @@ -181,7 +187,7 @@ private extension BroadcastUploadSocketConnection { repeat { isRunning = self?.shouldKeepRunning ?? false && RunLoop.current.run(mode: .default, before: .distantFuture) - } while (isRunning) + } while isRunning } } diff --git a/Sources/LiveKit/Broadcast/Uploader/DarwinNotificationCenter.swift b/Sources/LiveKit/Broadcast/Uploader/DarwinNotificationCenter.swift index 14a3a85d5..60f5cded7 100644 --- a/Sources/LiveKit/Broadcast/Uploader/DarwinNotificationCenter.swift +++ b/Sources/LiveKit/Broadcast/Uploader/DarwinNotificationCenter.swift @@ -1,10 +1,18 @@ -// -// DarwinNotificationCenter.swift -// Broadcast Extension -// -// Created by Alex-Dan Bumbu on 23/03/2021. -// Copyright © 2021 8x8, Inc. All rights reserved. -// +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import Foundation @@ -14,7 +22,6 @@ enum DarwinNotification: String { } class DarwinNotificationCenter { - static let shared = DarwinNotificationCenter() private let notificationCenter: CFNotificationCenter diff --git a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift index 287769d79..c318ddc3c 100644 --- a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift +++ b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift @@ -1,107 +1,115 @@ -// -// SampleHandler.swift -// Broadcast Extension -// -// Created by Alex-Dan Bumbu on 04.06.2021. -// +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #if os(iOS) -import OSLog -import Logging + import Logging + import OSLog -#if canImport(ReplayKit) -import ReplayKit -#endif + #if canImport(ReplayKit) + import ReplayKit + #endif -open class LKSampleHandler: RPBroadcastSampleHandler { + open class LKSampleHandler: RPBroadcastSampleHandler { + private var clientConnection: BroadcastUploadSocketConnection? + private var uploader: SampleUploader? - private var clientConnection: BroadcastUploadSocketConnection? - private var uploader: SampleUploader? + public var appGroupIdentifier: String? { + Bundle.main.infoDictionary?[BroadcastScreenCapturer.kAppGroupIdentifierKey] as? String + } - public var appGroupIdentifier: String? { - return Bundle.main.infoDictionary?[BroadcastScreenCapturer.kAppGroupIdentifierKey] as? String - } + public var socketFilePath: String { + guard let appGroupIdentifier, + let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) + else { + return "" + } - public var socketFilePath: String { - guard let appGroupIdentifier = appGroupIdentifier, - let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) - else { - return "" + return sharedContainer.appendingPathComponent(BroadcastScreenCapturer.kRTCScreensharingSocketFD).path } - return sharedContainer.appendingPathComponent(BroadcastScreenCapturer.kRTCScreensharingSocketFD).path - } - - public override init() { - super.init() + override public init() { + super.init() - if let connection = BroadcastUploadSocketConnection(filePath: self.socketFilePath) { - self.clientConnection = connection - self.setupConnection() + if let connection = BroadcastUploadSocketConnection(filePath: socketFilePath) { + clientConnection = connection + setupConnection() - self.uploader = SampleUploader(connection: connection) + uploader = SampleUploader(connection: connection) + } } - } - override public func broadcastStarted(withSetupInfo setupInfo: [String: NSObject]?) { - // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.d - DarwinNotificationCenter.shared.postNotification(.broadcastStarted) - self.openConnection() - } + override public func broadcastStarted(withSetupInfo _: [String: NSObject]?) { + // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.d + DarwinNotificationCenter.shared.postNotification(.broadcastStarted) + openConnection() + } - override public func broadcastPaused() { - // User has requested to pause the broadcast. Samples will stop being delivered. - } + override public func broadcastPaused() { + // User has requested to pause the broadcast. Samples will stop being delivered. + } - override public func broadcastResumed() { - // User has requested to resume the broadcast. Samples delivery will resume. - } + override public func broadcastResumed() { + // User has requested to resume the broadcast. Samples delivery will resume. + } - override public func broadcastFinished() { - // User has requested to finish the broadcast. - DarwinNotificationCenter.shared.postNotification(.broadcastStopped) - clientConnection?.close() - } + override public func broadcastFinished() { + // User has requested to finish the broadcast. + DarwinNotificationCenter.shared.postNotification(.broadcastStopped) + clientConnection?.close() + } - override public func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) { - switch sampleBufferType { - case RPSampleBufferType.video: - uploader?.send(sample: sampleBuffer) - default: - break + override public func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) { + switch sampleBufferType { + case RPSampleBufferType.video: + uploader?.send(sample: sampleBuffer) + default: + break + } } - } - private func setupConnection() { - clientConnection?.didClose = { [weak self] error in - logger.log(level: .debug, "client connection did close \(String(describing: error))") - - if let error = error { - self?.finishBroadcastWithError(error) - } else { - // the displayed failure message is more user friendly when using NSError instead of Error - let LKScreenSharingStopped = 10001 - let customError = NSError(domain: RPRecordingErrorDomain, code: LKScreenSharingStopped, userInfo: [NSLocalizedDescriptionKey: "Screen sharing stopped"]) - self?.finishBroadcastWithError(customError) + private func setupConnection() { + clientConnection?.didClose = { [weak self] error in + logger.log(level: .debug, "client connection did close \(String(describing: error))") + + if let error { + self?.finishBroadcastWithError(error) + } else { + // the displayed failure message is more user friendly when using NSError instead of Error + let LKScreenSharingStopped = 10001 + let customError = NSError(domain: RPRecordingErrorDomain, code: LKScreenSharingStopped, userInfo: [NSLocalizedDescriptionKey: "Screen sharing stopped"]) + self?.finishBroadcastWithError(customError) + } } } - } - private func openConnection() { - let queue = DispatchQueue(label: "broadcast.connectTimer") - let timer = DispatchSource.makeTimerSource(queue: queue) - timer.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(500)) - timer.setEventHandler { [weak self] in - guard self?.clientConnection?.open() == true else { - return + private func openConnection() { + let queue = DispatchQueue(label: "broadcast.connectTimer") + let timer = DispatchSource.makeTimerSource(queue: queue) + timer.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(500)) + timer.setEventHandler { [weak self] in + guard self?.clientConnection?.open() == true else { + return + } + + timer.cancel() } - timer.cancel() + timer.resume() } - - timer.resume() } -} #endif diff --git a/Sources/LiveKit/Broadcast/Uploader/SampleUploader.swift b/Sources/LiveKit/Broadcast/Uploader/SampleUploader.swift index 700a7c1d8..f567d5abc 100644 --- a/Sources/LiveKit/Broadcast/Uploader/SampleUploader.swift +++ b/Sources/LiveKit/Broadcast/Uploader/SampleUploader.swift @@ -1,154 +1,160 @@ -// -// SampleUploader.swift -// Broadcast Extension -// -// Created by Alex-Dan Bumbu on 22/03/2021. -// Copyright © 2021 8x8, Inc. All rights reserved. -// +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #if os(iOS) -import Foundation + import Foundation -#if canImport(ReplayKit) -import ReplayKit -#endif + #if canImport(ReplayKit) + import ReplayKit + #endif -private enum Constants { - static let bufferMaxLength = 10240 -} + private enum Constants { + static let bufferMaxLength = 10240 + } -class SampleUploader { + class SampleUploader { + private static var imageContext = CIContext(options: nil) - private static var imageContext = CIContext(options: nil) + @Atomic private var isReady = false + private var connection: BroadcastUploadSocketConnection - @Atomic private var isReady = false - private var connection: BroadcastUploadSocketConnection + private var dataToSend: Data? + private var byteIndex = 0 - private var dataToSend: Data? - private var byteIndex = 0 + private let serialQueue: DispatchQueue - private let serialQueue: DispatchQueue + init(connection: BroadcastUploadSocketConnection) { + self.connection = connection + serialQueue = DispatchQueue(label: "io.livekit.broadcast.sampleUploader") - init(connection: BroadcastUploadSocketConnection) { - self.connection = connection - self.serialQueue = DispatchQueue(label: "io.livekit.broadcast.sampleUploader") + setupConnection() + } - setupConnection() - } + @discardableResult func send(sample buffer: CMSampleBuffer) -> Bool { + guard isReady else { + return false + } - @discardableResult func send(sample buffer: CMSampleBuffer) -> Bool { - guard isReady else { - return false - } + isReady = false - isReady = false + dataToSend = prepare(sample: buffer) + byteIndex = 0 - dataToSend = prepare(sample: buffer) - byteIndex = 0 + serialQueue.async { [weak self] in + self?.sendDataChunk() + } - serialQueue.async { [weak self] in - self?.sendDataChunk() + return true } - - return true } -} -private extension SampleUploader { - - func setupConnection() { - connection.didOpen = { [weak self] in - self?.isReady = true - } - connection.streamHasSpaceAvailable = { [weak self] in - self?.serialQueue.async { - if let success = self?.sendDataChunk() { - self?.isReady = !success + private extension SampleUploader { + func setupConnection() { + connection.didOpen = { [weak self] in + self?.isReady = true + } + connection.streamHasSpaceAvailable = { [weak self] in + self?.serialQueue.async { + if let success = self?.sendDataChunk() { + self?.isReady = !success + } } } } - } - - @discardableResult func sendDataChunk() -> Bool { - guard let dataToSend = dataToSend else { - return false - } - var bytesLeft = dataToSend.count - byteIndex - var length = bytesLeft > Constants.bufferMaxLength ? Constants.bufferMaxLength : bytesLeft - - length = dataToSend[byteIndex..<(byteIndex + length)].withUnsafeBytes { - guard let ptr = $0.bindMemory(to: UInt8.self).baseAddress else { - return 0 + @discardableResult func sendDataChunk() -> Bool { + guard let dataToSend else { + return false } - return connection.writeToStream(buffer: ptr, maxLength: length) - } + var bytesLeft = dataToSend.count - byteIndex + var length = bytesLeft > Constants.bufferMaxLength ? Constants.bufferMaxLength : bytesLeft - if length > 0 { - byteIndex += length - bytesLeft -= length + length = dataToSend[byteIndex ..< (byteIndex + length)].withUnsafeBytes { + guard let ptr = $0.bindMemory(to: UInt8.self).baseAddress else { + return 0 + } - if bytesLeft == 0 { - self.dataToSend = nil - byteIndex = 0 + return connection.writeToStream(buffer: ptr, maxLength: length) } - } else { - logger.log(level: .debug, "writeBufferToStream failure") - } - return true - } + if length > 0 { + byteIndex += length + bytesLeft -= length + + if bytesLeft == 0 { + self.dataToSend = nil + byteIndex = 0 + } + } else { + logger.log(level: .debug, "writeBufferToStream failure") + } - func prepare(sample buffer: CMSampleBuffer) -> Data? { - guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else { - logger.log(level: .debug, "image buffer not available") - return nil + return true } - CVPixelBufferLockBaseAddress(imageBuffer, .readOnly) + func prepare(sample buffer: CMSampleBuffer) -> Data? { + guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else { + logger.log(level: .debug, "image buffer not available") + return nil + } - let scaleFactor = 1.0 - let width = CVPixelBufferGetWidth(imageBuffer)/Int(scaleFactor) - let height = CVPixelBufferGetHeight(imageBuffer)/Int(scaleFactor) + CVPixelBufferLockBaseAddress(imageBuffer, .readOnly) - let orientation = CMGetAttachment(buffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil)?.uintValue ?? 0 + let scaleFactor = 1.0 + let width = CVPixelBufferGetWidth(imageBuffer) / Int(scaleFactor) + let height = CVPixelBufferGetHeight(imageBuffer) / Int(scaleFactor) - let scaleTransform = CGAffineTransform(scaleX: CGFloat(1.0/scaleFactor), y: CGFloat(1.0/scaleFactor)) - let bufferData = self.jpegData(from: imageBuffer, scale: scaleTransform) + let orientation = CMGetAttachment(buffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil)?.uintValue ?? 0 - CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) + let scaleTransform = CGAffineTransform(scaleX: CGFloat(1.0 / scaleFactor), y: CGFloat(1.0 / scaleFactor)) + let bufferData = jpegData(from: imageBuffer, scale: scaleTransform) - guard let messageData = bufferData else { - logger.log(level: .debug, "corrupted image buffer") - return nil - } + CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) - let httpResponse = CFHTTPMessageCreateResponse(nil, 200, nil, kCFHTTPVersion1_1).takeRetainedValue() - CFHTTPMessageSetHeaderFieldValue(httpResponse, "Content-Length" as CFString, String(messageData.count) as CFString) - CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Width" as CFString, String(width) as CFString) - CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Height" as CFString, String(height) as CFString) - CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Orientation" as CFString, String(orientation) as CFString) - - CFHTTPMessageSetBody(httpResponse, messageData as CFData) + guard let messageData = bufferData else { + logger.log(level: .debug, "corrupted image buffer") + return nil + } - let serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse)?.takeRetainedValue() as Data? + let httpResponse = CFHTTPMessageCreateResponse(nil, 200, nil, kCFHTTPVersion1_1).takeRetainedValue() + CFHTTPMessageSetHeaderFieldValue(httpResponse, "Content-Length" as CFString, String(messageData.count) as CFString) + CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Width" as CFString, String(width) as CFString) + CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Height" as CFString, String(height) as CFString) + CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Orientation" as CFString, String(orientation) as CFString) - return serializedMessage - } + CFHTTPMessageSetBody(httpResponse, messageData as CFData) - func jpegData(from buffer: CVPixelBuffer, scale scaleTransform: CGAffineTransform) -> Data? { - let image = CIImage(cvPixelBuffer: buffer).transformed(by: scaleTransform) + let serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse)?.takeRetainedValue() as Data? - guard let colorSpace = image.colorSpace else { - return nil + return serializedMessage } - let options: [CIImageRepresentationOption: Float] = [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption: 1.0] + func jpegData(from buffer: CVPixelBuffer, scale scaleTransform: CGAffineTransform) -> Data? { + let image = CIImage(cvPixelBuffer: buffer).transformed(by: scaleTransform) - return SampleUploader.imageContext.jpegRepresentation(of: image, colorSpace: colorSpace, options: options) + guard let colorSpace = image.colorSpace else { + return nil + } + + let options: [CIImageRepresentationOption: Float] = [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption: 1.0] + + return SampleUploader.imageContext.jpegRepresentation(of: image, colorSpace: colorSpace, options: options) + } } -} #endif diff --git a/Sources/LiveKit/Core/DataChannelPair.swift b/Sources/LiveKit/Core/DataChannelPair.swift index 761abd116..bfe433eec 100644 --- a/Sources/LiveKit/Core/DataChannelPair.swift +++ b/Sources/LiveKit/Core/DataChannelPair.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,7 @@ import Foundation @_implementationOnly import WebRTC -internal class DataChannelPair: NSObject, Loggable { - +class DataChannelPair: NSObject, Loggable { // MARK: - Public public typealias OnDataPacket = (_ dataPacket: Livekit_DataPacket) -> Void @@ -35,26 +34,26 @@ internal class DataChannelPair: NSObject, Loggable { private var _lossyChannel: LKRTCDataChannel? public var isOpen: Bool { - guard let reliable = _reliableChannel, - let lossy = _lossyChannel else { + let lossy = _lossyChannel + else { return false } - return .open == reliable.readyState && .open == lossy.readyState + return reliable.readyState == .open && lossy.readyState == .open } public init(target: Livekit_SignalTarget, reliableChannel: LKRTCDataChannel? = nil, - lossyChannel: LKRTCDataChannel? = nil) { - + lossyChannel: LKRTCDataChannel? = nil) + { self.target = target - self._reliableChannel = reliableChannel - self._lossyChannel = lossyChannel + _reliableChannel = reliableChannel + _lossyChannel = lossyChannel } public func set(reliable channel: LKRTCDataChannel?) { - self._reliableChannel = channel + _reliableChannel = channel channel?.delegate = self if isOpen { @@ -63,7 +62,7 @@ internal class DataChannelPair: NSObject, Loggable { } public func set(lossy channel: LKRTCDataChannel?) { - self._lossyChannel = channel + _lossyChannel = channel channel?.delegate = self if isOpen { @@ -72,7 +71,6 @@ internal class DataChannelPair: NSObject, Loggable { } public func close() { - let reliable = _reliableChannel let lossy = _lossyChannel @@ -89,10 +87,9 @@ internal class DataChannelPair: NSObject, Loggable { } public func send(userPacket: Livekit_UserPacket, reliability: Reliability) throws { - guard let reliableChannel = _reliableChannel, - let lossyChannel = _lossyChannel else { - + let lossyChannel = _lossyChannel + else { throw InternalError.state(message: "Data channel is nil") } @@ -119,7 +116,6 @@ internal class DataChannelPair: NSObject, Loggable { } public func infos() -> [Livekit_DataChannelInfo] { - [_lossyChannel, _reliableChannel] .compactMap { $0 } .map { $0.toLKInfoType() } @@ -129,16 +125,13 @@ internal class DataChannelPair: NSObject, Loggable { // MARK: - RTCDataChannelDelegate extension DataChannelPair: LKRTCDataChannelDelegate { - - func dataChannelDidChangeState(_ dataChannel: LKRTCDataChannel) { - + func dataChannelDidChangeState(_: LKRTCDataChannel) { if isOpen { openCompleter.resume(returning: ()) } } - func dataChannel(_ dataChannel: LKRTCDataChannel, didReceiveMessageWith buffer: LKRTCDataBuffer) { - + func dataChannel(_: LKRTCDataChannel, didReceiveMessageWith buffer: LKRTCDataBuffer) { guard let dataPacket = try? Livekit_DataPacket(contiguousBytes: buffer.data) else { log("could not decode data message", .error) return diff --git a/Sources/LiveKit/Core/Engine+SignalClientDelegate.swift b/Sources/LiveKit/Core/Engine+SignalClientDelegate.swift index f71ef223c..e38f75045 100644 --- a/Sources/LiveKit/Core/Engine+SignalClientDelegate.swift +++ b/Sources/LiveKit/Core/Engine+SignalClientDelegate.swift @@ -19,17 +19,16 @@ import Foundation @_implementationOnly import WebRTC extension Engine: SignalClientDelegate { - - func signalClient(_ signalClient: SignalClient, didMutate state: SignalClient.State, oldState: SignalClient.State) { - + func signalClient(_: SignalClient, didMutate state: SignalClient.State, oldState: SignalClient.State) { // connectionState did update if state.connectionState != oldState.connectionState, // did disconnect - case .disconnected(let reason) = state.connectionState, + case let .disconnected(reason) = state.connectionState, // only attempt re-connect if disconnected(reason: network) case .networkError = reason, // engine is currently connected state - case .connected = _state.connectionState { + case .connected = _state.connectionState + { log("[reconnect] starting, reason: socket network error. connectionState: \(_state.connectionState)") Task { try await startReconnect() @@ -37,8 +36,7 @@ extension Engine: SignalClientDelegate { } } - func signalClient(_ signalClient: SignalClient, didReceive iceCandidate: LKRTCIceCandidate, target: Livekit_SignalTarget) { - + func signalClient(_: SignalClient, didReceive iceCandidate: LKRTCIceCandidate, target: Livekit_SignalTarget) { guard let transport = target == .subscriber ? subscriber : publisher else { log("Failed to add ice candidate, transport is nil for target: \(target)", .error) return @@ -47,28 +45,27 @@ extension Engine: SignalClientDelegate { Task { do { try await transport.add(iceCandidate: iceCandidate) - } catch let error { + } catch { log("Failed to add ice candidate for transport: \(transport), error: \(error)", .error) } } } - func signalClient(_ signalClient: SignalClient, didReceiveAnswer answer: LKRTCSessionDescription) { + func signalClient(_: SignalClient, didReceiveAnswer answer: LKRTCSessionDescription) { Task { do { let publisher = try await requirePublisher() try await publisher.set(remoteDescription: answer) - } catch let error { + } catch { log("Failed to set remote description, error: \(error)", .error) } } } func signalClient(_ signalClient: SignalClient, didReceiveOffer offer: LKRTCSessionDescription) { - log("Received offer, creating & sending answer...") - guard let subscriber = self.subscriber else { + guard let subscriber else { log("failed to send answer, subscriber is nil", .error) return } @@ -79,28 +76,27 @@ extension Engine: SignalClientDelegate { let answer = try await subscriber.createAnswer() try await subscriber.set(localDescription: answer) try await signalClient.send(answer: answer) - } catch let error { + } catch { log("Failed to send answer with error: \(error)", .error) } } } - func signalClient(_ signalClient: SignalClient, didUpdate token: String) { - + func signalClient(_: SignalClient, didUpdate token: String) { // update token _state.mutate { $0.token = token } } - func signalClient(_ signalClient: SignalClient, didReceive joinResponse: Livekit_JoinResponse) {} - func signalClient(_ signalClient: SignalClient, didPublish localTrack: Livekit_TrackPublishedResponse) {} - func signalClient(_ signalClient: SignalClient, didUnpublish localTrack: Livekit_TrackUnpublishedResponse) {} - func signalClient(_ signalClient: SignalClient, didUpdate participants: [Livekit_ParticipantInfo]) {} - func signalClient(_ signalClient: SignalClient, didUpdate room: Livekit_Room) {} - func signalClient(_ signalClient: SignalClient, didUpdate speakers: [Livekit_SpeakerInfo]) {} - func signalClient(_ signalClient: SignalClient, didUpdate connectionQuality: [Livekit_ConnectionQualityInfo]) {} - func signalClient(_ signalClient: SignalClient, didUpdateRemoteMute trackSid: String, muted: Bool) {} - func signalClient(_ signalClient: SignalClient, didUpdate trackStates: [Livekit_StreamStateInfo]) {} - func signalClient(_ signalClient: SignalClient, didUpdate trackSid: String, subscribedQualities: [Livekit_SubscribedQuality]) {} - func signalClient(_ signalClient: SignalClient, didUpdate subscriptionPermission: Livekit_SubscriptionPermissionUpdate) {} - func signalClient(_ signalClient: SignalClient, didReceiveLeave canReconnect: Bool, reason: Livekit_DisconnectReason) {} + func signalClient(_: SignalClient, didReceive _: Livekit_JoinResponse) {} + func signalClient(_: SignalClient, didPublish _: Livekit_TrackPublishedResponse) {} + func signalClient(_: SignalClient, didUnpublish _: Livekit_TrackUnpublishedResponse) {} + func signalClient(_: SignalClient, didUpdate _: [Livekit_ParticipantInfo]) {} + func signalClient(_: SignalClient, didUpdate _: Livekit_Room) {} + func signalClient(_: SignalClient, didUpdate _: [Livekit_SpeakerInfo]) {} + func signalClient(_: SignalClient, didUpdate _: [Livekit_ConnectionQualityInfo]) {} + func signalClient(_: SignalClient, didUpdateRemoteMute _: String, muted _: Bool) {} + func signalClient(_: SignalClient, didUpdate _: [Livekit_StreamStateInfo]) {} + func signalClient(_: SignalClient, didUpdate _: String, subscribedQualities _: [Livekit_SubscribedQuality]) {} + func signalClient(_: SignalClient, didUpdate _: Livekit_SubscriptionPermissionUpdate) {} + func signalClient(_: SignalClient, didReceiveLeave _: Bool, reason _: Livekit_DisconnectReason) {} } diff --git a/Sources/LiveKit/Core/Engine+TransportDelegate.swift b/Sources/LiveKit/Core/Engine+TransportDelegate.swift index 84f662168..fe667c31c 100644 --- a/Sources/LiveKit/Core/Engine+TransportDelegate.swift +++ b/Sources/LiveKit/Core/Engine+TransportDelegate.swift @@ -19,7 +19,6 @@ import Foundation @_implementationOnly import WebRTC extension Engine: TransportDelegate { - func transport(_ transport: Transport, didUpdate pcState: RTCPeerConnectionState) { log("target: \(transport.target), state: \(pcState)") @@ -35,7 +34,7 @@ extension Engine: TransportDelegate { if _state.connectionState.isConnected { // Attempt re-connect if primary or publisher transport failed - if (transport.isPrimary || (_state.hasPublished && transport.target == .publisher)) && [.disconnected, .failed].contains(pcState) { + if transport.isPrimary || (_state.hasPublished && transport.target == .publisher), [.disconnected, .failed].contains(pcState) { log("[reconnect] starting, reason: transport disconnected or failed") Task { try await startReconnect() @@ -54,12 +53,12 @@ extension Engine: TransportDelegate { func transport(_ transport: Transport, didAddTrack track: LKRTCMediaStreamTrack, rtpReceiver: LKRTCRtpReceiver, streams: [LKRTCMediaStream]) { log("did add track") if transport.target == .subscriber { - // execute block when connected execute(when: { state, _ in state.connectionState == .connected }, // always remove this block when disconnected - removeWhen: { state, _ in state.connectionState == .disconnected() }) { [weak self] in - guard let self = self else { return } + removeWhen: { state, _ in state.connectionState == .disconnected() }) + { [weak self] in + guard let self else { return } self.notify { $0.engine(self, didAddTrack: track, rtpReceiver: rtpReceiver, streams: streams) } } } @@ -72,11 +71,9 @@ extension Engine: TransportDelegate { } func transport(_ transport: Transport, didOpen dataChannel: LKRTCDataChannel) { - log("Server opened data channel \(dataChannel.label)(\(dataChannel.readyState))") if subscriberPrimary, transport.target == .subscriber { - switch dataChannel.label { case LKRTCDataChannel.labels.reliable: subscriberDC.set(reliable: dataChannel) case LKRTCDataChannel.labels.lossy: subscriberDC.set(lossy: dataChannel) @@ -85,5 +82,5 @@ extension Engine: TransportDelegate { } } - func transportShouldNegotiate(_ transport: Transport) {} + func transportShouldNegotiate(_: Transport) {} } diff --git a/Sources/LiveKit/Core/Engine+WebRTC.swift b/Sources/LiveKit/Core/Engine+WebRTC.swift index 538121bef..28735633d 100644 --- a/Sources/LiveKit/Core/Engine+WebRTC.swift +++ b/Sources/LiveKit/Core/Engine+WebRTC.swift @@ -19,7 +19,6 @@ import Foundation @_implementationOnly import WebRTC private extension Array where Element: LKRTCVideoCodecInfo { - func rewriteCodecsIfNeeded() -> [LKRTCVideoCodecInfo] { // rewrite H264's profileLevelId to 42e032 let codecs = map { $0.name == kRTCVideoCodecH264Name ? Engine.h264BaselineLevel5CodecInfo : $0 } @@ -29,32 +28,27 @@ private extension Array where Element: LKRTCVideoCodecInfo { } private class VideoEncoderFactory: LKRTCDefaultVideoEncoderFactory { - override func supportedCodecs() -> [LKRTCVideoCodecInfo] { super.supportedCodecs().rewriteCodecsIfNeeded() } } private class VideoDecoderFactory: LKRTCDefaultVideoDecoderFactory { - override func supportedCodecs() -> [LKRTCVideoCodecInfo] { super.supportedCodecs().rewriteCodecsIfNeeded() } } private class VideoEncoderFactorySimulcast: LKRTCVideoEncoderFactorySimulcast { - override func supportedCodecs() -> [LKRTCVideoCodecInfo] { super.supportedCodecs().rewriteCodecsIfNeeded() } } -internal extension Engine { - +extension Engine { static var bypassVoiceProcessing: Bool = false static let h264BaselineLevel5CodecInfo: LKRTCVideoCodecInfo = { - // this should never happen guard let profileLevelId = LKRTCH264ProfileLevelId(profile: .constrainedBaseline, level: .level5) else { logger.log("failed to generate profileLevelId", .error, type: Engine.self) @@ -70,21 +64,18 @@ internal extension Engine { // global properties are already lazy - static private let encoderFactory: LKRTCVideoEncoderFactory = { + private static let encoderFactory: LKRTCVideoEncoderFactory = { let encoderFactory = VideoEncoderFactory() return VideoEncoderFactorySimulcast(primary: encoderFactory, fallback: encoderFactory) }() - static private let decoderFactory = VideoDecoderFactory() + private static let decoderFactory = VideoDecoderFactory() - static let audioProcessingModule: LKRTCDefaultAudioProcessingModule = { - LKRTCDefaultAudioProcessingModule() - }() + static let audioProcessingModule: LKRTCDefaultAudioProcessingModule = .init() static let peerConnectionFactory: LKRTCPeerConnectionFactory = { - logger.log("Initializing SSL...", type: Engine.self) RTCInitializeSSL() @@ -109,7 +100,8 @@ internal extension Engine { } static func createPeerConnection(_ configuration: LKRTCConfiguration, - constraints: LKRTCMediaConstraints) -> LKRTCPeerConnection? { + constraints: LKRTCMediaConstraints) -> LKRTCPeerConnection? + { DispatchQueue.liveKitWebRTC.sync { peerConnectionFactory.peerConnection(with: configuration, constraints: constraints, delegate: nil) } @@ -134,7 +126,8 @@ internal extension Engine { } static func createDataChannelConfiguration(ordered: Bool = true, - maxRetransmits: Int32 = -1) -> LKRTCDataChannelConfiguration { + maxRetransmits: Int32 = -1) -> LKRTCDataChannelConfiguration + { let result = DispatchQueue.liveKitWebRTC.sync { LKRTCDataChannelConfiguration() } result.isOrdered = ordered result.maxRetransmits = maxRetransmits @@ -160,18 +153,18 @@ internal extension Engine { static func createRtpEncodingParameters(rid: String? = nil, encoding: MediaEncoding? = nil, scaleDownBy: Double? = nil, - active: Bool = true) -> LKRTCRtpEncodingParameters { - + active: Bool = true) -> LKRTCRtpEncodingParameters + { let result = DispatchQueue.liveKitWebRTC.sync { LKRTCRtpEncodingParameters() } result.isActive = active result.rid = rid - if let scaleDownBy = scaleDownBy { + if let scaleDownBy { result.scaleResolutionDownBy = NSNumber(value: scaleDownBy) } - if let encoding = encoding { + if let encoding { result.maxBitrateBps = NSNumber(value: encoding.maxBitrate) // VideoEncoding specific diff --git a/Sources/LiveKit/Core/Engine.swift b/Sources/LiveKit/Core/Engine.swift index 56b18599a..d27f0e1d8 100644 --- a/Sources/LiveKit/Core/Engine.swift +++ b/Sources/LiveKit/Core/Engine.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,20 +17,19 @@ import Foundation #if canImport(Network) -import Network + import Network #endif @_implementationOnly import WebRTC -internal class Engine: MulticastDelegate { - - internal let queue = DispatchQueue(label: "LiveKitSDK.engine", qos: .default) +class Engine: MulticastDelegate { + let queue = DispatchQueue(label: "LiveKitSDK.engine", qos: .default) // MARK: - Public public typealias ConditionEvalFunc = (_ newState: State, _ oldState: State?) -> Bool - internal struct State: ReconnectableState, Equatable { + struct State: ReconnectableState, Equatable { var connectOptions: ConnectOptions var url: String? var token: String? @@ -42,8 +41,8 @@ internal class Engine: MulticastDelegate { var hasPublished: Bool = false } - internal let primaryTransportConnectedCompleter = AsyncCompleter(label: "Primary transport connect", timeOut: .defaultTransportState) - internal let publisherTransportConnectedCompleter = AsyncCompleter(label: "Publisher transport connect", timeOut: .defaultTransportState) + let primaryTransportConnectedCompleter = AsyncCompleter(label: "Primary transport connect", timeOut: .defaultTransportState) + let publisherTransportConnectedCompleter = AsyncCompleter(label: "Publisher transport connect", timeOut: .defaultTransportState) public var _state: StateSync @@ -63,13 +62,13 @@ internal class Engine: MulticastDelegate { let block: () -> Void } - internal var subscriberPrimary: Bool = false + var subscriberPrimary: Bool = false private var primary: Transport? { subscriberPrimary ? subscriber : publisher } // MARK: - DataChannels - internal var subscriberDC = DataChannelPair(target: .subscriber) - internal var publisherDC = DataChannelPair(target: .publisher) + var subscriberDC = DataChannelPair(target: .subscriber) + var publisherDC = DataChannelPair(target: .publisher) private var _blockProcessQueue = DispatchQueue(label: "LiveKitSDK.engine.pendingBlocks", qos: .default) @@ -77,8 +76,7 @@ internal class Engine: MulticastDelegate { private var _queuedBlocks = [ConditionalExecutionEntry]() init(connectOptions: ConnectOptions) { - - self._state = StateSync(State(connectOptions: connectOptions)) + _state = StateSync(State(connectOptions: connectOptions)) super.init() // log sdk & os versions @@ -88,9 +86,9 @@ internal class Engine: MulticastDelegate { ConnectivityListener.shared.add(delegate: self) // trigger events when state mutates - self._state.onDidMutate = { [weak self] newState, oldState in + _state.onDidMutate = { [weak self] newState, oldState in - guard let self = self else { return } + guard let self else { return } assert(!(newState.connectionState == .reconnecting && newState.reconnectMode == .none), "reconnectMode should not be .none") @@ -102,7 +100,7 @@ internal class Engine: MulticastDelegate { // execution control self._blockProcessQueue.async { [weak self] in - guard let self = self, !self._queuedBlocks.isEmpty else { return } + guard let self, !self._queuedBlocks.isEmpty else { return } self.log("[execution control] processing pending entries (\(self._queuedBlocks.count))...") @@ -122,11 +120,11 @@ internal class Engine: MulticastDelegate { subscriberDC.onDataPacket = { [weak self] (dataPacket: Livekit_DataPacket) in - guard let self = self else { return } + guard let self else { return } switch dataPacket.value { - case .speaker(let update): self.notify { $0.engine(self, didUpdate: update.speakers) } - case .user(let userPacket): self.notify { $0.engine(self, didReceive: userPacket) } + case let .speaker(update): self.notify { $0.engine(self, didUpdate: update.speakers) } + case let .user(userPacket): self.notify { $0.engine(self, didReceive: userPacket) } default: return } } @@ -139,10 +137,10 @@ internal class Engine: MulticastDelegate { // Connect sequence, resets existing state func connect(_ url: String, _ token: String, - connectOptions: ConnectOptions? = nil) async throws { - + connectOptions: ConnectOptions? = nil) async throws + { // update options if specified - if let connectOptions = connectOptions, connectOptions != _state.connectOptions { + if let connectOptions, connectOptions != _state.connectOptions { _state.mutate { $0.connectOptions = connectOptions } } @@ -163,7 +161,7 @@ internal class Engine: MulticastDelegate { $0.connectionState = .connected } - } catch let error { + } catch { try await cleanUp(reason: .networkError(error)) } } @@ -179,33 +177,30 @@ internal class Engine: MulticastDelegate { // Resets state of transports func cleanUpRTC() async { // Close data channels - self.publisherDC.close() - self.subscriberDC.close() + publisherDC.close() + subscriberDC.close() // Close transports await publisher?.close() - self.publisher = nil + publisher = nil await subscriber?.close() - self.subscriber = nil + subscriber = nil // Reset publish state - self._state.mutate { $0.hasPublished = false } + _state.mutate { $0.hasPublished = false } } func publisherShouldNegotiate() async throws { - log() let publisher = try await requirePublisher() publisher.negotiate() - self._state.mutate { $0.hasPublished = true } + _state.mutate { $0.hasPublished = true } } func send(userPacket: Livekit_UserPacket, reliability: Reliability = .reliable) async throws { - - func ensurePublisherConnected () async throws { - + func ensurePublisherConnected() async throws { guard subscriberPrimary else { return } let publisher = try await requirePublisher() @@ -221,8 +216,8 @@ internal class Engine: MulticastDelegate { try await ensurePublisherConnected() // At this point publisher should be .connected and dc should be .open - assert(self.publisher?.isConnected ?? false, "publisher is not .connected") - assert(self.publisherDC.isOpen, "publisher data channel is not .open") + assert(publisher?.isConnected ?? false, "publisher is not .connected") + assert(publisherDC.isOpen, "publisher data channel is not .open") // Should return true if successful try publisherDC.send(userPacket: userPacket, reliability: reliability) @@ -231,10 +226,8 @@ internal class Engine: MulticastDelegate { // MARK: - Internal -internal extension Engine { - +extension Engine { func configureTransports(joinResponse: Livekit_JoinResponse) async throws { - log("Configuring transports...") guard subscriber == nil, publisher == nil else { @@ -246,7 +239,7 @@ internal extension Engine { subscriberPrimary = joinResponse.subscriberPrimary log("subscriberPrimary: \(joinResponse.subscriberPrimary)") - let connectOptions = self._state.connectOptions + let connectOptions = _state.connectOptions // Make a copy, instead of modifying the user-supplied RTCConfiguration object. let rtcConfiguration = LKRTCConfiguration.liveKitDefault() @@ -274,7 +267,7 @@ internal extension Engine { delegate: self) publisher.onOffer = { [weak self] offer in - guard let self = self else { return } + guard let self else { return } log("Publisher onOffer \(offer.sdp)") try await signalClient.send(offer: offer) } @@ -305,10 +298,8 @@ internal extension Engine { // MARK: - Execution control (Internal) -internal extension Engine { - +extension Engine { func executeIfConnected(_ block: @escaping @convention(block) () -> Void) { - if case .connected = _state.connectionState { // execute immediately block() @@ -317,15 +308,15 @@ internal extension Engine { func execute(when condition: @escaping ConditionEvalFunc, removeWhen removeCondition: @escaping ConditionEvalFunc, - _ block: @escaping () -> Void) { - + _ block: @escaping () -> Void) + { // already matches condition, execute immediately if _state.read({ condition($0, nil) }) { log("[execution control] executing immediately...") block() } else { _blockProcessQueue.async { [weak self] in - guard let self = self else { return } + guard let self else { return } // create an entry and enqueue block self.log("[execution control] enqueuing entry...") @@ -342,8 +333,7 @@ internal extension Engine { // MARK: - Connection / Reconnection logic -internal extension Engine { - +extension Engine { // full connect sequence, doesn't update connection state func fullConnectSequence(_ url: String, _ token: String) async throws { // This should never happen since Engine is owned by Room @@ -361,18 +351,17 @@ internal extension Engine { try await signalClient.resumeResponseQueue() try await primaryTransportConnectedCompleter.wait() _state.mutate { $0.connectStopwatch.split(label: "engine") } - log("\(self._state.connectStopwatch)") + log("\(_state.connectStopwatch)") } func startReconnect() async throws { - guard case .connected = _state.connectionState else { log("[reconnect] must be called with connected state", .warning) throw EngineError.state(message: "Must be called with connected state") } guard let url = _state.url, let token = _state.token else { - log("[reconnect] url or token is nil", . warning) + log("[reconnect] url or token is nil", .warning) throw EngineError.state(message: "url or token is nil") } @@ -404,7 +393,7 @@ internal extension Engine { subscriber?.isRestartingIce = true // Only if published, continue... - guard let publisher = publisher, _state.hasPublished else { return } + guard let publisher, _state.hasPublished else { return } log("[reconnect] waiting for publisher to connect...") @@ -422,8 +411,9 @@ internal extension Engine { log("[Reconnect] starting .full reconnect sequence...") try await cleanUp(isFullReconnect: true) - guard let url = self._state.url, - let token = self._state.token else { + guard let url = _state.url, + let token = _state.token + else { throw EngineError.state(message: "url or token is nil") } @@ -431,19 +421,20 @@ internal extension Engine { } let retryingTask = Task.retrying(maxRetryCount: _state.connectOptions.reconnectAttempts, - retryDelay: _state.connectOptions.reconnectAttemptDelay) { totalAttempts, currentAttempt in + retryDelay: _state.connectOptions.reconnectAttemptDelay) + { totalAttempts, currentAttempt in // Not reconnecting state anymore guard case .reconnecting = _state.connectionState else { return } // Full reconnect failed, give up - guard .full != _state.reconnectMode else { return } + guard _state.reconnectMode != .full else { return } self.log("[Reconnect] retry in \(_state.connectOptions.reconnectAttemptDelay) seconds, \(currentAttempt)/\(totalAttempts) tries left...") // Try full reconnect for the final attempt if totalAttempts == currentAttempt, _state.nextPreferredReconnectMode == nil { - _state.mutate { $0.nextPreferredReconnectMode = .full } + _state.mutate { $0.nextPreferredReconnectMode = .full } } let mode: ReconnectMode = self._state.mutate { @@ -466,7 +457,7 @@ internal extension Engine { // Re-connect sequence successful log("[reconnect] sequence completed") _state.mutate { $0.connectionState = .connected } - } catch let error { + } catch { log("[Reconnect] Sequence failed with error: \(error)") // Finally disconnect if all attempts fail try await cleanUp(reason: .networkError(error)) @@ -476,14 +467,13 @@ internal extension Engine { // MARK: - Session Migration -internal extension Engine { - +extension Engine { func sendSyncState() async throws { - let room = try await requireRoom() - guard let subscriber = subscriber, - let previousAnswer = subscriber.localDescription else { + guard let subscriber, + let previousAnswer = subscriber.localDescription + else { // No-op return } @@ -499,7 +489,7 @@ internal extension Engine { let trackSids = room._state.remoteParticipants.values.flatMap { participant in participant._state.tracks.values .filter { $0.subscribed != autoSubscribe } - .map { $0.sid } + .map(\.sid) } log("trackSids: \(trackSids)") @@ -519,15 +509,14 @@ internal extension Engine { // MARK: - Private helpers -internal extension Engine { - +extension Engine { func requireRoom() async throws -> Room { guard let room = _room else { throw EngineError.state(message: "Room is nil") } return room } func requirePublisher() async throws -> Transport { - guard let publisher = publisher else { throw EngineError.state(message: "Publisher is nil") } + guard let publisher else { throw EngineError.state(message: "Publisher is nil") } return publisher } } @@ -535,7 +524,6 @@ internal extension Engine { // MARK: - ConnectivityListenerDelegate extension Engine: ConnectivityListenerDelegate { - func connectivityListener(_: ConnectivityListener, didSwitch path: NWPath) { log("didSwitch path: \(path)") Task { diff --git a/Sources/LiveKit/Core/Room+Convenience.swift b/Sources/LiveKit/Core/Room+Convenience.swift index ec0e1a5f5..4eba0dfbc 100644 --- a/Sources/LiveKit/Core/Room+Convenience.swift +++ b/Sources/LiveKit/Core/Room+Convenience.swift @@ -16,12 +16,11 @@ import Foundation -extension Room { - - public var allParticipants: [Sid: Participant] { +public extension Room { + var allParticipants: [Sid: Participant] { var result: [Sid: Participant] = remoteParticipants - if let localParticipant = localParticipant { + if let localParticipant { result.updateValue(localParticipant, forKey: localParticipant.sid) } diff --git a/Sources/LiveKit/Core/Room+EngineDelegate.swift b/Sources/LiveKit/Core/Room+EngineDelegate.swift index 9f9a9b591..deca66786 100644 --- a/Sources/LiveKit/Core/Room+EngineDelegate.swift +++ b/Sources/LiveKit/Core/Room+EngineDelegate.swift @@ -19,24 +19,21 @@ import Foundation @_implementationOnly import WebRTC extension Room: EngineDelegate { - - func engine(_ engine: Engine, didMutate state: Engine.State, oldState: Engine.State) { - + func engine(_: Engine, didMutate state: Engine.State, oldState: Engine.State) { if state.connectionState != oldState.connectionState { // connectionState did update // only if quick-reconnect if case .connected = state.connectionState, case .quick = state.reconnectMode { - resetTrackSettings() } // Re-send track permissions - if case .connected = state.connectionState, let localParticipant = localParticipant { + if case .connected = state.connectionState, let localParticipant { Task { do { try await localParticipant.sendTrackSubscriptionPermissions() - } catch let error { + } catch { log("Failed to send track subscription permissions, error: \(error)", .error) } } @@ -55,7 +52,7 @@ extension Room: EngineDelegate { if case .connected = state.connectionState { let didReconnect = oldState.connectionState == .reconnecting delegates.notify { $0.room?(self, didConnect: didReconnect) } - } else if case .disconnected(let reason) = state.connectionState { + } else if case let .disconnected(reason) = state.connectionState { if case .connecting = oldState.connectionState { let error = reason?.networkError ?? NetworkError.disconnected(message: "Did fail to connect", rawError: nil) delegates.notify { $0.room?(self, didFailToConnect: error) } @@ -65,7 +62,7 @@ extension Room: EngineDelegate { } } - if state.connectionState.isReconnecting && state.reconnectMode == .full && oldState.reconnectMode != .full { + if state.connectionState.isReconnecting, state.reconnectMode == .full, oldState.reconnectMode != .full { Task { // Started full reconnect await cleanUpParticipants(notify: true) @@ -79,7 +76,6 @@ extension Room: EngineDelegate { } func engine(_ engine: Engine, didUpdate speakers: [Livekit_SpeakerInfo]) { - let activeSpeakers = _state.mutate { state -> [Participant] in var activeSpeakers: [Participant] = [] @@ -87,7 +83,8 @@ extension Room: EngineDelegate { for speaker in speakers { seenSids[speaker.sid] = true if let localParticipant = state.localParticipant, - speaker.sid == localParticipant.sid { + speaker.sid == localParticipant.sid + { localParticipant._state.mutate { $0.audioLevel = speaker.level $0.isSpeaking = true @@ -124,7 +121,7 @@ extension Room: EngineDelegate { } engine.executeIfConnected { [weak self] in - guard let self = self else { return } + guard let self else { return } self.delegates.notify(label: { "room.didUpdate speakers: \(activeSpeakers)" }) { $0.room?(self, didUpdate: activeSpeakers) @@ -132,8 +129,7 @@ extension Room: EngineDelegate { } } - func engine(_ engine: Engine, didAddTrack track: LKRTCMediaStreamTrack, rtpReceiver: LKRTCRtpReceiver, streams: [LKRTCMediaStream]) { - + func engine(_: Engine, didAddTrack track: LKRTCMediaStreamTrack, rtpReceiver: LKRTCRtpReceiver, streams: [LKRTCMediaStream]) { guard !streams.isEmpty else { log("Received onTrack with no streams!", .warning) return @@ -160,10 +156,10 @@ extension Room: EngineDelegate { } } - func engine(_ engine: Engine, didRemove track: LKRTCMediaStreamTrack) { + func engine(_: Engine, didRemove track: LKRTCMediaStreamTrack) { // find the publication - guard let publication = _state.remoteParticipants.values.map({ $0._state.tracks.values }).joined() - .first(where: { $0.sid == track.trackId }) else { return } + guard let publication = _state.remoteParticipants.values.map(\._state.tracks.values).joined() + .first(where: { $0.sid == track.trackId }) else { return } publication.set(track: nil) } @@ -172,15 +168,15 @@ extension Room: EngineDelegate { let participant = _state.remoteParticipants[userPacket.participantSid] engine.executeIfConnected { [weak self] in - guard let self = self else { return } + guard let self else { return } self.delegates.notify(label: { "room.didReceive data: \(userPacket.payload)" }) { $0.room?(self, participant: participant, didReceiveData: userPacket.payload, topic: userPacket.topic) } - if let participant = participant { - participant.delegates.notify(label: { "participant.didReceive data: \(userPacket.payload)" }) { [weak participant] (delegate) -> Void in - guard let participant = participant else { return } + if let participant { + participant.delegates.notify(label: { "participant.didReceive data: \(userPacket.payload)" }) { [weak participant] delegate in + guard let participant else { return } delegate.participant?(participant, didReceiveData: userPacket.payload, topic: userPacket.topic) } } diff --git a/Sources/LiveKit/Core/Room+MulticastDelegate.swift b/Sources/LiveKit/Core/Room+MulticastDelegate.swift index 3018c0758..5b918a476 100644 --- a/Sources/LiveKit/Core/Room+MulticastDelegate.swift +++ b/Sources/LiveKit/Core/Room+MulticastDelegate.swift @@ -17,7 +17,6 @@ import Foundation extension Room: MulticastDelegateProtocol { - public func add(delegate: RoomDelegate) { delegates.add(delegate: delegate) } diff --git a/Sources/LiveKit/Core/Room+SignalClientDelegate.swift b/Sources/LiveKit/Core/Room+SignalClientDelegate.swift index 179095afa..07d256133 100644 --- a/Sources/LiveKit/Core/Room+SignalClientDelegate.swift +++ b/Sources/LiveKit/Core/Room+SignalClientDelegate.swift @@ -19,9 +19,7 @@ import Foundation @_implementationOnly import WebRTC extension Room: SignalClientDelegate { - - func signalClient(_ signalClient: SignalClient, didReceiveLeave canReconnect: Bool, reason: Livekit_DisconnectReason) { - + func signalClient(_: SignalClient, didReceiveLeave canReconnect: Bool, reason: Livekit_DisconnectReason) { log("canReconnect: \(canReconnect), reason: \(reason)") if canReconnect { @@ -35,20 +33,18 @@ extension Room: SignalClientDelegate { } } - func signalClient(_ signalClient: SignalClient, didUpdate trackSid: String, subscribedQualities: [Livekit_SubscribedQuality]) { - - log("qualities: \(subscribedQualities.map({ String(describing: $0) }).joined(separator: ", "))") + func signalClient(_: SignalClient, didUpdate trackSid: String, subscribedQualities: [Livekit_SubscribedQuality]) { + log("qualities: \(subscribedQualities.map { String(describing: $0) }.joined(separator: ", "))") guard let localParticipant = _state.localParticipant else { return } localParticipant.onSubscribedQualitiesUpdate(trackSid: trackSid, subscribedQualities: subscribedQualities) } - func signalClient(_ signalClient: SignalClient, didReceive joinResponse: Livekit_JoinResponse) { - + func signalClient(_: SignalClient, didReceive joinResponse: Livekit_JoinResponse) { log("server version: \(joinResponse.serverVersion), region: \(joinResponse.serverRegion)", .info) - if self.e2eeManager != nil && !joinResponse.sifTrailer.isEmpty { - self.e2eeManager?.keyProvider().setSifTrailer(trailer: joinResponse.sifTrailer) + if e2eeManager != nil, !joinResponse.sifTrailer.isEmpty { + e2eeManager?.keyProvider().setSifTrailer(trailer: joinResponse.sifTrailer) } _state.mutate { @@ -71,7 +67,7 @@ extension Room: SignalClientDelegate { } } - func signalClient(_ signalClient: SignalClient, didUpdate room: Livekit_Room) { + func signalClient(_: SignalClient, didUpdate room: Livekit_Room) { _state.mutate { $0.metadata = room.metadata $0.isRecording = room.activeRecording @@ -81,14 +77,13 @@ extension Room: SignalClientDelegate { } } - func signalClient(_ signalClient: SignalClient, didUpdate speakers: [Livekit_SpeakerInfo]) { + func signalClient(_: SignalClient, didUpdate speakers: [Livekit_SpeakerInfo]) { log("speakers: \(speakers)", .trace) let activeSpeakers = _state.mutate { state -> [Participant] in var lastSpeakers = state.activeSpeakers.reduce(into: [Sid: Participant]()) { $0[$1.sid] = $1 } for speaker in speakers { - guard let participant = speaker.sid == state.localParticipant?.sid ? state.localParticipant : state.remoteParticipants[speaker.sid] else { continue } @@ -111,7 +106,7 @@ extension Room: SignalClientDelegate { } engine.executeIfConnected { [weak self] in - guard let self = self else { return } + guard let self else { return } self.delegates.notify(label: { "room.didUpdate speakers: \(speakers)" }) { $0.room?(self, didUpdate: activeSpeakers) @@ -119,12 +114,13 @@ extension Room: SignalClientDelegate { } } - func signalClient(_ signalClient: SignalClient, didUpdate connectionQuality: [Livekit_ConnectionQualityInfo]) { + func signalClient(_: SignalClient, didUpdate connectionQuality: [Livekit_ConnectionQualityInfo]) { log("connectionQuality: \(connectionQuality)", .trace) for entry in connectionQuality { if let localParticipant = _state.localParticipant, - entry.participantSid == localParticipant.sid { + entry.participantSid == localParticipant.sid + { // update for LocalParticipant localParticipant._state.mutate { $0.connectionQuality = entry.quality.toLKType() } } else if let participant = _state.remoteParticipants[entry.participantSid] { @@ -134,7 +130,7 @@ extension Room: SignalClientDelegate { } } - func signalClient(_ signalClient: SignalClient, didUpdateRemoteMute trackSid: String, muted: Bool) { + func signalClient(_: SignalClient, didUpdateRemoteMute trackSid: String, muted: Bool) { log("trackSid: \(trackSid) muted: \(muted)") guard let publication = _state.localParticipant?._state.tracks[trackSid] as? LocalTrackPublication else { @@ -151,20 +147,19 @@ extension Room: SignalClientDelegate { } } - func signalClient(_ signalClient: SignalClient, didUpdate subscriptionPermission: Livekit_SubscriptionPermissionUpdate) { - + func signalClient(_: SignalClient, didUpdate subscriptionPermission: Livekit_SubscriptionPermissionUpdate) { log("did update subscriptionPermission: \(subscriptionPermission)") guard let participant = _state.remoteParticipants[subscriptionPermission.participantSid], - let publication = participant.getTrackPublication(sid: subscriptionPermission.trackSid) else { + let publication = participant.getTrackPublication(sid: subscriptionPermission.trackSid) + else { return } publication.set(subscriptionAllowed: subscriptionPermission.allowed) } - func signalClient(_ signalClient: SignalClient, didUpdate trackStates: [Livekit_StreamStateInfo]) { - + func signalClient(_: SignalClient, didUpdate trackStates: [Livekit_StreamStateInfo]) { log("did update trackStates: \(trackStates.map { "(\($0.trackSid): \(String(describing: $0.state)))" }.joined(separator: ", "))") for update in trackStates { @@ -177,16 +172,14 @@ extension Room: SignalClientDelegate { } } - func signalClient(_ signalClient: SignalClient, didUpdate participants: [Livekit_ParticipantInfo]) { + func signalClient(_: SignalClient, didUpdate participants: [Livekit_ParticipantInfo]) { log("participants: \(participants)") var disconnectedParticipants = [Sid]() var newParticipants = [RemoteParticipant]() _state.mutate { - for info in participants { - if info.sid == $0.localParticipant?.sid { $0.localParticipant?.updateFromInfo(info: info) continue @@ -196,7 +189,6 @@ extension Room: SignalClientDelegate { // when it's disconnected, send updates disconnectedParticipants.append(info.sid) } else { - let isNewParticipant = $0.remoteParticipants[info.sid] == nil let participant = $0.getOrCreateRemoteParticipant(sid: info.sid, info: info, room: self) @@ -216,9 +208,8 @@ extension Room: SignalClientDelegate { } for participant in newParticipants { - engine.executeIfConnected { [weak self] in - guard let self = self else { return } + guard let self else { return } self.delegates.notify(label: { "room.participantDidJoin participant: \(participant)" }) { $0.room?(self, participantDidJoin: participant) @@ -227,11 +218,12 @@ extension Room: SignalClientDelegate { } } - func signalClient(_ signalClient: SignalClient, didUnpublish localTrack: Livekit_TrackUnpublishedResponse) { + func signalClient(_: SignalClient, didUnpublish localTrack: Livekit_TrackUnpublishedResponse) { log() - guard let localParticipant = localParticipant, - let publication = localParticipant._state.tracks[localTrack.trackSid] as? LocalTrackPublication else { + guard let localParticipant, + let publication = localParticipant._state.tracks[localTrack.trackSid] as? LocalTrackPublication + else { log("track publication not found", .warning) return } @@ -240,16 +232,16 @@ extension Room: SignalClientDelegate { do { try await localParticipant.unpublish(publication: publication) log("Unpublished track(\(localTrack.trackSid)") - } catch let error { + } catch { log("Failed to unpublish track(\(localTrack.trackSid), error: \(error)", .warning) } } } - func signalClient(_ signalClient: SignalClient, didMutate state: SignalClient.State, oldState: SignalClient.State) {} - func signalClient(_ signalClient: SignalClient, didReceiveAnswer answer: LKRTCSessionDescription) {} - func signalClient(_ signalClient: SignalClient, didReceiveOffer offer: LKRTCSessionDescription) {} - func signalClient(_ signalClient: SignalClient, didReceive iceCandidate: LKRTCIceCandidate, target: Livekit_SignalTarget) {} - func signalClient(_ signalClient: SignalClient, didPublish localTrack: Livekit_TrackPublishedResponse) {} - func signalClient(_ signalClient: SignalClient, didUpdate token: String) {} + func signalClient(_: SignalClient, didMutate _: SignalClient.State, oldState _: SignalClient.State) {} + func signalClient(_: SignalClient, didReceiveAnswer _: LKRTCSessionDescription) {} + func signalClient(_: SignalClient, didReceiveOffer _: LKRTCSessionDescription) {} + func signalClient(_: SignalClient, didReceive _: LKRTCIceCandidate, target _: Livekit_SignalTarget) {} + func signalClient(_: SignalClient, didPublish _: Livekit_TrackPublishedResponse) {} + func signalClient(_: SignalClient, didUpdate _: String) {} } diff --git a/Sources/LiveKit/Core/Room.swift b/Sources/LiveKit/Core/Room.swift index ba8ee0111..c552dd396 100644 --- a/Sources/LiveKit/Core/Room.swift +++ b/Sources/LiveKit/Core/Room.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,17 +17,16 @@ import Foundation #if canImport(Network) -import Network + import Network #endif @objc public class Room: NSObject, ObservableObject, Loggable { - // MARK: - MulticastDelegate - internal var delegates = MulticastDelegate() + var delegates = MulticastDelegate() - internal let queue = DispatchQueue(label: "LiveKitSDK.room", qos: .default) + let queue = DispatchQueue(label: "LiveKitSDK.room", qos: .default) // MARK: - Public @@ -90,11 +89,11 @@ public class Room: NSObject, ObservableObject, Loggable { // MARK: - Internal // Reference to Engine - internal let engine: Engine + let engine: Engine public var e2eeManager: E2EEManager? - internal struct State: Equatable { + struct State: Equatable { var options: RoomOptions var sid: String? @@ -115,7 +114,6 @@ public class Room: NSObject, ObservableObject, Loggable { @discardableResult mutating func getOrCreateRemoteParticipant(sid: Sid, info: Livekit_ParticipantInfo? = nil, room: Room) -> RemoteParticipant { - if let participant = remoteParticipants[sid] { return participant } @@ -126,13 +124,12 @@ public class Room: NSObject, ObservableObject, Loggable { } } - internal var _state: StateSync + var _state: StateSync // MARK: Objective-C Support @objc - public convenience override init() { - + override public convenience init() { self.init(delegate: nil, connectOptions: ConnectOptions(), roomOptions: RoomOptions()) @@ -141,10 +138,10 @@ public class Room: NSObject, ObservableObject, Loggable { @objc public init(delegate: RoomDelegateObjC? = nil, connectOptions: ConnectOptions? = nil, - roomOptions: RoomOptions? = nil) { - - self._state = StateSync(State(options: roomOptions ?? RoomOptions())) - self.engine = Engine(connectOptions: connectOptions ?? ConnectOptions()) + roomOptions: RoomOptions? = nil) + { + _state = StateSync(State(options: roomOptions ?? RoomOptions())) + engine = Engine(connectOptions: connectOptions ?? ConnectOptions()) super.init() log() @@ -156,7 +153,7 @@ public class Room: NSObject, ObservableObject, Loggable { engine.add(delegate: self) engine.signalClient.add(delegate: self) - if let delegate = delegate { + if let delegate { log("delegate: \(String(describing: delegate))") delegates.add(delegate: delegate) } @@ -167,17 +164,17 @@ public class Room: NSObject, ObservableObject, Loggable { // trigger events when state mutates _state.onDidMutate = { [weak self] newState, oldState in - guard let self = self else { return } + guard let self else { return } // metadata updated if let metadata = newState.metadata, metadata != oldState.metadata, // don't notify if empty string (first time only) - oldState.metadata == nil ? !metadata.isEmpty : true { - + oldState.metadata == nil ? !metadata.isEmpty : true + { // proceed only if connected... self.engine.executeIfConnected { [weak self] in - guard let self = self else { return } + guard let self else { return } self.delegates.notify(label: { "room.didUpdate metadata: \(metadata)" }) { $0.room?(self, didUpdate: metadata) @@ -190,7 +187,7 @@ public class Room: NSObject, ObservableObject, Loggable { // proceed only if connected... self.engine.executeIfConnected { [weak self] in - guard let self = self else { return } + guard let self else { return } self.delegates.notify(label: { "room.didUpdate isRecording: \(newState.isRecording)" }) { $0.room?(self, didUpdate: newState.isRecording) @@ -217,8 +214,8 @@ public class Room: NSObject, ObservableObject, Loggable { public func connect(_ url: String, _ token: String, connectOptions: ConnectOptions? = nil, - roomOptions: RoomOptions? = nil) async throws { - + roomOptions: RoomOptions? = nil) async throws + { log("connecting to room...", .info) let state = _state.copy() @@ -229,14 +226,14 @@ public class Room: NSObject, ObservableObject, Loggable { } // update options if specified - if let roomOptions = roomOptions, roomOptions != state.options { + if let roomOptions, roomOptions != state.options { _state.mutate { $0.options = roomOptions } } // enable E2EE if roomOptions?.e2eeOptions != nil { - self.e2eeManager = E2EEManager(e2eeOptions: roomOptions!.e2eeOptions!) - self.e2eeManager!.setup(room: self) + e2eeManager = E2EEManager(e2eeOptions: roomOptions!.e2eeOptions!) + e2eeManager!.setup(room: self) } try await engine.connect(url, token, connectOptions: connectOptions) @@ -246,13 +243,12 @@ public class Room: NSObject, ObservableObject, Loggable { @objc public func disconnect() async { - // Return if already disconnected state if case .disconnected = connectionState { return } do { try await engine.signalClient.sendLeave() - } catch let error { + } catch { log("Failed to send leave with error: \(error)") } @@ -262,11 +258,11 @@ public class Room: NSObject, ObservableObject, Loggable { // MARK: - Internal -internal extension Room { - +extension Room { // Resets state of Room - func cleanUp(reason: DisconnectReason? = nil, isFullReconnect: Bool = false) async { - + func cleanUp(reason: DisconnectReason? = nil, + isFullReconnect: Bool = false) async + { log("Reason: \(String(describing: reason))") // Start Engine cleanUp sequence @@ -299,15 +295,13 @@ internal extension Room { // MARK: - Internal -internal extension Room { - +extension Room { func cleanUpParticipants(notify _notify: Bool = true) async { - log("notify: \(_notify)") // Stop all local & remote tracks let allParticipants = ([[localParticipant], - _state.remoteParticipants.map { $0.value }] as [[Participant?]]) + _state.remoteParticipants.map(\.value)] as [[Participant?]]) .joined() .compactMap { $0 } @@ -322,7 +316,6 @@ internal extension Room { } func onParticipantDisconnect(sid: Sid) async throws { - guard let participant = _state.mutate({ $0.remoteParticipants.removeValue(forKey: sid) }) else { throw EngineError.state(message: "Participant not found for \(sid)") } @@ -333,19 +326,16 @@ internal extension Room { // MARK: - Debugging -extension Room { - - public func sendSimulate(scenario: SimulateScenario) async throws { +public extension Room { + func sendSimulate(scenario: SimulateScenario) async throws { try await engine.signalClient.sendSimulate(scenario: scenario) } } // MARK: - Session Migration -internal extension Room { - +extension Room { func resetTrackSettings() { - log("resetting track settings...") // create an array of RemoteTrackPublication @@ -363,12 +353,10 @@ internal extension Room { // MARK: - AppStateDelegate extension Room: AppStateDelegate { - func appDidEnterBackground() { - guard _state.options.suspendLocalVideoTracksInBackground else { return } - guard let localParticipant = localParticipant else { return } + guard let localParticipant else { return } let cameraVideoTracks = localParticipant.localVideoTracks.filter { $0.source == .camera } @@ -378,7 +366,7 @@ extension Room: AppStateDelegate { for cameraVideoTrack in cameraVideoTracks { do { try await cameraVideoTrack.suspend() - } catch let error { + } catch { log("Failed to suspend video track with error: \(error)") } } @@ -386,8 +374,7 @@ extension Room: AppStateDelegate { } func appWillEnterForeground() { - - guard let localParticipant = localParticipant else { return } + guard let localParticipant else { return } let cameraVideoTracks = localParticipant.localVideoTracks.filter { $0.source == .camera } @@ -397,7 +384,7 @@ extension Room: AppStateDelegate { for cameraVideoTrack in cameraVideoTracks { do { try await cameraVideoTrack.resume() - } catch let error { + } catch { log("Failed to resumed video track with error: \(error)") } } @@ -415,11 +402,11 @@ extension Room: AppStateDelegate { // MARK: - Devices -extension Room { +public extension Room { /// Set this to true to bypass initialization of voice processing. /// Must be set before RTCPeerConnectionFactory gets initialized. @objc - public static var bypassVoiceProcessing: Bool { + static var bypassVoiceProcessing: Bool { get { Engine.bypassVoiceProcessing } set { Engine.bypassVoiceProcessing = newValue } } diff --git a/Sources/LiveKit/Core/SignalClient.swift b/Sources/LiveKit/Core/SignalClient.swift index 87c957546..e71749db2 100644 --- a/Sources/LiveKit/Core/SignalClient.swift +++ b/Sources/LiveKit/Core/SignalClient.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,7 @@ import Foundation @_implementationOnly import WebRTC -internal class SignalClient: MulticastDelegate { - +class SignalClient: MulticastDelegate { // MARK: - Types typealias AddTrackRequestPopulator = (inout Livekit_AddTrackRequest) throws -> R @@ -33,15 +32,15 @@ internal class SignalClient: MulticastDelegate { // MARK: - Internal - internal let joinResponseCompleter = AsyncCompleter(label: "Join response", timeOut: .defaultJoinResponse) - internal let _addTrackCompleters = CompleterMapActor(label: "Completers for add track", timeOut: .defaultPublish) + let joinResponseCompleter = AsyncCompleter(label: "Join response", timeOut: .defaultJoinResponse) + let _addTrackCompleters = CompleterMapActor(label: "Completers for add track", timeOut: .defaultPublish) - internal struct State: ReconnectableState, Equatable { + struct State: ReconnectableState, Equatable { var reconnectMode: ReconnectMode? var connectionState: ConnectionState = .disconnected() } - internal var _state = StateSync(State()) + var _state = StateSync(State()) // MARK: - Private @@ -61,9 +60,9 @@ internal class SignalClient: MulticastDelegate { log() // trigger events when state mutates - self._state.onDidMutate = { [weak self] newState, oldState in + _state.onDidMutate = { [weak self] newState, oldState in - guard let self = self else { return } + guard let self else { return } // connectionState did update if newState.connectionState != oldState.connectionState { @@ -82,8 +81,8 @@ internal class SignalClient: MulticastDelegate { _ token: String, connectOptions: ConnectOptions? = nil, reconnectMode: ReconnectMode? = nil, - adaptiveStream: Bool) async throws { - + adaptiveStream: Bool) async throws + { await cleanUp() log("reconnectMode: \(String(describing: reconnectMode))") @@ -92,8 +91,8 @@ internal class SignalClient: MulticastDelegate { token, connectOptions: connectOptions, reconnectMode: reconnectMode, - adaptiveStream: adaptiveStream) else { - + adaptiveStream: adaptiveStream) + else { throw InternalError.parse(message: "Failed to parse url") } @@ -122,8 +121,7 @@ internal class SignalClient: MulticastDelegate { } self.log("Did exit WebSocket message loop...") } - } catch let error { - + } catch { defer { Task { await cleanUp(reason: .networkError(error)) @@ -137,21 +135,20 @@ internal class SignalClient: MulticastDelegate { token, connectOptions: connectOptions, adaptiveStream: adaptiveStream, - validate: true) else { - + validate: true) + else { throw InternalError.parse(message: "Failed to parse validation url") } log("Validating with url: \(validateUrl)...") let validationResponse = try await HTTP.requestString(from: validateUrl) - self.log("Validate response: \(validationResponse)") + log("Validate response: \(validationResponse)") // re-throw with validation response throw SignalClientError.connect(message: "Validation response: \"\(validationResponse)\"") } } func cleanUp(reason: DisconnectReason? = nil) async { - log("reason: \(String(describing: reason))") _state.mutate { $0.connectionState = .disconnected(reason: reason) } @@ -161,13 +158,12 @@ internal class SignalClient: MulticastDelegate { if let socket = _webSocket { socket.reset() - self._webSocket = nil + _webSocket = nil } latestJoinResponse = nil _state.mutate { - joinResponseCompleter.cancel() // reset state @@ -183,10 +179,8 @@ internal class SignalClient: MulticastDelegate { // MARK: - Private private extension SignalClient { - // send request or enqueue while reconnecting func sendRequest(_ request: Livekit_SignalRequest, enqueueIfReconnecting: Bool = true) async throws { - guard !(_state.connectionState.isReconnecting && request.canEnqueue() && enqueueIfReconnecting) else { log("Queuing request while reconnecting, request: \(request)") await _requestQueue.enqueue(request) @@ -209,16 +203,15 @@ private extension SignalClient { } func onWebSocketMessage(message: URLSessionWebSocketTask.Message) { - var response: Livekit_SignalResponse? - if case .data(let data) = message { + if case let .data(data) = message { response = try? Livekit_SignalResponse(contiguousBytes: data) - } else if case .string(let string) = message { + } else if case let .string(string) = message { response = try? Livekit_SignalResponse(jsonString: string) } - guard let response = response else { + guard let response else { log("Failed to decode SignalResponse", .warning) return } @@ -229,7 +222,6 @@ private extension SignalClient { } func processSignalResponse(_ response: Livekit_SignalResponse) async { - guard case .connected = connectionState else { log("Not connected", .warning) return @@ -241,33 +233,33 @@ private extension SignalClient { } switch message { - case .join(let joinResponse): + case let .join(joinResponse): await _responseQueue.suspend() latestJoinResponse = joinResponse restartPingTimer() notify { $0.signalClient(self, didReceive: joinResponse) } joinResponseCompleter.resume(returning: joinResponse) - case .answer(let sd): + case let .answer(sd): notify { $0.signalClient(self, didReceiveAnswer: sd.toRTCType()) } - case .offer(let sd): + case let .offer(sd): notify { $0.signalClient(self, didReceiveOffer: sd.toRTCType()) } - case .trickle(let trickle): + case let .trickle(trickle): guard let rtcCandidate = try? Engine.createIceCandidate(fromJsonString: trickle.candidateInit) else { return } notify { $0.signalClient(self, didReceive: rtcCandidate, target: trickle.target) } - case .update(let update): + case let .update(update): notify { $0.signalClient(self, didUpdate: update.participants) } - case .roomUpdate(let update): + case let .roomUpdate(update): notify { $0.signalClient(self, didUpdate: update.room) } - case .trackPublished(let trackPublished): + case let .trackPublished(trackPublished): // not required to be handled because we use completer pattern for this case notify { $0.signalClient(self, didPublish: trackPublished) } @@ -275,35 +267,35 @@ private extension SignalClient { // Complete await _addTrackCompleters.resume(returning: trackPublished.track, for: trackPublished.cid) - case .trackUnpublished(let trackUnpublished): + case let .trackUnpublished(trackUnpublished): notify { $0.signalClient(self, didUnpublish: trackUnpublished) } - case .speakersChanged(let speakers): + case let .speakersChanged(speakers): notify { $0.signalClient(self, didUpdate: speakers.speakers) } - case .connectionQuality(let quality): + case let .connectionQuality(quality): notify { $0.signalClient(self, didUpdate: quality.updates) } - case .mute(let mute): + case let .mute(mute): notify { $0.signalClient(self, didUpdateRemoteMute: mute.sid, muted: mute.muted) } - case .leave(let leave): + case let .leave(leave): notify { $0.signalClient(self, didReceiveLeave: leave.canReconnect, reason: leave.reason) } - case .streamStateUpdate(let states): + case let .streamStateUpdate(states): notify { $0.signalClient(self, didUpdate: states.streamStates) } - case .subscribedQualityUpdate(let update): + case let .subscribedQualityUpdate(update): // ignore 0.15.1 if latestJoinResponse?.serverVersion == "0.15.1" { return } - notify { $0.signalClient(self, didUpdate: update.trackSid, subscribedQualities: update.subscribedQualities)} - case .subscriptionPermissionUpdate(let permissionUpdate): + notify { $0.signalClient(self, didUpdate: update.trackSid, subscribedQualities: update.subscribedQualities) } + case let .subscriptionPermissionUpdate(permissionUpdate): notify { $0.signalClient(self, didUpdate: permissionUpdate) } - case .refreshToken(let token): + case let .refreshToken(token): notify { $0.signalClient(self, didUpdate: token) } - case .pong(let r): + case let .pong(r): onReceivedPong(r) case .reconnect: log("received reconnect message") @@ -317,10 +309,8 @@ private extension SignalClient { // MARK: - Internal -internal extension SignalClient { - +extension SignalClient { func resumeResponseQueue() async throws { - await _responseQueue.resume { response in await processSignalResponse(response) } @@ -329,21 +319,18 @@ internal extension SignalClient { // MARK: - Send methods -internal extension SignalClient { - +extension SignalClient { func sendQueuedRequests() async throws { - await _requestQueue.resume { element in do { try await sendRequest(element, enqueueIfReconnecting: false) - } catch let error { + } catch { log("Failed to send queued request \(element) with error: \(error)", .error) } } } func send(offer: LKRTCSessionDescription) async throws { - let r = Livekit_SignalRequest.with { $0.offer = offer.toPBType() } @@ -352,7 +339,6 @@ internal extension SignalClient { } func send(answer: LKRTCSessionDescription) async throws { - let r = Livekit_SignalRequest.with { $0.answer = answer.toPBType() } @@ -361,7 +347,6 @@ internal extension SignalClient { } func sendCandidate(candidate: LKRTCIceCandidate, target: Livekit_SignalTarget) async throws { - let r = try Livekit_SignalRequest.with { $0.trickle = try Livekit_TrickleRequest.with { $0.target = target @@ -373,7 +358,6 @@ internal extension SignalClient { } func sendMuteTrack(trackSid: String, muted: Bool) async throws { - let r = Livekit_SignalRequest.with { $0.mute = Livekit_MuteTrackRequest.with { $0.sid = trackSid @@ -389,8 +373,8 @@ internal extension SignalClient { type: Livekit_TrackType, source: Livekit_TrackSource = .unknown, encryption: Livekit_Encryption.TypeEnum = .none, - _ populator: AddTrackRequestPopulator) async throws -> AddTrackResult { - + _ populator: AddTrackRequestPopulator) async throws -> AddTrackResult + { var addTrackRequest = Livekit_AddTrackRequest.with { $0.cid = cid $0.name = name @@ -418,7 +402,6 @@ internal extension SignalClient { } func sendUpdateTrackSettings(sid: Sid, settings: TrackSettings) async throws { - let r = Livekit_SignalRequest.with { $0.trackSetting = Livekit_UpdateTrackSettings.with { $0.trackSids = [sid] @@ -434,8 +417,8 @@ internal extension SignalClient { } func sendUpdateVideoLayers(trackSid: Sid, - layers: [Livekit_VideoLayer]) async throws { - + layers: [Livekit_VideoLayer]) async throws + { let r = Livekit_SignalRequest.with { $0.updateLayers = Livekit_UpdateVideoLayers.with { $0.trackSid = trackSid @@ -448,8 +431,8 @@ internal extension SignalClient { func sendUpdateSubscription(participantSid: Sid, trackSid: String, - subscribed: Bool) async throws { - + subscribed: Bool) async throws + { let p = Livekit_ParticipantTracks.with { $0.participantSid = participantSid $0.trackSids = [trackSid] @@ -467,12 +450,12 @@ internal extension SignalClient { } func sendUpdateSubscriptionPermission(allParticipants: Bool, - trackPermissions: [ParticipantTrackPermission]) async throws { - + trackPermissions: [ParticipantTrackPermission]) async throws + { let r = Livekit_SignalRequest.with { $0.subscriptionPermission = Livekit_SubscriptionPermission.with { $0.allParticipants = allParticipants - $0.trackPermissions = trackPermissions.map({ $0.toPBType() }) + $0.trackPermissions = trackPermissions.map { $0.toPBType() } } } @@ -480,7 +463,6 @@ internal extension SignalClient { } func sendUpdateLocalMetadata(_ metadata: String, name: String) async throws { - let r = Livekit_SignalRequest.with { $0.updateMetadata = Livekit_UpdateParticipantMetadata.with { $0.metadata = metadata @@ -495,12 +477,12 @@ internal extension SignalClient { offer: Livekit_SessionDescription?, subscription: Livekit_UpdateSubscription, publishTracks: [Livekit_TrackPublishedResponse]? = nil, - dataChannels: [Livekit_DataChannelInfo]? = nil) async throws { - + dataChannels: [Livekit_DataChannelInfo]? = nil) async throws + { let r = Livekit_SignalRequest.with { $0.syncState = Livekit_SyncState.with { $0.answer = answer - if let offer = offer { + if let offer { $0.offer = offer } $0.subscription = subscription @@ -513,7 +495,6 @@ internal extension SignalClient { } func sendLeave() async throws { - let r = Livekit_SignalRequest.with { $0.leave = Livekit_LeaveRequest.with { $0.canReconnect = false @@ -525,7 +506,6 @@ internal extension SignalClient { } func sendSimulate(scenario: SimulateScenario) async throws { - var shouldDisconnect = false let r = Livekit_SignalRequest.with { @@ -534,7 +514,7 @@ internal extension SignalClient { case .nodeFailure: $0.nodeFailure = true case .migration: $0.migration = true case .serverLeave: $0.serverLeave = true - case .speakerUpdate(let secs): $0.speakerUpdate = Int32(secs) + case let .speakerUpdate(secs): $0.speakerUpdate = Int32(secs) case .forceTCP: $0.switchCandidateProtocol = Livekit_CandidateProtocol.tcp shouldDisconnect = true @@ -557,7 +537,6 @@ internal extension SignalClient { } private func sendPing() async throws { - let r = Livekit_SignalRequest.with { $0.ping = Int64(Date().timeIntervalSince1970) } @@ -569,9 +548,7 @@ internal extension SignalClient { // MARK: - Server ping/pong logic private extension SignalClient { - func onPingIntervalTimer() async throws { - guard let jr = latestJoinResponse else { return } try await sendPing() @@ -582,7 +559,7 @@ private extension SignalClient { pingTimeoutTimer = { let timer = DispatchQueueTimer(timeInterval: TimeInterval(jr.pingTimeout), queue: self.queue) timer.handler = { [weak self] in - guard let self = self else { return } + guard let self else { return } self.log("ping/pong timed out", .error) Task { await self.cleanUp(reason: .networkError(SignalClientError.serverPingTimedOut())) @@ -594,8 +571,7 @@ private extension SignalClient { } } - func onReceivedPong(_ r: Int64) { - + func onReceivedPong(_: Int64) { log("ping/pong received pong from server", .trace) // clear timeout timer pingTimeoutTimer = nil @@ -626,10 +602,9 @@ private extension SignalClient { } } -internal extension Livekit_SignalRequest { - +extension Livekit_SignalRequest { func canEnqueue() -> Bool { - switch self.message { + switch message { case .syncState: return false case .trickle: return false case .offer: return false @@ -642,7 +617,6 @@ internal extension Livekit_SignalRequest { } private extension SignalClient { - func requireWebSocket() async throws -> WebSocket { // This shouldn't happen guard let result = _webSocket else { diff --git a/Sources/LiveKit/Core/Transport.swift b/Sources/LiveKit/Core/Transport.swift index 3233ba913..237d87370 100644 --- a/Sources/LiveKit/Core/Transport.swift +++ b/Sources/LiveKit/Core/Transport.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ import SwiftProtobuf @_implementationOnly import WebRTC -internal class Transport: MulticastDelegate { - +class Transport: MulticastDelegate { typealias OnOfferBlock = (LKRTCSessionDescription) async throws -> Void // MARK: - Public @@ -55,11 +54,11 @@ internal class Transport: MulticastDelegate { public lazy var negotiate = Utils.createDebounceFunc(on: _queue, wait: 0.1, onCreateWorkItem: { [weak self] workItem in - self?._debounceWorkItem = workItem + self?._debounceWorkItem = workItem }, fnc: { [weak self] in - Task { [weak self] in - try await self?.createAndSendOffer() - } + Task { [weak self] in + try await self?.createAndSendOffer() + } }) // MARK: - Private @@ -78,18 +77,18 @@ internal class Transport: MulticastDelegate { init(config: LKRTCConfiguration, target: Livekit_SignalTarget, primary: Bool, - delegate: TransportDelegate) throws { - + delegate: TransportDelegate) throws + { // try create peerConnection guard let pc = Engine.createPeerConnection(config, - constraints: .defaultPCConstraints) else { - + constraints: .defaultPCConstraints) + else { throw EngineError.webRTC(message: "failed to create peerConnection") } self.target = target - self.isPrimary = primary - self._pc = pc + isPrimary = primary + _pc = pc super.init() log() @@ -103,8 +102,7 @@ internal class Transport: MulticastDelegate { } func add(iceCandidate candidate: LKRTCIceCandidate) async throws { - - if remoteDescription != nil && !isRestartingIce { + if remoteDescription != nil, !isRestartingIce { return try await _pc.add(candidate) } @@ -112,13 +110,12 @@ internal class Transport: MulticastDelegate { } func set(remoteDescription sd: LKRTCSessionDescription) async throws { - try await _pc.setRemoteDescription(sd) await _pendingCandidatesQueue.resume { candidate in do { try await add(iceCandidate: candidate) - } catch let error { + } catch { log("Failed to add(iceCandidate:) with error: \(error)", .error) } } @@ -132,8 +129,7 @@ internal class Transport: MulticastDelegate { } func createAndSendOffer(iceRestart: Bool = false) async throws { - - guard let onOffer = onOffer else { + guard let onOffer else { log("onOffer is nil", .warning) return } @@ -166,9 +162,8 @@ internal class Transport: MulticastDelegate { } func close() async { - // prevent debounced negotiate firing - self._debounceWorkItem?.cancel() + _debounceWorkItem?.cancel() DispatchQueue.liveKitWebRTC.sync { // Stop listening to delegate @@ -186,7 +181,6 @@ internal class Transport: MulticastDelegate { // MARK: - Stats extension Transport { - func statistics(for sender: LKRTCRtpSender) async -> LKRTCStatisticsReport { await _pc.statistics(for: sender) } @@ -199,28 +193,27 @@ extension Transport { // MARK: - RTCPeerConnectionDelegate extension Transport: LKRTCPeerConnectionDelegate { - - internal func peerConnection(_ peerConnection: LKRTCPeerConnection, didChange state: RTCPeerConnectionState) { + func peerConnection(_: LKRTCPeerConnection, didChange state: RTCPeerConnectionState) { log("did update state \(state) for \(target)") notify { $0.transport(self, didUpdate: state) } } - internal func peerConnection(_ peerConnection: LKRTCPeerConnection, - didGenerate candidate: LKRTCIceCandidate) { - + func peerConnection(_: LKRTCPeerConnection, + didGenerate candidate: LKRTCIceCandidate) + { log("Did generate ice candidates \(candidate) for \(target)") notify { $0.transport(self, didGenerate: candidate) } } - internal func peerConnectionShouldNegotiate(_ peerConnection: LKRTCPeerConnection) { + func peerConnectionShouldNegotiate(_: LKRTCPeerConnection) { log("ShouldNegotiate for \(target)") notify { $0.transportShouldNegotiate(self) } } - internal func peerConnection(_ peerConnection: LKRTCPeerConnection, - didAdd rtpReceiver: LKRTCRtpReceiver, - streams mediaStreams: [LKRTCMediaStream]) { - + func peerConnection(_: LKRTCPeerConnection, + didAdd rtpReceiver: LKRTCRtpReceiver, + streams mediaStreams: [LKRTCMediaStream]) + { guard let track = rtpReceiver.track else { log("Track is empty for \(target)", .warning) return @@ -230,9 +223,9 @@ extension Transport: LKRTCPeerConnectionDelegate { notify { $0.transport(self, didAddTrack: track, rtpReceiver: rtpReceiver, streams: mediaStreams) } } - internal func peerConnection(_ peerConnection: LKRTCPeerConnection, - didRemove rtpReceiver: LKRTCRtpReceiver) { - + func peerConnection(_: LKRTCPeerConnection, + didRemove rtpReceiver: LKRTCRtpReceiver) + { guard let track = rtpReceiver.track else { log("Track is empty for \(target)", .warning) return @@ -242,25 +235,23 @@ extension Transport: LKRTCPeerConnectionDelegate { notify { $0.transport(self, didRemove: track) } } - internal func peerConnection(_ peerConnection: LKRTCPeerConnection, didOpen dataChannel: LKRTCDataChannel) { + func peerConnection(_: LKRTCPeerConnection, didOpen dataChannel: LKRTCDataChannel) { log("Received data channel \(dataChannel.label) for \(target)") notify { $0.transport(self, didOpen: dataChannel) } } - internal func peerConnection(_ peerConnection: LKRTCPeerConnection, didChange newState: RTCIceConnectionState) {} - internal func peerConnection(_ peerConnection: LKRTCPeerConnection, didRemove stream: LKRTCMediaStream) {} - internal func peerConnection(_ peerConnection: LKRTCPeerConnection, didChange stateChanged: RTCSignalingState) {} - internal func peerConnection(_ peerConnection: LKRTCPeerConnection, didAdd stream: LKRTCMediaStream) {} - internal func peerConnection(_ peerConnection: LKRTCPeerConnection, didChange newState: RTCIceGatheringState) {} - internal func peerConnection(_ peerConnection: LKRTCPeerConnection, didRemove candidates: [LKRTCIceCandidate]) {} + func peerConnection(_: LKRTCPeerConnection, didChange _: RTCIceConnectionState) {} + func peerConnection(_: LKRTCPeerConnection, didRemove _: LKRTCMediaStream) {} + func peerConnection(_: LKRTCPeerConnection, didChange _: RTCSignalingState) {} + func peerConnection(_: LKRTCPeerConnection, didAdd _: LKRTCMediaStream) {} + func peerConnection(_: LKRTCPeerConnection, didChange _: RTCIceGatheringState) {} + func peerConnection(_: LKRTCPeerConnection, didRemove _: [LKRTCIceCandidate]) {} } // MARK: - Private private extension Transport { - func createOffer(for constraints: [String: String]? = nil) async throws -> LKRTCSessionDescription { - let mediaConstraints = LKRTCMediaConstraints(mandatoryConstraints: constraints, optionalConstraints: nil) @@ -270,10 +261,8 @@ private extension Transport { // MARK: - Internal -internal extension Transport { - +extension Transport { func createAnswer(for constraints: [String: String]? = nil) async throws -> LKRTCSessionDescription { - let mediaConstraints = LKRTCMediaConstraints(mandatoryConstraints: constraints, optionalConstraints: nil) @@ -285,8 +274,8 @@ internal extension Transport { } func addTransceiver(with track: LKRTCMediaStreamTrack, - transceiverInit: LKRTCRtpTransceiverInit) throws -> LKRTCRtpTransceiver { - + transceiverInit: LKRTCRtpTransceiverInit) throws -> LKRTCRtpTransceiver + { guard let transceiver = DispatchQueue.liveKitWebRTC.sync(execute: { _pc.addTransceiver(with: track, init: transceiverInit) }) else { throw EngineError.webRTC(message: "Failed to add transceiver") } @@ -302,8 +291,8 @@ internal extension Transport { func dataChannel(for label: String, configuration: LKRTCDataChannelConfiguration, - delegate: LKRTCDataChannelDelegate? = nil) -> LKRTCDataChannel? { - + delegate: LKRTCDataChannelDelegate? = nil) -> LKRTCDataChannel? + { let result = DispatchQueue.liveKitWebRTC.sync { _pc.dataChannel(forLabel: label, configuration: configuration) } result?.delegate = delegate return result diff --git a/Sources/LiveKit/E2EE/E2EEManager.swift b/Sources/LiveKit/E2EE/E2EEManager.swift index e1172ec3c..bbbb623b8 100644 --- a/Sources/LiveKit/E2EE/E2EEManager.swift +++ b/Sources/LiveKit/E2EE/E2EEManager.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,8 @@ import Foundation @objc public class E2EEManager: NSObject, ObservableObject, Loggable { - // Private delegate adapter to hide RTCFrameCryptorDelegate symbol private class DelegateAdapter: NSObject, LKRTCFrameCryptorDelegate { - weak var target: E2EEManager? init(target: E2EEManager? = nil) { @@ -32,30 +30,31 @@ public class E2EEManager: NSObject, ObservableObject, Loggable { func frameCryptor(_ frameCryptor: LKRTCFrameCryptor, didStateChangeWithParticipantId participantId: String, - with stateChanged: FrameCryptionState) { + with stateChanged: FrameCryptionState) + { // Redirect target?.frameCryptor(frameCryptor, didStateChangeWithParticipantId: participantId, with: stateChanged) } } // Reference to Room - internal weak var room: Room? - internal var enabled: Bool = true + weak var room: Room? + var enabled: Bool = true public var e2eeOptions: E2EEOptions - internal var frameCryptors = [[String: Sid]: LKRTCFrameCryptor]() - internal var trackPublications = [LKRTCFrameCryptor: TrackPublication]() - private lazy var delegateAdapter: DelegateAdapter = { DelegateAdapter(target: self) }() + var frameCryptors = [[String: Sid]: LKRTCFrameCryptor]() + var trackPublications = [LKRTCFrameCryptor: TrackPublication]() + private lazy var delegateAdapter: DelegateAdapter = .init(target: self) public init(e2eeOptions: E2EEOptions) { self.e2eeOptions = e2eeOptions } public func keyProvider() -> BaseKeyProvider { - return self.e2eeOptions.keyProvider + e2eeOptions.keyProvider } - internal func getFrameCryptors() -> [[String: Sid]: LKRTCFrameCryptor] { - return self.frameCryptors + func getFrameCryptors() -> [[String: Sid]: LKRTCFrameCryptor] { + frameCryptors } public func setup(room: Room) { @@ -64,7 +63,7 @@ public class E2EEManager: NSObject, ObservableObject, Loggable { } self.room = room self.room?.delegates.add(delegate: self) - self.room?.localParticipant?.tracks.forEach({ (_: Sid, publication: TrackPublication) in + self.room?.localParticipant?.tracks.forEach { (_: Sid, publication: TrackPublication) in if publication.encryptionType == EncryptionType.none { self.log("E2EEManager::setup: local participant \(self.room!.localParticipant!.identity) track \(publication.sid) encryptionType is none, skip") return @@ -75,10 +74,10 @@ public class E2EEManager: NSObject, ObservableObject, Loggable { } let fc = addRtpSender(sender: publication.track!.rtpSender!, participantId: self.room!.localParticipant!.identity, trackSid: publication.sid) trackPublications[fc] = publication - }) + } - self.room?.remoteParticipants.forEach({ (_: Sid, participant: RemoteParticipant) in - participant.tracks.forEach({ (_: Sid, publication: TrackPublication) in + self.room?.remoteParticipants.forEach { (_: Sid, participant: RemoteParticipant) in + participant.tracks.forEach { (_: Sid, publication: TrackPublication) in if publication.encryptionType == EncryptionType.none { self.log("E2EEManager::setup: remote participant \(participant.identity) track \(publication.sid) encryptionType is none, skip") return @@ -89,8 +88,8 @@ public class E2EEManager: NSObject, ObservableObject, Loggable { } let fc = addRtpReceiver(receiver: publication.track!.rtpReceiver!, participantId: participant.identity, trackSid: publication.sid) trackPublications[fc] = publication - }) - }) + } + } } public func enableE2EE(enabled: Bool) { @@ -101,25 +100,25 @@ public class E2EEManager: NSObject, ObservableObject, Loggable { } func addRtpSender(sender: LKRTCRtpSender, participantId: String, trackSid: Sid) -> LKRTCFrameCryptor { - self.log("addRtpSender \(participantId) to E2EEManager") - let frameCryptor = LKRTCFrameCryptor(factory: Engine.peerConnectionFactory, rtpSender: sender, participantId: participantId, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: self.e2eeOptions.keyProvider.rtcKeyProvider!) + log("addRtpSender \(participantId) to E2EEManager") + let frameCryptor = LKRTCFrameCryptor(factory: Engine.peerConnectionFactory, rtpSender: sender, participantId: participantId, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: e2eeOptions.keyProvider.rtcKeyProvider!) frameCryptor.delegate = delegateAdapter frameCryptors[[participantId: trackSid]] = frameCryptor - frameCryptor.enabled = self.enabled + frameCryptor.enabled = enabled return frameCryptor } func addRtpReceiver(receiver: LKRTCRtpReceiver, participantId: String, trackSid: Sid) -> LKRTCFrameCryptor { - self.log("addRtpReceiver \(participantId) to E2EEManager") - let frameCryptor = LKRTCFrameCryptor(factory: Engine.peerConnectionFactory, rtpReceiver: receiver, participantId: participantId, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: self.e2eeOptions.keyProvider.rtcKeyProvider!) + log("addRtpReceiver \(participantId) to E2EEManager") + let frameCryptor = LKRTCFrameCryptor(factory: Engine.peerConnectionFactory, rtpReceiver: receiver, participantId: participantId, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: e2eeOptions.keyProvider.rtcKeyProvider!) frameCryptor.delegate = delegateAdapter frameCryptors[[participantId: trackSid]] = frameCryptor - frameCryptor.enabled = self.enabled + frameCryptor.enabled = enabled return frameCryptor } public func cleanUp() { - self.room?.delegates.remove(delegate: self) + room?.delegates.remove(delegate: self) for (_, frameCryptor) in frameCryptors { frameCryptor.delegate = nil } @@ -129,42 +128,40 @@ public class E2EEManager: NSObject, ObservableObject, Loggable { } extension E2EEManager { - func frameCryptor(_ frameCryptor: LKRTCFrameCryptor, didStateChangeWithParticipantId participantId: String, with state: FrameCryptionState) { - self.log("frameCryptor didStateChangeWithParticipantId \(participantId) with state \(state.rawValue)") + log("frameCryptor didStateChangeWithParticipantId \(participantId) with state \(state.rawValue)") let publication: TrackPublication? = trackPublications[frameCryptor] if publication == nil { - self.log("frameCryptor didStateChangeWithParticipantId \(participantId) with state \(state.rawValue) publication is nil") + log("frameCryptor didStateChangeWithParticipantId \(participantId) with state \(state.rawValue) publication is nil") return } - if self.room == nil { - self.log("frameCryptor didStateChangeWithParticipantId \(participantId) with state \(state.rawValue) room is nil") + if room == nil { + log("frameCryptor didStateChangeWithParticipantId \(participantId) with state \(state.rawValue) room is nil") return } - self.room?.delegates.notify { delegate in + room?.delegates.notify { delegate in delegate.room?(self.room!, publication: publication!, didUpdateE2EEState: state.toLKType()) } } } extension E2EEManager: RoomDelegate { - - public func room(_ room: Room, localParticipant: LocalParticipant, didPublish publication: LocalTrackPublication) { + public func room(_: Room, localParticipant: LocalParticipant, didPublish publication: LocalTrackPublication) { if publication.encryptionType == EncryptionType.none { - self.log("E2EEManager::RoomDelegate: local participant \(localParticipant.identity) track \(publication.sid) encryptionType is none, skip") + log("E2EEManager::RoomDelegate: local participant \(localParticipant.identity) track \(publication.sid) encryptionType is none, skip") return } if publication.track?.rtpSender == nil { - self.log("E2EEManager::RoomDelegate: publication.track?.rtpSender is nil, skip to create FrameCryptor!") + log("E2EEManager::RoomDelegate: publication.track?.rtpSender is nil, skip to create FrameCryptor!") return } let fc = addRtpSender(sender: publication.track!.rtpSender!, participantId: localParticipant.identity, trackSid: publication.sid) trackPublications[fc] = publication } - public func room(_ room: Room, localParticipant: LocalParticipant, didUnpublish publication: LocalTrackPublication) { + public func room(_: Room, localParticipant: LocalParticipant, didUnpublish publication: LocalTrackPublication) { let frameCryptor = frameCryptors.first(where: { (key: [String: Sid], _: LKRTCFrameCryptor) -> Bool in - return key[localParticipant.identity] == publication.sid + key[localParticipant.identity] == publication.sid })?.value frameCryptor?.delegate = nil @@ -176,22 +173,22 @@ extension E2EEManager: RoomDelegate { } } - public func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) { + public func room(_: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track _: Track) { if publication.encryptionType == EncryptionType.none { - self.log("E2EEManager::RoomDelegate: remote participant \(participant.identity) track \(publication.sid) encryptionType is none, skip") + log("E2EEManager::RoomDelegate: remote participant \(participant.identity) track \(publication.sid) encryptionType is none, skip") return } if publication.track?.rtpReceiver == nil { - self.log("E2EEManager::RoomDelegate: publication.track?.rtpReceiver is nil, skip to create FrameCryptor!") + log("E2EEManager::RoomDelegate: publication.track?.rtpReceiver is nil, skip to create FrameCryptor!") return } let fc = addRtpReceiver(receiver: publication.track!.rtpReceiver!, participantId: participant.identity, trackSid: publication.sid) trackPublications[fc] = publication } - public func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) { + public func room(_: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track _: Track) { let frameCryptor = frameCryptors.first(where: { (key: [String: Sid], _: LKRTCFrameCryptor) -> Bool in - return key[participant.identity] == publication.sid + key[participant.identity] == publication.sid })?.value frameCryptor?.delegate = nil diff --git a/Sources/LiveKit/E2EE/KeyProvider.swift b/Sources/LiveKit/E2EE/KeyProvider.swift index eec2e7998..2a9390895 100644 --- a/Sources/LiveKit/E2EE/KeyProvider.swift +++ b/Sources/LiveKit/E2EE/KeyProvider.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,13 +34,13 @@ public class KeyProviderOptions { ratchetSalt: Data = defaultRatchetSalt.data(using: .utf8)!, ratchetWindowSize: Int32 = defaultRatchetWindowSize, uncryptedMagicBytes: Data = defaultMagicBytes.data(using: .utf8)!, - failureTolerance: Int32 = defaultFailureTolerance - ) { + failureTolerance _: Int32 = defaultFailureTolerance) + { self.sharedKey = sharedKey self.ratchetSalt = ratchetSalt self.ratchetWindowSize = ratchetWindowSize self.uncryptedMagicBytes = uncryptedMagicBytes - self.failureTolerance = defaultFailureTolerance + failureTolerance = defaultFailureTolerance } } @@ -48,36 +48,35 @@ public class BaseKeyProvider: Loggable { var options: KeyProviderOptions var rtcKeyProvider: LKRTCFrameCryptorKeyProvider? public init(isSharedKey: Bool, sharedKey: String? = nil) { - self.options = KeyProviderOptions(sharedKey: isSharedKey) - self.rtcKeyProvider = LKRTCFrameCryptorKeyProvider(ratchetSalt: options.ratchetSalt, - ratchetWindowSize: options.ratchetWindowSize, - sharedKeyMode: isSharedKey, - uncryptedMagicBytes: options.uncryptedMagicBytes, - failureTolerance: options.failureTolerance) - if isSharedKey && sharedKey != nil { + options = KeyProviderOptions(sharedKey: isSharedKey) + rtcKeyProvider = LKRTCFrameCryptorKeyProvider(ratchetSalt: options.ratchetSalt, + ratchetWindowSize: options.ratchetWindowSize, + sharedKeyMode: isSharedKey, + uncryptedMagicBytes: options.uncryptedMagicBytes, + failureTolerance: options.failureTolerance) + if isSharedKey, sharedKey != nil { let keyData = sharedKey!.data(using: .utf8)! - self.rtcKeyProvider?.setSharedKey(keyData, with: 0) + rtcKeyProvider?.setSharedKey(keyData, with: 0) } } public init(options: KeyProviderOptions = KeyProviderOptions()) { self.options = options - self.rtcKeyProvider = LKRTCFrameCryptorKeyProvider(ratchetSalt: options.ratchetSalt, - ratchetWindowSize: options.ratchetWindowSize, - sharedKeyMode: options.sharedKey, - uncryptedMagicBytes: options.uncryptedMagicBytes) + rtcKeyProvider = LKRTCFrameCryptorKeyProvider(ratchetSalt: options.ratchetSalt, + ratchetWindowSize: options.ratchetWindowSize, + sharedKeyMode: options.sharedKey, + uncryptedMagicBytes: options.uncryptedMagicBytes) } public func setKey(key: String, participantId: String? = nil, index: Int32? = 0) { - if options.sharedKey { let keyData = key.data(using: .utf8)! - self.rtcKeyProvider?.setSharedKey(keyData, with: index ?? 0) + rtcKeyProvider?.setSharedKey(keyData, with: index ?? 0) return } if participantId == nil { - self.log("setKey: Please provide valid participantId for non-SharedKey mode.") + log("setKey: Please provide valid participantId for non-SharedKey mode.") return } @@ -91,7 +90,7 @@ public class BaseKeyProvider: Loggable { } if participantId == nil { - self.log("ratchetKey: Please provide valid participantId for non-SharedKey mode.") + log("ratchetKey: Please provide valid participantId for non-SharedKey mode.") return nil } @@ -104,7 +103,7 @@ public class BaseKeyProvider: Loggable { } if participantId == nil { - self.log("exportKey: Please provide valid participantId for non-SharedKey mode.") + log("exportKey: Please provide valid participantId for non-SharedKey mode.") return nil } diff --git a/Sources/LiveKit/E2EE/Options.swift b/Sources/LiveKit/E2EE/Options.swift index 3d204e1e7..c3ca39747 100644 --- a/Sources/LiveKit/E2EE/Options.swift +++ b/Sources/LiveKit/E2EE/Options.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,6 @@ public enum EncryptionType: Int { } extension EncryptionType { - func toPBType() -> Livekit_Encryption.TypeEnum { switch self { case .none: return .none diff --git a/Sources/LiveKit/E2EE/State.swift b/Sources/LiveKit/E2EE/State.swift index f337c2d55..f48853d1e 100644 --- a/Sources/LiveKit/E2EE/State.swift +++ b/Sources/LiveKit/E2EE/State.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,8 +29,8 @@ public enum E2EEState: Int { case internal_error } -extension E2EEState { - public func toString() -> String { +public extension E2EEState { + func toString() -> String { switch self { case .new: return "new" case .ok: return "ok" diff --git a/Sources/LiveKit/Errors.swift b/Sources/LiveKit/Errors.swift index 343a2c2cf..20441b23f 100644 --- a/Sources/LiveKit/Errors.swift +++ b/Sources/LiveKit/Errors.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,15 +21,13 @@ import Foundation public protocol LiveKitError: Error, CustomStringConvertible {} extension LiveKitError { - - internal func buildDescription(_ name: String, _ message: String? = nil, rawError: Error? = nil) -> String { + func buildDescription(_ name: String, _ message: String? = nil, rawError: Error? = nil) -> String { "\(String(describing: type(of: self))).\(name)" + (message != nil ? " \(message!)" : "") + (rawError != nil ? " rawError: \(rawError!.localizedDescription)" : "") } } -extension LiveKitError where Self: LocalizedError { - - public var localizedDescription: String { +public extension LiveKitError where Self: LocalizedError { + var localizedDescription: String { description } } @@ -52,10 +50,10 @@ public enum InternalError: LiveKitError { public var description: String { switch self { - case .state(let message): return buildDescription("state", message) - case .parse(let message): return buildDescription("parse", message) - case .convert(let message): return buildDescription("convert", message) - case .timeout(let message): return buildDescription("timeout", message) + case let .state(message): return buildDescription("state", message) + case let .parse(message): return buildDescription("parse", message) + case let .convert(message): return buildDescription("convert", message) + case let .timeout(message): return buildDescription("timeout", message) } } } @@ -68,9 +66,9 @@ public enum EngineError: LiveKitError { public var description: String { switch self { - case .webRTC(let message, _): return buildDescription("webRTC", message) - case .state(let message): return buildDescription("state", message) - case .timedOut(let message): return buildDescription("timedOut", message) + case let .webRTC(message, _): return buildDescription("webRTC", message) + case let .state(message): return buildDescription("state", message) + case let .timedOut(message): return buildDescription("timedOut", message) } } } @@ -86,13 +84,13 @@ public enum TrackError: LiveKitError { public var description: String { switch self { - case .state(let message): return buildDescription("state", message) - case .type(let message): return buildDescription("type", message) - case .duplicate(let message): return buildDescription("duplicate", message) - case .capturer(let message): return buildDescription("capturer", message) - case .publish(let message): return buildDescription("publish", message) - case .unpublish(let message): return buildDescription("unpublish", message) - case .timedOut(let message): return buildDescription("timedOut", message) + case let .state(message): return buildDescription("state", message) + case let .type(message): return buildDescription("type", message) + case let .duplicate(message): return buildDescription("duplicate", message) + case let .capturer(message): return buildDescription("capturer", message) + case let .publish(message): return buildDescription("publish", message) + case let .unpublish(message): return buildDescription("unpublish", message) + case let .timedOut(message): return buildDescription("timedOut", message) } } } @@ -107,12 +105,12 @@ public enum SignalClientError: LiveKitError { public var description: String { switch self { - case .state(let message): return buildDescription("state", message) - case .socketError(let rawError): return buildDescription("socketError", rawError: rawError) - case .close(let message): return buildDescription("close", message) - case .connect(let message): return buildDescription("connect", message) - case .timedOut(let message): return buildDescription("timedOut", message) - case .serverPingTimedOut(let message): return buildDescription("serverPingTimedOut", message) + case let .state(message): return buildDescription("state", message) + case let .socketError(rawError): return buildDescription("socketError", rawError: rawError) + case let .close(message): return buildDescription("close", message) + case let .connect(message): return buildDescription("connect", message) + case let .timedOut(message): return buildDescription("timedOut", message) + case let .serverPingTimedOut(message): return buildDescription("serverPingTimedOut", message) } } } @@ -123,19 +121,18 @@ public enum NetworkError: LiveKitError { public var description: String { switch self { - case .disconnected(let message, let rawError): return buildDescription("disconnected", message, rawError: rawError) - case .response(let message): return buildDescription("response", message) + case let .disconnected(message, rawError): return buildDescription("disconnected", message, rawError: rawError) + case let .response(message): return buildDescription("response", message) } } } public enum TransportError: LiveKitError { - case timedOut(message: String? = nil) public var description: String { switch self { - case .timedOut(let message): return buildDescription("timedOut", message) + case let .timedOut(message): return buildDescription("timedOut", message) } } } diff --git a/Sources/LiveKit/Extensions/CustomStringConvertible.swift b/Sources/LiveKit/Extensions/CustomStringConvertible.swift index a7d3cb740..01b0b764d 100644 --- a/Sources/LiveKit/Extensions/CustomStringConvertible.swift +++ b/Sources/LiveKit/Extensions/CustomStringConvertible.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,35 +19,30 @@ import Foundation @_implementationOnly import WebRTC extension TrackSettings: CustomStringConvertible { - public var description: String { "TrackSettings(enabled: \(enabled), dimensions: \(dimensions), videoQuality: \(videoQuality))" } } extension Livekit_VideoLayer: CustomStringConvertible { - public var description: String { "VideoLayer(quality: \(quality), dimensions: \(width)x\(height), bitrate: \(bitrate))" } } -extension TrackPublication { - - public override var description: String { +public extension TrackPublication { + override var description: String { "\(String(describing: type(of: self)))(sid: \(sid), kind: \(kind), source: \(source))" } } extension Livekit_AddTrackRequest: CustomStringConvertible { - public var description: String { "AddTrackRequest(cid: \(cid), name: \(name), type: \(type), source: \(source), width: \(width), height: \(height), muted: \(muted))" } } extension Livekit_TrackInfo: CustomStringConvertible { - public var description: String { "TrackInfo(sid: \(sid), " + "name: \(name), " + @@ -57,13 +52,12 @@ extension Livekit_TrackInfo: CustomStringConvertible { "height: \(height), " + "muted: \(muted), " + "simulcast: \(simulcast), " + - "codecs: \(codecs.map({ String(describing: $0) })), " + - "layers: \(layers.map({ String(describing: $0) })))" + "codecs: \(codecs.map { String(describing: $0) }), " + + "layers: \(layers.map { String(describing: $0) }))" } } extension Livekit_SubscribedQuality: CustomStringConvertible { - public var description: String { "SubscribedQuality(quality: \(quality), enabled: \(enabled))" } @@ -71,23 +65,20 @@ extension Livekit_SubscribedQuality: CustomStringConvertible { // MARK: - NSObject -extension Room { - - public override var description: String { +public extension Room { + override var description: String { "Room(sid: \(sid ?? "nil"), name: \(name ?? "nil"), serverVersion: \(serverVersion ?? "nil"), serverRegion: \(serverRegion ?? "nil"))" } } -extension Participant { - - public override var description: String { +public extension Participant { + override var description: String { "\(String(describing: type(of: self)))(sid: \(sid))" } } -extension Track { - - public override var description: String { +public extension Track { + override var description: String { "\(String(describing: type(of: self)))(sid: \(sid ?? "nil"), name: \(name), source: \(source))" } } diff --git a/Sources/LiveKit/Extensions/DispatchQueue.swift b/Sources/LiveKit/Extensions/DispatchQueue.swift index 5ffe4c825..f26159845 100644 --- a/Sources/LiveKit/Extensions/DispatchQueue.swift +++ b/Sources/LiveKit/Extensions/DispatchQueue.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,7 @@ import Foundation -extension DispatchQueue { - +public extension DispatchQueue { // The queue which SDK uses to invoke WebRTC methods - public static let liveKitWebRTC = DispatchQueue(label: "LiveKitSDK.webRTC", qos: .default) + static let liveKitWebRTC = DispatchQueue(label: "LiveKitSDK.webRTC", qos: .default) } diff --git a/Sources/LiveKit/Extensions/Logger.swift b/Sources/LiveKit/Extensions/Logger.swift index 3d83be73b..6649640bc 100644 --- a/Sources/LiveKit/Extensions/Logger.swift +++ b/Sources/LiveKit/Extensions/Logger.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,17 +18,14 @@ import Foundation import Logging /// Allows to extend with custom `log` method which automatically captures current type (class name). -public protocol Loggable { - -} +public protocol Loggable {} private var _scopedMetadataKey = "scopedMetadata" public typealias ScopedMetadata = CustomStringConvertible -internal typealias ScopedMetadataContainer = [String: ScopedMetadata] +typealias ScopedMetadataContainer = [String: ScopedMetadata] public extension Loggable { - /// attach logger metadata to this instance that will be automatically included in every log onward func set(loggerMetadata data: ScopedMetadata?, for key: String) { var _data = _scopedMetadata() @@ -46,8 +43,8 @@ public extension Loggable { file: String = #fileID, type type_: Any.Type? = nil, function: String = #function, - line: UInt = #line) { - + line: UInt = #line) + { logger.log(message ?? "", level, file: file, @@ -58,18 +55,17 @@ public extension Loggable { } } -internal extension Logger { - +extension Logger { /// Adds `type` param to capture current type (usually class) func log(_ message: Logger.Message, _ level: Logger.Level = .debug, - source: @autoclosure () -> String? = nil, + source _: @autoclosure () -> String? = nil, file: String = #fileID, type: Any.Type, function: String = #function, line: UInt = #line, - metaData: ScopedMetadataContainer = ScopedMetadataContainer()) { - + metaData: ScopedMetadataContainer = ScopedMetadataContainer()) + { func _buildScopedMetadataString() -> String { guard !metaData.isEmpty else { return "" } return " [\(metaData.map { "\($0): \($1)" }.joined(separator: ", "))]" diff --git a/Sources/LiveKit/Extensions/PixelBuffer.swift b/Sources/LiveKit/Extensions/PixelBuffer.swift index 8e59b00ee..f0288a377 100644 --- a/Sources/LiveKit/Extensions/PixelBuffer.swift +++ b/Sources/LiveKit/Extensions/PixelBuffer.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,12 @@ * limitations under the License. */ -import Foundation import CoreImage import CoreMedia +import Foundation -extension CVPixelBuffer { - - public static func from(_ data: Data, width: Int, height: Int, pixelFormat: OSType) -> CVPixelBuffer { +public extension CVPixelBuffer { + static func from(_ data: Data, width: Int, height: Int, pixelFormat: OSType) -> CVPixelBuffer { data.withUnsafeBytes { buffer in var pixelBuffer: CVPixelBuffer! @@ -33,8 +32,8 @@ extension CVPixelBuffer { var source = buffer.baseAddress! for plane in 0 ..< CVPixelBufferGetPlaneCount(pixelBuffer) { - let dest = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, plane) - let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, plane) + let dest = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, plane) + let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, plane) let bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, plane) let planeSize = height * bytesPerRow @@ -47,13 +46,11 @@ extension CVPixelBuffer { } } -extension CMSampleBuffer { - - public static func from(_ pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? { - +public extension CMSampleBuffer { + static func from(_ pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? { var sampleBuffer: CMSampleBuffer? - var timimgInfo = CMSampleTimingInfo() + var timimgInfo = CMSampleTimingInfo() var formatDescription: CMFormatDescription? CMVideoFormatDescriptionCreateForImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, @@ -136,18 +133,17 @@ extension CMSampleBuffer { } } -extension Data { - - public init(pixelBuffer: CVPixelBuffer) { +public extension Data { + init(pixelBuffer: CVPixelBuffer) { CVPixelBufferLockBaseAddress(pixelBuffer, [.readOnly]) defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, [.readOnly]) } // Calculate sum of planes' size var totalSize = 0 for plane in 0 ..< CVPixelBufferGetPlaneCount(pixelBuffer) { - let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, plane) + let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, plane) let bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, plane) - let planeSize = height * bytesPerRow + let planeSize = height * bytesPerRow totalSize += planeSize } @@ -155,10 +151,10 @@ extension Data { var dest = rawFrame for plane in 0 ..< CVPixelBufferGetPlaneCount(pixelBuffer) { - let source = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, plane) - let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, plane) + let source = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, plane) + let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, plane) let bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, plane) - let planeSize = height * bytesPerRow + let planeSize = height * bytesPerRow memcpy(dest, source, planeSize) dest += planeSize @@ -262,7 +258,7 @@ extension OSType { kCVPixelFormatType_14Bayer_GRBG: "kCVPixelFormatType_14Bayer_GRBG", kCVPixelFormatType_14Bayer_GBRG: "kCVPixelFormatType_14Bayer_GBRG", kCVPixelFormatType_14Bayer_BGGR: "kCVPixelFormatType_14Bayer_BGGR", - kCVPixelFormatType_128RGBAFloat: "kCVPixelFormatType_128RGBAFloat" + kCVPixelFormatType_128RGBAFloat: "kCVPixelFormatType_128RGBAFloat", ] return types[self] ?? "Unknown type" diff --git a/Sources/LiveKit/Extensions/Primitives.swift b/Sources/LiveKit/Extensions/Primitives.swift index 980a8b0f6..8404562b7 100644 --- a/Sources/LiveKit/Extensions/Primitives.swift +++ b/Sources/LiveKit/Extensions/Primitives.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,34 +17,29 @@ import Foundation extension String { - - internal func unpack() -> (sid: Sid, trackId: String) { + func unpack() -> (sid: Sid, trackId: String) { let parts = split(separator: "|") if parts.count == 2 { return (String(parts[0]), String(parts[1])) } return (self, "") } - } extension Bool { - - internal func toString() -> String { + func toString() -> String { self ? "true" : "false" } } extension URL { - - internal var isSecure: Bool { + var isSecure: Bool { scheme == "https" || scheme == "wss" } } -extension Double { - - public func rounded(to places: Int) -> Double { +public extension Double { + func rounded(to places: Int) -> Double { let divisor = pow(10.0, Double(places)) return (self * divisor).rounded() / divisor } diff --git a/Sources/LiveKit/Extensions/RTCConfiguration.swift b/Sources/LiveKit/Extensions/RTCConfiguration.swift index e7fc239cf..88b507662 100644 --- a/Sources/LiveKit/Extensions/RTCConfiguration.swift +++ b/Sources/LiveKit/Extensions/RTCConfiguration.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,8 @@ import Foundation @_implementationOnly import WebRTC -internal extension LKRTCConfiguration { - +extension LKRTCConfiguration { static func liveKitDefault() -> LKRTCConfiguration { - let result = DispatchQueue.liveKitWebRTC.sync { LKRTCConfiguration() } result.sdpSemantics = .unifiedPlan result.continualGatheringPolicy = .gatherContinually diff --git a/Sources/LiveKit/Extensions/RTCDataChannel+Util.swift b/Sources/LiveKit/Extensions/RTCDataChannel+Util.swift index 06906f974..ec6578ee0 100644 --- a/Sources/LiveKit/Extensions/RTCDataChannel+Util.swift +++ b/Sources/LiveKit/Extensions/RTCDataChannel+Util.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ import Foundation @_implementationOnly import WebRTC extension LKRTCDataChannel { - - struct 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 bb7dc7c21..bb0637eac 100644 --- a/Sources/LiveKit/Extensions/RTCI420Buffer.swift +++ b/Sources/LiveKit/Extensions/RTCI420Buffer.swift @@ -18,15 +18,13 @@ import Foundation @_implementationOnly import WebRTC -internal extension LKRTCI420Buffer { - +extension LKRTCI420Buffer { func toPixelBuffer() -> CVPixelBuffer? { - // default options let options = [ kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true, - kCVPixelBufferIOSurfacePropertiesKey as String: [:] as [String: Any] + kCVPixelBufferIOSurfacePropertiesKey as String: [:] as [String: Any], ] as [String: Any] var outputPixelBuffer: CVPixelBuffer? @@ -37,7 +35,7 @@ internal extension LKRTCI420Buffer { options as CFDictionary, &outputPixelBuffer) - guard status == kCVReturnSuccess, let outputPixelBuffer = outputPixelBuffer else { + guard status == kCVReturnSuccess, let outputPixelBuffer else { return nil } @@ -45,7 +43,8 @@ internal extension LKRTCI420Buffer { let pixelFormat = CVPixelBufferGetPixelFormatType(outputPixelBuffer) if pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange || - pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange { + pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange + { // NV12 let dstY = CVPixelBufferGetBaseAddressOfPlane(outputPixelBuffer, 0) let dstYStride = CVPixelBufferGetBytesPerRowOfPlane(outputPixelBuffer, 0) @@ -70,7 +69,6 @@ internal extension LKRTCI420Buffer { let bytesPerRow = CVPixelBufferGetBytesPerRow(outputPixelBuffer) if pixelFormat == kCVPixelFormatType_32BGRA { - LKRTCYUVHelper.i420(toARGB: dataY, srcStrideY: strideY, srcU: dataU, @@ -82,7 +80,6 @@ internal extension LKRTCI420Buffer { width: width, height: height) } else if pixelFormat == kCVPixelFormatType_32ARGB { - LKRTCYUVHelper.i420(toBGRA: dataY, srcStrideY: strideY, srcU: dataU, diff --git a/Sources/LiveKit/Extensions/RTCMediaConstraints.swift b/Sources/LiveKit/Extensions/RTCMediaConstraints.swift index c7eb65ebc..24877be99 100644 --- a/Sources/LiveKit/Extensions/RTCMediaConstraints.swift +++ b/Sources/LiveKit/Extensions/RTCMediaConstraints.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import Foundation @_implementationOnly import WebRTC extension LKRTCMediaConstraints { - // static let defaultOfferConstraints = RTCMediaConstraints( // mandatoryConstraints: [ // kRTCMediaConstraintsOfferToReceiveAudio: kRTCMediaConstraintsValueFalse, diff --git a/Sources/LiveKit/Extensions/RTCVideoCapturerDelegate+Buffer.swift b/Sources/LiveKit/Extensions/RTCVideoCapturerDelegate+Buffer.swift index ef51d4c0e..5f8b5a98c 100644 --- a/Sources/LiveKit/Extensions/RTCVideoCapturerDelegate+Buffer.swift +++ b/Sources/LiveKit/Extensions/RTCVideoCapturerDelegate+Buffer.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,12 @@ import Foundation #if canImport(ReplayKit) -import ReplayKit + import ReplayKit #endif @_implementationOnly import WebRTC extension FixedWidthInteger { - func roundUp(toMultipleOf powerOfTwo: Self) -> Self { // Check that powerOfTwo really is. precondition(powerOfTwo > 0 && powerOfTwo & (powerOfTwo &- 1) == 0) @@ -34,31 +33,28 @@ extension FixedWidthInteger { } extension Dimensions { - // Ensures width and height are even numbers - internal func toEncodeSafeDimensions() -> Dimensions { + func toEncodeSafeDimensions() -> Dimensions { Dimensions(width: Swift.max(Self.encodeSafeSize, width.roundUp(toMultipleOf: 2)), height: Swift.max(Self.encodeSafeSize, height.roundUp(toMultipleOf: 2))) - } - internal static func * (a: Dimensions, b: Double) -> Dimensions { + static func * (a: Dimensions, b: Double) -> Dimensions { Dimensions(width: Int32((Double(a.width) * b).rounded()), height: Int32((Double(a.height) * b).rounded())) } - internal var isRenderSafe: Bool { + var isRenderSafe: Bool { width >= Self.renderSafeSize && height >= Self.renderSafeSize } - internal var isEncodeSafe: Bool { + var isEncodeSafe: Bool { width >= Self.encodeSafeSize && height >= Self.encodeSafeSize } } extension CGImagePropertyOrientation { - - internal func toRTCRotation() -> RTCVideoRotation { + func toRTCRotation() -> RTCVideoRotation { switch self { case .up, .upMirrored, .down, .downMirrored: return ._0 case .left, .leftMirrored: return ._90 @@ -68,8 +64,7 @@ extension CGImagePropertyOrientation { } } -internal extension LKRTCVideoCapturerDelegate { - +extension LKRTCVideoCapturerDelegate { typealias OnResolveSourceDimensions = (Dimensions) -> Void /// capture a `CVPixelBuffer`, all other capture methods call this method internally. @@ -77,8 +72,8 @@ internal extension LKRTCVideoCapturerDelegate { didCapture pixelBuffer: CVPixelBuffer, timeStampNs: Int64 = VideoCapturer.createTimeStampNs(), rotation: RTCVideoRotation = ._0, - onResolveSourceDimensions: OnResolveSourceDimensions? = nil) { - + onResolveSourceDimensions: OnResolveSourceDimensions? = nil) + { // check if pixel format is supported by WebRTC let pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer) guard VideoCapturer.supportedPixelFormats.contains(where: { $0.uint32Value == pixelFormat }) else { @@ -103,7 +98,6 @@ internal extension LKRTCVideoCapturerDelegate { onResolveSourceDimensions?(sourceDimensions) DispatchQueue.liveKitWebRTC.sync { - let rtcBuffer = LKRTCCVPixelBuffer(pixelBuffer: pixelBuffer) let rtcFrame = LKRTCVideoFrame(buffer: rtcBuffer, rotation: rotation, @@ -116,12 +110,13 @@ internal extension LKRTCVideoCapturerDelegate { /// capture a `CMSampleBuffer` func capturer(_ capturer: LKRTCVideoCapturer, didCapture sampleBuffer: CMSampleBuffer, - onResolveSourceDimensions: OnResolveSourceDimensions? = nil) { - + onResolveSourceDimensions: OnResolveSourceDimensions? = nil) + { // check if buffer is ready guard CMSampleBufferGetNumSamples(sampleBuffer) == 1, CMSampleBufferIsValid(sampleBuffer), - CMSampleBufferDataIsReady(sampleBuffer) else { + CMSampleBufferDataIsReady(sampleBuffer) + else { logger.log("Failed to capture, buffer is not ready", .warning, type: type(of: self)) return } @@ -132,7 +127,8 @@ internal extension LKRTCVideoCapturerDelegate { // Check rotation tags. Extensions see these tags, but `RPScreenRecorder` does not appear to set them. // On iOS 12.0 and 13.0 rotation tags (other than up) are set by extensions. if let sampleOrientation = CMGetAttachment(sampleBuffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil), - let coreSampleOrientation = sampleOrientation.uint32Value { + let coreSampleOrientation = sampleOrientation.uint32Value + { rotation = CGImagePropertyOrientation(rawValue: coreSampleOrientation)?.toRTCRotation() } } @@ -154,7 +150,6 @@ internal extension LKRTCVideoCapturerDelegate { } extension CVPixelBuffer { - func toDimensions() -> Dimensions { Dimensions(width: Int32(CVPixelBufferGetWidth(self)), height: Int32(CVPixelBufferGetHeight(self))) diff --git a/Sources/LiveKit/Extensions/String.swift b/Sources/LiveKit/Extensions/String.swift index 573c07881..50991dcb4 100644 --- a/Sources/LiveKit/Extensions/String.swift +++ b/Sources/LiveKit/Extensions/String.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,13 +21,14 @@ class Identity { let publish: String? init(identity: String, - publish: String?) { + publish: String?) + { self.identity = identity self.publish = publish } } -internal extension Livekit_ParticipantInfo { +extension Livekit_ParticipantInfo { // parses identity string for the &publish= param of identity func parseIdentity() -> Identity { let segments = identity.split(separator: "#", maxSplits: 1) diff --git a/Sources/LiveKit/Extensions/TimeInterval.swift b/Sources/LiveKit/Extensions/TimeInterval.swift index 303cd254c..c2af7fb24 100644 --- a/Sources/LiveKit/Extensions/TimeInterval.swift +++ b/Sources/LiveKit/Extensions/TimeInterval.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Sources/LiveKit/LiveKit+DeviceHelpers.swift b/Sources/LiveKit/LiveKit+DeviceHelpers.swift index 16066a4e1..62eea9be0 100644 --- a/Sources/LiveKit/LiveKit+DeviceHelpers.swift +++ b/Sources/LiveKit/LiveKit+DeviceHelpers.swift @@ -16,21 +16,18 @@ import AVFoundation -extension LiveKit { - +public extension LiveKit { /// Helper method to ensure authorization for video(camera) / audio(microphone) permissions in a single call. - public static func ensureDeviceAccess(for types: Set) async -> Bool { - + static func ensureDeviceAccess(for types: Set) async -> Bool { assert(!types.isEmpty, "Please specify at least 1 type") for type in types { - assert([.video, .audio].contains(type), "types must be .video or .audio") let status = AVCaptureDevice.authorizationStatus(for: type) switch status { case .notDetermined: - if !(await AVCaptureDevice.requestAccess(for: type)) { + if await !(AVCaptureDevice.requestAccess(for: type)) { return false } case .restricted, .denied: return false diff --git a/Sources/LiveKit/LiveKit.swift b/Sources/LiveKit/LiveKit.swift index 686f6ea7d..48d1615fd 100644 --- a/Sources/LiveKit/LiveKit.swift +++ b/Sources/LiveKit/LiveKit.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import Logging @_implementationOnly import WebRTC -internal let logger = Logger(label: "LiveKitSDK") +let logger = Logger(label: "LiveKitSDK") /// The open source platform for real-time communication. /// @@ -33,16 +33,15 @@ internal let logger = Logger(label: "LiveKitSDK") /// to try out the features. @objc public class LiveKit: NSObject { - @objc(sdkVersion) public static let version = "1.1.3" @objc public static func setLoggerStandardOutput() { - LoggingSystem.bootstrap({ + LoggingSystem.bootstrap { var logHandler = StreamLogHandler.standardOutput(label: $0) logHandler.logLevel = .debug return logHandler - }) + } } } diff --git a/Sources/LiveKit/Participant/LocalParticipant.swift b/Sources/LiveKit/Participant/LocalParticipant.swift index 8160f98c6..a6dea48bc 100644 --- a/Sources/LiveKit/Participant/LocalParticipant.swift +++ b/Sources/LiveKit/Participant/LocalParticipant.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,13 @@ import Foundation #if canImport(ReplayKit) -import ReplayKit + import ReplayKit #endif @_implementationOnly import WebRTC @objc public class LocalParticipant: Participant { - @objc public var localAudioTracks: [LocalTrackPublication] { audioTracks.compactMap { $0 as? LocalTrackPublication } } @@ -34,9 +33,9 @@ public class LocalParticipant: Participant { private var allParticipantsAllowed: Bool = true private var trackPermissions: [ParticipantTrackPermission] = [] - internal convenience init(from info: Livekit_ParticipantInfo, - room: Room) { - + convenience init(from info: Livekit_ParticipantInfo, + room: Room) + { self.init(sid: info.sid, identity: info.identity, name: info.name, @@ -45,14 +44,13 @@ public class LocalParticipant: Participant { updateFromInfo(info: info) } - internal func getTrackPublication(sid: Sid) -> LocalTrackPublication? { + func getTrackPublication(sid: Sid) -> LocalTrackPublication? { _state.tracks[sid] as? LocalTrackPublication } @objc @discardableResult - internal func publish(track: LocalTrack, publishOptions: PublishOptions? = nil) async throws -> LocalTrackPublication { - + func publish(track: LocalTrack, publishOptions: PublishOptions? = nil) async throws -> LocalTrackPublication { log("[publish] \(track) options: \(String(describing: publishOptions ?? nil))...", .info) guard let publisher = room.engine.publisher else { @@ -84,8 +82,7 @@ public class LocalParticipant: Participant { transInit.direction = .sendOnly if let track = track as? LocalVideoTrack { - - guard let dimensions = dimensions else { + guard let dimensions else { throw TrackError.publish(message: "VideoCapturer dimensions are unknown") } @@ -121,7 +118,7 @@ public class LocalParticipant: Participant { self.log("[publish] maxBitrate: \(encoding.maxBitrate)") transInit.sendEncodings = [ - Engine.createRtpEncodingParameters(encoding: encoding) + Engine.createRtpEncodingParameters(encoding: encoding), ] } @@ -151,11 +148,11 @@ public class LocalParticipant: Participant { track.set(transport: publisher, rtpSender: transceiver.sender) if track is LocalVideoTrack { - let publishOptions = (publishOptions as? VideoPublishOptions) ?? self.room._state.options.defaultVideoPublishOptions + let publishOptions = (publishOptions as? VideoPublishOptions) ?? room._state.options.defaultVideoPublishOptions // if screen share or simulcast is enabled, // degrade resolution by using server's layer switching logic instead of WebRTC's logic if track.source == .screenShareVideo || publishOptions.simulcast { - self.log("[publish] set degradationPreference to .maintainResolution") + log("[publish] set degradationPreference to .maintainResolution") let params = transceiver.sender.parameters params.degradationPreference = NSNumber(value: RTCDegradationPreference.maintainResolution.rawValue) // changing params directly doesn't work so we need to update params @@ -164,7 +161,7 @@ public class LocalParticipant: Participant { } } - try await self.room.engine.publisherShouldNegotiate() + try await room.engine.publisherShouldNegotiate() let publication = LocalTrackPublication(info: addTrackResult.trackInfo, track: track, participant: self) @@ -206,13 +203,13 @@ public class LocalParticipant: Participant { } @objc - public override func unpublishAll(notify _notify: Bool = true) async { + override public func unpublishAll(notify _notify: Bool = true) async { // Build a list of Publications let publications = _state.tracks.values.compactMap { $0 as? LocalTrackPublication } for publication in publications { do { try await unpublish(publication: publication, notify: _notify) - } catch let error { + } catch { log("Failed to unpublish track \(publication.sid) with error \(error)", .error) } } @@ -222,7 +219,6 @@ public class LocalParticipant: Participant { /// this will also stop the track @objc public func unpublish(publication: LocalTrackPublication, notify _notify: Bool = true) async throws { - func _notifyDidUnpublish() async { guard _notify else { return } delegates.notify(label: { "localParticipant.didUnpublish \(publication)" }) { @@ -272,8 +268,8 @@ public class LocalParticipant: Participant { reliability: Reliability = .reliable, destinations: [Sid]? = nil, topic: String? = nil, - options: DataPublishOptions? = nil) async throws { - + options: DataPublishOptions? = nil) async throws + { let options = options ?? room._state.options.defaultDataPublishOptions let userPacket = Livekit_UserPacket.with { @@ -305,8 +301,8 @@ public class LocalParticipant: Participant { */ @objc public func setTrackSubscriptionPermissions(allParticipantsAllowed: Bool, - trackPermissions: [ParticipantTrackPermission] = []) async throws { - + trackPermissions: [ParticipantTrackPermission] = []) async throws + { self.allParticipantsAllowed = allParticipantsAllowed self.trackPermissions = trackPermissions @@ -343,16 +339,14 @@ public class LocalParticipant: Participant { try await room.engine.signalClient.sendUpdateLocalMetadata(metadata ?? "", name: name) } - internal func sendTrackSubscriptionPermissions() async throws { - + func sendTrackSubscriptionPermissions() async throws { guard room.engine._state.connectionState == .connected else { return } try await room.engine.signalClient.sendUpdateSubscriptionPermission(allParticipants: allParticipantsAllowed, trackPermissions: trackPermissions) } - internal func onSubscribedQualitiesUpdate(trackSid: String, subscribedQualities: [Livekit_SubscribedQuality]) { - + func onSubscribedQualitiesUpdate(trackSid: String, subscribedQualities: [Livekit_SubscribedQuality]) { if !room._state.options.dynacast { return } @@ -367,7 +361,6 @@ public class LocalParticipant: Participant { var hasChanged = false for quality in subscribedQualities { - var rid: String switch quality.quality { case Livekit_VideoQuality.high: rid = "f" @@ -388,7 +381,7 @@ public class LocalParticipant: Participant { } // Non simulcast streams don't have rids, handle here. - if encodings.count == 1 && subscribedQualities.count >= 1 { + if encodings.count == 1, subscribedQualities.count >= 1 { let encoding = encodings[0] let quality = subscribedQualities[0] @@ -404,8 +397,7 @@ public class LocalParticipant: Participant { } } - internal override func set(permissions newValue: ParticipantPermissions) -> Bool { - + override func set(permissions newValue: ParticipantPermissions) -> Bool { let didUpdate = super.set(permissions: newValue) if didUpdate { @@ -424,8 +416,7 @@ public class LocalParticipant: Participant { // MARK: - Session Migration extension LocalParticipant { - - internal func publishedTracksInfo() -> [Livekit_TrackPublishedResponse] { + func publishedTracksInfo() -> [Livekit_TrackPublishedResponse] { _state.tracks.values.filter { $0.track != nil } .map { publication in Livekit_TrackPublishedResponse.with { @@ -437,8 +428,7 @@ extension LocalParticipant { } } - internal func republishTracks() async throws { - + func republishTracks() async throws { let mediaTracks = _state.tracks.values.map { $0.track as? LocalTrack }.compactMap { $0 } await unpublishAll() @@ -453,14 +443,13 @@ extension LocalParticipant { // MARK: - Simplified API -extension LocalParticipant { - +public extension LocalParticipant { @objc @discardableResult - public func setCamera(enabled: Bool, - captureOptions: CameraCaptureOptions? = nil, - publishOptions: VideoPublishOptions? = nil) async throws -> LocalTrackPublication? { - + func setCamera(enabled: Bool, + captureOptions: CameraCaptureOptions? = nil, + publishOptions: VideoPublishOptions? = nil) async throws -> LocalTrackPublication? + { try await set(source: .camera, enabled: enabled, captureOptions: captureOptions, @@ -469,10 +458,10 @@ extension LocalParticipant { @objc @discardableResult - public func setMicrophone(enabled: Bool, - captureOptions: AudioCaptureOptions? = nil, - publishOptions: AudioPublishOptions? = nil) async throws -> LocalTrackPublication? { - + func setMicrophone(enabled: Bool, + captureOptions: AudioCaptureOptions? = nil, + publishOptions: AudioPublishOptions? = nil) async throws -> LocalTrackPublication? + { try await set(source: .microphone, enabled: enabled, captureOptions: captureOptions, @@ -490,17 +479,17 @@ extension LocalParticipant { /// For advanced usage, you can create a relevant ``LocalVideoTrack`` and call ``LocalParticipant/publishVideoTrack(track:publishOptions:)``. @objc @discardableResult - public func setScreenShare(enabled: Bool) async throws -> LocalTrackPublication? { + func setScreenShare(enabled: Bool) async throws -> LocalTrackPublication? { try await set(source: .screenShareVideo, enabled: enabled) } @objc @discardableResult - public func set(source: Track.Source, - enabled: Bool, - captureOptions: CaptureOptions? = nil, - publishOptions: PublishOptions? = nil) async throws -> LocalTrackPublication? { - + func set(source: Track.Source, + enabled: Bool, + captureOptions: CaptureOptions? = nil, + publishOptions: PublishOptions? = nil) async throws -> LocalTrackPublication? + { // Try to get existing publication if let publication = getTrackPublication(source: source) as? LocalTrackPublication { if enabled { @@ -537,12 +526,12 @@ extension LocalParticipant { // return publishVideoTrack(track: localTrack, publishOptions: publishOptions as? VideoPublishOptions).then(on: queue) { $0 } // } #elseif os(macOS) - if #available(macOS 12.3, *) { - let mainDisplay = try await MacOSScreenCapturer.mainDisplaySource() - let track = LocalVideoTrack.createMacOSScreenShareTrack(source: mainDisplay, - options: (captureOptions as? ScreenShareCaptureOptions) ?? self.room._state.options.defaultScreenShareCaptureOptions) - return try await publish(videoTrack: track, publishOptions: publishOptions as? VideoPublishOptions) - } + if #available(macOS 12.3, *) { + let mainDisplay = try await MacOSScreenCapturer.mainDisplaySource() + let track = LocalVideoTrack.createMacOSScreenShareTrack(source: mainDisplay, + options: (captureOptions as? ScreenShareCaptureOptions) ?? self.room._state.options.defaultScreenShareCaptureOptions) + return try await publish(videoTrack: track, publishOptions: publishOptions as? VideoPublishOptions) + } #endif } } diff --git a/Sources/LiveKit/Participant/Participant+Convenience.swift b/Sources/LiveKit/Participant/Participant+Convenience.swift index d597c0e2b..51c6055fe 100644 --- a/Sources/LiveKit/Participant/Participant+Convenience.swift +++ b/Sources/LiveKit/Participant/Participant+Convenience.swift @@ -16,21 +16,20 @@ import Foundation -extension Participant { - - public var firstCameraPublication: TrackPublication? { +public extension Participant { + var firstCameraPublication: TrackPublication? { videoTracks.first(where: { $0.source == .camera }) } - public var firstScreenSharePublication: TrackPublication? { + var firstScreenSharePublication: TrackPublication? { videoTracks.first(where: { $0.source == .screenShareVideo }) } - public var firstAudioPublication: TrackPublication? { + var firstAudioPublication: TrackPublication? { audioTracks.first } - public var firstTrackEncryptionType: EncryptionType { + var firstTrackEncryptionType: EncryptionType { if let pub = firstCameraPublication { return pub.encryptionType } else if let pub = firstScreenSharePublication { @@ -42,19 +41,19 @@ extension Participant { } } - public var firstCameraVideoTrack: VideoTrack? { + var firstCameraVideoTrack: VideoTrack? { guard let pub = firstCameraPublication, !pub.muted, pub.subscribed, let track = pub.track else { return nil } return track as? VideoTrack } - public var firstScreenShareVideoTrack: VideoTrack? { + var firstScreenShareVideoTrack: VideoTrack? { guard let pub = firstScreenSharePublication, !pub.muted, pub.subscribed, let track = pub.track else { return nil } return track as? VideoTrack } - public var firstAudioTrack: AudioTrack? { + var firstAudioTrack: AudioTrack? { guard let pub = firstAudioPublication, !pub.muted, let track = pub.track else { return nil } return track as? AudioTrack diff --git a/Sources/LiveKit/Participant/Participant+Equatable.swift b/Sources/LiveKit/Participant/Participant+Equatable.swift index a849d7979..a482b128a 100644 --- a/Sources/LiveKit/Participant/Participant+Equatable.swift +++ b/Sources/LiveKit/Participant/Participant+Equatable.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import Foundation // Objects are considered equal if both states are equal public extension Participant { - override var hash: Int { var hasher = Hasher() hasher.combine(_state.copy()) @@ -28,6 +27,6 @@ public extension Participant { override func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self._state.copy() == other._state.copy() + return _state.copy() == other._state.copy() } } diff --git a/Sources/LiveKit/Participant/Participant+Identifiable.swift b/Sources/LiveKit/Participant/Participant+Identifiable.swift index 241778765..1fdf9ae68 100644 --- a/Sources/LiveKit/Participant/Participant+Identifiable.swift +++ b/Sources/LiveKit/Participant/Participant+Identifiable.swift @@ -19,7 +19,6 @@ import Foundation // Identify by sid extension Participant: Identifiable { - public typealias ID = Sid public var id: Sid { diff --git a/Sources/LiveKit/Participant/Participant+MulticastDelegate.swift b/Sources/LiveKit/Participant/Participant+MulticastDelegate.swift index 1790e0e20..a8c679903 100644 --- a/Sources/LiveKit/Participant/Participant+MulticastDelegate.swift +++ b/Sources/LiveKit/Participant/Participant+MulticastDelegate.swift @@ -17,7 +17,6 @@ import Foundation extension Participant: MulticastDelegateProtocol { - @objc(addDelegate:) public func add(delegate: ParticipantDelegate) { delegates.add(delegate: delegate) diff --git a/Sources/LiveKit/Participant/Participant.swift b/Sources/LiveKit/Participant/Participant.swift index 551b4a588..9d804d75e 100644 --- a/Sources/LiveKit/Participant/Participant.swift +++ b/Sources/LiveKit/Participant/Participant.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,11 @@ import Foundation @objc public class Participant: NSObject, ObservableObject, Loggable { - // MARK: - MulticastDelegate - internal var delegates = MulticastDelegate() + var delegates = MulticastDelegate() - internal let queue = DispatchQueue(label: "LiveKitSDK.participant", qos: .default) + let queue = DispatchQueue(label: "LiveKitSDK.participant", qos: .default) @objc public var sid: Sid { _state.sid } @@ -67,14 +66,14 @@ public class Participant: NSObject, ObservableObject, Loggable { _state.tracks.values.filter { $0.kind == .video } } - internal var info: Livekit_ParticipantInfo? + var info: Livekit_ParticipantInfo? // Reference to the Room this Participant belongs to public let room: Room // MARK: - Internal - internal struct State: Equatable, Hashable { + struct State: Equatable, Hashable { let sid: Sid var identity: String var name: String @@ -87,13 +86,13 @@ public class Participant: NSObject, ObservableObject, Loggable { var tracks = [String: TrackPublication]() } - internal var _state: StateSync - - internal init(sid: String, - identity: String, - name: String, - room: Room) { + var _state: StateSync + init(sid: String, + identity: String, + name: String, + room: Room) + { self.room = room // initial state @@ -108,7 +107,7 @@ public class Participant: NSObject, ObservableObject, Loggable { // trigger events when state mutates _state.onDidMutate = { [weak self] newState, oldState in - guard let self = self else { return } + guard let self else { return } if newState.isSpeaking != oldState.isSpeaking { self.delegates.notify(label: { "participant.didUpdate isSpeaking: \(self.isSpeaking)" }) { @@ -119,8 +118,8 @@ public class Participant: NSObject, ObservableObject, Loggable { // metadata updated if let metadata = newState.metadata, metadata != oldState.metadata, // don't notify if empty string (first time only) - oldState.metadata == nil ? !metadata.isEmpty : true { - + oldState.metadata == nil ? !metadata.isEmpty : true + { self.delegates.notify(label: { "participant.didUpdate metadata: \(metadata)" }) { $0.participant?(self, didUpdate: metadata) } @@ -142,7 +141,6 @@ public class Participant: NSObject, ObservableObject, Loggable { } if newState.connectionQuality != oldState.connectionQuality { - self.delegates.notify(label: { "participant.didUpdate connectionQuality: \(self.connectionQuality)" }) { $0.participant?(self, didUpdate: self.connectionQuality) } @@ -161,23 +159,22 @@ public class Participant: NSObject, ObservableObject, Loggable { } } - internal func cleanUp(notify _notify: Bool = true) async { + func cleanUp(notify _notify: Bool = true) async { await unpublishAll(notify: _notify) // Reset state _state.mutate { $0 = State(sid: $0.sid, identity: $0.identity, name: $0.name) } } - internal func unpublishAll(notify _notify: Bool = true) async { + func unpublishAll(notify _: Bool = true) async { fatalError("Unimplemented") } - internal func addTrack(publication: TrackPublication) { + func addTrack(publication: TrackPublication) { _state.mutate { $0.tracks[publication.sid] = publication } publication.track?._state.mutate { $0.sid = publication.sid } } - internal func updateFromInfo(info: Livekit_ParticipantInfo) { - + func updateFromInfo(info: Livekit_ParticipantInfo) { _state.mutate { $0.identity = info.identity $0.name = info.name @@ -190,9 +187,8 @@ public class Participant: NSObject, ObservableObject, Loggable { } @discardableResult - internal func set(permissions newValue: ParticipantPermissions) -> Bool { - - guard self.permissions != newValue else { + func set(permissions newValue: ParticipantPermissions) -> Bool { + guard permissions != newValue else { // no change return false } @@ -205,17 +201,16 @@ public class Participant: NSObject, ObservableObject, Loggable { // MARK: - Simplified API -extension Participant { - - public func isCameraEnabled() -> Bool { +public extension Participant { + func isCameraEnabled() -> Bool { !(getTrackPublication(source: .camera)?.muted ?? true) } - public func isMicrophoneEnabled() -> Bool { + func isMicrophoneEnabled() -> Bool { !(getTrackPublication(source: .microphone)?.muted ?? true) } - public func isScreenShareEnabled() -> Bool { + func isScreenShareEnabled() -> Bool { !(getTrackPublication(source: .screenShareVideo)?.muted ?? true) } diff --git a/Sources/LiveKit/Participant/RemoteParticipant.swift b/Sources/LiveKit/Participant/RemoteParticipant.swift index 85105376c..c47451cb7 100644 --- a/Sources/LiveKit/Participant/RemoteParticipant.swift +++ b/Sources/LiveKit/Participant/RemoteParticipant.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,27 +20,25 @@ import Foundation @objc public class RemoteParticipant: Participant { - - internal init(sid: Sid, - info: Livekit_ParticipantInfo?, - room: Room) { - + init(sid: Sid, + info: Livekit_ParticipantInfo?, + room: Room) + { super.init(sid: sid, identity: info?.identity ?? "", name: info?.name ?? "", room: room) - if let info = info { + if let info { updateFromInfo(info: info) } } - internal func getTrackPublication(sid: Sid) -> RemoteTrackPublication? { + func getTrackPublication(sid: Sid) -> RemoteTrackPublication? { _state.tracks[sid] as? RemoteTrackPublication } override func updateFromInfo(info: Livekit_ParticipantInfo) { - super.updateFromInfo(info: info) var validTrackPublications = [String: RemoteTrackPublication]() @@ -59,10 +57,9 @@ public class RemoteParticipant: Participant { } room.engine.executeIfConnected { [weak self] in - guard let self = self else { return } + guard let self else { return } for publication in newTrackPublications.values { - self.delegates.notify(label: { "participant.didPublish \(publication)" }) { $0.participant?(self, didPublish: publication) } @@ -80,15 +77,14 @@ public class RemoteParticipant: Participant { Task { do { try await unpublish(publication: unpublishRemoteTrackPublication) - } catch let error { + } catch { log("Failed to unpublish with error: \(error)") } } } } - internal func addSubscribedMediaTrack(rtcTrack: LKRTCMediaStreamTrack, rtpReceiver: LKRTCRtpReceiver, sid: Sid) async throws { - + func addSubscribedMediaTrack(rtcTrack: LKRTCMediaStreamTrack, rtpReceiver: LKRTCRtpReceiver, sid: Sid) async throws { let track: Track guard let publication = getTrackPublication(sid: sid) else { @@ -137,11 +133,9 @@ public class RemoteParticipant: Participant { room.delegates.notify(label: { "room.didSubscribe \(publication)" }) { $0.room?(self.room, participant: self, didSubscribe: publication, track: track) } - } - internal override func cleanUp(notify _notify: Bool = true) async { - + override func cleanUp(notify _notify: Bool = true) async { await super.cleanUp(notify: _notify) room.delegates.notify(label: { "room.participantDidLeave" }) { @@ -149,20 +143,19 @@ public class RemoteParticipant: Participant { } } - public override func unpublishAll(notify _notify: Bool = true) async { + override public func unpublishAll(notify _notify: Bool = true) async { // Build a list of Publications let publications = _state.tracks.values.compactMap { $0 as? RemoteTrackPublication } for publication in publications { do { try await unpublish(publication: publication, notify: _notify) - } catch let error { + } catch { log("Failed to unpublish track \(publication.sid) with error \(error)", .error) } } } - internal func unpublish(publication: RemoteTrackPublication, notify _notify: Bool = true) async throws { - + func unpublish(publication: RemoteTrackPublication, notify _notify: Bool = true) async throws { func _notifyUnpublish() async { guard _notify else { return } delegates.notify(label: { "participant.didUnpublish \(publication)" }) { diff --git a/Sources/LiveKit/Protocols/AudioRenderer.swift b/Sources/LiveKit/Protocols/AudioRenderer.swift index 8f33a43f9..f221b3996 100644 --- a/Sources/LiveKit/Protocols/AudioRenderer.swift +++ b/Sources/LiveKit/Protocols/AudioRenderer.swift @@ -14,8 +14,8 @@ * limitations under the License. */ -import Foundation import CoreMedia +import Foundation @_implementationOnly import WebRTC @@ -26,7 +26,6 @@ public protocol AudioRenderer { } class AudioRendererAdapter: NSObject, LKRTCAudioRenderer { - private weak var target: AudioRenderer? init(target: AudioRenderer) { @@ -41,11 +40,11 @@ class AudioRendererAdapter: NSObject, LKRTCAudioRenderer { override func isEqual(_ object: Any?) -> Bool { guard let other = object as? AudioRendererAdapter else { return false } - return self.target === other.target + return target === other.target } override var hash: Int { - guard let target = target else { return 0 } + guard let target else { return 0 } return ObjectIdentifier(target).hashValue } } diff --git a/Sources/LiveKit/Protocols/EngineDelegate.swift b/Sources/LiveKit/Protocols/EngineDelegate.swift index cbf16ddce..c080619f0 100644 --- a/Sources/LiveKit/Protocols/EngineDelegate.swift +++ b/Sources/LiveKit/Protocols/EngineDelegate.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import Foundation @_implementationOnly import WebRTC -internal protocol EngineDelegate: AnyObject { +protocol EngineDelegate: AnyObject { func engine(_ engine: Engine, didMutate state: Engine.State, oldState: Engine.State) func engine(_ engine: Engine, didUpdate speakers: [Livekit_SpeakerInfo]) func engine(_ engine: Engine, didAddTrack track: LKRTCMediaStreamTrack, rtpReceiver: LKRTCRtpReceiver, streams: [LKRTCMediaStream]) diff --git a/Sources/LiveKit/Protocols/MediaEncoding.swift b/Sources/LiveKit/Protocols/MediaEncoding.swift index 6f5f81d99..598de8ca0 100644 --- a/Sources/LiveKit/Protocols/MediaEncoding.swift +++ b/Sources/LiveKit/Protocols/MediaEncoding.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Sources/LiveKit/Protocols/ParticipantDelegate.swift b/Sources/LiveKit/Protocols/ParticipantDelegate.swift index 39596d34f..7fe52a5b4 100644 --- a/Sources/LiveKit/Protocols/ParticipantDelegate.swift +++ b/Sources/LiveKit/Protocols/ParticipantDelegate.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,6 @@ import Foundation /// and register it on new participants inside ``RoomDelegate/room(_:participantDidJoin:)-9bkm4`` @objc public protocol ParticipantDelegate: AnyObject { - /// A ``Participant``'s metadata has updated. /// `participant` Can be a ``LocalParticipant`` or a ``RemoteParticipant``. @objc(participant:didUpdateMetadata:) optional diff --git a/Sources/LiveKit/Protocols/RoomDelegate.swift b/Sources/LiveKit/Protocols/RoomDelegate.swift index 2a079bbd0..220c613b6 100644 --- a/Sources/LiveKit/Protocols/RoomDelegate.swift +++ b/Sources/LiveKit/Protocols/RoomDelegate.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ import Foundation /// See the source code of [Swift Example App](https://github.com/livekit/client-example-swift) for more examples. @objc public protocol RoomDelegateObjC: AnyObject { - @objc(room:didUpdateConnectionState:oldConnectionState:) optional func room(_ room: Room, didUpdate connectionState: ConnectionStateObjC, oldValue oldConnectionState: ConnectionStateObjC) @@ -146,6 +145,5 @@ public protocol RoomDelegate: RoomDelegateObjC { /// Default implementation for ``RoomDelegate`` public extension RoomDelegate { - - func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) {} + func room(_: Room, didUpdate _: ConnectionState, oldValue _: ConnectionState) {} } diff --git a/Sources/LiveKit/Protocols/SignalClientDelegate.swift b/Sources/LiveKit/Protocols/SignalClientDelegate.swift index d110f154c..cab534115 100644 --- a/Sources/LiveKit/Protocols/SignalClientDelegate.swift +++ b/Sources/LiveKit/Protocols/SignalClientDelegate.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,7 @@ import Foundation @_implementationOnly import WebRTC -internal protocol SignalClientDelegate: AnyObject { - +protocol SignalClientDelegate: AnyObject { func signalClient(_ signalClient: SignalClient, didMutate state: SignalClient.State, oldState: SignalClient.State) func signalClient(_ signalClient: SignalClient, didReceive joinResponse: Livekit_JoinResponse) diff --git a/Sources/LiveKit/Protocols/TrackDelegate.swift b/Sources/LiveKit/Protocols/TrackDelegate.swift index 6f1d5396f..bd82d9063 100644 --- a/Sources/LiveKit/Protocols/TrackDelegate.swift +++ b/Sources/LiveKit/Protocols/TrackDelegate.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ public protocol TrackDelegate: AnyObject { func track(_ track: Track, didUpdateStatistics: TrackStatistics) } -internal protocol TrackDelegateInternal: TrackDelegate { +protocol TrackDelegateInternal: TrackDelegate { /// Used to report track state mutation to TrackPublication if attached. func track(_ track: Track, didMutateState newState: Track.State, oldState: Track.State) } diff --git a/Sources/LiveKit/Protocols/TransportDelegate.swift b/Sources/LiveKit/Protocols/TransportDelegate.swift index 3ace73560..a5299f393 100644 --- a/Sources/LiveKit/Protocols/TransportDelegate.swift +++ b/Sources/LiveKit/Protocols/TransportDelegate.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import Foundation @_implementationOnly import WebRTC -internal protocol TransportDelegate: AnyObject { +protocol TransportDelegate: AnyObject { func transport(_ transport: Transport, didUpdate state: RTCPeerConnectionState) func transport(_ transport: Transport, didGenerate iceCandidate: LKRTCIceCandidate) func transport(_ transport: Transport, didOpen dataChannel: LKRTCDataChannel) diff --git a/Sources/LiveKit/Protocols/VideoRenderer.swift b/Sources/LiveKit/Protocols/VideoRenderer.swift index 9979a2254..a212359b4 100644 --- a/Sources/LiveKit/Protocols/VideoRenderer.swift +++ b/Sources/LiveKit/Protocols/VideoRenderer.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,8 +35,7 @@ public protocol VideoRenderer { func render(frame: VideoFrame) } -internal class VideoRendererAdapter: NSObject, LKRTCVideoRenderer { - +class VideoRendererAdapter: NSObject, LKRTCVideoRenderer { private weak var target: VideoRenderer? init(target: VideoRenderer) { @@ -56,11 +55,11 @@ internal class VideoRendererAdapter: NSObject, LKRTCVideoRenderer { override func isEqual(_ object: Any?) -> Bool { guard let other = object as? VideoRendererAdapter else { return false } - return self.target === other.target + return target === other.target } override var hash: Int { - guard let target = target else { return 0 } + guard let target else { return 0 } return ObjectIdentifier(target).hashValue } } diff --git a/Sources/LiveKit/Protocols/VideoViewDelegate.swift b/Sources/LiveKit/Protocols/VideoViewDelegate.swift index a2f520374..31c96bb55 100644 --- a/Sources/LiveKit/Protocols/VideoViewDelegate.swift +++ b/Sources/LiveKit/Protocols/VideoViewDelegate.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Sources/LiveKit/Support/AppStateListener.swift b/Sources/LiveKit/Support/AppStateListener.swift index 501b53a52..dcfc02f8b 100644 --- a/Sources/LiveKit/Support/AppStateListener.swift +++ b/Sources/LiveKit/Support/AppStateListener.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,17 +17,16 @@ import Foundation #if canImport(UIKit) -import UIKit + import UIKit #endif -internal protocol AppStateDelegate: AnyObject { +protocol AppStateDelegate: AnyObject { func appDidEnterBackground() func appWillEnterForeground() func appWillTerminate() } -internal class AppStateListener: MulticastDelegate { - +class AppStateListener: MulticastDelegate { static let shared = AppStateListener() private init() { @@ -36,29 +35,32 @@ internal class AppStateListener: MulticastDelegate { let center = NotificationCenter.default #if os(iOS) - center.addObserver(forName: UIApplication.didEnterBackgroundNotification, - object: nil, - queue: OperationQueue.main) { (_) in + center.addObserver(forName: UIApplication.didEnterBackgroundNotification, + object: nil, + queue: OperationQueue.main) + { _ in - self.log("UIApplication.didEnterBackground") - self.notify { $0.appDidEnterBackground() } - } + self.log("UIApplication.didEnterBackground") + self.notify { $0.appDidEnterBackground() } + } - center.addObserver(forName: UIApplication.willEnterForegroundNotification, - object: nil, - queue: OperationQueue.main) { (_) in + center.addObserver(forName: UIApplication.willEnterForegroundNotification, + object: nil, + queue: OperationQueue.main) + { _ in - self.log("UIApplication.willEnterForeground") - self.notify { $0.appWillEnterForeground() } - } + self.log("UIApplication.willEnterForeground") + self.notify { $0.appWillEnterForeground() } + } - center.addObserver(forName: UIApplication.willTerminateNotification, - object: nil, - queue: OperationQueue.main) { (_) in + center.addObserver(forName: UIApplication.willTerminateNotification, + object: nil, + queue: OperationQueue.main) + { _ in - self.log("UIApplication.willTerminate") - self.notify { $0.appWillTerminate() } - } + self.log("UIApplication.willTerminate") + self.notify { $0.appWillTerminate() } + } #endif } } diff --git a/Sources/LiveKit/Support/AsyncCompleter.swift b/Sources/LiveKit/Support/AsyncCompleter.swift index 591c37ef0..36d9da7cd 100644 --- a/Sources/LiveKit/Support/AsyncCompleter.swift +++ b/Sources/LiveKit/Support/AsyncCompleter.swift @@ -16,7 +16,7 @@ import Foundation -internal enum AsyncCompleterError: LiveKitError { +enum AsyncCompleterError: LiveKitError { case timedOut case cancelled @@ -29,8 +29,7 @@ internal enum AsyncCompleterError: LiveKitError { } /// Manages a map of AsyncCompleters -internal actor CompleterMapActor { - +actor CompleterMapActor { public let label: String private let _timeOut: DispatchTimeInterval @@ -38,7 +37,7 @@ internal actor CompleterMapActor { public init(label: String, timeOut: DispatchTimeInterval) { self.label = label - self._timeOut = timeOut + _timeOut = timeOut } public func completer(for key: String) -> AsyncCompleter { @@ -68,8 +67,7 @@ internal actor CompleterMapActor { } } -internal class AsyncCompleter: Loggable { - +class AsyncCompleter: Loggable { public let label: String private let _timeOut: DispatchTimeInterval @@ -83,7 +81,7 @@ internal class AsyncCompleter: Loggable { public init(label: String, timeOut: DispatchTimeInterval) { self.label = label - self._timeOut = timeOut + _timeOut = timeOut } deinit { @@ -149,7 +147,7 @@ internal class AsyncCompleter: Loggable { // Create time-out block let timeOutBlock = DispatchWorkItem { [weak self] in - guard let self = self else { return } + guard let self else { return } self.log("\(self.label) timedOut") self._continuation?.resume(throwing: AsyncCompleterError.timedOut) self._continuation = nil diff --git a/Sources/LiveKit/Support/AsyncQueueActor.swift b/Sources/LiveKit/Support/AsyncQueueActor.swift index 9981f8966..14a930dc1 100644 --- a/Sources/LiveKit/Support/AsyncQueueActor.swift +++ b/Sources/LiveKit/Support/AsyncQueueActor.swift @@ -16,8 +16,7 @@ import Foundation -internal actor AsyncQueueActor { - +actor AsyncQueueActor { public enum State { case resumed case suspended diff --git a/Sources/LiveKit/Support/AsyncRetry.swift b/Sources/LiveKit/Support/AsyncRetry.swift index 73f161eb1..13588b263 100644 --- a/Sources/LiveKit/Support/AsyncRetry.swift +++ b/Sources/LiveKit/Support/AsyncRetry.swift @@ -17,18 +17,16 @@ import Foundation extension Task where Failure == Error { - static func retrying( priority: TaskPriority? = nil, maxRetryCount: Int = 3, retryDelay: TimeInterval = 1, @_implicitSelfCapture operation: @escaping (_ totalAttempts: Int, _ currentAttempt: Int) async throws -> Success ) -> Task { - assert(maxRetryCount >= 1, "Value must be larger than 1") return Task(priority: priority) { - for currentCount in 0..<(maxRetryCount - 1) { + for currentCount in 0 ..< (maxRetryCount - 1) { do { return try await operation(maxRetryCount, currentCount + 1) } catch { diff --git a/Sources/LiveKit/Support/ConnectivityListener.swift b/Sources/LiveKit/Support/ConnectivityListener.swift index ebf40a00e..4f7bc25da 100644 --- a/Sources/LiveKit/Support/ConnectivityListener.swift +++ b/Sources/LiveKit/Support/ConnectivityListener.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,20 +17,18 @@ import Foundation import Network -internal protocol ConnectivityListenerDelegate: AnyObject { - +protocol ConnectivityListenerDelegate: AnyObject { func connectivityListener(_: ConnectivityListener, didUpdate hasConnectivity: Bool) // network remains to have connectivity but path changed func connectivityListener(_: ConnectivityListener, didSwitch path: NWPath) } -internal extension ConnectivityListenerDelegate { - func connectivityListener(_: ConnectivityListener, didUpdate hasConnectivity: Bool) {} - func connectivityListener(_: ConnectivityListener, didSwitch path: NWPath) {} +extension ConnectivityListenerDelegate { + func connectivityListener(_: ConnectivityListener, didUpdate _: Bool) {} + func connectivityListener(_: ConnectivityListener, didSwitch _: NWPath) {} } -internal class ConnectivityListener: MulticastDelegate { - +class ConnectivityListener: MulticastDelegate { static let shared = ConnectivityListener() public private(set) var hasConnectivity: Bool? { @@ -56,13 +54,12 @@ internal class ConnectivityListener: MulticastDelegate NWInterface.InterfaceType? { let path = monitor.currentPath return path.availableInterfaces.filter { @@ -86,36 +82,35 @@ internal extension ConnectivityListener { } private extension ConnectivityListener { - - func set(path newValue: NWPath, notify _notify: Bool = false) { - - log("status: \(newValue.status), interfaces: \(newValue.availableInterfaces.map({ "\(String(describing: $0.type))-\(String(describing: $0.index))" })), gateways: \(newValue.gateways), activeIp: \(String(describing: newValue.availableInterfaces.first?.ipv4))") + func set(path newValue: NWPath, notify _: Bool = false) { + log("status: \(newValue.status), interfaces: \(newValue.availableInterfaces.map { "\(String(describing: $0.type))-\(String(describing: $0.index))" }), gateways: \(newValue.gateways), activeIp: \(String(describing: newValue.availableInterfaces.first?.ipv4))") // check if different path - guard newValue != self.path else { return } + guard newValue != path else { return } // keep old values - let oldValue = self.path - let oldIpValue = self.ipv4 + let oldValue = path + let oldIpValue = ipv4 // update new values let newIpValue = newValue.availableInterfaces.first?.ipv4 - self.path = newValue - self.ipv4 = newIpValue - self.hasConnectivity = newValue.isSatisfied() + path = newValue + ipv4 = newIpValue + hasConnectivity = newValue.isSatisfied() // continue if old value exists - guard let oldValue = oldValue else { return } + guard let oldValue else { return } if oldValue.isSatisfied(), !newValue.isSatisfied() { // satisfied -> unsatisfied // active connection did disconnect log("starting satisfied monitor timer") - self.isPossiblySwitchingNetwork = true + isPossiblySwitchingNetwork = true switchNetworkTimer?.invalidate() switchNetworkTimer = Timer.scheduledTimer(withTimeInterval: switchInterval, - repeats: false) { [weak self] _ in - guard let self = self else { return } + repeats: false) + { [weak self] _ in + guard let self else { return } self.log("satisfied monitor timer invalidated") self.isPossiblySwitchingNetwork = false self.switchNetworkTimer = nil @@ -125,7 +120,7 @@ private extension ConnectivityListener { // did switch network switchNetworkTimer?.invalidate() switchNetworkTimer = nil - self.isPossiblySwitchingNetwork = false + isPossiblySwitchingNetwork = false log("didSwitch type: quick on & off") notify { $0.connectivityListener(self, didSwitch: newValue) } @@ -145,18 +140,15 @@ private extension ConnectivityListener { } } -internal extension NWPath { - +extension NWPath { func isSatisfied() -> Bool { if case .satisfied = status { return true } return false } } -internal extension NWInterface { - +extension NWInterface { func address(family: Int32) -> String? { - var address: String? // get list of all interfaces on the local machine: @@ -185,6 +177,6 @@ internal extension NWInterface { return address } - var ipv4: String? { self.address(family: AF_INET) } - var ipv6: String? { self.address(family: AF_INET6) } + var ipv4: String? { address(family: AF_INET) } + var ipv6: String? { address(family: AF_INET6) } } diff --git a/Sources/LiveKit/Support/DispatchQueueTimer.swift b/Sources/LiveKit/Support/DispatchQueueTimer.swift index 4f06bc581..28f77c07b 100644 --- a/Sources/LiveKit/Support/DispatchQueueTimer.swift +++ b/Sources/LiveKit/Support/DispatchQueueTimer.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,7 @@ import Foundation -internal class DispatchQueueTimer: Loggable { - +class DispatchQueueTimer: Loggable { public enum State { case suspended case resumed @@ -32,7 +31,7 @@ internal class DispatchQueueTimer: Loggable { public init(timeInterval: TimeInterval, queue: DispatchQueue? = nil) { self.timeInterval = timeInterval self.queue = queue - self.timer = createTimer() + timer = createTimer() } deinit { @@ -71,7 +70,7 @@ internal class DispatchQueueTimer: Loggable { private func createTimer() -> DispatchSourceTimer { let timer = DispatchSource.makeTimerSource(queue: queue) - timer.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval) + timer.schedule(deadline: .now() + timeInterval, repeating: timeInterval) timer.setEventHandler { [weak self] in self?.handler?() } return timer } diff --git a/Sources/LiveKit/Support/DisplayLink.swift b/Sources/LiveKit/Support/DisplayLink.swift index 24053852a..70a864da5 100644 --- a/Sources/LiveKit/Support/DisplayLink.swift +++ b/Sources/LiveKit/Support/DisplayLink.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,12 @@ import Foundation -internal protocol DisplayLink { +protocol DisplayLink { var onFrame: ((DisplayLinkFrame) -> Void)? { get set } var isPaused: Bool { get set } } -internal struct DisplayLinkFrame { +struct DisplayLinkFrame { // The system timestamp for the frame to be drawn var timestamp: TimeInterval // The duration between each display update @@ -30,107 +30,103 @@ internal struct DisplayLinkFrame { #if os(iOS) -import QuartzCore + import QuartzCore -typealias PlatformDisplayLink = iOSDisplayLink + typealias PlatformDisplayLink = iOSDisplayLink -internal class iOSDisplayLink: DisplayLink { + class iOSDisplayLink: DisplayLink { + var onFrame: ((DisplayLinkFrame) -> Void)? - var onFrame: ((DisplayLinkFrame) -> Void)? - - var isPaused: Bool { - get { _displayLink.isPaused } - set { _displayLink.isPaused = newValue } - } + var isPaused: Bool { + get { _displayLink.isPaused } + set { _displayLink.isPaused = newValue } + } - let _displayLink: CADisplayLink + let _displayLink: CADisplayLink - let _target = DisplayLinkTarget() + let _target = DisplayLinkTarget() - init() { - _displayLink = CADisplayLink(target: _target, selector: #selector(DisplayLinkTarget.frame(_:))) - _displayLink.isPaused = true - _displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) + init() { + _displayLink = CADisplayLink(target: _target, selector: #selector(DisplayLinkTarget.frame(_:))) + _displayLink.isPaused = true + _displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) - _target.callback = { [weak self] (frame) in - guard let self = self else { return } - self.onFrame?(frame) + _target.callback = { [weak self] frame in + guard let self else { return } + self.onFrame?(frame) + } } - } - deinit { - _displayLink.invalidate() - } - - class DisplayLinkTarget { - - var callback: ((DisplayLinkFrame) -> Void)? + deinit { + _displayLink.invalidate() + } - @objc - dynamic func frame(_ _displayLink: CADisplayLink) { + class DisplayLinkTarget { + var callback: ((DisplayLinkFrame) -> Void)? - let frame = DisplayLinkFrame( - timestamp: _displayLink.timestamp, - duration: _displayLink.duration) + @objc + dynamic func frame(_ _displayLink: CADisplayLink) { + let frame = DisplayLinkFrame( + timestamp: _displayLink.timestamp, + duration: _displayLink.duration + ) - callback?(frame) + callback?(frame) + } } } -} #elseif os(macOS) -import CoreVideo + import CoreVideo -typealias PlatformDisplayLink = macOSDisplayLink + typealias PlatformDisplayLink = macOSDisplayLink -internal class macOSDisplayLink: DisplayLink { + class macOSDisplayLink: DisplayLink { + var onFrame: ((DisplayLinkFrame) -> Void)? - var onFrame: ((DisplayLinkFrame) -> Void)? - - var isPaused: Bool = true { - didSet { - guard isPaused != oldValue else { return } - if isPaused == true { - CVDisplayLinkStop(_displayLink) - } else { - CVDisplayLinkStart(_displayLink) + var isPaused: Bool = true { + didSet { + guard isPaused != oldValue else { return } + if isPaused == true { + CVDisplayLinkStop(_displayLink) + } else { + CVDisplayLinkStart(_displayLink) + } } } - } - - private var _displayLink: CVDisplayLink = { - var dl: CVDisplayLink? - CVDisplayLinkCreateWithActiveCGDisplays(&dl) - return dl! - }() - init() { + private var _displayLink: CVDisplayLink = { + var dl: CVDisplayLink? + CVDisplayLinkCreateWithActiveCGDisplays(&dl) + return dl! + }() - CVDisplayLinkSetOutputHandler(_displayLink, { [weak self] (_, inNow, inOutputTime, _, _) -> CVReturn in + init() { + CVDisplayLinkSetOutputHandler(_displayLink) { [weak self] _, inNow, inOutputTime, _, _ -> CVReturn in - guard let self = self else { return kCVReturnSuccess } + guard let self else { return kCVReturnSuccess } - let frame = DisplayLinkFrame( - timestamp: inNow.pointee.timeInterval, - duration: inOutputTime.pointee.timeInterval - inNow.pointee.timeInterval) + let frame = DisplayLinkFrame( + timestamp: inNow.pointee.timeInterval, + duration: inOutputTime.pointee.timeInterval - inNow.pointee.timeInterval + ) - self.onFrame?(frame) + self.onFrame?(frame) - return kCVReturnSuccess - }) - } + return kCVReturnSuccess + } + } - deinit { - isPaused = true + deinit { + isPaused = true + } } -} - -internal extension CVTimeStamp { - var timeInterval: TimeInterval { - TimeInterval(videoTime) / TimeInterval(videoTimeScale) + extension CVTimeStamp { + var timeInterval: TimeInterval { + TimeInterval(videoTime) / TimeInterval(videoTimeScale) + } } -} #endif diff --git a/Sources/LiveKit/Support/Global.swift b/Sources/LiveKit/Support/Global.swift index 590bad708..ce9b27d35 100644 --- a/Sources/LiveKit/Support/Global.swift +++ b/Sources/LiveKit/Support/Global.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ // merge a ClosedRange -internal func merge(range range1: ClosedRange, with range2: ClosedRange) -> ClosedRange where T: Comparable { - min(range1.lowerBound, range2.lowerBound)...max(range1.upperBound, range2.upperBound) +func merge(range range1: ClosedRange, with range2: ClosedRange) -> ClosedRange where T: Comparable { + min(range1.lowerBound, range2.lowerBound) ... max(range1.upperBound, range2.upperBound) } diff --git a/Sources/LiveKit/Support/HTTP.swift b/Sources/LiveKit/Support/HTTP.swift index ac0fe7272..bf3560075 100644 --- a/Sources/LiveKit/Support/HTTP.swift +++ b/Sources/LiveKit/Support/HTTP.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,12 @@ import Foundation -internal class HTTP: NSObject { - +class HTTP: NSObject { private static let operationQueue = OperationQueue() - private static let session: URLSession = URLSession(configuration: .default, - delegate: nil, - delegateQueue: operationQueue) + private static let session: URLSession = .init(configuration: .default, + delegate: nil, + delegateQueue: operationQueue) public static func requestData(from url: URL) async throws -> Data { let request = URLRequest(url: url, diff --git a/Sources/LiveKit/Support/MulticastDelegate.swift b/Sources/LiveKit/Support/MulticastDelegate.swift index ce16b7db2..759c6a11f 100644 --- a/Sources/LiveKit/Support/MulticastDelegate.swift +++ b/Sources/LiveKit/Support/MulticastDelegate.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,24 +30,22 @@ public protocol MulticastDelegateProtocol { /// /// > Note: `NSHashTable` may not immediately deinit the un-referenced object, due to Apple's implementation, therefore `.count` is unreliable. public class MulticastDelegate: NSObject, Loggable { - - internal let multicastQueue: DispatchQueue + let multicastQueue: DispatchQueue private let set = NSHashTable.weakObjects() init(label: String = "livekit.multicast", qos: DispatchQoS = .default) { - self.multicastQueue = DispatchQueue(label: label, qos: qos, attributes: []) + multicastQueue = DispatchQueue(label: label, qos: qos, attributes: []) } /// Add a single delegate. public func add(delegate: T) { - guard let delegate = delegate as AnyObject? else { log("MulticastDelegate: delegate is not an AnyObject") return } multicastQueue.sync { [weak self] in - guard let self = self else { return } + guard let self else { return } self.set.add(delegate) } } @@ -56,34 +54,30 @@ public class MulticastDelegate: NSObject, Loggable { /// /// In most cases this is not required to be called explicitly since all delegates are weak. public func remove(delegate: T) { - guard let delegate = delegate as AnyObject? else { log("MulticastDelegate: delegate is not an AnyObject") return } multicastQueue.sync { [weak self] in - guard let self = self else { return } + guard let self else { return } self.set.remove(delegate) } } /// Remove all delegates. public func removeAllDelegates() { - multicastQueue.sync { [weak self] in - guard let self = self else { return } + guard let self else { return } self.set.removeAllObjects() } } /// Notify delegates inside the queue. /// Label is captured inside the queue for thread safety reasons. - internal func notify(label: (() -> String)? = nil, _ fnc: @escaping (T) -> Void) { - + func notify(label: (() -> String)? = nil, _ fnc: @escaping (T) -> Void) { multicastQueue.async { - - if let label = label { + if let label { self.log("[notify] \(label())", .debug) } diff --git a/Sources/LiveKit/Support/NativeView.swift b/Sources/LiveKit/Support/NativeView.swift index 50eb9e995..8c9713f6d 100644 --- a/Sources/LiveKit/Support/NativeView.swift +++ b/Sources/LiveKit/Support/NativeView.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,53 +17,53 @@ import Foundation #if canImport(UIKit) -import UIKit + import UIKit #elseif canImport(AppKit) -import AppKit + import AppKit #endif #if os(iOS) -public typealias NativeViewType = UIView + public typealias NativeViewType = UIView #elseif os(macOS) -public typealias NativeViewType = NSView + public typealias NativeViewType = NSView #endif /// A simple abstraction of a View that is native to the platform. /// When built for iOS this will be a UIView. /// When built for macOS this will be a NSView. public class NativeView: NativeViewType { - override init(frame: CGRect) { super.init(frame: frame) } - required init?(coder: NSCoder) { + @available(*, unavailable) + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } #if os(iOS) - public override func layoutSubviews() { - super.layoutSubviews() - performLayout() - } + override public func layoutSubviews() { + super.layoutSubviews() + performLayout() + } #else - public override func layout() { - super.layout() - performLayout() - } + override public func layout() { + super.layout() + performLayout() + } #endif #if os(macOS) - // for compatibility with macOS - func setNeedsLayout() { - needsLayout = true - } + // for compatibility with macOS + func setNeedsLayout() { + needsLayout = true + } #endif #if os(macOS) - func bringSubviewToFront(_ view: NSView) { - addSubview(view) - } + func bringSubviewToFront(_ view: NSView) { + addSubview(view) + } #endif func performLayout() { diff --git a/Sources/LiveKit/Support/NativeViewRepresentable.swift b/Sources/LiveKit/Support/NativeViewRepresentable.swift index fe1603e45..b0067f927 100644 --- a/Sources/LiveKit/Support/NativeViewRepresentable.swift +++ b/Sources/LiveKit/Support/NativeViewRepresentable.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,15 +18,15 @@ import Foundation import SwiftUI #if canImport(UIKit) -import UIKit + import UIKit #elseif canImport(AppKit) -import AppKit + import AppKit #endif #if os(iOS) -public typealias NativeViewRepresentableType = UIViewRepresentable + public typealias NativeViewRepresentableType = UIViewRepresentable #elseif os(macOS) -public typealias NativeViewRepresentableType = NSViewRepresentable + public typealias NativeViewRepresentableType = NSViewRepresentable #endif // multiplatform version of UI/NSViewRepresentable @@ -40,30 +40,30 @@ protocol NativeViewRepresentable: NativeViewRepresentableType { } extension NativeViewRepresentable { - #if os(iOS) - public func makeUIView(context: Context) -> Self.ViewType { - return makeView(context: context) - } + public func makeUIView(context: Context) -> Self.ViewType { + makeView(context: context) + } + + public func updateUIView(_ view: Self.ViewType, context: Context) { + updateView(view, context: context) + } - public func updateUIView(_ view: Self.ViewType, context: Context) { - updateView(view, context: context) - } + public static func dismantleUIView(_ view: Self.ViewType, coordinator: Self.Coordinator) { + dismantleView(view, coordinator: coordinator) + } - public static func dismantleUIView(_ view: Self.ViewType, coordinator: Self.Coordinator) { - dismantleView(view, coordinator: coordinator) - } #elseif os(macOS) - public func makeNSView(context: Context) -> Self.ViewType { - return makeView(context: context) - } + public func makeNSView(context: Context) -> Self.ViewType { + makeView(context: context) + } - public func updateNSView(_ view: Self.ViewType, context: Context) { - updateView(view, context: context) - } + public func updateNSView(_ view: Self.ViewType, context: Context) { + updateView(view, context: context) + } - public static func dismantleNSView(_ view: Self.ViewType, coordinator: Self.Coordinator) { - dismantleView(view, coordinator: coordinator) - } + public static func dismantleNSView(_ view: Self.ViewType, coordinator: Self.Coordinator) { + dismantleView(view, coordinator: coordinator) + } #endif } diff --git a/Sources/LiveKit/Support/StateSync.swift b/Sources/LiveKit/Support/StateSync.swift index dd850e3b0..913d69862 100644 --- a/Sources/LiveKit/Support/StateSync.swift +++ b/Sources/LiveKit/Support/StateSync.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,11 @@ * limitations under the License. */ -import Foundation import Combine +import Foundation @dynamicMemberLookup -internal final class StateSync { - +final class StateSync { typealias OnDidMutate = (_ newState: Value, _ oldState: Value) -> Void private let subject: CurrentValueSubject @@ -69,7 +68,6 @@ internal final class StateSync { } extension StateSync: CustomStringConvertible { - var description: String { "StateSync(\(String(describing: copy()))" } diff --git a/Sources/LiveKit/Support/Stopwatch.swift b/Sources/LiveKit/Support/Stopwatch.swift index 1303e1c3c..8b2147fba 100644 --- a/Sources/LiveKit/Support/Stopwatch.swift +++ b/Sources/LiveKit/Support/Stopwatch.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import Foundation public struct Stopwatch { - public struct Entry: Equatable { let label: String let time: TimeInterval @@ -27,12 +26,12 @@ public struct Stopwatch { public private(set) var start: TimeInterval public private(set) var splits = [Entry]() - internal init(label: String) { + init(label: String) { self.label = label - self.start = ProcessInfo.processInfo.systemUptime + start = ProcessInfo.processInfo.systemUptime } - internal mutating func split(label: String = "") { + mutating func split(label: String = "") { splits.append(Entry(label: label, time: ProcessInfo.processInfo.systemUptime)) } @@ -43,7 +42,6 @@ public struct Stopwatch { } extension Stopwatch: Equatable { - public static func == (lhs: Stopwatch, rhs: Stopwatch) -> Bool { lhs.start == rhs.start && lhs.splits == rhs.splits @@ -51,9 +49,7 @@ extension Stopwatch: Equatable { } extension Stopwatch: CustomStringConvertible { - public var description: String { - var e = [String]() var s = start for x in splits { diff --git a/Sources/LiveKit/Support/TextView.swift b/Sources/LiveKit/Support/TextView.swift index c8a660af7..9cf738db9 100644 --- a/Sources/LiveKit/Support/TextView.swift +++ b/Sources/LiveKit/Support/TextView.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,68 +17,68 @@ import Foundation #if canImport(UIKit) -import UIKit + import UIKit #elseif canImport(AppKit) -import AppKit + import AppKit #endif -internal class TextView: NativeView { - +class TextView: NativeView { #if os(iOS) - private class DebugUILabel: UILabel { - override func drawText(in rect: CGRect) { - let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines) - super.drawText(in: textRect) + private class DebugUILabel: UILabel { + override func drawText(in _: CGRect) { + let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines) + super.drawText(in: textRect) + } } - } - private let _textView: DebugUILabel + + private let _textView: DebugUILabel #elseif os(macOS) - private let _textView: NSTextField + private let _textView: NSTextField #endif var text: String? { get { #if os(iOS) - _textView.text + _textView.text #elseif os(macOS) - _textView.stringValue + _textView.stringValue #endif } set { #if os(iOS) - _textView.text = newValue + _textView.text = newValue #elseif os(macOS) - _textView.stringValue = newValue ?? "" + _textView.stringValue = newValue ?? "" #endif } } override init(frame: CGRect) { - #if os(iOS) - _textView = DebugUILabel(frame: .zero) - _textView.numberOfLines = 0 - _textView.adjustsFontSizeToFitWidth = false - _textView.lineBreakMode = .byWordWrapping - _textView.textColor = .white - _textView.font = .systemFont(ofSize: 11) - _textView.backgroundColor = .clear - _textView.textAlignment = .right + _textView = DebugUILabel(frame: .zero) + _textView.numberOfLines = 0 + _textView.adjustsFontSizeToFitWidth = false + _textView.lineBreakMode = .byWordWrapping + _textView.textColor = .white + _textView.font = .systemFont(ofSize: 11) + _textView.backgroundColor = .clear + _textView.textAlignment = .right #elseif os(macOS) - _textView = NSTextField() - _textView.drawsBackground = false - _textView.isBordered = false - _textView.isEditable = false - _textView.isSelectable = false - _textView.font = .systemFont(ofSize: 11) - _textView.alignment = .right + _textView = NSTextField() + _textView.drawsBackground = false + _textView.isBordered = false + _textView.isEditable = false + _textView.isSelectable = false + _textView.font = .systemFont(ofSize: 11) + _textView.alignment = .right #endif super.init(frame: frame) addSubview(_textView) } - required init?(coder: NSCoder) { + @available(*, unavailable) + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/Sources/LiveKit/Support/UnfairLock.swift b/Sources/LiveKit/Support/UnfairLock.swift index 7c632b2ab..e17e99f1a 100644 --- a/Sources/LiveKit/Support/UnfairLock.swift +++ b/Sources/LiveKit/Support/UnfairLock.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ import Foundation // // Read http://www.russbishop.net/the-law for more information on why this is necessary // -internal class UnfairLock { - +class UnfairLock { private var _lock: UnsafeMutablePointer init() { diff --git a/Sources/LiveKit/Support/Utils.swift b/Sources/LiveKit/Support/Utils.swift index 7da252368..f30e8dd71 100644 --- a/Sources/LiveKit/Support/Utils.swift +++ b/Sources/LiveKit/Support/Utils.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,15 +18,15 @@ import Foundation @_implementationOnly import WebRTC -internal typealias DebouncFunc = () -> Void +typealias DebouncFunc = () -> Void -internal enum OS { +enum OS { case macOS case iOS } extension OS: CustomStringConvertible { - internal var description: String { + var description: String { switch self { case .macOS: return "macOS" case .iOS: return "iOS" @@ -34,8 +34,7 @@ extension OS: CustomStringConvertible { } } -internal func format(bps: UInt64) -> String { - +func format(bps: UInt64) -> String { let bpsDivider: Double = 1000 let ordinals = ["", "K", "M", "G", "T", "P", "E"] @@ -50,22 +49,21 @@ internal func format(bps: UInt64) -> String { return String(rate.rounded(to: 2)) + ordinals[ordinal] + "bps" } -internal class Utils { - +class Utils { private static let processInfo = ProcessInfo() /// Returns current OS. - internal static func os() -> OS { + static func os() -> OS { #if os(macOS) - .macOS + .macOS #elseif os(iOS) - .iOS + .iOS #endif } /// Returns os version as a string. /// format: `12.1`, `15.3.1`, `15.0.1` - internal static func osVersionString() -> String { + static func osVersionString() -> String { let osVersion = processInfo.operatingSystemVersion var versions = [osVersion.majorVersion] if osVersion.minorVersion != 0 || osVersion.patchVersion != 0 { @@ -74,45 +72,46 @@ internal class Utils { if osVersion.patchVersion != 0 { versions.append(osVersion.patchVersion) } - return versions.map({ String($0) }).joined(separator: ".") + return versions.map { String($0) }.joined(separator: ".") } /// Returns a model identifier. /// format: `MacBookPro18,3`, `iPhone13,3` or `iOSSimulator,arm64` - internal static func modelIdentifier() -> String? { + static func modelIdentifier() -> String? { #if os(macOS) - let service = IOServiceGetMatchingService(kIOMasterPortDefault, - IOServiceMatching("IOPlatformExpertDevice")) - defer { IOObjectRelease(service) } - - guard let modelData = IORegistryEntryCreateCFProperty(service, - "model" as CFString, - kCFAllocatorDefault, - 0).takeRetainedValue() as? Data else { - return nil - } + let service = IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceMatching("IOPlatformExpertDevice")) + defer { IOObjectRelease(service) } + + guard let modelData = IORegistryEntryCreateCFProperty(service, + "model" as CFString, + kCFAllocatorDefault, + 0).takeRetainedValue() as? Data + else { + return nil + } - return modelData.withUnsafeBytes({ (pointer: UnsafeRawBufferPointer) -> String? in - guard let cString = pointer.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return nil } - return String(cString: cString) - }) + return modelData.withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> String? in + guard let cString = pointer.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return nil } + return String(cString: cString) + } #elseif os(iOS) - var systemInfo = utsname() - uname(&systemInfo) - let machineMirror = Mirror(reflecting: systemInfo.machine) - let identifier = machineMirror.children.reduce("") { identifier, element in - guard let value = element.value as? Int8, value != 0 else { return identifier } - return identifier + String(UnicodeScalar(UInt8(value))) - } - // for simulator, the following codes are returned - guard !["i386", "x86_64", "arm64"].contains(where: { $0 == identifier }) else { - return "iOSSimulator,\(identifier)" - } - return identifier + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + // for simulator, the following codes are returned + guard !["i386", "x86_64", "arm64"].contains(where: { $0 == identifier }) else { + return "iOSSimulator,\(identifier)" + } + return identifier #endif } - internal static func networkTypeString() -> String? { + static func networkTypeString() -> String? { // wifi, wired, cellular, vpn, empty if not known guard let interface = ConnectivityListener.shared.activeInterfaceType() else { return nil @@ -126,7 +125,7 @@ internal class Utils { } } - internal static func buildUrl( + static func buildUrl( _ url: String, _ token: String, connectOptions: ConnectOptions? = nil, @@ -135,7 +134,6 @@ internal class Utils { validate: Bool = false, forceSecure: Bool = false ) -> URL? { - // use default options if nil let connectOptions = connectOptions ?? ConnectOptions() @@ -155,9 +153,10 @@ internal class Utils { // if already ending with `rtc` or `validate` // and is not a dir, remove it - if !parsedUrl.hasDirectoryPath - && !pathSegments.isEmpty - && ["rtc", "validate"].contains(pathSegments.last!) { + if !parsedUrl.hasDirectoryPath, + !pathSegments.isEmpty, + ["rtc", "validate"].contains(pathSegments.last!) + { pathSegments.removeLast() } // add the correct segment @@ -177,7 +176,7 @@ internal class Utils { URLQueryItem(name: "version", value: LiveKit.version), // Additional client info URLQueryItem(name: "os", value: String(describing: os())), - URLQueryItem(name: "os_version", value: osVersionString()) + URLQueryItem(name: "os_version", value: osVersionString()), ] if let modelIdentifier = modelIdentifier() { @@ -189,7 +188,7 @@ internal class Utils { } // only for quick-reconnect - queryItems.append(URLQueryItem(name: "reconnect", value: .quick == reconnectMode ? "1" : "0")) + queryItems.append(URLQueryItem(name: "reconnect", value: reconnectMode == .quick ? "1" : "0")) queryItems.append(URLQueryItem(name: "auto_subscribe", value: connectOptions.autoSubscribe ? "1" : "0")) queryItems.append(URLQueryItem(name: "adaptive_stream", value: adaptiveStream ? "1" : "0")) @@ -202,10 +201,11 @@ internal class Utils { return builder.url } - internal static func createDebounceFunc(on queue: DispatchQueue, - wait: TimeInterval, - onCreateWorkItem: ((DispatchWorkItem) -> Void)? = nil, - fnc: @escaping @convention(block) () -> Void) -> DebouncFunc { + static func createDebounceFunc(on queue: DispatchQueue, + wait: TimeInterval, + onCreateWorkItem: ((DispatchWorkItem) -> Void)? = nil, + fnc: @escaping @convention(block) () -> Void) -> DebouncFunc + { var workItem: DispatchWorkItem? return { workItem?.cancel() @@ -215,12 +215,11 @@ internal class Utils { } } - internal static func computeEncodings( + static func computeEncodings( dimensions: Dimensions, publishOptions: VideoPublishOptions?, isScreenShare: Bool = false ) -> [LKRTCRtpEncodingParameters] { - let publishOptions = publishOptions ?? VideoPublishOptions() let preferredEncoding: VideoEncoding? = isScreenShare ? publishOptions.screenShareEncoding : publishOptions.encoding let encoding = preferredEncoding ?? dimensions.computeSuggestedPreset(in: dimensions.computeSuggestedPresets(isScreenShare: isScreenShare)) @@ -242,7 +241,7 @@ internal class Utils { let midPreset = presets[safe: 1] var resultPresets = [baseParameters] - if dimensions.max >= 960, let midPreset = midPreset { + if dimensions.max >= 960, let midPreset { resultPresets = [lowPreset, midPreset, baseParameters] } else if dimensions.max >= 480 { resultPresets = [lowPreset, baseParameters] @@ -252,18 +251,18 @@ internal class Utils { } } -internal extension Collection { +extension Collection { /// Returns the element at the specified index if it is within bounds, otherwise nil. subscript(safe index: Index) -> Element? { - return indices.contains(index) ? self[index] : nil + indices.contains(index) ? self[index] : nil } } -internal extension MutableCollection { +extension MutableCollection { subscript(safe index: Index) -> Element? { get { indices.contains(index) ? self[index] : nil } set { - if let newValue = newValue, indices.contains(index) { + if let newValue, indices.contains(index) { self[index] = newValue } } diff --git a/Sources/LiveKit/Support/WebSocket.swift b/Sources/LiveKit/Support/WebSocket.swift index 745d186aa..df19d76cb 100644 --- a/Sources/LiveKit/Support/WebSocket.swift +++ b/Sources/LiveKit/Support/WebSocket.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,9 @@ import Foundation -internal typealias WebSocketStream = AsyncThrowingStream - -internal class WebSocket: NSObject, Loggable, AsyncSequence, URLSessionWebSocketDelegate { +typealias WebSocketStream = AsyncThrowingStream +class WebSocket: NSObject, Loggable, AsyncSequence, URLSessionWebSocketDelegate { typealias AsyncIterator = WebSocketStream.Iterator typealias Element = URLSessionWebSocketTask.Message @@ -36,19 +35,14 @@ internal class WebSocket: NSObject, Loggable, AsyncSequence, URLSessionWebSocket return URLSession(configuration: config, delegate: self, delegateQueue: nil) }() - private lazy var task: URLSessionWebSocketTask = { - urlSession.webSocketTask(with: request) - }() + private lazy var task: URLSessionWebSocketTask = urlSession.webSocketTask(with: request) - private lazy var stream: WebSocketStream = { - return WebSocketStream { continuation in - streamContinuation = continuation - waitForNextValue() - } - }() + private lazy var stream: WebSocketStream = WebSocketStream { continuation in + streamContinuation = continuation + waitForNextValue() + } init(url: URL) { - request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: .defaultSocketConnect) @@ -59,7 +53,6 @@ internal class WebSocket: NSObject, Loggable, AsyncSequence, URLSessionWebSocket } public func connect() async throws { - try await withCheckedThrowingContinuation { continuation in connectContinuation = continuation task.resume() @@ -77,7 +70,7 @@ internal class WebSocket: NSObject, Loggable, AsyncSequence, URLSessionWebSocket // MARK: - AsyncSequence func makeAsyncIterator() -> AsyncIterator { - return stream.makeAsyncIterator() + stream.makeAsyncIterator() } private func waitForNextValue() { @@ -112,14 +105,14 @@ internal class WebSocket: NSObject, Loggable, AsyncSequence, URLSessionWebSocket // MARK: - URLSessionWebSocketDelegate - func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) { + func urlSession(_: URLSession, webSocketTask _: URLSessionWebSocketTask, didOpenWithProtocol _: String?) { connectContinuation?.resume() connectContinuation = nil } - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + func urlSession(_: URLSession, task _: URLSessionTask, didCompleteWithError error: Error?) { log("didCompleteWithError: \(String(describing: error))", .error) - let error = error ?? NetworkError.disconnected(message: "WebSocket didCompleteWithError") + let error = error ?? NetworkError.disconnected(message: "WebSocket didCompleteWithError") connectContinuation?.resume(throwing: error) connectContinuation = nil streamContinuation?.finish() diff --git a/Sources/LiveKit/SwiftUI/SwiftUIAudioRoutePickerButton.swift b/Sources/LiveKit/SwiftUI/SwiftUIAudioRoutePickerButton.swift index 9006819f6..cff45679b 100644 --- a/Sources/LiveKit/SwiftUI/SwiftUIAudioRoutePickerButton.swift +++ b/Sources/LiveKit/SwiftUI/SwiftUIAudioRoutePickerButton.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,38 +14,36 @@ * limitations under the License. */ +import AVKit import Foundation import SwiftUI -import AVKit @_implementationOnly import WebRTC public struct SwiftUIAudioRoutePickerButton: NativeViewRepresentable { - typealias ViewType = AVRoutePickerView public init() { // } - func makeView(context: Context) -> AVRoutePickerView { - + func makeView(context _: Context) -> AVRoutePickerView { let routePickerView = AVRoutePickerView() #if os(iOS) - routePickerView.prioritizesVideoDevices = false + routePickerView.prioritizesVideoDevices = false #elseif os(macOS) - routePickerView.isRoutePickerButtonBordered = false + routePickerView.isRoutePickerButtonBordered = false #endif return routePickerView } - func updateView(_ nsView: AVRoutePickerView, context: Context) { + func updateView(_: AVRoutePickerView, context _: Context) { // } - static func dismantleView(_ nsView: AVRoutePickerView, coordinator: ()) { + static func dismantleView(_: AVRoutePickerView, coordinator _: ()) { // } } diff --git a/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift b/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift index bde15ddad..3ae3a9351 100644 --- a/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift +++ b/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,23 +18,22 @@ import Foundation import SwiftUI /// This class receives ``TrackDelegate`` events since a struct can't be used for a delegate -internal class TrackDelegateReceiver: TrackDelegate, Loggable { - +class TrackDelegateReceiver: TrackDelegate, Loggable { @Binding var dimensions: Dimensions? @Binding var statistics: TrackStatistics? init(dimensions: Binding, statistics: Binding) { - self._dimensions = dimensions - self._statistics = statistics + _dimensions = dimensions + _statistics = statistics } - func track(_ track: VideoTrack, didUpdate dimensions: Dimensions?) { + func track(_: VideoTrack, didUpdate dimensions: Dimensions?) { Task.detached { @MainActor in self.dimensions = dimensions } } - func track(_ track: Track, didUpdateStatistics statistics: TrackStatistics) { + func track(_: Track, didUpdateStatistics statistics: TrackStatistics) { Task.detached { @MainActor in self.statistics = statistics } @@ -42,15 +41,14 @@ internal class TrackDelegateReceiver: TrackDelegate, Loggable { } /// This class receives ``VideoViewDelegate`` events since a struct can't be used for a delegate -internal class VideoViewDelegateReceiver: VideoViewDelegate, Loggable { - +class VideoViewDelegateReceiver: VideoViewDelegate, Loggable { @Binding var isRendering: Bool init(isRendering: Binding) { - self._isRendering = isRendering + _isRendering = isRendering } - func videoView(_ videoView: VideoView, didUpdate isRendering: Bool) { + func videoView(_: VideoView, didUpdate isRendering: Bool) { Task.detached { @MainActor in self.isRendering = isRendering } @@ -60,7 +58,6 @@ internal class VideoViewDelegateReceiver: VideoViewDelegate, Loggable { /// A ``VideoView`` that can be used in SwiftUI. /// Supports both iOS and macOS. public struct SwiftUIVideoView: NativeViewRepresentable { - typealias ViewType = VideoView /// Pass a ``VideoTrack`` of a ``Participant``. @@ -83,21 +80,21 @@ public struct SwiftUIVideoView: NativeViewRepresentable { debugMode: Bool = false, isRendering: Binding = .constant(false), dimensions: Binding = .constant(nil), - trackStatistics: Binding = .constant(nil)) { - + trackStatistics: Binding = .constant(nil)) + { self.track = track self.layoutMode = layoutMode self.mirrorMode = mirrorMode self.renderMode = renderMode self.debugMode = debugMode - self._isRendering = isRendering - self._dimensions = dimensions + _isRendering = isRendering + _dimensions = dimensions - self.trackDelegateReceiver = TrackDelegateReceiver(dimensions: dimensions, - statistics: trackStatistics) + trackDelegateReceiver = TrackDelegateReceiver(dimensions: dimensions, + statistics: trackStatistics) - self.videoViewDelegateReceiver = VideoViewDelegateReceiver(isRendering: isRendering) + videoViewDelegateReceiver = VideoViewDelegateReceiver(isRendering: isRendering) // update binding value Task.detached { @MainActor in @@ -116,7 +113,7 @@ public struct SwiftUIVideoView: NativeViewRepresentable { return view } - public func updateView(_ videoView: VideoView, context: Context) { + public func updateView(_ videoView: VideoView, context _: Context) { videoView.track = track videoView.layoutMode = layoutMode videoView.mirrorMode = mirrorMode @@ -125,11 +122,11 @@ public struct SwiftUIVideoView: NativeViewRepresentable { // update Task.detached { @MainActor in - self.isRendering = videoView.isRendering + isRendering = videoView.isRendering } } - public static func dismantleView(_ videoView: VideoView, coordinator: ()) { + public static func dismantleView(_ videoView: VideoView, coordinator _: ()) { videoView.track = nil } } diff --git a/Sources/LiveKit/Track/AudioManager.swift b/Sources/LiveKit/Track/AudioManager.swift index 4cc7a9407..9291c0a0f 100644 --- a/Sources/LiveKit/Track/AudioManager.swift +++ b/Sources/LiveKit/Track/AudioManager.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import Foundation @objc public class AudioBuffer: NSObject { - private let _audioBuffer: LKRTCAudioBuffer public var channels: Int { _audioBuffer.channels } @@ -32,8 +31,8 @@ public class AudioBuffer: NSObject { _audioBuffer.rawBuffer(forChannel: channel) } - internal init(audioBuffer: LKRTCAudioBuffer) { - self._audioBuffer = audioBuffer + init(audioBuffer: LKRTCAudioBuffer) { + _audioBuffer = audioBuffer } } @@ -44,9 +43,8 @@ public protocol AudioCustomProcessingDelegate { func audioProcessingRelease() } -internal class AudioCustomProcessingDelegateAdapter: NSObject, LKRTCAudioCustomProcessingDelegate { - - internal weak var target: AudioCustomProcessingDelegate? +class AudioCustomProcessingDelegateAdapter: NSObject, LKRTCAudioCustomProcessingDelegate { + weak var target: AudioCustomProcessingDelegate? init(target: AudioCustomProcessingDelegate? = nil) { self.target = target @@ -68,18 +66,17 @@ internal class AudioCustomProcessingDelegateAdapter: NSObject, LKRTCAudioCustomP override func isEqual(_ object: Any?) -> Bool { guard let other = object as? AudioCustomProcessingDelegateAdapter else { return false } - return self.target === other.target + return target === other.target } override var hash: Int { - guard let target = target else { return 0 } + guard let target else { return 0 } return ObjectIdentifier(target).hashValue } } // Audio Session Configuration related public class AudioManager: Loggable { - // MARK: - Public public static let shared = AudioManager() @@ -104,7 +101,6 @@ public class AudioManager: Loggable { } public struct State: Equatable { - // Only consider State mutated when public vars change public static func == (lhs: AudioManager.State, rhs: AudioManager.State) -> Bool { lhs.localTracksCount == rhs.localTracksCount && @@ -113,19 +109,18 @@ public class AudioManager: Loggable { } // Keep this var within State so it's protected by UnfairLock - internal var customConfigureFunc: ConfigureAudioSessionFunc? + var customConfigureFunc: ConfigureAudioSessionFunc? public var localTracksCount: Int = 0 public var remoteTracksCount: Int = 0 public var preferSpeakerOutput: Bool = true public var trackState: TrackState { - - if localTracksCount > 0 && remoteTracksCount == 0 { + if localTracksCount > 0, remoteTracksCount == 0 { return .localOnly - } else if localTracksCount == 0 && remoteTracksCount > 0 { + } else if localTracksCount == 0, remoteTracksCount > 0 { return .remoteOnly - } else if localTracksCount > 0 && remoteTracksCount > 0 { + } else if localTracksCount > 0, remoteTracksCount > 0 { return .localAndRemote } @@ -191,7 +186,7 @@ public class AudioManager: Loggable { public var onDeviceUpdate: DeviceUpdateFunc? { didSet { Engine.audioDeviceModule.setDevicesUpdatedHandler { [weak self] in - guard let self = self else { return } + guard let self else { return } self.onDeviceUpdate?(self) } } @@ -199,11 +194,11 @@ public class AudioManager: Loggable { // MARK: - Internal - internal var localTracksCount: Int { _state.localTracksCount } + var localTracksCount: Int { _state.localTracksCount } - internal var remoteTracksCount: Int { _state.remoteTracksCount } + var remoteTracksCount: Int { _state.remoteTracksCount } - internal enum `Type` { + enum `Type` { case local case remote } @@ -216,18 +211,18 @@ public class AudioManager: Loggable { private init() { // trigger events when state mutates _state.onDidMutate = { [weak self] newState, oldState in - guard let self = self else { return } + guard let self else { return } log("\(oldState) -> \(newState)") #if os(iOS) - let configureFunc = newState.customConfigureFunc ?? defaultConfigureAudioSessionFunc - configureFunc(newState, oldState) + let configureFunc = newState.customConfigureFunc ?? defaultConfigureAudioSessionFunc + configureFunc(newState, oldState) #endif } } - internal func trackDidStart(_ type: Type) { + func trackDidStart(_ type: Type) { // async mutation _state.mutate { state in if type == .local { state.localTracksCount += 1 } @@ -235,7 +230,7 @@ public class AudioManager: Loggable { } } - internal func trackDidStop(_ type: Type) { + func trackDidStop(_ type: Type) { // async mutation _state.mutate { state in if type == .local { state.localTracksCount -= 1 } @@ -244,87 +239,86 @@ public class AudioManager: Loggable { } #if os(iOS) - /// The default implementation when audio session configuration is requested by the SDK. - /// Configure the `RTCAudioSession` of `WebRTC` framework. - /// - /// > Note: It is recommended to use `RTCAudioSessionConfiguration.webRTC()` to obtain an instance of `RTCAudioSessionConfiguration` instead of instantiating directly. - /// - /// - Parameters: - /// - configuration: A configured RTCAudioSessionConfiguration - /// - setActive: passing true/false will call `AVAudioSession.setActive` internally - public func defaultConfigureAudioSessionFunc(newState: State, oldState: State) { - - DispatchQueue.liveKitWebRTC.async { [weak self] in - - guard let self = self else { return } - - // prepare config - let configuration = LKRTCAudioSessionConfiguration.webRTC() - - if newState.trackState == .remoteOnly && newState.preferSpeakerOutput { - /* .playback */ - configuration.category = AVAudioSession.Category.playback.rawValue - configuration.mode = AVAudioSession.Mode.spokenAudio.rawValue - configuration.categoryOptions = [ - .mixWithOthers - ] - - } else if [.localOnly, .localAndRemote].contains(newState.trackState) || - (newState.trackState == .remoteOnly && !newState.preferSpeakerOutput) { - - /* .playAndRecord */ - configuration.category = AVAudioSession.Category.playAndRecord.rawValue - - if newState.preferSpeakerOutput { - // use .videoChat if speakerOutput is preferred - configuration.mode = AVAudioSession.Mode.videoChat.rawValue + /// The default implementation when audio session configuration is requested by the SDK. + /// Configure the `RTCAudioSession` of `WebRTC` framework. + /// + /// > Note: It is recommended to use `RTCAudioSessionConfiguration.webRTC()` to obtain an instance of `RTCAudioSessionConfiguration` instead of instantiating directly. + /// + /// - Parameters: + /// - configuration: A configured RTCAudioSessionConfiguration + /// - setActive: passing true/false will call `AVAudioSession.setActive` internally + public func defaultConfigureAudioSessionFunc(newState: State, oldState: State) { + DispatchQueue.liveKitWebRTC.async { [weak self] in + + guard let self else { return } + + // prepare config + let configuration = LKRTCAudioSessionConfiguration.webRTC() + + if newState.trackState == .remoteOnly && newState.preferSpeakerOutput { + /* .playback */ + configuration.category = AVAudioSession.Category.playback.rawValue + configuration.mode = AVAudioSession.Mode.spokenAudio.rawValue + configuration.categoryOptions = [ + .mixWithOthers, + ] + + } else if [.localOnly, .localAndRemote].contains(newState.trackState) || + (newState.trackState == .remoteOnly && !newState.preferSpeakerOutput) + { + /* .playAndRecord */ + configuration.category = AVAudioSession.Category.playAndRecord.rawValue + + if newState.preferSpeakerOutput { + // use .videoChat if speakerOutput is preferred + configuration.mode = AVAudioSession.Mode.videoChat.rawValue + } else { + // use .voiceChat if speakerOutput is not preferred + configuration.mode = AVAudioSession.Mode.voiceChat.rawValue + } + + configuration.categoryOptions = [ + .allowBluetooth, + .allowBluetoothA2DP, + .allowAirPlay, + ] + } else { - // use .voiceChat if speakerOutput is not preferred - configuration.mode = AVAudioSession.Mode.voiceChat.rawValue + /* .soloAmbient */ + configuration.category = AVAudioSession.Category.soloAmbient.rawValue + configuration.mode = AVAudioSession.Mode.default.rawValue + configuration.categoryOptions = [] } - configuration.categoryOptions = [ - .allowBluetooth, - .allowBluetoothA2DP, - .allowAirPlay - ] - - } else { - /* .soloAmbient */ - configuration.category = AVAudioSession.Category.soloAmbient.rawValue - configuration.mode = AVAudioSession.Mode.default.rawValue - configuration.categoryOptions = [] - } + var setActive: Bool? - var setActive: Bool? + if newState.trackState != .none, oldState.trackState == .none { + // activate audio session when there is any local/remote audio track + setActive = true + } else if newState.trackState == .none, oldState.trackState != .none { + // deactivate audio session when there are no more local/remote audio tracks + setActive = false + } - if newState.trackState != .none, oldState.trackState == .none { - // activate audio session when there is any local/remote audio track - setActive = true - } else if newState.trackState == .none, oldState.trackState != .none { - // deactivate audio session when there are no more local/remote audio tracks - setActive = false - } + // configure session + let session = LKRTCAudioSession.sharedInstance() + session.lockForConfiguration() + // always unlock + defer { session.unlockForConfiguration() } - // configure session - let session = LKRTCAudioSession.sharedInstance() - session.lockForConfiguration() - // always unlock - defer { session.unlockForConfiguration() } + do { + self.log("configuring audio session category: \(configuration.category), mode: \(configuration.mode), setActive: \(String(describing: setActive))") - do { - self.log("configuring audio session category: \(configuration.category), mode: \(configuration.mode), setActive: \(String(describing: setActive))") + if let setActive { + try session.setConfiguration(configuration, active: setActive) + } else { + try session.setConfiguration(configuration) + } - if let setActive = setActive { - try session.setConfiguration(configuration, active: setActive) - } else { - try session.setConfiguration(configuration) + } catch { + self.log("Failed to configure audio session with error: \(error)", .error) } - - } catch let error { - self.log("Failed to configure audio session with error: \(error)", .error) } } - } #endif } diff --git a/Sources/LiveKit/Track/AudioTrack.swift b/Sources/LiveKit/Track/AudioTrack.swift index b7f694366..0562dc99b 100644 --- a/Sources/LiveKit/Track/AudioTrack.swift +++ b/Sources/LiveKit/Track/AudioTrack.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,5 +19,4 @@ import Foundation @_implementationOnly import WebRTC @objc -public protocol AudioTrack where Self: Track { -} +public protocol AudioTrack where Self: Track {} diff --git a/Sources/LiveKit/Track/Capturers/BufferCapturer.swift b/Sources/LiveKit/Track/Capturers/BufferCapturer.swift index 684e88799..f69851927 100644 --- a/Sources/LiveKit/Track/Capturers/BufferCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/BufferCapturer.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ * limitations under the License. */ -import Foundation import CoreMedia +import Foundation @_implementationOnly import WebRTC @@ -29,7 +29,6 @@ import CoreMedia /// since dimensions must be resolved at the time of publishing (to compute video parameters). /// public class BufferCapturer: VideoCapturer { - private let capturer = Engine.createVideoCapturer() /// The ``BufferCaptureOptions`` used for this capturer. @@ -42,7 +41,6 @@ public class BufferCapturer: VideoCapturer { /// Capture a ``CMSampleBuffer``. public func capture(_ sampleBuffer: CMSampleBuffer) { - delegate?.capturer(capturer, didCapture: sampleBuffer) { sourceDimensions in let targetDimensions = sourceDimensions @@ -61,12 +59,13 @@ public class BufferCapturer: VideoCapturer { /// Capture a ``CVPixelBuffer``. public func capture(_ pixelBuffer: CVPixelBuffer, timeStampNs: Int64 = VideoCapturer.createTimeStampNs(), - rotation: VideoRotation = ._0) { - + rotation: VideoRotation = ._0) + { delegate?.capturer(capturer, didCapture: pixelBuffer, timeStampNs: timeStampNs, - rotation: rotation.toRTCType()) { sourceDimensions in + rotation: rotation.toRTCType()) + { sourceDimensions in let targetDimensions = sourceDimensions .aspectFit(size: self.options.dimensions.max) @@ -82,12 +81,12 @@ public class BufferCapturer: VideoCapturer { } } -extension LocalVideoTrack { - +public extension LocalVideoTrack { /// Creates a track that can directly capture `CVPixelBuffer` or `CMSampleBuffer` for convienience - public static func createBufferTrack(name: String = Track.screenShareVideoName, - source: VideoTrack.Source = .screenShareVideo, - options: BufferCaptureOptions = BufferCaptureOptions()) -> LocalVideoTrack { + static func createBufferTrack(name: String = Track.screenShareVideoName, + source: VideoTrack.Source = .screenShareVideo, + options: BufferCaptureOptions = BufferCaptureOptions()) -> LocalVideoTrack + { let videoSource = Engine.createVideoSource(forScreenShare: source == .screenShareVideo) let capturer = BufferCapturer(delegate: videoSource, options: options) return LocalVideoTrack( diff --git a/Sources/LiveKit/Track/Capturers/CameraCapturer.swift b/Sources/LiveKit/Track/Capturers/CameraCapturer.swift index 09bb8d093..b6209a4b5 100644 --- a/Sources/LiveKit/Track/Capturers/CameraCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/CameraCapturer.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,12 @@ import Foundation #if canImport(ReplayKit) -import ReplayKit + import ReplayKit #endif @_implementationOnly import WebRTC public class CameraCapturer: VideoCapturer { - @objc public static func captureDevices() -> [AVCaptureDevice] { DispatchQueue.liveKitWebRTC.sync { LKRTCCameraVideoCapturer.captureDevices() } @@ -51,11 +50,11 @@ public class CameraCapturer: VideoCapturer { public var isMultitaskingAccessSupported: Bool { #if os(iOS) || os(tvOS) - if #available(iOS 16, *, tvOS 17, *) { - self.capturer.captureSession.beginConfiguration() - defer { self.capturer.captureSession.commitConfiguration() } - return self.capturer.captureSession.isMultitaskingCameraAccessSupported - } + if #available(iOS 16, *, tvOS 17, *) { + self.capturer.captureSession.beginConfiguration() + defer { self.capturer.captureSession.commitConfiguration() } + return self.capturer.captureSession.isMultitaskingCameraAccessSupported + } #endif return false } @@ -63,30 +62,26 @@ public class CameraCapturer: VideoCapturer { public var isMultitaskingAccessEnabled: Bool { get { #if os(iOS) || os(tvOS) - if #available(iOS 16, *, tvOS 17, *) { - return self.capturer.captureSession.isMultitaskingCameraAccessEnabled - } + if #available(iOS 16, *, tvOS 17, *) { + return self.capturer.captureSession.isMultitaskingCameraAccessEnabled + } #endif return false } set { #if os(iOS) || os(tvOS) - if #available(iOS 16, *, tvOS 17, *) { - self.capturer.captureSession.isMultitaskingCameraAccessEnabled = newValue - } + if #available(iOS 16, *, tvOS 17, *) { + self.capturer.captureSession.isMultitaskingCameraAccessEnabled = newValue + } #endif } } // Used to hide LKRTCVideoCapturerDelegate symbol - private lazy var adapter: VideoCapturerDelegateAdapter = { - VideoCapturerDelegateAdapter(cameraCapturer: self) - }() + private lazy var adapter: VideoCapturerDelegateAdapter = .init(cameraCapturer: self) // RTCCameraVideoCapturer used internally for now - private lazy var capturer: LKRTCCameraVideoCapturer = { - DispatchQueue.liveKitWebRTC.sync { LKRTCCameraVideoCapturer(delegate: adapter) } - }() + private lazy var capturer: LKRTCCameraVideoCapturer = DispatchQueue.liveKitWebRTC.sync { LKRTCCameraVideoCapturer(delegate: adapter) } init(delegate: LKRTCVideoCapturerDelegate, options: CameraCaptureOptions) { self.options = options @@ -112,7 +107,6 @@ public class CameraCapturer: VideoCapturer { @objc @discardableResult public func set(cameraPosition position: AVCaptureDevice.Position) async throws -> Bool { - log("set(cameraPosition:) \(position)") // update options to use new position @@ -122,37 +116,37 @@ public class CameraCapturer: VideoCapturer { return try await restartCapture() } - public override func startCapture() async throws -> Bool { - + override public func startCapture() async throws -> Bool { let didStart = try await super.startCapture() // Already started guard didStart else { return false } - let preferredPixelFormat = self.capturer.preferredOutputPixelFormat() - self.log("CameraCapturer.preferredPixelFormat: \(preferredPixelFormat.toString())") + let preferredPixelFormat = capturer.preferredOutputPixelFormat() + log("CameraCapturer.preferredPixelFormat: \(preferredPixelFormat.toString())") let devices = CameraCapturer.captureDevices() // TODO: FaceTime Camera for macOS uses .unspecified, fall back to first device guard let device = devices.first(where: { $0.position == self.options.position }) ?? devices.first else { - self.log("No camera video capture devices available", .error) + log("No camera video capture devices available", .error) throw TrackError.capturer(message: "No camera video capture devices available") } // list of all formats in order of dimensions size let formats = DispatchQueue.liveKitWebRTC.sync { LKRTCCameraVideoCapturer.supportedFormats(for: device) } // create an array of sorted touples by dimensions size - let sortedFormats = formats.map({ (format: $0, dimensions: Dimensions(from: CMVideoFormatDescriptionGetDimensions($0.formatDescription))) }) + let sortedFormats = formats.map { (format: $0, dimensions: Dimensions(from: CMVideoFormatDescriptionGetDimensions($0.formatDescription))) } .sorted { $0.dimensions.area < $1.dimensions.area } - self.log("sortedFormats: \(sortedFormats.map { "(dimensions: \(String(describing: $0.dimensions)), fps: \(String(describing: $0.format.fpsRange())))" }), target dimensions: \(self.options.dimensions)") + log("sortedFormats: \(sortedFormats.map { "(dimensions: \(String(describing: $0.dimensions)), fps: \(String(describing: $0.format.fpsRange())))" }), target dimensions: \(options.dimensions)") // default to the largest supported dimensions (backup) var selectedFormat = sortedFormats.last - if let preferredFormat = self.options.preferredFormat, - let foundFormat = sortedFormats.first(where: { $0.format == preferredFormat }) { + if let preferredFormat = options.preferredFormat, + let foundFormat = sortedFormats.first(where: { $0.format == preferredFormat }) + { // Use the preferred capture format if specified in options selectedFormat = foundFormat } else { @@ -166,39 +160,39 @@ public class CameraCapturer: VideoCapturer { } // format should be resolved at this point - guard let selectedFormat = selectedFormat else { - self.log("Unable to resolve format", .error) + guard let selectedFormat else { + log("Unable to resolve format", .error) throw TrackError.capturer(message: "Unable to determine format for camera capturer") } let fpsRange = selectedFormat.format.fpsRange() // this should never happen - guard fpsRange != 0...0 else { - self.log("unable to resolve fps range", .error) + guard fpsRange != 0 ... 0 else { + log("unable to resolve fps range", .error) throw TrackError.capturer(message: "Unable to determine supported fps range for format: \(selectedFormat)") } // default to fps in options - var selectedFps = self.options.fps + var selectedFps = options.fps if !fpsRange.contains(selectedFps) { // log a warning, but continue - self.log("requested fps: \(self.options.fps) is out of range: \(fpsRange) and will be clamped", .warning) + log("requested fps: \(options.fps) is out of range: \(fpsRange) and will be clamped", .warning) // clamp to supported fps range selectedFps = selectedFps.clamped(to: fpsRange) } - self.log("starting camera capturer device: \(device), format: \(selectedFormat), fps: \(selectedFps)(\(fpsRange))", .info) + log("starting camera capturer device: \(device), format: \(selectedFormat), fps: \(selectedFps)(\(fpsRange))", .info) // adapt if requested dimensions and camera's dimensions don't match - if let videoSource = self.delegate as? LKRTCVideoSource, - selectedFormat.dimensions != self.options.dimensions { - + if let videoSource = delegate as? LKRTCVideoSource, + selectedFormat.dimensions != self.options.dimensions + { // self.log("adaptOutputFormat to: \(options.dimensions) fps: \(self.options.fps)") - videoSource.adaptOutputFormat(toWidth: self.options.dimensions.width, - height: self.options.dimensions.height, - fps: Int32(self.options.fps)) + videoSource.adaptOutputFormat(toWidth: options.dimensions.width, + height: options.dimensions.height, + fps: Int32(options.fps)) } try await capturer.startCapture(with: device, format: selectedFormat.format, fps: selectedFps) @@ -209,8 +203,7 @@ public class CameraCapturer: VideoCapturer { return true } - public override func stopCapture() async throws -> Bool { - + override public func stopCapture() async throws -> Bool { let didStop = try await super.stopCapture() // Already stopped @@ -219,23 +212,22 @@ public class CameraCapturer: VideoCapturer { await capturer.stopCapture() // Update internal vars - self.device = nil - self.dimensions = nil + device = nil + dimensions = nil return true } } -internal class VideoCapturerDelegateAdapter: NSObject, LKRTCVideoCapturerDelegate { - +class VideoCapturerDelegateAdapter: NSObject, LKRTCVideoCapturerDelegate { weak var cameraCapturer: CameraCapturer? - internal init(cameraCapturer: CameraCapturer? = nil) { + init(cameraCapturer: CameraCapturer? = nil) { self.cameraCapturer = cameraCapturer } func capturer(_ capturer: LKRTCVideoCapturer, didCapture frame: LKRTCVideoFrame) { - guard let cameraCapturer = cameraCapturer else { return } + guard let cameraCapturer else { return } // Resolve real dimensions (apply frame rotation) cameraCapturer.dimensions = Dimensions(width: frame.width, height: frame.height).apply(rotation: frame.rotation) // Pass frame to video source @@ -243,17 +235,16 @@ internal class VideoCapturerDelegateAdapter: NSObject, LKRTCVideoCapturerDelegat } } -extension LocalVideoTrack { - +public extension LocalVideoTrack { @objc - public static func createCameraTrack() -> LocalVideoTrack { + static func createCameraTrack() -> LocalVideoTrack { createCameraTrack(name: nil, options: nil) } @objc - public static func createCameraTrack(name: String? = nil, - options: CameraCaptureOptions? = nil) -> LocalVideoTrack { - + static func createCameraTrack(name: String? = nil, + options: CameraCaptureOptions? = nil) -> LocalVideoTrack + { let videoSource = Engine.createVideoSource(forScreenShare: false) let capturer = CameraCapturer(delegate: videoSource, options: options ?? CameraCaptureOptions()) return LocalVideoTrack( @@ -277,27 +268,23 @@ extension AVCaptureDevice.Position: CustomStringConvertible { } extension Comparable { - // clamp a value within the range func clamped(to limits: ClosedRange) -> Self { - return min(max(self, limits.lowerBound), limits.upperBound) + min(max(self, limits.lowerBound), limits.upperBound) } } extension AVFrameRateRange { - // convert to a ClosedRange func toRange() -> ClosedRange { - Int(minFrameRate)...Int(maxFrameRate) + Int(minFrameRate) ... Int(maxFrameRate) } } extension AVCaptureDevice.Format { - // computes a ClosedRange of supported FPSs for this format func fpsRange() -> ClosedRange { - - videoSupportedFrameRateRanges.map { $0.toRange() }.reduce(into: 0...0) { result, current in + videoSupportedFrameRateRanges.map { $0.toRange() }.reduce(into: 0 ... 0) { result, current in result = merge(range: result, with: current) } } diff --git a/Sources/LiveKit/Track/Capturers/InAppCapturer.swift b/Sources/LiveKit/Track/Capturers/InAppCapturer.swift index ffe8392a7..ba2192e25 100644 --- a/Sources/LiveKit/Track/Capturers/InAppCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/InAppCapturer.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,13 @@ import Foundation #if canImport(ReplayKit) -import ReplayKit + import ReplayKit #endif @_implementationOnly import WebRTC @available(macOS 11.0, iOS 11.0, *) public class InAppScreenCapturer: VideoCapturer { - private let capturer = Engine.createVideoCapturer() private var options: ScreenShareCaptureOptions @@ -33,15 +32,14 @@ public class InAppScreenCapturer: VideoCapturer { super.init(delegate: delegate) } - public override func startCapture() async throws -> Bool { - + override public func startCapture() async throws -> Bool { let didStart = try await super.startCapture() // Already started guard didStart else { return false } // TODO: force pixel format kCVPixelFormatType_420YpCbCr8BiPlanarFullRange - try await RPScreenRecorder.shared().startCapture { sampleBuffer, type, _ in + try await RPScreenRecorder.shared().startCapture { sampleBuffer, type, _ in // Only process .video if type == .video { @@ -65,8 +63,7 @@ public class InAppScreenCapturer: VideoCapturer { return true } - public override func stopCapture() async throws -> Bool { - + override public func stopCapture() async throws -> Bool { let didStop = try await super.stopCapture() // Already stopped @@ -78,11 +75,12 @@ public class InAppScreenCapturer: VideoCapturer { } } -extension LocalVideoTrack { +public extension LocalVideoTrack { /// Creates a track that captures in-app screen only (due to limitation of ReplayKit) @available(macOS 11.0, iOS 11.0, *) - public static func createInAppScreenShareTrack(name: String = Track.screenShareVideoName, - options: ScreenShareCaptureOptions = ScreenShareCaptureOptions()) -> LocalVideoTrack { + static func createInAppScreenShareTrack(name: String = Track.screenShareVideoName, + options: ScreenShareCaptureOptions = ScreenShareCaptureOptions()) -> LocalVideoTrack + { let videoSource = Engine.createVideoSource(forScreenShare: true) let capturer = InAppScreenCapturer(delegate: videoSource, options: options) return LocalVideoTrack( diff --git a/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift b/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift index 894ea2e5c..4576dcc0a 100644 --- a/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,469 +14,455 @@ * limitations under the License. */ -import Foundation import AVFoundation +import Foundation #if canImport(ScreenCaptureKit) -import ScreenCaptureKit + import ScreenCaptureKit #endif @_implementationOnly import WebRTC #if os(macOS) -@available(macOS 12.3, *) -public class MacOSScreenCapturer: VideoCapturer { - - private let capturer = Engine.createVideoCapturer() - - // TODO: Make it possible to change dynamically - public let captureSource: MacOSScreenCaptureSource? - - // SCStream - private var _scStream: SCStream? - - // cached frame for resending to maintain minimum of 1 fps - private var _lastFrame: LKRTCVideoFrame? - private var _resendTimer: Task? - - /// The ``ScreenShareCaptureOptions`` used for this capturer. - /// It is possible to modify the options but `restartCapture` must be called. - public var options: ScreenShareCaptureOptions - - init(delegate: LKRTCVideoCapturerDelegate, captureSource: MacOSScreenCaptureSource, options: ScreenShareCaptureOptions) { - self.captureSource = captureSource - self.options = options - super.init(delegate: delegate) - } + @available(macOS 12.3, *) + public class MacOSScreenCapturer: VideoCapturer { + private let capturer = Engine.createVideoCapturer() - public override func startCapture() async throws -> Bool { + // TODO: Make it possible to change dynamically + public let captureSource: MacOSScreenCaptureSource? - let didStart = try await super.startCapture() + // SCStream + private var _scStream: SCStream? - // Already started - guard didStart else { return false } + // cached frame for resending to maintain minimum of 1 fps + private var _lastFrame: LKRTCVideoFrame? + private var _resendTimer: Task? - guard let captureSource = self.captureSource else { - throw TrackError.capturer(message: "captureSource is nil") - } + /// The ``ScreenShareCaptureOptions`` used for this capturer. + /// It is possible to modify the options but `restartCapture` must be called. + public var options: ScreenShareCaptureOptions - let filter: SCContentFilter - if let windowSource = captureSource as? MacOSWindow, - let nativeWindowSource = windowSource.nativeType as? SCWindow { - filter = SCContentFilter(desktopIndependentWindow: nativeWindowSource) - } else if let displaySource = captureSource as? MacOSDisplay, - let content = displaySource.scContent as? SCShareableContent, - let nativeDisplay = displaySource.nativeType as? SCDisplay { - let excludedApps = content.applications.filter { app in - Bundle.main.bundleIdentifier == app.bundleIdentifier - } - filter = SCContentFilter(display: nativeDisplay, excludingApplications: excludedApps, exceptingWindows: []) - } else { - throw TrackError.capturer(message: "Unable to resolve SCContentFilter") + init(delegate: LKRTCVideoCapturerDelegate, captureSource: MacOSScreenCaptureSource, options: ScreenShareCaptureOptions) { + self.captureSource = captureSource + self.options = options + super.init(delegate: delegate) } - let configuration = SCStreamConfiguration() + override public func startCapture() async throws -> Bool { + let didStart = try await super.startCapture() - let mainDisplay = CGMainDisplayID() - // try to capture in max resolution - configuration.width = CGDisplayPixelsWide(mainDisplay) * 2 - configuration.height = CGDisplayPixelsHigh(mainDisplay) * 2 + // Already started + guard didStart else { return false } - configuration.scalesToFit = false - configuration.minimumFrameInterval = CMTime(value: 1, timescale: CMTimeScale(self.options.fps)) - configuration.queueDepth = 5 - configuration.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange - configuration.showsCursor = self.options.showCursor + guard let captureSource else { + throw TrackError.capturer(message: "captureSource is nil") + } - // Why does SCStream hold strong reference to delegate? - let stream = SCStream(filter: filter, configuration: configuration, delegate: nil) - try stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: nil) - try await stream.startCapture() + let filter: SCContentFilter + if let windowSource = captureSource as? MacOSWindow, + let nativeWindowSource = windowSource.nativeType as? SCWindow + { + filter = SCContentFilter(desktopIndependentWindow: nativeWindowSource) + } else if let displaySource = captureSource as? MacOSDisplay, + let content = displaySource.scContent as? SCShareableContent, + let nativeDisplay = displaySource.nativeType as? SCDisplay + { + let excludedApps = content.applications.filter { app in + Bundle.main.bundleIdentifier == app.bundleIdentifier + } + filter = SCContentFilter(display: nativeDisplay, excludingApplications: excludedApps, exceptingWindows: []) + } else { + throw TrackError.capturer(message: "Unable to resolve SCContentFilter") + } - self._scStream = stream + let configuration = SCStreamConfiguration() - return true - } + let mainDisplay = CGMainDisplayID() + // try to capture in max resolution + configuration.width = CGDisplayPixelsWide(mainDisplay) * 2 + configuration.height = CGDisplayPixelsHigh(mainDisplay) * 2 - public override func stopCapture() async throws -> Bool { + configuration.scalesToFit = false + configuration.minimumFrameInterval = CMTime(value: 1, timescale: CMTimeScale(options.fps)) + configuration.queueDepth = 5 + configuration.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + configuration.showsCursor = options.showCursor - let didStop = try await super.stopCapture() + // Why does SCStream hold strong reference to delegate? + let stream = SCStream(filter: filter, configuration: configuration, delegate: nil) + try stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: nil) + try await stream.startCapture() - // Already stopped - guard didStop else { return false } + _scStream = stream - guard let stream = _scStream else { - throw TrackError.capturer(message: "SCStream is nil") + return true } - // Stop resending paused frames - _resendTimer?.cancel() - _resendTimer = nil - - try await stream.stopCapture() - try stream.removeStreamOutput(self, type: .screen) - _scStream = nil - - return true - } + override public func stopCapture() async throws -> Bool { + let didStop = try await super.stopCapture() - // common capture func - private func capture(_ sampleBuffer: CMSampleBuffer, cropRect: CGRect? = nil) { + // Already stopped + guard didStop else { return false } - guard let delegate = delegate else { return } + guard let stream = _scStream else { + throw TrackError.capturer(message: "SCStream is nil") + } - // Get the pixel buffer that contains the image data. - guard let pixelBuffer = sampleBuffer.imageBuffer else { return } + // Stop resending paused frames + _resendTimer?.cancel() + _resendTimer = nil - let timeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) - let timeStampNs = Int64(CMTimeGetSeconds(timeStamp) * Double(NSEC_PER_SEC)) + try await stream.stopCapture() + try stream.removeStreamOutput(self, type: .screen) + _scStream = nil - let sourceDimensions: Dimensions - if let cropRect = cropRect { - // use dimensions from provided rect - sourceDimensions = Dimensions(width: Int32((cropRect.width * 2).rounded(.down)), - height: Int32((cropRect.height * 2).rounded(.down))) - } else { - // use pixel buffer dimensions - sourceDimensions = Dimensions(width: Int32(CVPixelBufferGetWidth(pixelBuffer)), - height: Int32(CVPixelBufferGetHeight(pixelBuffer))) + return true } - let targetDimensions = sourceDimensions - .aspectFit(size: self.options.dimensions.max) - .toEncodeSafeDimensions() - - // notify capturer for dimensions - defer { self.dimensions = targetDimensions } - - let rtcBuffer = LKRTCCVPixelBuffer(pixelBuffer: pixelBuffer, - adaptedWidth: targetDimensions.width, - adaptedHeight: targetDimensions.height, - cropWidth: sourceDimensions.width, - cropHeight: sourceDimensions.height, - cropX: Int32(cropRect?.origin.x ?? 0), - cropY: Int32(cropRect?.origin.y ?? 0)) + // common capture func + private func capture(_ sampleBuffer: CMSampleBuffer, cropRect: CGRect? = nil) { + guard let delegate else { return } + + // Get the pixel buffer that contains the image data. + guard let pixelBuffer = sampleBuffer.imageBuffer else { return } + + let timeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + let timeStampNs = Int64(CMTimeGetSeconds(timeStamp) * Double(NSEC_PER_SEC)) + + let sourceDimensions: Dimensions + if let cropRect { + // use dimensions from provided rect + sourceDimensions = Dimensions(width: Int32((cropRect.width * 2).rounded(.down)), + height: Int32((cropRect.height * 2).rounded(.down))) + } else { + // use pixel buffer dimensions + sourceDimensions = Dimensions(width: Int32(CVPixelBufferGetWidth(pixelBuffer)), + height: Int32(CVPixelBufferGetHeight(pixelBuffer))) + } - let rtcFrame = LKRTCVideoFrame(buffer: rtcBuffer, - rotation: ._0, - timeStampNs: timeStampNs) + let targetDimensions = sourceDimensions + .aspectFit(size: options.dimensions.max) + .toEncodeSafeDimensions() - // feed frame to WebRTC - delegate.capturer(capturer, didCapture: rtcFrame) + // notify capturer for dimensions + defer { self.dimensions = targetDimensions } - // cache last frame - _lastFrame = rtcFrame - } -} + let rtcBuffer = LKRTCCVPixelBuffer(pixelBuffer: pixelBuffer, + adaptedWidth: targetDimensions.width, + adaptedHeight: targetDimensions.height, + cropWidth: sourceDimensions.width, + cropHeight: sourceDimensions.height, + cropX: Int32(cropRect?.origin.x ?? 0), + cropY: Int32(cropRect?.origin.y ?? 0)) -// MARK: - Frame resend logic + let rtcFrame = LKRTCVideoFrame(buffer: rtcBuffer, + rotation: ._0, + timeStampNs: timeStampNs) -@available(macOS 12.3, *) -extension MacOSScreenCapturer { + // feed frame to WebRTC + delegate.capturer(capturer, didCapture: rtcFrame) - private func _capturePreviousFrame() async throws { - - // Must be .started - guard case .started = captureState else { - log("CaptureState is not .started, resend timer should not trigger.", .warning) - return + // cache last frame + _lastFrame = rtcFrame } - - log("No movement detected, resending frame...") - - guard let delegate = delegate, let frame = _lastFrame else { return } - - // create a new frame with new time stamp - let newFrame = LKRTCVideoFrame(buffer: frame.buffer, - rotation: frame.rotation, - timeStampNs: Self.createTimeStampNs()) - - // feed frame to WebRTC - delegate.capturer(capturer, didCapture: newFrame) } -} - -// MARK: - SCStreamOutput -@available(macOS 12.3, *) -extension MacOSScreenCapturer: SCStreamOutput { + // MARK: - Frame resend logic - public func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) { + @available(macOS 12.3, *) + extension MacOSScreenCapturer { + private func _capturePreviousFrame() async throws { + // Must be .started + guard case .started = captureState else { + log("CaptureState is not .started, resend timer should not trigger.", .warning) + return + } - guard case .started = captureState else { - log("Skipping capture since captureState is not .started") - return - } + log("No movement detected, resending frame...") - guard let attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, - createIfNecessary: false) as? [[SCStreamFrameInfo: Any]], - let attachments = attachmentsArray.first else { return } + guard let delegate, let frame = _lastFrame else { return } - // Validate the status of the frame. If it isn't `.complete`, return nil. - guard let statusRawValue = attachments[SCStreamFrameInfo.status] as? Int, - let status = SCFrameStatus(rawValue: statusRawValue) else { - return - } + // create a new frame with new time stamp + let newFrame = LKRTCVideoFrame(buffer: frame.buffer, + rotation: frame.rotation, + timeStampNs: Self.createTimeStampNs()) - /// @constant SCFrameStatusComplete new frame was generated. - /// @constant SCFrameStatusIdle new frame was not generated because the display did not change. - /// @constant SCFrameStatusBlank new frame was not generated because the display has gone blank. - /// @constant SCFrameStatusSuspended new frame was not generated because updates haves been suspended - /// @constant SCFrameStatusStarted new frame that is indicated as the first frame sent after the stream has started. - /// @constant SCFrameStatusStopped the stream was stopped. - guard status == .complete else { - return + // feed frame to WebRTC + delegate.capturer(capturer, didCapture: newFrame) } + } - // Get the pixel buffer that contains the image data. - // guard let pixelBuffer = sampleBuffer.imageBuffer else { return } - - // Get the backing IOSurface. - // guard let surfaceRef = CVPixelBufferGetIOSurface(pixelBuffer)?.takeUnretainedValue() else { return } - // let surface = unsafeBitCast(surfaceRef, to: IOSurface.self) - - // Retrieve the content rectangle, scale, and scale factor. - - // let contentScale = attachments[.contentScale] as? CGFloat, - // let scaleFactor = attachments[.scaleFactor] as? CGFloat + // MARK: - SCStreamOutput - guard let dict = attachments[.contentRect] as? NSDictionary, - let contentRect = CGRect(dictionaryRepresentation: dict) else { - return - } + @available(macOS 12.3, *) + extension MacOSScreenCapturer: SCStreamOutput { + public func stream(_: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of _: SCStreamOutputType) { + guard case .started = captureState else { + log("Skipping capture since captureState is not .started") + return + } - capture(sampleBuffer, cropRect: contentRect) + guard let attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, + createIfNecessary: false) as? [[SCStreamFrameInfo: Any]], + let attachments = attachmentsArray.first else { return } - _resendTimer?.cancel() - _resendTimer = Task.detached(priority: .utility) { [weak self] in - while true { - try? await Task.sleep(nanoseconds: UInt64(1 * 1_000_000_000)) - if Task.isCancelled { break } - guard let self else { break } - try await self._capturePreviousFrame() + // Validate the status of the frame. If it isn't `.complete`, return nil. + guard let statusRawValue = attachments[SCStreamFrameInfo.status] as? Int, + let status = SCFrameStatus(rawValue: statusRawValue) + else { + return } - } - } -} -// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate + /// @constant SCFrameStatusComplete new frame was generated. + /// @constant SCFrameStatusIdle new frame was not generated because the display did not change. + /// @constant SCFrameStatusBlank new frame was not generated because the display has gone blank. + /// @constant SCFrameStatusSuspended new frame was not generated because updates haves been suspended + /// @constant SCFrameStatusStarted new frame that is indicated as the first frame sent after the stream has started. + /// @constant SCFrameStatusStopped the stream was stopped. + guard status == .complete else { + return + } -@available(macOS 12.3, *) -extension LocalVideoTrack { + // Get the pixel buffer that contains the image data. + // guard let pixelBuffer = sampleBuffer.imageBuffer else { return } - @objc - public static func createMacOSScreenShareTrack(name: String = Track.screenShareVideoName, - source: MacOSScreenCaptureSource, - options: ScreenShareCaptureOptions = ScreenShareCaptureOptions()) -> LocalVideoTrack { - - let videoSource = Engine.createVideoSource(forScreenShare: true) - let capturer = MacOSScreenCapturer(delegate: videoSource, captureSource: source, options: options) - return LocalVideoTrack( - name: name, - source: .screenShareVideo, - capturer: capturer, - videoSource: videoSource - ) - } -} + // Get the backing IOSurface. + // guard let surfaceRef = CVPixelBufferGetIOSurface(pixelBuffer)?.takeUnretainedValue() else { return } + // let surface = unsafeBitCast(surfaceRef, to: IOSurface.self) -@objc -public enum MacOSScreenShareSourceType: Int { - case any - case display - case window -} + // Retrieve the content rectangle, scale, and scale factor. -@objc -public protocol MacOSScreenCaptureSource: AnyObject { + // let contentScale = attachments[.contentScale] as? CGFloat, + // let scaleFactor = attachments[.scaleFactor] as? CGFloat -} + guard let dict = attachments[.contentRect] as? NSDictionary, + let contentRect = CGRect(dictionaryRepresentation: dict) + else { + return + } -@objc -public class MacOSRunningApplication: NSObject { + capture(sampleBuffer, cropRect: contentRect) - public let processID: pid_t - public let bundleIdentifier: String - public let applicationName: String + _resendTimer?.cancel() + _resendTimer = Task.detached(priority: .utility) { [weak self] in + while true { + try? await Task.sleep(nanoseconds: UInt64(1 * 1_000_000_000)) + if Task.isCancelled { break } + guard let self else { break } + try await self._capturePreviousFrame() + } + } + } + } - public let nativeType: Any? + // MARK: - AVCaptureVideoDataOutputSampleBufferDelegate @available(macOS 12.3, *) - internal init?(from scRunningApplication: SCRunningApplication?) { - guard let scRunningApplication = scRunningApplication else { return nil } - self.bundleIdentifier = scRunningApplication.bundleIdentifier - self.applicationName = scRunningApplication.applicationName - self.processID = scRunningApplication.processID - self.nativeType = scRunningApplication + public extension LocalVideoTrack { + @objc + static func createMacOSScreenShareTrack(name: String = Track.screenShareVideoName, + source: MacOSScreenCaptureSource, + options: ScreenShareCaptureOptions = ScreenShareCaptureOptions()) -> LocalVideoTrack + { + let videoSource = Engine.createVideoSource(forScreenShare: true) + let capturer = MacOSScreenCapturer(delegate: videoSource, captureSource: source, options: options) + return LocalVideoTrack( + name: name, + source: .screenShareVideo, + capturer: capturer, + videoSource: videoSource + ) + } } - internal init?(from processID: pid_t?) { - - guard let processID = processID, - let app = NSRunningApplication(processIdentifier: processID) else { return nil } - - self.processID = processID - self.bundleIdentifier = app.bundleIdentifier ?? "" - self.applicationName = app.localizedName ?? "" - self.nativeType = nil + @objc + public enum MacOSScreenShareSourceType: Int { + case any + case display + case window } -} - -@objc -public class MacOSWindow: NSObject, MacOSScreenCaptureSource { - public let windowID: CGWindowID - public let frame: CGRect - public let title: String? - public let windowLayer: Int - public let owningApplication: MacOSRunningApplication? - public let isOnScreen: Bool - public let nativeType: Any? + @objc + public protocol MacOSScreenCaptureSource: AnyObject {} - @available(macOS 12.3, *) - internal init(from scWindow: SCWindow) { - self.windowID = scWindow.windowID - self.frame = scWindow.frame - self.title = scWindow.title - self.windowLayer = scWindow.windowLayer - self.owningApplication = MacOSRunningApplication(from: scWindow.owningApplication) - self.isOnScreen = scWindow.isOnScreen - self.nativeType = scWindow - } + @objc + public class MacOSRunningApplication: NSObject { + public let processID: pid_t + public let bundleIdentifier: String + public let applicationName: String + + public let nativeType: Any? + + @available(macOS 12.3, *) + init?(from scRunningApplication: SCRunningApplication?) { + guard let scRunningApplication else { return nil } + bundleIdentifier = scRunningApplication.bundleIdentifier + applicationName = scRunningApplication.applicationName + processID = scRunningApplication.processID + nativeType = scRunningApplication + } - internal init(from windowID: CGWindowID) { - self.windowID = windowID + init?(from processID: pid_t?) { + guard let processID, + let app = NSRunningApplication(processIdentifier: processID) else { return nil } - let list = CGWindowListCopyWindowInfo([.optionIncludingWindow], windowID)! as Array + self.processID = processID + bundleIdentifier = app.bundleIdentifier ?? "" + applicationName = app.localizedName ?? "" + nativeType = nil + } + } - guard let info = list.first as? NSDictionary else { - fatalError("Window information not available") + @objc + public class MacOSWindow: NSObject, MacOSScreenCaptureSource { + public let windowID: CGWindowID + public let frame: CGRect + public let title: String? + public let windowLayer: Int + public let owningApplication: MacOSRunningApplication? + public let isOnScreen: Bool + public let nativeType: Any? + + @available(macOS 12.3, *) + init(from scWindow: SCWindow) { + windowID = scWindow.windowID + frame = scWindow.frame + title = scWindow.title + windowLayer = scWindow.windowLayer + owningApplication = MacOSRunningApplication(from: scWindow.owningApplication) + isOnScreen = scWindow.isOnScreen + nativeType = scWindow } - self.frame = { + init(from windowID: CGWindowID) { + self.windowID = windowID - guard let dict = info.object(forKey: kCGWindowBounds) as? NSDictionary, - let frame = CGRect.init(dictionaryRepresentation: dict) else { - // - return CGRect() - } + let list = CGWindowListCopyWindowInfo([.optionIncludingWindow], windowID)! as Array - return frame - }() + guard let info = list.first as? NSDictionary else { + fatalError("Window information not available") + } - self.title = info.object(forKey: kCGWindowName) as? String - self.windowLayer = (info.object(forKey: kCGWindowLayer) as? NSNumber)?.intValue ?? 0 - self.owningApplication = MacOSRunningApplication(from: (info.object(forKey: kCGWindowOwnerPID) as? NSNumber)?.int32Value as? pid_t) - self.isOnScreen = (info.object(forKey: kCGWindowIsOnscreen) as? NSNumber)?.boolValue ?? false - self.nativeType = nil + frame = { + guard let dict = info.object(forKey: kCGWindowBounds) as? NSDictionary, + let frame = CGRect(dictionaryRepresentation: dict) + else { + // + return CGRect() + } + + return frame + }() + + title = info.object(forKey: kCGWindowName) as? String + windowLayer = (info.object(forKey: kCGWindowLayer) as? NSNumber)?.intValue ?? 0 + owningApplication = MacOSRunningApplication(from: (info.object(forKey: kCGWindowOwnerPID) as? NSNumber)?.int32Value as? pid_t) + isOnScreen = (info.object(forKey: kCGWindowIsOnscreen) as? NSNumber)?.boolValue ?? false + nativeType = nil + } } -} - -@objc -public class MacOSDisplay: NSObject, MacOSScreenCaptureSource { - - public let displayID: CGDirectDisplayID - public let width: Int - public let height: Int - public let frame: CGRect - - public let nativeType: Any? - public let scContent: Any? - @available(macOS 12.3, *) - internal init(from scDisplay: SCDisplay, content: SCShareableContent) { - self.displayID = scDisplay.displayID - self.width = scDisplay.width - self.height = scDisplay.height - self.frame = scDisplay.frame - self.nativeType = scDisplay - self.scContent = content - } + @objc + public class MacOSDisplay: NSObject, MacOSScreenCaptureSource { + public let displayID: CGDirectDisplayID + public let width: Int + public let height: Int + public let frame: CGRect + + public let nativeType: Any? + public let scContent: Any? + + @available(macOS 12.3, *) + init(from scDisplay: SCDisplay, content: SCShareableContent) { + displayID = scDisplay.displayID + width = scDisplay.width + height = scDisplay.height + frame = scDisplay.frame + nativeType = scDisplay + scContent = content + } - // legacy - internal init(from displayID: CGDirectDisplayID) { - self.displayID = displayID - self.width = CGDisplayPixelsWide(displayID) - self.height = CGDisplayPixelsHigh(displayID) - self.frame = CGRect(x: 0, - y: 0, - width: width, - height: height) - self.nativeType = nil - self.scContent = nil + // legacy + init(from displayID: CGDirectDisplayID) { + self.displayID = displayID + width = CGDisplayPixelsWide(displayID) + height = CGDisplayPixelsHigh(displayID) + frame = CGRect(x: 0, + y: 0, + width: width, + height: height) + nativeType = nil + scContent = nil + } } -} - -// MARK: - Filter extension -extension MacOSWindow { + // MARK: - Filter extension - /// Source is related to current running application - @objc - public var isCurrentApplication: Bool { - owningApplication?.bundleIdentifier == Bundle.main.bundleIdentifier + public extension MacOSWindow { + /// Source is related to current running application + @objc + var isCurrentApplication: Bool { + owningApplication?.bundleIdentifier == Bundle.main.bundleIdentifier + } } -} - -// MARK: - Enumerate sources -@available(macOS 12.3, *) -extension MacOSScreenCapturer { + // MARK: - Enumerate sources - internal static let queue = DispatchQueue(label: "LiveKitSDK.MacOSScreenCapturer.sources", qos: .default) + @available(macOS 12.3, *) + public extension MacOSScreenCapturer { + internal static let queue = DispatchQueue(label: "LiveKitSDK.MacOSScreenCapturer.sources", qos: .default) - /// Convenience method to get a ``MacOSDisplay`` of the main display. - @objc - public static func mainDisplaySource() async throws -> MacOSDisplay { + /// Convenience method to get a ``MacOSDisplay`` of the main display. + @objc + static func mainDisplaySource() async throws -> MacOSDisplay { + let displaySources = try await sources(for: .display) - let displaySources = try await sources(for: .display) + guard let source = displaySources.compactMap({ $0 as? MacOSDisplay }).first(where: { $0.displayID == CGMainDisplayID() }) else { + throw TrackError.capturer(message: "Main display source not found") + } - guard let source = displaySources.compactMap({ $0 as? MacOSDisplay }).first(where: { $0.displayID == CGMainDisplayID() }) else { - throw TrackError.capturer(message: "Main display source not found") + return source } - return source - } - - /// Enumerate ``MacOSDisplay`` or ``MacOSWindow`` sources. - @objc - public static func sources(for type: MacOSScreenShareSourceType, includeCurrentApplication: Bool = false) async throws -> [MacOSScreenCaptureSource] { - let content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true) - let displays = content.displays.map { MacOSDisplay(from: $0, content: content) } - let windows = content.windows - // remove windows from this app - .filter { includeCurrentApplication || $0.owningApplication?.bundleIdentifier != Bundle.main.bundleIdentifier } - // remove windows that don't have an associated bundleIdentifier - .filter { $0.owningApplication?.bundleIdentifier != nil } - // remove windows that windowLayer isn't 0 - .filter { $0.windowLayer == 0 } - // remove windows that are unusually small - .filter { $0.frame.size.width >= 100 && $0.frame.size.height >= 100 } - // sort the windows by app name - .sorted { $0.owningApplication?.applicationName ?? "" < $1.owningApplication?.applicationName ?? "" } - .map { MacOSWindow(from: $0) } - - switch type { - case .any: return displays + windows - case .display: return displays - case .window: return windows + /// Enumerate ``MacOSDisplay`` or ``MacOSWindow`` sources. + @objc + static func sources(for type: MacOSScreenShareSourceType, includeCurrentApplication: Bool = false) async throws -> [MacOSScreenCaptureSource] { + let content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true) + let displays = content.displays.map { MacOSDisplay(from: $0, content: content) } + let windows = content.windows + // remove windows from this app + .filter { includeCurrentApplication || $0.owningApplication?.bundleIdentifier != Bundle.main.bundleIdentifier } + // remove windows that don't have an associated bundleIdentifier + .filter { $0.owningApplication?.bundleIdentifier != nil } + // remove windows that windowLayer isn't 0 + .filter { $0.windowLayer == 0 } + // remove windows that are unusually small + .filter { $0.frame.size.width >= 100 && $0.frame.size.height >= 100 } + // sort the windows by app name + .sorted { $0.owningApplication?.applicationName ?? "" < $1.owningApplication?.applicationName ?? "" } + .map { MacOSWindow(from: $0) } + + switch type { + case .any: return displays + windows + case .display: return displays + case .window: return windows + } } - } - @objc - public static func displaySources() async throws -> [MacOSDisplay] { - let result = try await sources(for: .display) - // Cast - return result.compactMap({ $0 as? MacOSDisplay }) - } + @objc + static func displaySources() async throws -> [MacOSDisplay] { + let result = try await sources(for: .display) + // Cast + return result.compactMap { $0 as? MacOSDisplay } + } - @objc - public static func windowSources() async throws -> [MacOSWindow] { - let result = try await sources(for: .window) - // Cast - return result.compactMap({ $0 as? MacOSWindow }) + @objc + static func windowSources() async throws -> [MacOSWindow] { + let result = try await sources(for: .window) + // Cast + return result.compactMap { $0 as? MacOSWindow } + } } -} #endif diff --git a/Sources/LiveKit/Track/Capturers/VideoCapturer+MulticastDelegate.swift b/Sources/LiveKit/Track/Capturers/VideoCapturer+MulticastDelegate.swift index 938955942..59780a6f0 100644 --- a/Sources/LiveKit/Track/Capturers/VideoCapturer+MulticastDelegate.swift +++ b/Sources/LiveKit/Track/Capturers/VideoCapturer+MulticastDelegate.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import Foundation extension VideoCapturer: MulticastDelegateProtocol { - @objc(addDelegate:) public func add(delegate: VideoCapturerDelegate) { delegates.add(delegate: delegate) diff --git a/Sources/LiveKit/Track/Capturers/VideoCapturer.swift b/Sources/LiveKit/Track/Capturers/VideoCapturer.swift index 18cfbdd0d..8161199d9 100644 --- a/Sources/LiveKit/Track/Capturers/VideoCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/VideoCapturer.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,11 @@ import Foundation @_implementationOnly import WebRTC -internal protocol VideoCapturerProtocol { +protocol VideoCapturerProtocol { var capturer: LKRTCVideoCapturer { get } } extension VideoCapturerProtocol { - public var capturer: LKRTCVideoCapturer { fatalError("Must be implemented") } @@ -31,7 +30,6 @@ extension VideoCapturerProtocol { @objc public protocol VideoCapturerDelegate: AnyObject { - @objc(capturer:didUpdateDimensions:) optional func capturer(_ capturer: VideoCapturer, didUpdate dimensions: Dimensions?) @@ -41,12 +39,11 @@ public protocol VideoCapturerDelegate: AnyObject { // Intended to be a base class for video capturers public class VideoCapturer: NSObject, Loggable, VideoCapturerProtocol { - // MARK: - MulticastDelegate - internal var delegates = MulticastDelegate() + var delegates = MulticastDelegate() - internal let queue = DispatchQueue(label: "LiveKitSDK.videoCapturer", qos: .default) + let queue = DispatchQueue(label: "LiveKitSDK.videoCapturer", qos: .default) /// Array of supported pixel formats that can be used to capture a frame. /// @@ -68,16 +65,16 @@ public class VideoCapturer: NSObject, Loggable, VideoCapturerProtocol { case started } - internal weak var delegate: LKRTCVideoCapturerDelegate? + weak var delegate: LKRTCVideoCapturerDelegate? - internal let dimensionsCompleter = AsyncCompleter(label: "Dimensions", timeOut: .defaultCaptureStart) + let dimensionsCompleter = AsyncCompleter(label: "Dimensions", timeOut: .defaultCaptureStart) - internal struct State: Equatable { + struct State: Equatable { // Counts calls to start/stopCapturer so multiple Tracks can use the same VideoCapturer. var startStopCounter: Int = 0 } - internal var _state = StateSync(State()) + var _state = StateSync(State()) public internal(set) var dimensions: Dimensions? { didSet { @@ -99,7 +96,7 @@ public class VideoCapturer: NSObject, Loggable, VideoCapturerProtocol { super.init() _state.onDidMutate = { [weak self] newState, oldState in - guard let self = self else { return } + guard let self else { return } if oldState.startStopCounter != newState.startStopCounter { self.log("startStopCounter \(oldState.startStopCounter) -> \(newState.startStopCounter)") } @@ -117,8 +114,7 @@ public class VideoCapturer: NSObject, Loggable, VideoCapturerProtocol { @objc @discardableResult public func startCapture() async throws -> Bool { - - let didStart = self._state.mutate { + let didStart = _state.mutate { // Counter was 0, so did start capturing with this call let didStart = $0.startStopCounter == 0 $0.startStopCounter += 1 @@ -130,7 +126,7 @@ public class VideoCapturer: NSObject, Loggable, VideoCapturerProtocol { return false } - self.delegates.notify(label: { "capturer.didUpdate state: \(CapturerState.started)" }) { + delegates.notify(label: { "capturer.didUpdate state: \(CapturerState.started)" }) { $0.capturer?(self, didUpdate: .started) } @@ -144,8 +140,7 @@ public class VideoCapturer: NSObject, Loggable, VideoCapturerProtocol { @objc @discardableResult public func stopCapture() async throws -> Bool { - - let didStop = self._state.mutate { + let didStop = _state.mutate { // Counter was already 0, so did NOT stop capturing with this call if $0.startStopCounter <= 0 { return false @@ -159,7 +154,7 @@ public class VideoCapturer: NSObject, Loggable, VideoCapturerProtocol { return false } - self.delegates.notify(label: { "capturer.didUpdate state: \(CapturerState.stopped)" }) { + delegates.notify(label: { "capturer.didUpdate state: \(CapturerState.stopped)" }) { $0.capturer?(self, didUpdate: .stopped) } diff --git a/Sources/LiveKit/Track/Local/LocalAudioTrack.swift b/Sources/LiveKit/Track/Local/LocalAudioTrack.swift index bef902487..394a2315b 100644 --- a/Sources/LiveKit/Track/Local/LocalAudioTrack.swift +++ b/Sources/LiveKit/Track/Local/LocalAudioTrack.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,10 @@ import Foundation @objc public class LocalAudioTrack: Track, LocalTrack, AudioTrack { - - internal init(name: String, - source: Track.Source, - track: LKRTCMediaStreamTrack) { - + init(name: String, + source: Track.Source, + track: LKRTCMediaStreamTrack) + { super.init(name: name, kind: .audio, source: source, @@ -32,8 +31,8 @@ public class LocalAudioTrack: Track, LocalTrack, AudioTrack { } public static func createTrack(name: String = Track.microphoneName, - options: AudioCaptureOptions? = nil) -> LocalAudioTrack { - + options: AudioCaptureOptions? = nil) -> LocalAudioTrack + { let options = options ?? AudioCaptureOptions() let constraints: [String: String] = [ @@ -43,7 +42,7 @@ public class LocalAudioTrack: Track, LocalTrack, AudioTrack { "googTypingNoiseDetection": options.typingNoiseDetection.toString(), "googHighpassFilter": options.highpassFilter.toString(), "googNoiseSuppression2": options.experimentalNoiseSuppression.toString(), - "googAutoGainControl2": options.experimentalAutoGainControl.toString() + "googAutoGainControl2": options.experimentalAutoGainControl.toString(), ] let audioConstraints = DispatchQueue.liveKitWebRTC.sync { LKRTCMediaConstraints(mandatoryConstraints: nil, @@ -59,7 +58,7 @@ public class LocalAudioTrack: Track, LocalTrack, AudioTrack { } @discardableResult - internal override func onPublish() async throws -> Bool { + override func onPublish() async throws -> Bool { let didPublish = try await super.onPublish() if didPublish { AudioManager.shared.trackDidStart(.local) @@ -68,7 +67,7 @@ public class LocalAudioTrack: Track, LocalTrack, AudioTrack { } @discardableResult - internal override func onUnpublish() async throws -> Bool { + override func onUnpublish() async throws -> Bool { let didUnpublish = try await super.onUnpublish() if didUnpublish { AudioManager.shared.trackDidStop(.local) @@ -85,9 +84,8 @@ public class LocalAudioTrack: Track, LocalTrack, AudioTrack { } } -extension LocalAudioTrack { - - public var publishOptions: PublishOptions? { super._publishOptions } +public extension LocalAudioTrack { + var publishOptions: PublishOptions? { super._publishOptions } - public var publishState: Track.PublishState { super._publishState } + var publishState: Track.PublishState { super._publishState } } diff --git a/Sources/LiveKit/Track/Local/LocalTrack.swift b/Sources/LiveKit/Track/Local/LocalTrack.swift index c3184eb70..afd374d79 100644 --- a/Sources/LiveKit/Track/Local/LocalTrack.swift +++ b/Sources/LiveKit/Track/Local/LocalTrack.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import Foundation @objc public protocol LocalTrack where Self: Track { - @objc var publishOptions: PublishOptions? { get } diff --git a/Sources/LiveKit/Track/Local/LocalVideoTrack.swift b/Sources/LiveKit/Track/Local/LocalVideoTrack.swift index 7bb484986..ee4e17ff0 100644 --- a/Sources/LiveKit/Track/Local/LocalVideoTrack.swift +++ b/Sources/LiveKit/Track/Local/LocalVideoTrack.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,17 +20,16 @@ import Foundation @objc public class LocalVideoTrack: Track, LocalTrack, VideoTrack { - @objc public internal(set) var capturer: VideoCapturer - internal var videoSource: LKRTCVideoSource - - internal init(name: String, - source: Track.Source, - capturer: VideoCapturer, - videoSource: LKRTCVideoSource) { + var videoSource: LKRTCVideoSource + init(name: String, + source: Track.Source, + capturer: VideoCapturer, + videoSource: LKRTCVideoSource) + { let rtcTrack = Engine.createVideoTrack(source: videoSource) rtcTrack.isEnabled = true @@ -67,7 +66,6 @@ public class LocalVideoTrack: Track, LocalTrack, VideoTrack { } public extension LocalVideoTrack { - func add(videoRenderer: VideoRenderer) { super._add(videoRenderer: videoRenderer) } @@ -77,17 +75,15 @@ public extension LocalVideoTrack { } } -extension LocalVideoTrack { - - public var publishOptions: PublishOptions? { super._publishOptions } +public extension LocalVideoTrack { + var publishOptions: PublishOptions? { super._publishOptions } - public var publishState: Track.PublishState { super._publishState } + var publishState: Track.PublishState { super._publishState } } -extension LocalVideoTrack { - +public extension LocalVideoTrack { /// Clone with same ``VideoCapturer``. - public func clone() -> LocalVideoTrack { + func clone() -> LocalVideoTrack { LocalVideoTrack(name: name, source: source, capturer: capturer, diff --git a/Sources/LiveKit/Track/Remote/RemoteAudioTrack.swift b/Sources/LiveKit/Track/Remote/RemoteAudioTrack.swift index 2081eb097..b248840f5 100644 --- a/Sources/LiveKit/Track/Remote/RemoteAudioTrack.swift +++ b/Sources/LiveKit/Track/Remote/RemoteAudioTrack.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import Foundation @objc public class RemoteAudioTrack: Track, RemoteTrack, AudioTrack { - /// Volume with range 0.0 - 1.0 public var volume: Double { get { @@ -35,8 +34,8 @@ public class RemoteAudioTrack: Track, RemoteTrack, AudioTrack { init(name: String, source: Track.Source, - track: LKRTCMediaStreamTrack) { - + track: LKRTCMediaStreamTrack) + { super.init(name: name, kind: .audio, source: source, @@ -56,7 +55,7 @@ public class RemoteAudioTrack: Track, RemoteTrack, AudioTrack { } public func add(audioRenderer: AudioRenderer) { - guard let audioTrack = mediaTrack as? LKRTCAudioTrack else { return } + guard let audioTrack = mediaTrack as? LKRTCAudioTrack else { return } audioTrack.add(AudioRendererAdapter(target: audioRenderer)) } diff --git a/Sources/LiveKit/Track/Remote/RemoteTrack.swift b/Sources/LiveKit/Track/Remote/RemoteTrack.swift index c913ea79e..36d3aff76 100644 --- a/Sources/LiveKit/Track/Remote/RemoteTrack.swift +++ b/Sources/LiveKit/Track/Remote/RemoteTrack.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,4 @@ import Foundation @objc -public protocol RemoteTrack where Self: Track { - -} +public protocol RemoteTrack where Self: Track {} diff --git a/Sources/LiveKit/Track/Remote/RemoteVideoTrack.swift b/Sources/LiveKit/Track/Remote/RemoteVideoTrack.swift index c5bee80ec..8a834eb9c 100644 --- a/Sources/LiveKit/Track/Remote/RemoteVideoTrack.swift +++ b/Sources/LiveKit/Track/Remote/RemoteVideoTrack.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,10 @@ import Foundation @objc public class RemoteVideoTrack: Track, RemoteTrack, VideoTrack { - init(name: String, source: Track.Source, - track: LKRTCMediaStreamTrack) { - + track: LKRTCMediaStreamTrack) + { super.init(name: name, kind: .video, source: source, @@ -33,7 +32,6 @@ public class RemoteVideoTrack: Track, RemoteTrack, VideoTrack { } public extension RemoteVideoTrack { - func add(videoRenderer: VideoRenderer) { super._add(videoRenderer: videoRenderer) } diff --git a/Sources/LiveKit/Track/Support/Extensions.swift b/Sources/LiveKit/Track/Support/Extensions.swift index d3d7c9af2..ddf3762c9 100644 --- a/Sources/LiveKit/Track/Support/Extensions.swift +++ b/Sources/LiveKit/Track/Support/Extensions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,17 @@ * limitations under the License. */ -import Foundation import CoreImage +import Foundation #if canImport(ReplayKit) -import ReplayKit + import ReplayKit #endif -extension CIImage { - +public extension CIImage { /// Convenience method to convert ``CIImage`` to ``CVPixelBuffer`` /// since ``CIImage/pixelBuffer`` is not always available. - public func toPixelBuffer() -> CVPixelBuffer? { + func toPixelBuffer() -> CVPixelBuffer? { var pixelBuffer: CVPixelBuffer? // get current size @@ -35,7 +34,7 @@ extension CIImage { let options = [ kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true, - kCVPixelBufferIOSurfacePropertiesKey as String: [:] as [String: Any] + kCVPixelBufferIOSurfacePropertiesKey as String: [:] as [String: Any], ] as [String: Any] let status: CVReturn = CVPixelBufferCreate(kCFAllocatorDefault, @@ -47,7 +46,7 @@ extension CIImage { let ciContext = CIContext() - if let pixelBuffer = pixelBuffer, status == kCVReturnSuccess { + if let pixelBuffer, status == kCVReturnSuccess { ciContext.render(self, to: pixelBuffer) } @@ -55,13 +54,12 @@ extension CIImage { } } -extension CGImage { - +public extension CGImage { /// Convenience method to convert ``CGImage`` to ``CVPixelBuffer`` - public func toPixelBuffer(pixelFormatType: OSType = kCVPixelFormatType_32ARGB, - colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB(), - alphaInfo: CGImageAlphaInfo = .noneSkipFirst) -> CVPixelBuffer? { - + func toPixelBuffer(pixelFormatType: OSType = kCVPixelFormatType_32ARGB, + colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB(), + alphaInfo: CGImageAlphaInfo = .noneSkipFirst) -> CVPixelBuffer? + { var maybePixelBuffer: CVPixelBuffer? let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] @@ -99,22 +97,22 @@ extension CGImage { } #if os(iOS) -@available(iOS 12, *) -extension RPSystemBroadcastPickerView { - - /// Convenience function to show broadcast extension picker - public static func show(for preferredExtension: String? = nil, - showsMicrophoneButton: Bool = true) { - // Must be called on main thread - assert(Thread.current.isMainThread, "must be called on main thread") - - let view = RPSystemBroadcastPickerView() - view.preferredExtension = preferredExtension - view.showsMicrophoneButton = showsMicrophoneButton - let selector = NSSelectorFromString("buttonPressed:") - if view.responds(to: selector) { - view.perform(selector, with: nil) + @available(iOS 12, *) + public extension RPSystemBroadcastPickerView { + /// Convenience function to show broadcast extension picker + static func show(for preferredExtension: String? = nil, + showsMicrophoneButton: Bool = true) + { + // Must be called on main thread + assert(Thread.current.isMainThread, "must be called on main thread") + + let view = RPSystemBroadcastPickerView() + view.preferredExtension = preferredExtension + view.showsMicrophoneButton = showsMicrophoneButton + let selector = NSSelectorFromString("buttonPressed:") + if view.responds(to: selector) { + view.perform(selector, with: nil) + } } } -} #endif diff --git a/Sources/LiveKit/Track/Track+Equatable.swift b/Sources/LiveKit/Track/Track+Equatable.swift index ed2b8b8d9..4e82f5ff8 100644 --- a/Sources/LiveKit/Track/Track+Equatable.swift +++ b/Sources/LiveKit/Track/Track+Equatable.swift @@ -19,10 +19,9 @@ import Foundation // MARK: - Equatable for NSObject public extension Track { - override func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.mediaTrack.trackId == other.mediaTrack.trackId + return mediaTrack.trackId == other.mediaTrack.trackId } override var hash: Int { diff --git a/Sources/LiveKit/Track/Track+MulticastDelegate.swift b/Sources/LiveKit/Track/Track+MulticastDelegate.swift index 1a0fab03e..394e7d037 100644 --- a/Sources/LiveKit/Track/Track+MulticastDelegate.swift +++ b/Sources/LiveKit/Track/Track+MulticastDelegate.swift @@ -17,7 +17,6 @@ import Foundation extension Track: MulticastDelegateProtocol { - @objc(addDelegate:) public func add(delegate: TrackDelegate) { delegates.add(delegate: delegate) diff --git a/Sources/LiveKit/Track/Track.swift b/Sources/LiveKit/Track/Track.swift index c5eca5e40..31942d5ca 100644 --- a/Sources/LiveKit/Track/Track.swift +++ b/Sources/LiveKit/Track/Track.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import Foundation @objc public class Track: NSObject, Loggable { - // MARK: - Static constants @objc @@ -97,27 +96,27 @@ public class Track: NSObject, Loggable { // MARK: - Internal - internal var delegates = MulticastDelegate() + var delegates = MulticastDelegate() - internal let queue = DispatchQueue(label: "LiveKitSDK.track", qos: .default) + let queue = DispatchQueue(label: "LiveKitSDK.track", qos: .default) /// Only for ``LocalTrack``s. - internal private(set) var _publishState: PublishState = .unpublished + private(set) var _publishState: PublishState = .unpublished /// ``publishOptions`` used for this track if already published. /// Only for ``LocalTrack``s. - internal var _publishOptions: PublishOptions? + var _publishOptions: PublishOptions? - internal let mediaTrack: LKRTCMediaStreamTrack + let mediaTrack: LKRTCMediaStreamTrack - internal private(set) var rtpSender: LKRTCRtpSender? - internal private(set) var rtpReceiver: LKRTCRtpReceiver? + private(set) var rtpSender: LKRTCRtpSender? + private(set) var rtpReceiver: LKRTCRtpReceiver? // Weak reference to all VideoViews attached to this track. Must be accessed from main thread. - internal var videoRenderers = NSHashTable.weakObjects() + var videoRenderers = NSHashTable.weakObjects() // internal var rtcVideoRenderers = NSHashTable.weakObjects() - internal struct State: Equatable { + struct State: Equatable { let name: String let kind: Kind let source: Source @@ -131,18 +130,18 @@ public class Track: NSObject, Loggable { var reportStatistics: Bool = false } - internal var _state: StateSync + var _state: StateSync // MARK: - Private private weak var transport: Transport? private let statisticsTimer = DispatchQueueTimer(timeInterval: 1, queue: .liveKitWebRTC) - internal init(name: String, - kind: Kind, - source: Source, - track: LKRTCMediaStreamTrack) { - + init(name: String, + kind: Kind, + source: Source, + track: LKRTCMediaStreamTrack) + { _state = StateSync(State( name: name, kind: kind, @@ -156,7 +155,7 @@ public class Track: NSObject, Loggable { // trigger events when state mutates _state.onDidMutate = { [weak self] newState, oldState in - guard let self = self else { return } + guard let self else { return } if oldState.dimensions != newState.dimensions { log("Track.dimensions \(String(describing: oldState.dimensions)) -> \(String(describing: newState.dimensions))") @@ -183,19 +182,19 @@ public class Track: NSObject, Loggable { log("sid: \(String(describing: sid))") } - internal func set(transport: Transport, rtpSender: LKRTCRtpSender) { + func set(transport: Transport, rtpSender: LKRTCRtpSender) { self.transport = transport self.rtpSender = rtpSender resumeOrSuspendStatisticsTimer() } - internal func set(transport: Transport, rtpReceiver: LKRTCRtpReceiver) { + func set(transport: Transport, rtpReceiver: LKRTCRtpReceiver) { self.transport = transport self.rtpReceiver = rtpReceiver resumeOrSuspendStatisticsTimer() } - internal func resumeOrSuspendStatisticsTimer() { + func resumeOrSuspendStatisticsTimer() { if _state.reportStatistics, rtpSender != nil || rtpReceiver != nil { statisticsTimer.resume() } else { @@ -231,7 +230,7 @@ public class Track: NSObject, Loggable { // Returns true if didEnable @discardableResult - internal func enable() async throws -> Bool { + func enable() async throws -> Bool { guard !mediaTrack.isEnabled else { return false } mediaTrack.isEnabled = true return true @@ -239,16 +238,16 @@ public class Track: NSObject, Loggable { // Returns true if didDisable @discardableResult - internal func disable() async throws -> Bool { + func disable() async throws -> Bool { guard mediaTrack.isEnabled else { return false } mediaTrack.isEnabled = false return true } - internal func set(muted newValue: Bool, - notify _notify: Bool = true, - shouldSendSignal: Bool = false) { - + func set(muted newValue: Bool, + notify _notify: Bool = true, + shouldSendSignal: Bool = false) + { guard _state.muted != newValue else { return } _state.mutate { $0.muted = newValue } @@ -268,33 +267,31 @@ public class Track: NSObject, Loggable { // Returns true if state updated @discardableResult - internal func onPublish() async throws -> Bool { + func onPublish() async throws -> Bool { // For LocalTrack only... guard self is LocalTrack else { return false } - guard self._publishState != .published else { return false } - self._publishState = .published + guard _publishState != .published else { return false } + _publishState = .published return true } // Returns true if state updated @discardableResult - internal func onUnpublish() async throws -> Bool { + func onUnpublish() async throws -> Bool { // For LocalTrack only... guard self is LocalTrack else { return false } - guard self._publishState != .unpublished else { return false } - self._publishState = .unpublished + guard _publishState != .unpublished else { return false } + _publishState = .unpublished return true } } // MARK: - Internal -internal extension Track { - +extension Track { // returns true when value is updated @discardableResult func set(dimensions newValue: Dimensions?) -> Bool { - guard _state.dimensions != newValue else { return false } _state.mutate { $0.dimensions = newValue } @@ -316,11 +313,10 @@ internal extension Track { // MARK: - Local extension Track { - // workaround for error: // @objc can only be used with members of classes, @objc protocols, and concrete extensions of classes // - internal func _mute() async throws { + func _mute() async throws { // LocalTrack only, already muted guard self is LocalTrack, !muted else { return } try await disable() @@ -328,7 +324,7 @@ extension Track { set(muted: true, shouldSendSignal: true) } - internal func _unmute() async throws { + func _unmute() async throws { // LocalTrack only, already un-muted guard self is LocalTrack, muted else { return } try await enable() @@ -343,10 +339,8 @@ extension Track { // @objc can only be used with members of classes, @objc protocols, and concrete extensions of classes // extension Track { - - internal func _add(videoRenderer: VideoRenderer) { - - guard self is VideoTrack, let rtcVideoTrack = self.mediaTrack as? LKRTCVideoTrack else { + func _add(videoRenderer: VideoRenderer) { + guard self is VideoTrack, let rtcVideoTrack = mediaTrack as? LKRTCVideoTrack else { log("mediaTrack is not a RTCVideoTrack", .error) return } @@ -358,9 +352,8 @@ extension Track { rtcVideoTrack.add(VideoRendererAdapter(target: videoRenderer)) } - internal func _remove(videoRenderer: VideoRenderer) { - - guard self is VideoTrack, let rtcVideoTrack = self.mediaTrack as? LKRTCVideoTrack else { + func _remove(videoRenderer: VideoRenderer) { + guard self is VideoTrack, let rtcVideoTrack = mediaTrack as? LKRTCVideoTrack else { log("mediaTrack is not a RTCVideoTrack", .error) return } @@ -376,7 +369,6 @@ extension Track { // MARK: - Identifiable (SwiftUI) extension Track: Identifiable { - public var id: String { "\(type(of: self))-\(sid ?? String(hash))" } @@ -385,45 +377,40 @@ extension Track: Identifiable { // MARK: - Stats public extension OutboundRtpStreamStatistics { - func formattedBps() -> String { format(bps: bps) } var bps: UInt64 { - guard let previous = previous, + guard let previous, let currentBytesSent = bytesSent, let previousBytesSent = previous.bytesSent else { return 0 } let secondsDiff = (timestamp - previous.timestamp) / (1000 * 1000) - return UInt64(Double(((currentBytesSent - previousBytesSent) * 8)) / abs(secondsDiff)) + return UInt64(Double((currentBytesSent - previousBytesSent) * 8) / abs(secondsDiff)) } } public extension InboundRtpStreamStatistics { - func formattedBps() -> String { format(bps: bps) } var bps: UInt64 { - guard let previous = previous, + guard let previous, let currentBytesReceived = bytesReceived, let previousBytesReceived = previous.bytesReceived else { return 0 } let secondsDiff = (timestamp - previous.timestamp) / (1000 * 1000) - return UInt64(Double(((currentBytesReceived - previousBytesReceived) * 8)) / abs(secondsDiff)) + return UInt64(Double((currentBytesReceived - previousBytesReceived) * 8) / abs(secondsDiff)) } } extension Track { - func onStatsTimer() { - - guard let transport = transport else { return } + guard let transport else { return } statisticsTimer.suspend() Task { - defer { statisticsTimer.resume() } var statisticsReport: LKRTCStatisticsReport? @@ -436,7 +423,7 @@ extension Track { } assert(statisticsReport != nil, "statisticsReport is nil") - guard let statisticsReport = statisticsReport else { return } + guard let statisticsReport else { return } let trackStatistics = TrackStatistics(from: Array(statisticsReport.statistics.values), prevStatistics: prevStatistics) diff --git a/Sources/LiveKit/Track/VideoCaptureInterceptor.swift b/Sources/LiveKit/Track/VideoCaptureInterceptor.swift index 33ea2894b..b4e50c49e 100644 --- a/Sources/LiveKit/Track/VideoCaptureInterceptor.swift +++ b/Sources/LiveKit/Track/VideoCaptureInterceptor.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,10 @@ import Foundation @_implementationOnly import WebRTC public class VideoCaptureInterceptor: NSObject, Loggable { - public typealias CaptureFunc = (_ capture: VideoFrame) -> Void public typealias InterceptFunc = (_ frame: VideoFrame, _ capture: @escaping CaptureFunc) -> Void private class DelegateAdapter: NSObject, LKRTCVideoCapturerDelegate { - weak var target: VideoCaptureInterceptor? init(target: VideoCaptureInterceptor? = nil) { @@ -39,7 +37,7 @@ public class VideoCaptureInterceptor: NSObject, Loggable { let output = Engine.createVideoSource(forScreenShare: true) let interceptFunc: InterceptFunc - private lazy var delegateAdapter: DelegateAdapter = { DelegateAdapter(target: self) }() + private lazy var delegateAdapter: DelegateAdapter = .init(target: self) public init(_ interceptFunc: @escaping InterceptFunc) { self.interceptFunc = interceptFunc @@ -53,11 +51,12 @@ public class VideoCaptureInterceptor: NSObject, Loggable { // MARK: - Internal - internal func capturer(_ capturer: LKRTCVideoCapturer, didCapture frame: LKRTCVideoFrame) { + func capturer(_ capturer: LKRTCVideoCapturer, didCapture frame: LKRTCVideoFrame) { // create capture func to pass to intercept func - let captureFunc = { [weak self, weak capturer] (frame: VideoFrame) -> Void in - guard let self = self, - let capturer = capturer else { + let captureFunc = { [weak self, weak capturer] (frame: VideoFrame) in + guard let self, + let capturer + else { return } diff --git a/Sources/LiveKit/Track/VideoTrack.swift b/Sources/LiveKit/Track/VideoTrack.swift index 63a1d5f6c..6e44012a7 100644 --- a/Sources/LiveKit/Track/VideoTrack.swift +++ b/Sources/LiveKit/Track/VideoTrack.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import Foundation @objc public protocol VideoTrack where Self: Track { - @objc(addVideoRenderer:) func add(videoRenderer: VideoRenderer) @@ -29,8 +28,7 @@ public protocol VideoTrack where Self: Track { } // Directly add/remove renderers for better performance -internal protocol VideoTrack_Internal where Self: Track { - +protocol VideoTrack_Internal where Self: Track { func add(rtcVideoRenderer: LKRTCVideoRenderer) func remove(rtcVideoRenderer: LKRTCVideoRenderer) diff --git a/Sources/LiveKit/TrackPublications/LocalTrackPublication.swift b/Sources/LiveKit/TrackPublications/LocalTrackPublication.swift index 7d6666c35..4f2efc371 100644 --- a/Sources/LiveKit/TrackPublications/LocalTrackPublication.swift +++ b/Sources/LiveKit/TrackPublications/LocalTrackPublication.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,18 +18,16 @@ import Foundation @objc public class LocalTrackPublication: TrackPublication { - // indicates whether the track was suspended(muted) by the SDK - internal var _suspended: Bool = false + var _suspended: Bool = false // keep reference to cancel later private weak var debounceWorkItem: DispatchWorkItem? // stream state is always active for local tracks - public override var streamState: StreamState { .active } + override public var streamState: StreamState { .active } public func mute() async throws { - guard let track = track as? LocalTrack else { throw InternalError.state(message: "track is nil or not a LocalTrack") } @@ -38,7 +36,6 @@ public class LocalTrackPublication: TrackPublication { } public func unmute() async throws { - guard let track = track as? LocalTrack else { throw InternalError.state(message: "track is nil or not a LocalTrack") } @@ -46,7 +43,7 @@ public class LocalTrackPublication: TrackPublication { try await track._unmute() } - internal override func set(track newValue: Track?) -> Track? { + override func set(track newValue: Track?) -> Track? { let oldValue = super.set(track: newValue) // listen for VideoCapturerDelegate @@ -76,8 +73,7 @@ public class LocalTrackPublication: TrackPublication { }) } -internal extension LocalTrackPublication { - +extension LocalTrackPublication { func suspend() async throws { // Do nothing if already muted guard !muted else { return } @@ -94,16 +90,13 @@ internal extension LocalTrackPublication { } extension LocalTrackPublication: VideoCapturerDelegate { - - public func capturer(_ capturer: VideoCapturer, didUpdate dimensions: Dimensions?) { + public func capturer(_: VideoCapturer, didUpdate _: Dimensions?) { shouldRecomputeSenderParameters() } } extension LocalTrackPublication { - - internal func recomputeSenderParameters() { - + func recomputeSenderParameters() { guard let track = track as? LocalVideoTrack, let sender = track.rtpSender else { return } @@ -117,7 +110,7 @@ extension LocalTrackPublication { // get current parameters let parameters = sender.parameters - guard let participant = participant else { return } + guard let participant else { return } let publishOptions = (track.publishOptions as? VideoPublishOptions) ?? participant.room._state.options.defaultVideoPublishOptions // re-compute encodings @@ -152,7 +145,7 @@ extension LocalTrackPublication { let layers = dimensions.videoLayers(for: encodings) - self.log("Using encodings layers: \(layers.map { String(describing: $0) }.joined(separator: ", "))") + log("Using encodings layers: \(layers.map { String(describing: $0) }.joined(separator: ", "))") Task { let participant = try await requireParticipant() diff --git a/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift b/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift index 8ac8a9df6..9f98af64e 100644 --- a/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift +++ b/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ * limitations under the License. */ -import Foundation import CoreGraphics +import Foundation @_implementationOnly import WebRTC @@ -28,7 +28,6 @@ public enum SubscriptionState: Int, Codable { @objc public class RemoteTrackPublication: TrackPublication { - public var subscriptionAllowed: Bool { _state.subscriptionAllowed } public var enabled: Bool { _state.trackSettings.enabled } override public var muted: Bool { track?.muted ?? _state.metadataMuted } @@ -39,10 +38,10 @@ public class RemoteTrackPublication: TrackPublication { // this must be on .main queue private var asTimer = DispatchQueueTimer(timeInterval: 0.3, queue: .main) - internal override init(info: Livekit_TrackInfo, - track: Track? = nil, - participant: Participant) { - + override init(info: Livekit_TrackInfo, + track: Track? = nil, + participant: Participant) + { super.init(info: info, track: track, participant: participant) @@ -60,19 +59,18 @@ public class RemoteTrackPublication: TrackPublication { set(metadataMuted: info.muted) } - public override var subscribed: Bool { + override public var subscribed: Bool { if !subscriptionAllowed { return false } return _state.preferSubscribed != false && super.subscribed } public var subscriptionState: SubscriptionState { if !subscriptionAllowed { return .notAllowed } - return self.subscribed ? .subscribed : .unsubscribed + return subscribed ? .subscribed : .unsubscribed } /// Subscribe or unsubscribe from this track. public func set(subscribed newValue: Bool) async throws { - guard _state.preferSubscribed != newValue else { return } let participant = try await requireParticipant() @@ -113,8 +111,7 @@ public class RemoteTrackPublication: TrackPublication { } @discardableResult - internal override func set(track newValue: Track?) -> Track? { - + override func set(track newValue: Track?) -> Track? { log("RemoteTrackPublication set track: \(String(describing: newValue))") let oldValue = super.set(track: newValue) @@ -122,8 +119,7 @@ public class RemoteTrackPublication: TrackPublication { // always suspend adaptiveStream timer first asTimer.suspend() - if let newValue = newValue { - + if let newValue { // Copy meta-data to track newValue._state.mutate { $0.sid = sid @@ -146,7 +142,7 @@ public class RemoteTrackPublication: TrackPublication { notify: false) } - if let oldValue = oldValue, newValue == nil, let participant = participant as? RemoteParticipant { + if let oldValue, newValue == nil, let participant = participant as? RemoteParticipant { participant.delegates.notify(label: { "participant.didUnsubscribe \(self)" }) { $0.participant?(participant, didUnsubscribe: self, track: oldValue) } @@ -163,12 +159,10 @@ public class RemoteTrackPublication: TrackPublication { // MARK: - Private private extension RemoteTrackPublication { - - var isAdaptiveStreamEnabled: Bool { (participant?.room._state.options ?? RoomOptions()).adaptiveStream && .video == kind } + var isAdaptiveStreamEnabled: Bool { (participant?.room._state.options ?? RoomOptions()).adaptiveStream && kind == .video } var engineConnectionState: ConnectionState { - - guard let participant = participant else { + guard let participant else { log("Participant is nil", .warning) return .disconnected() } @@ -186,13 +180,11 @@ private extension RemoteTrackPublication { // MARK: - Internal -internal extension RemoteTrackPublication { - +extension RemoteTrackPublication { func set(metadataMuted newValue: Bool) { - guard _state.metadataMuted != newValue else { return } - guard let participant = participant else { + guard let participant else { log("Participant is nil", .warning) return } @@ -214,7 +206,7 @@ internal extension RemoteTrackPublication { guard _state.subscriptionAllowed != newValue else { return } _state.mutate { $0.subscriptionAllowed = newValue } - guard let participant = self.participant as? RemoteParticipant else { return } + guard let participant = participant as? RemoteParticipant else { return } participant.delegates.notify(label: { "participant.didUpdate permission: \(newValue)" }) { $0.participant?(participant, didUpdate: self, permission: newValue) } @@ -226,8 +218,7 @@ internal extension RemoteTrackPublication { // MARK: - TrackSettings -internal extension RemoteTrackPublication { - +extension RemoteTrackPublication { // reset track settings func resetTrackSettings() { // track is initially disabled when adaptive stream is enabled @@ -237,7 +228,6 @@ internal extension RemoteTrackPublication { // attempt to send track settings func send(trackSettings newValue: TrackSettings) async throws { - let participant = try await requireParticipant() log("[adaptiveStream] sending \(newValue), sid: \(sid)") @@ -261,22 +251,21 @@ internal extension RemoteTrackPublication { do { try await participant.room.engine.signalClient.sendUpdateTrackSettings(sid: sid, settings: newValue) _state.mutate { $0.isSendingTrackSettings = false } - } catch let error { + } catch { // Revert track settings on failure _state.mutate { $0.trackSettings = state.trackSettings $0.isSendingTrackSettings = false } - log("Failed to send track settings: \(newValue), sid: \(self.sid), error: \(error)") + log("Failed to send track settings: \(newValue), sid: \(sid), error: \(error)") } } } // MARK: - Adaptive Stream -internal extension Collection where Element == VideoRenderer { - +extension Collection { func containsOneOrMoreAdaptiveStreamEnabledRenderers() -> Bool { // not visible if no entry if isEmpty { return false } @@ -285,7 +274,6 @@ internal extension Collection where Element == VideoRenderer { } func largestSize() -> CGSize? { - func maxCGSize(_ s1: CGSize, _ s2: CGSize) -> CGSize { CGSize(width: Swift.max(s1.width, s2.width), height: Swift.max(s1.height, s2.height)) @@ -293,23 +281,21 @@ internal extension Collection where Element == VideoRenderer { // use post-layout nativeRenderer's view size otherwise return nil // which results lower layer to be requested (enabled: true, dimensions: 0x0) - return filter { $0.adaptiveStreamIsEnabled } + return filter(\.adaptiveStreamIsEnabled) .compactMap { $0.adaptiveStreamSize != .zero ? $0.adaptiveStreamSize : nil } - .reduce(into: nil as CGSize?, { previous, current in + .reduce(into: nil as CGSize?) { previous, current in guard let unwrappedPrevious = previous else { previous = current return } previous = maxCGSize(unwrappedPrevious, current) - }) + } } } extension RemoteTrackPublication { - // executed on .main private func onAdaptiveStreamTimer() { - // this should never happen assert(Thread.current.isMainThread, "this method must be called from main thread") @@ -347,7 +333,7 @@ extension RemoteTrackPublication { // log when flipping from enabled -> disabled if oldSettings.enabled, !newSettings.enabled { - let viewsString = videoRenderers.enumerated().map { (i, v) in "videoRenderer\(i)(adaptiveStreamIsEnabled: \(v.adaptiveStreamIsEnabled), adaptiveStreamSize: \(v.adaptiveStreamSize))" }.joined(separator: ", ") + let viewsString = videoRenderers.enumerated().map { i, v in "videoRenderer\(i)(adaptiveStreamIsEnabled: \(v.adaptiveStreamIsEnabled), adaptiveStreamSize: \(v.adaptiveStreamSize))" }.joined(separator: ", ") log("[adaptiveStream] disabling sid: \(sid), videoRenderersCount: \(videoRenderers.count), \(viewsString)") } @@ -359,7 +345,7 @@ extension RemoteTrackPublication { Task { do { try await send(trackSettings: newSettings) - } catch let error { + } catch { // Revert to old settings on failure _state.mutate { $0.trackSettings = oldSettings } log("[adaptiveStream] failed to send trackSettings, sid: \(self.sid) error: \(error)", .error) diff --git a/Sources/LiveKit/TrackPublications/TrackPublication+Equatable.swift b/Sources/LiveKit/TrackPublications/TrackPublication+Equatable.swift index 73c830933..2a63cd786 100644 --- a/Sources/LiveKit/TrackPublications/TrackPublication+Equatable.swift +++ b/Sources/LiveKit/TrackPublications/TrackPublication+Equatable.swift @@ -19,7 +19,6 @@ import Foundation // Objects are considered equal if both states are equal public extension TrackPublication { - override var hash: Int { var hasher = Hasher() hasher.combine(_state.copy()) @@ -28,6 +27,6 @@ public extension TrackPublication { override func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self._state.copy() == other._state.copy() + return _state.copy() == other._state.copy() } } diff --git a/Sources/LiveKit/TrackPublications/TrackPublication+Identifiable.swift b/Sources/LiveKit/TrackPublications/TrackPublication+Identifiable.swift index bca145eca..36a7c76ea 100644 --- a/Sources/LiveKit/TrackPublications/TrackPublication+Identifiable.swift +++ b/Sources/LiveKit/TrackPublications/TrackPublication+Identifiable.swift @@ -19,7 +19,6 @@ import Foundation // Identify by sid extension TrackPublication: Identifiable { - public typealias ID = Sid public var id: Sid { diff --git a/Sources/LiveKit/TrackPublications/TrackPublication.swift b/Sources/LiveKit/TrackPublications/TrackPublication.swift index da7066657..c05993adf 100644 --- a/Sources/LiveKit/TrackPublications/TrackPublication.swift +++ b/Sources/LiveKit/TrackPublications/TrackPublication.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,12 @@ * limitations under the License. */ -import Foundation -import CoreGraphics import Combine +import CoreGraphics +import Foundation @objc public class TrackPublication: NSObject, ObservableObject, Loggable { - // MARK: - Public properties @objc @@ -63,12 +62,12 @@ public class TrackPublication: NSObject, ObservableObject, Loggable { // MARK: - Internal - internal let queue = DispatchQueue(label: "LiveKitSDK.publication", qos: .default) + let queue = DispatchQueue(label: "LiveKitSDK.publication", qos: .default) /// Reference to the ``Participant`` this publication belongs to. - internal weak var participant: Participant? + weak var participant: Participant? - internal struct State: Equatable, Hashable { + struct State: Equatable, Hashable { let sid: Sid let kind: Track.Kind let source: Track.Source @@ -95,12 +94,12 @@ public class TrackPublication: NSObject, ObservableObject, Loggable { var latestInfo: Livekit_TrackInfo? } - internal var _state: StateSync - - internal init(info: Livekit_TrackInfo, - track: Track? = nil, - participant: Participant) { + var _state: StateSync + init(info: Livekit_TrackInfo, + track: Track? = nil, + participant: Participant) + { _state = StateSync(State( sid: info.sid, kind: info.type.toLKType(), @@ -119,15 +118,15 @@ public class TrackPublication: NSObject, ObservableObject, Loggable { super.init() - self.set(track: track) + set(track: track) // listen for events from Track track?.add(delegate: self) // trigger events when state mutates - self._state.onDidMutate = { [weak self] newState, oldState in + _state.onDidMutate = { [weak self] newState, oldState in - guard let self = self else { return } + guard let self else { return } if newState.streamState != oldState.streamState { if let participant = self.participant as? RemoteParticipant, let trackPublication = self as? RemoteTrackPublication { @@ -148,7 +147,7 @@ public class TrackPublication: NSObject, ObservableObject, Loggable { log("sid: \(sid)") } - internal func notifyObjectWillChange() { + func notifyObjectWillChange() { // Notify UI that the object has changed Task.detached { @MainActor in // Notify TrackPublication @@ -163,8 +162,7 @@ public class TrackPublication: NSObject, ObservableObject, Loggable { } } - internal func updateFromInfo(info: Livekit_TrackInfo) { - + func updateFromInfo(info: Livekit_TrackInfo) { _state.mutate { // only muted and name can conceivably update $0.name = info.name @@ -178,15 +176,15 @@ public class TrackPublication: NSObject, ObservableObject, Loggable { } @discardableResult - internal func set(track newValue: Track?) -> Track? { + func set(track newValue: Track?) -> Track? { // keep ref to old value - let oldValue = self.track + let oldValue = track // continue only if updated - guard self.track != newValue else { return oldValue } + guard track != newValue else { return oldValue } log("\(String(describing: oldValue)) -> \(String(describing: newValue))") // listen for visibility updates - self.track?.remove(delegate: self) + track?.remove(delegate: self) newValue?.add(delegate: self) _state.mutate { $0.track = newValue } @@ -198,7 +196,6 @@ public class TrackPublication: NSObject, ObservableObject, Loggable { // MARK: - TrackDelegate extension TrackPublication: TrackDelegateInternal { - func track(_ track: Track, didMutateState newState: Track.State, oldState: Track.State) { // Notify on UI updating changes if newState.muted != oldState.muted { @@ -207,8 +204,7 @@ extension TrackPublication: TrackDelegateInternal { } } - public func track(_ track: Track, didUpdate muted: Bool, shouldSendSignal: Bool) { - + public func track(_: Track, didUpdate muted: Bool, shouldSendSignal: Bool) { log("muted: \(muted) shouldSendSignal: \(shouldSendSignal)") Task { @@ -236,11 +232,9 @@ extension TrackPublication: TrackDelegateInternal { // MARK: - Internal helpers -internal extension TrackPublication { - +extension TrackPublication { func requireParticipant() async throws -> Participant { - - guard let participant = participant else { + guard let participant else { throw EngineError.state(message: "Participant is nil") } diff --git a/Sources/LiveKit/Types/AudioDevice.swift b/Sources/LiveKit/Types/AudioDevice.swift index 732ac2b91..beac6d7a7 100644 --- a/Sources/LiveKit/Types/AudioDevice.swift +++ b/Sources/LiveKit/Types/AudioDevice.swift @@ -20,19 +20,17 @@ import Foundation @objc public class AudioDevice: NSObject, MediaDevice { - public var deviceId: String { _ioDevice.deviceId } public var name: String { _ioDevice.name } public var isDefault: Bool { _ioDevice.isDefault } - internal let _ioDevice: LKRTCIODevice + let _ioDevice: LKRTCIODevice - internal init(ioDevice: LKRTCIODevice) { - self._ioDevice = ioDevice + init(ioDevice: LKRTCIODevice) { + _ioDevice = ioDevice } } extension AudioDevice: Identifiable { - public var id: String { deviceId } } diff --git a/Sources/LiveKit/Types/AudioEncoding+Comparable.swift b/Sources/LiveKit/Types/AudioEncoding+Comparable.swift index 6f891f649..363546025 100644 --- a/Sources/LiveKit/Types/AudioEncoding+Comparable.swift +++ b/Sources/LiveKit/Types/AudioEncoding+Comparable.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import Foundation extension AudioEncoding: Comparable { - public static func < (lhs: AudioEncoding, rhs: AudioEncoding) -> Bool { lhs.maxBitrate < rhs.maxBitrate } diff --git a/Sources/LiveKit/Types/AudioEncoding.swift b/Sources/LiveKit/Types/AudioEncoding.swift index c236b9565..37ea1b6ad 100644 --- a/Sources/LiveKit/Types/AudioEncoding.swift +++ b/Sources/LiveKit/Types/AudioEncoding.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import Foundation @objc public class AudioEncoding: NSObject, MediaEncoding { - @objc public var maxBitrate: Int @@ -31,12 +30,12 @@ public class AudioEncoding: NSObject, MediaEncoding { // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.maxBitrate == other.maxBitrate + return maxBitrate == other.maxBitrate } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(maxBitrate) return hasher.finalize() @@ -46,21 +45,20 @@ public class AudioEncoding: NSObject, MediaEncoding { // MARK: - Presets @objc -extension AudioEncoding { - +public extension AudioEncoding { internal static let presets = [ presetTelephone, presetSpeech, presetMusic, presetMusicStereo, presetMusicHighQuality, - presetMusicHighQualityStereo + presetMusicHighQualityStereo, ] - public static let presetTelephone = AudioEncoding(maxBitrate: 12_000) - public static let presetSpeech = AudioEncoding(maxBitrate: 20_000) - public static let presetMusic = AudioEncoding(maxBitrate: 32_000) - public static let presetMusicStereo = AudioEncoding(maxBitrate: 48_000) - public static let presetMusicHighQuality = AudioEncoding(maxBitrate: 64_000) - public static let presetMusicHighQualityStereo = AudioEncoding(maxBitrate: 96_000) + static let presetTelephone = AudioEncoding(maxBitrate: 12000) + static let presetSpeech = AudioEncoding(maxBitrate: 20000) + static let presetMusic = AudioEncoding(maxBitrate: 32000) + static let presetMusicStereo = AudioEncoding(maxBitrate: 48000) + static let presetMusicHighQuality = AudioEncoding(maxBitrate: 64000) + static let presetMusicHighQualityStereo = AudioEncoding(maxBitrate: 96000) } diff --git a/Sources/LiveKit/Types/ConnectionQuality.swift b/Sources/LiveKit/Types/ConnectionQuality.swift index 39eff10e4..dfb6861d8 100644 --- a/Sources/LiveKit/Types/ConnectionQuality.swift +++ b/Sources/LiveKit/Types/ConnectionQuality.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ public enum ConnectionQuality: Int { } extension Livekit_ConnectionQuality { - func toLKType() -> ConnectionQuality { switch self { case .poor: return .poor diff --git a/Sources/LiveKit/Types/ConnectionState+ObjC.swift b/Sources/LiveKit/Types/ConnectionState+ObjC.swift index 0a1914758..e9f8e6c87 100644 --- a/Sources/LiveKit/Types/ConnectionState+ObjC.swift +++ b/Sources/LiveKit/Types/ConnectionState+ObjC.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Sources/LiveKit/Types/ConnectionState.swift b/Sources/LiveKit/Types/ConnectionState.swift index 62340ef3d..5b47a0670 100644 --- a/Sources/LiveKit/Types/ConnectionState.swift +++ b/Sources/LiveKit/Types/ConnectionState.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ public enum ConnectionState { case reconnecting case connected - internal func toObjCType() -> ConnectionStateObjC { + func toObjCType() -> ConnectionStateObjC { switch self { case .disconnected: return .disconnected case .connecting: return .connecting @@ -45,7 +45,6 @@ extension ConnectionState: Identifiable { } extension ConnectionState: Equatable { - public static func == (lhs: ConnectionState, rhs: ConnectionState) -> Bool { switch (lhs, rhs) { case (.disconnected, .disconnected), @@ -73,8 +72,8 @@ extension ConnectionState: Equatable { } public var disconnectedWithNetworkError: Error? { - guard case .disconnected(let reason) = self, - case .networkError(let error) = reason else { return nil } + guard case let .disconnected(reason) = self, + case let .networkError(error) = reason else { return nil } return error } } diff --git a/Sources/LiveKit/Types/Dimensions.swift b/Sources/LiveKit/Types/Dimensions.swift index ec205e630..1eb40d904 100644 --- a/Sources/LiveKit/Types/Dimensions.swift +++ b/Sources/LiveKit/Types/Dimensions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,13 @@ * limitations under the License. */ -import Foundation import CoreMedia +import Foundation @_implementationOnly import WebRTC @objc public class Dimensions: NSObject { - @objc public let width: Int32 @@ -35,35 +34,35 @@ public class Dimensions: NSObject { } public init(from dimensions: CMVideoDimensions) { - self.width = dimensions.width - self.height = dimensions.height + width = dimensions.width + height = dimensions.height } // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.width == other.width && self.height == other.height + return width == other.width && height == other.height } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(width) hasher.combine(height) return hasher.finalize() } - public override var description: String { + override public var description: String { "Dimensions(\(width)x\(height))" } } // MARK: - Static constants -extension Dimensions { - public static let aspectRatio169 = 16.0 / 9.0 - public static let aspectRatio43 = 4.0 / 3.0 - public static let zero = Dimensions(width: 0, height: 0) +public extension Dimensions { + static let aspectRatio169 = 16.0 / 9.0 + static let aspectRatio43 = 4.0 / 3.0 + static let zero = Dimensions(width: 0, height: 0) internal static let renderSafeSize: Int32 = 8 internal static let encodeSafeSize: Int32 = 16 @@ -79,7 +78,6 @@ extension Dimensions { // } extension Dimensions { - var aspectRatio: Double { let w = Double(width) let h = Double(height) @@ -168,8 +166,8 @@ extension Dimensions { return result } - internal func videoLayers(for encodings: [LKRTCRtpEncodingParameters]) -> [Livekit_VideoLayer] { - encodings.filter { $0.isActive }.map { encoding in + func videoLayers(for encodings: [LKRTCRtpEncodingParameters]) -> [Livekit_VideoLayer] { + encodings.filter(\.isActive).map { encoding in let scaleDownBy = encoding.scaleResolutionDownBy?.doubleValue ?? 1.0 return Livekit_VideoLayer.with { $0.width = UInt32((Double(self.width) / scaleDownBy).rounded(.up)) @@ -184,14 +182,12 @@ extension Dimensions { // MARK: - Convert extension Dimensions { - func toCGSize() -> CGSize { CGSize(width: Int(width), height: Int(height)) } func apply(rotation: RTCVideoRotation) -> Dimensions { - - if ._90 == rotation || ._270 == rotation { + if rotation == ._90 || rotation == ._270 { return swapped() } @@ -203,7 +199,6 @@ extension Dimensions { @objc public extension Dimensions { - // 16:9 aspect ratio presets static let h90_169 = Dimensions(width: 160, height: 90) @@ -215,13 +210,13 @@ public extension Dimensions { static let h540_169 = Dimensions(width: 960, height: 540) - static let h720_169 = Dimensions(width: 1_280, height: 720) + static let h720_169 = Dimensions(width: 1280, height: 720) - static let h1080_169 = Dimensions(width: 1_920, height: 1_080) + static let h1080_169 = Dimensions(width: 1920, height: 1080) - static let h1440_169 = Dimensions(width: 2_560, height: 1_440) + static let h1440_169 = Dimensions(width: 2560, height: 1440) - static let h2160_169 = Dimensions(width: 3_840, height: 2_160) + static let h2160_169 = Dimensions(width: 3840, height: 2160) // 4:3 aspect ratio presets static let h120_43 = Dimensions(width: 160, height: 120) @@ -238,7 +233,7 @@ public extension Dimensions { static let h720_43 = Dimensions(width: 960, height: 720) - static let h1080_43 = Dimensions(width: 1_440, height: 1_080) + static let h1080_43 = Dimensions(width: 1440, height: 1080) - static let h1440_43 = Dimensions(width: 1_920, height: 1_440) + static let h1440_43 = Dimensions(width: 1920, height: 1440) } diff --git a/Sources/LiveKit/Types/DisconnectReason.swift b/Sources/LiveKit/Types/DisconnectReason.swift index 41d361d8c..78645fbfe 100644 --- a/Sources/LiveKit/Types/DisconnectReason.swift +++ b/Sources/LiveKit/Types/DisconnectReason.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,6 @@ public enum DisconnectReason { } extension Livekit_DisconnectReason { - func toLKType() -> DisconnectReason { switch self { case .clientInitiated: return .user @@ -46,12 +45,11 @@ extension Livekit_DisconnectReason { } extension DisconnectReason: Equatable { - public static func == (lhs: DisconnectReason, rhs: DisconnectReason) -> Bool { lhs.isEqual(to: rhs) } - public func isEqual(to rhs: DisconnectReason, includingAssociatedValues: Bool = true) -> Bool { + public func isEqual(to rhs: DisconnectReason, includingAssociatedValues _: Bool = true) -> Bool { switch (self, rhs) { case (.user, .user): return true case (.networkError, .networkError): return true @@ -68,7 +66,7 @@ extension DisconnectReason: Equatable { } var networkError: Error? { - if case .networkError(let error) = self { + if case let .networkError(error) = self { return error } diff --git a/Sources/LiveKit/Types/IceCandidate.swift b/Sources/LiveKit/Types/IceCandidate.swift index 9ada324b4..bb72aa14e 100644 --- a/Sources/LiveKit/Types/IceCandidate.swift +++ b/Sources/LiveKit/Types/IceCandidate.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,6 @@ struct IceCandidate: Codable { } extension LKRTCIceCandidate { - func toLKType() -> IceCandidate { IceCandidate(sdp: sdp, sdpMLineIndex: sdpMLineIndex, diff --git a/Sources/LiveKit/Types/IceServer.swift b/Sources/LiveKit/Types/IceServer.swift index ccc19a676..e036a04d9 100644 --- a/Sources/LiveKit/Types/IceServer.swift +++ b/Sources/LiveKit/Types/IceServer.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,15 +21,14 @@ import Foundation /// Options used when establishing a connection. @objc public class IceServer: NSObject { - public let urls: [String] public let username: String? public let credential: String? public init(urls: [String], username: String?, - credential: String?) { - + credential: String?) + { self.urls = urls self.username = username self.credential = credential @@ -37,9 +36,7 @@ public class IceServer: NSObject { } extension IceServer { - func toRTCType() -> LKRTCIceServer { - DispatchQueue.liveKitWebRTC.sync { LKRTCIceServer(urlStrings: urls, username: username, credential: credential) } @@ -47,7 +44,6 @@ extension IceServer { } extension Livekit_ICEServer { - func toRTCType() -> LKRTCIceServer { let rtcUsername = !username.isEmpty ? username : nil let rtcCredential = !credential.isEmpty ? credential : nil diff --git a/Sources/LiveKit/Types/MediaDevice.swift b/Sources/LiveKit/Types/MediaDevice.swift index abf11fe41..e5acae173 100644 --- a/Sources/LiveKit/Types/MediaDevice.swift +++ b/Sources/LiveKit/Types/MediaDevice.swift @@ -18,7 +18,6 @@ import Foundation @objc public protocol MediaDevice: AnyObject { - var deviceId: String { get } var name: String { get } } diff --git a/Sources/LiveKit/Types/Options/AudioCaptureOptions.swift b/Sources/LiveKit/Types/Options/AudioCaptureOptions.swift index 12c7f4b53..6db7d9b19 100644 --- a/Sources/LiveKit/Types/Options/AudioCaptureOptions.swift +++ b/Sources/LiveKit/Types/Options/AudioCaptureOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import Foundation @objc public class AudioCaptureOptions: NSObject, CaptureOptions { - @objc public let echoCancellation: Bool @@ -46,8 +45,8 @@ public class AudioCaptureOptions: NSObject, CaptureOptions { noiseSuppression: Bool = true, autoGainControl: Bool = true, typingNoiseDetection: Bool = true, - highpassFilter: Bool = true) { - + highpassFilter: Bool = true) + { self.echoCancellation = echoCancellation self.noiseSuppression = noiseSuppression self.autoGainControl = autoGainControl @@ -57,18 +56,18 @@ public class AudioCaptureOptions: NSObject, CaptureOptions { // MARK: - Equatable - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.echoCancellation == other.echoCancellation && - self.noiseSuppression == other.noiseSuppression && - self.autoGainControl == other.autoGainControl && - self.typingNoiseDetection == other.typingNoiseDetection && - self.highpassFilter == other.highpassFilter && - self.experimentalNoiseSuppression == other.experimentalNoiseSuppression && - self.experimentalAutoGainControl == other.experimentalAutoGainControl + return echoCancellation == other.echoCancellation && + noiseSuppression == other.noiseSuppression && + autoGainControl == other.autoGainControl && + typingNoiseDetection == other.typingNoiseDetection && + highpassFilter == other.highpassFilter && + experimentalNoiseSuppression == other.experimentalNoiseSuppression && + experimentalAutoGainControl == other.experimentalAutoGainControl } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(echoCancellation) hasher.combine(noiseSuppression) diff --git a/Sources/LiveKit/Types/Options/AudioPublishOptions.swift b/Sources/LiveKit/Types/Options/AudioPublishOptions.swift index 7a8182d97..103e15be4 100644 --- a/Sources/LiveKit/Types/Options/AudioPublishOptions.swift +++ b/Sources/LiveKit/Types/Options/AudioPublishOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import Foundation @objc public class AudioPublishOptions: NSObject, PublishOptions { - @objc public let name: String? @@ -31,8 +30,8 @@ public class AudioPublishOptions: NSObject, PublishOptions { public init(name: String? = nil, encoding: AudioEncoding? = nil, - dtx: Bool = true) { - + dtx: Bool = true) + { self.name = name self.encoding = encoding self.dtx = dtx @@ -40,14 +39,14 @@ public class AudioPublishOptions: NSObject, PublishOptions { // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.name == other.name && - self.encoding == other.encoding && - self.dtx == other.dtx + return name == other.name && + encoding == other.encoding && + dtx == other.dtx } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(name) hasher.combine(encoding) diff --git a/Sources/LiveKit/Types/Options/BufferCaptureOptions.swift b/Sources/LiveKit/Types/Options/BufferCaptureOptions.swift index 2252bc2a4..d5096e2fe 100644 --- a/Sources/LiveKit/Types/Options/BufferCaptureOptions.swift +++ b/Sources/LiveKit/Types/Options/BufferCaptureOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import Foundation @objc public class BufferCaptureOptions: NSObject, VideoCaptureOptions { - @objc public let dimensions: Dimensions @@ -28,25 +27,26 @@ public class BufferCaptureOptions: NSObject, VideoCaptureOptions { public let fps: Int public init(dimensions: Dimensions = .h1080_169, - fps: Int = 15) { + fps: Int = 15) + { self.dimensions = dimensions self.fps = fps } public init(from options: ScreenShareCaptureOptions) { - self.dimensions = options.dimensions - self.fps = options.fps + dimensions = options.dimensions + fps = options.fps } // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.dimensions == other.dimensions && - self.fps == other.fps + return dimensions == other.dimensions && + fps == other.fps } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(dimensions) hasher.combine(fps) diff --git a/Sources/LiveKit/Types/Options/CameraCaptureOptions.swift b/Sources/LiveKit/Types/Options/CameraCaptureOptions.swift index 0765ca8e3..e8dcab3ff 100644 --- a/Sources/LiveKit/Types/Options/CameraCaptureOptions.swift +++ b/Sources/LiveKit/Types/Options/CameraCaptureOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,11 @@ * limitations under the License. */ -import Foundation import AVFoundation +import Foundation @objc public class CameraCaptureOptions: NSObject, VideoCaptureOptions { - @objc public let position: AVCaptureDevice.Position @@ -35,19 +34,19 @@ public class CameraCaptureOptions: NSObject, VideoCaptureOptions { public let fps: Int @objc - public override init() { - self.position = .front - self.preferredFormat = nil - self.dimensions = .h720_169 - self.fps = 30 + override public init() { + position = .front + preferredFormat = nil + dimensions = .h720_169 + fps = 30 } @objc public init(position: AVCaptureDevice.Position = .front, preferredFormat: AVCaptureDevice.Format? = nil, dimensions: Dimensions = .h720_169, - fps: Int = 30) { - + fps: Int = 30) + { self.position = position self.preferredFormat = preferredFormat self.dimensions = dimensions @@ -57,8 +56,8 @@ public class CameraCaptureOptions: NSObject, VideoCaptureOptions { public func copyWith(position: AVCaptureDevice.Position? = nil, preferredFormat: AVCaptureDevice.Format? = nil, dimensions: Dimensions? = nil, - fps: Int? = nil) -> CameraCaptureOptions { - + fps: Int? = nil) -> CameraCaptureOptions + { CameraCaptureOptions(position: position ?? self.position, preferredFormat: preferredFormat ?? self.preferredFormat, dimensions: dimensions ?? self.dimensions, @@ -67,15 +66,15 @@ public class CameraCaptureOptions: NSObject, VideoCaptureOptions { // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.position == other.position && - self.preferredFormat == other.preferredFormat && - self.dimensions == other.dimensions && - self.fps == other.fps + return position == other.position && + preferredFormat == other.preferredFormat && + dimensions == other.dimensions && + fps == other.fps } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(position) hasher.combine(preferredFormat) diff --git a/Sources/LiveKit/Types/Options/CaptureOptions.swift b/Sources/LiveKit/Types/Options/CaptureOptions.swift index 935b38dbd..108d1774c 100644 --- a/Sources/LiveKit/Types/Options/CaptureOptions.swift +++ b/Sources/LiveKit/Types/Options/CaptureOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,4 @@ import Foundation @objc -public protocol CaptureOptions { - -} +public protocol CaptureOptions {} diff --git a/Sources/LiveKit/Types/Options/ConnectOptions+Copy.swift b/Sources/LiveKit/Types/Options/ConnectOptions+Copy.swift index d0df09f0e..f737eee3f 100644 --- a/Sources/LiveKit/Types/Options/ConnectOptions+Copy.swift +++ b/Sources/LiveKit/Types/Options/ConnectOptions+Copy.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,11 @@ import Foundation public extension ConnectOptions { - func copyWith(autoSubscribe: Bool? = nil, reconnectAttempts: Int? = nil, reconnectAttemptDelay: TimeInterval? = nil, - protocolVersion: ProtocolVersion? = nil) -> ConnectOptions { - + protocolVersion: ProtocolVersion? = nil) -> ConnectOptions + { ConnectOptions(autoSubscribe: autoSubscribe ?? self.autoSubscribe, reconnectAttempts: reconnectAttempts ?? self.reconnectAttempts, reconnectAttemptDelay: reconnectAttemptDelay ?? self.reconnectAttemptDelay, diff --git a/Sources/LiveKit/Types/Options/ConnectOptions.swift b/Sources/LiveKit/Types/Options/ConnectOptions.swift index 6e1b3ba6f..04a82c83f 100644 --- a/Sources/LiveKit/Types/Options/ConnectOptions.swift +++ b/Sources/LiveKit/Types/Options/ConnectOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import Foundation /// Options used when establishing a connection. @objc public class ConnectOptions: NSObject { - /// Automatically subscribe to ``RemoteParticipant``'s tracks. /// Defaults to true. @objc @@ -47,13 +46,13 @@ public class ConnectOptions: NSObject { public let protocolVersion: ProtocolVersion @objc - public override init() { - self.autoSubscribe = true - self.publishOnlyMode = nil - self.reconnectAttempts = 3 - self.reconnectAttemptDelay = .defaultReconnectAttemptDelay - self.iceServers = [] - self.protocolVersion = .v9 + override public init() { + autoSubscribe = true + publishOnlyMode = nil + reconnectAttempts = 3 + reconnectAttemptDelay = .defaultReconnectAttemptDelay + iceServers = [] + protocolVersion = .v9 } @objc @@ -62,8 +61,8 @@ public class ConnectOptions: NSObject { reconnectAttempts: Int = 3, reconnectAttemptDelay: TimeInterval = .defaultReconnectAttemptDelay, iceServers: [IceServer] = [], - protocolVersion: ProtocolVersion = .v9) { - + protocolVersion: ProtocolVersion = .v9) + { self.autoSubscribe = autoSubscribe self.publishOnlyMode = publishOnlyMode self.reconnectAttempts = reconnectAttempts @@ -74,17 +73,17 @@ public class ConnectOptions: NSObject { // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.autoSubscribe == other.autoSubscribe && - self.publishOnlyMode == other.publishOnlyMode && - self.reconnectAttempts == other.reconnectAttempts && - self.reconnectAttemptDelay == other.reconnectAttemptDelay && - self.iceServers == other.iceServers && - self.protocolVersion == other.protocolVersion + return autoSubscribe == other.autoSubscribe && + publishOnlyMode == other.publishOnlyMode && + reconnectAttempts == other.reconnectAttempts && + reconnectAttemptDelay == other.reconnectAttemptDelay && + iceServers == other.iceServers && + protocolVersion == other.protocolVersion } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(autoSubscribe) hasher.combine(publishOnlyMode) diff --git a/Sources/LiveKit/Types/Options/DataPublishOptions.swift b/Sources/LiveKit/Types/Options/DataPublishOptions.swift index 108a05fc2..43fb78537 100644 --- a/Sources/LiveKit/Types/Options/DataPublishOptions.swift +++ b/Sources/LiveKit/Types/Options/DataPublishOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import Foundation @objc public class DataPublishOptions: NSObject, PublishOptions { - @objc public let name: String? @@ -30,8 +29,8 @@ public class DataPublishOptions: NSObject, PublishOptions { public init(name: String? = nil, destinations: [String] = [], - topic: String? = nil) { - + topic: String? = nil) + { self.name = name self.destinations = destinations self.topic = topic @@ -39,14 +38,14 @@ public class DataPublishOptions: NSObject, PublishOptions { // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.name == other.name && - self.destinations == other.destinations && - self.topic == other.topic + return name == other.name && + destinations == other.destinations && + topic == other.topic } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(name) hasher.combine(destinations) diff --git a/Sources/LiveKit/Types/Options/PublishOptions.swift b/Sources/LiveKit/Types/Options/PublishOptions.swift index 7449241ce..6154a4f8f 100644 --- a/Sources/LiveKit/Types/Options/PublishOptions.swift +++ b/Sources/LiveKit/Types/Options/PublishOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Sources/LiveKit/Types/Options/RoomOptions.swift b/Sources/LiveKit/Types/Options/RoomOptions.swift index 38b1b14e3..f10e0f943 100644 --- a/Sources/LiveKit/Types/Options/RoomOptions.swift +++ b/Sources/LiveKit/Types/Options/RoomOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import Foundation @objc public class RoomOptions: NSObject { - // default options for capturing @objc public let defaultCameraCaptureOptions: CameraCaptureOptions @@ -77,8 +76,8 @@ public class RoomOptions: NSObject { dynacast: Bool = false, stopLocalTrackOnUnpublish: Bool = true, suspendLocalVideoTracksInBackground: Bool = true, - e2eeOptions: E2EEOptions? = nil) { - + e2eeOptions: E2EEOptions? = nil) + { self.defaultCameraCaptureOptions = defaultCameraCaptureOptions self.defaultScreenShareCaptureOptions = defaultScreenShareCaptureOptions self.defaultAudioCaptureOptions = defaultAudioCaptureOptions @@ -94,21 +93,21 @@ public class RoomOptions: NSObject { // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.defaultCameraCaptureOptions == other.defaultCameraCaptureOptions && - self.defaultScreenShareCaptureOptions == other.defaultScreenShareCaptureOptions && - self.defaultAudioCaptureOptions == other.defaultAudioCaptureOptions && - self.defaultVideoPublishOptions == other.defaultVideoPublishOptions && - self.defaultAudioPublishOptions == other.defaultAudioPublishOptions && - self.defaultDataPublishOptions == other.defaultDataPublishOptions && - self.adaptiveStream == other.adaptiveStream && - self.dynacast == other.dynacast && - self.stopLocalTrackOnUnpublish == other.stopLocalTrackOnUnpublish && - self.suspendLocalVideoTracksInBackground == other.suspendLocalVideoTracksInBackground + return defaultCameraCaptureOptions == other.defaultCameraCaptureOptions && + defaultScreenShareCaptureOptions == other.defaultScreenShareCaptureOptions && + defaultAudioCaptureOptions == other.defaultAudioCaptureOptions && + defaultVideoPublishOptions == other.defaultVideoPublishOptions && + defaultAudioPublishOptions == other.defaultAudioPublishOptions && + defaultDataPublishOptions == other.defaultDataPublishOptions && + adaptiveStream == other.adaptiveStream && + dynacast == other.dynacast && + stopLocalTrackOnUnpublish == other.stopLocalTrackOnUnpublish && + suspendLocalVideoTracksInBackground == other.suspendLocalVideoTracksInBackground } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(defaultCameraCaptureOptions) hasher.combine(defaultScreenShareCaptureOptions) diff --git a/Sources/LiveKit/Types/Options/ScreenShareCaptureOptions.swift b/Sources/LiveKit/Types/Options/ScreenShareCaptureOptions.swift index 40a17a773..61192580c 100644 --- a/Sources/LiveKit/Types/Options/ScreenShareCaptureOptions.swift +++ b/Sources/LiveKit/Types/Options/ScreenShareCaptureOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import Foundation @objc public class ScreenShareCaptureOptions: NSObject, VideoCaptureOptions { - @objc public let dimensions: Dimensions @@ -35,7 +34,8 @@ public class ScreenShareCaptureOptions: NSObject, VideoCaptureOptions { public init(dimensions: Dimensions = .h1080_169, fps: Int = 15, showCursor: Bool = true, - useBroadcastExtension: Bool = false) { + useBroadcastExtension: Bool = false) + { self.dimensions = dimensions self.fps = fps self.showCursor = showCursor @@ -44,15 +44,15 @@ public class ScreenShareCaptureOptions: NSObject, VideoCaptureOptions { // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.dimensions == other.dimensions && - self.fps == other.fps && - self.showCursor == other.showCursor && - self.useBroadcastExtension == other.useBroadcastExtension + return dimensions == other.dimensions && + fps == other.fps && + showCursor == other.showCursor && + useBroadcastExtension == other.useBroadcastExtension } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(dimensions) hasher.combine(fps) diff --git a/Sources/LiveKit/Types/Options/VideoCaptureOptions.swift b/Sources/LiveKit/Types/Options/VideoCaptureOptions.swift index 8701c3fa1..8fbfedffa 100644 --- a/Sources/LiveKit/Types/Options/VideoCaptureOptions.swift +++ b/Sources/LiveKit/Types/Options/VideoCaptureOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Sources/LiveKit/Types/Options/VideoPublishOptions.swift b/Sources/LiveKit/Types/Options/VideoPublishOptions.swift index d5d1a9fd5..b4dbb7538 100644 --- a/Sources/LiveKit/Types/Options/VideoPublishOptions.swift +++ b/Sources/LiveKit/Types/Options/VideoPublishOptions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import Foundation @objc public class VideoPublishOptions: NSObject, PublishOptions { - @objc public let name: String? @@ -45,8 +44,8 @@ public class VideoPublishOptions: NSObject, PublishOptions { screenShareEncoding: VideoEncoding? = nil, simulcast: Bool = true, simulcastLayers: [VideoParameters] = [], - screenShareSimulcastLayers: [VideoParameters] = []) { - + screenShareSimulcastLayers: [VideoParameters] = []) + { self.name = name self.encoding = encoding self.screenShareEncoding = screenShareEncoding @@ -57,17 +56,17 @@ public class VideoPublishOptions: NSObject, PublishOptions { // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.name == other.name && - self.encoding == other.encoding && - self.screenShareEncoding == other.screenShareEncoding && - self.simulcast == other.simulcast && - self.simulcastLayers == other.simulcastLayers && - self.screenShareSimulcastLayers == other.screenShareSimulcastLayers + return name == other.name && + encoding == other.encoding && + screenShareEncoding == other.screenShareEncoding && + simulcast == other.simulcast && + simulcastLayers == other.simulcastLayers && + screenShareSimulcastLayers == other.screenShareSimulcastLayers } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(name) hasher.combine(encoding) diff --git a/Sources/LiveKit/Types/Other.swift b/Sources/LiveKit/Types/Other.swift index 7b04bf745..96f77494c 100644 --- a/Sources/LiveKit/Types/Other.swift +++ b/Sources/LiveKit/Types/Other.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,7 @@ public enum Reliability: Int { case lossy } -internal extension Reliability { - +extension Reliability { func toPBType() -> Livekit_DataPacket.Kind { if self == .lossy { return .lossy } return .reliable diff --git a/Sources/LiveKit/Types/ParticipantPermissions.swift b/Sources/LiveKit/Types/ParticipantPermissions.swift index c1523ba88..243557db1 100644 --- a/Sources/LiveKit/Types/ParticipantPermissions.swift +++ b/Sources/LiveKit/Types/ParticipantPermissions.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import Foundation @objc public class ParticipantPermissions: NSObject { - /// ``Participant`` can subscribe to tracks in the room @objc public let canSubscribe: Bool @@ -39,12 +38,12 @@ public class ParticipantPermissions: NSObject { @objc public let recorder: Bool - internal init(canSubscribe: Bool = false, - canPublish: Bool = false, - canPublishData: Bool = false, - hidden: Bool = false, - recorder: Bool = false) { - + init(canSubscribe: Bool = false, + canPublish: Bool = false, + canPublishData: Bool = false, + hidden: Bool = false, + recorder: Bool = false) + { self.canSubscribe = canSubscribe self.canPublish = canPublish self.canPublishData = canPublishData @@ -54,16 +53,16 @@ public class ParticipantPermissions: NSObject { // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.canSubscribe == other.canSubscribe && - self.canPublish == other.canPublish && - self.canPublishData == other.canPublishData && - self.hidden == other.hidden && - self.recorder == other.recorder + return canSubscribe == other.canSubscribe && + canPublish == other.canPublish && + canPublishData == other.canPublishData && + hidden == other.hidden && + recorder == other.recorder } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(canSubscribe) hasher.combine(canPublish) @@ -75,7 +74,6 @@ public class ParticipantPermissions: NSObject { } extension Livekit_ParticipantPermission { - func toLKType() -> ParticipantPermissions { ParticipantPermissions(canSubscribe: canSubscribe, canPublish: canPublish, diff --git a/Sources/LiveKit/Types/ParticipantTrackPermission.swift b/Sources/LiveKit/Types/ParticipantTrackPermission.swift index be36e0c59..e5b4a430f 100644 --- a/Sources/LiveKit/Types/ParticipantTrackPermission.swift +++ b/Sources/LiveKit/Types/ParticipantTrackPermission.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,8 +41,8 @@ public class ParticipantTrackPermission: NSObject { @objc public init(participantSid: String, allTracksAllowed: Bool, - allowedTrackSids: [String] = [String]()) { - + allowedTrackSids: [String] = [String]()) + { self.participantSid = participantSid self.allTracksAllowed = allTracksAllowed self.allowedTrackSids = allowedTrackSids @@ -50,9 +50,8 @@ public class ParticipantTrackPermission: NSObject { } extension ParticipantTrackPermission { - func toPBType() -> Livekit_TrackPermission { - return Livekit_TrackPermission.with { + Livekit_TrackPermission.with { $0.participantSid = self.participantSid $0.allTracks = self.allTracksAllowed $0.trackSids = self.allowedTrackSids diff --git a/Sources/LiveKit/Types/ProtocolVersion.swift b/Sources/LiveKit/Types/ProtocolVersion.swift index b70390b89..8455bb624 100644 --- a/Sources/LiveKit/Types/ProtocolVersion.swift +++ b/Sources/LiveKit/Types/ProtocolVersion.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,6 @@ public enum ProtocolVersion: Int { // MARK: - Comparable extension ProtocolVersion: Comparable { - public static func < (lhs: Self, rhs: Self) -> Bool { lhs.rawValue < rhs.rawValue } @@ -40,8 +39,7 @@ extension ProtocolVersion: Comparable { // MARK: - CustomStringConvertible extension ProtocolVersion: CustomStringConvertible { - public var description: String { - String(self.rawValue) + String(rawValue) } } diff --git a/Sources/LiveKit/Types/SessionDescription.swift b/Sources/LiveKit/Types/SessionDescription.swift index f1c0625de..21fa43841 100644 --- a/Sources/LiveKit/Types/SessionDescription.swift +++ b/Sources/LiveKit/Types/SessionDescription.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import Foundation @_implementationOnly import WebRTC extension LKRTCSessionDescription { - func toPBType() -> Livekit_SessionDescription { var sd = Livekit_SessionDescription() sd.sdp = sdp @@ -36,7 +35,6 @@ extension LKRTCSessionDescription { } extension Livekit_SessionDescription { - func toRTCType() -> LKRTCSessionDescription { var sdpType: RTCSdpType switch type { diff --git a/Sources/LiveKit/Types/Statistics.swift b/Sources/LiveKit/Types/Statistics.swift index 702b587c9..60dcc05fd 100644 --- a/Sources/LiveKit/Types/Statistics.swift +++ b/Sources/LiveKit/Types/Statistics.swift @@ -19,7 +19,7 @@ import Foundation /// Stats spec defined at https://www.w3.org/TR/webrtc-stats/ public enum StatisticsType: String { - case codec = "codec" + case codec case inboundRtp = "inbound-rtp" case outboundRtp = "outbound-rtp" case remoteInboundRtp = "remote-inbound-rtp" @@ -28,88 +28,87 @@ public enum StatisticsType: String { case mediaPlayout = "media-playout" case peerConnection = "peer-connection" case dataChannel = "data-channel" - case transport = "transport" + case transport case candidatePair = "candidate-pair" case localCandidate = "local-candidate" case remoteCandidate = "remote-candidate" - case certificate = "certificate" + case certificate } public enum QualityLimitationReason: String { - case none = "none" - case cpu = "cpu" - case bandwidth = "bandwidth" - case other = "other" + case none + case cpu + case bandwidth + case other } public enum DtlsRole: String { - case client = "client" - case server = "server" - case unknown = "unknown" + case client + case server + case unknown } public enum IceCandidatePairState: String { - case frozen = "frozen" - case waiting = "waiting" + case frozen + case waiting case inProgress = "in-progress" - case failed = "failed" - case succeeded = "succeeded" + case failed + case succeeded } public enum DataChannelState: String { - case connecting = "connecting" - case open = "open" - case closing = "closing" - case closed = "closed" + case connecting + case open + case closing + case closed } public enum IceRole: String { - case unknown = "unknown" - case controlling = "controlling" - case controlled = "controlled" + case unknown + case controlling + case controlled } public enum DtlsTransportState: String { - case new = "new" - case connecting = "connecting" - case connected = "connected" - case closed = "closed" - case failed = "failed" + case new + case connecting + case connected + case closed + case failed } public enum IceTransportState: String { - case new = "new" - case checking = "checking" - case connected = "connected" - case completed = "completed" - case disconnected = "disconnected" - case failed = "failed" - case closed = "closed" + case new + case checking + case connected + case completed + case disconnected + case failed + case closed } public enum IceCandidateType: String { - case host = "host" - case srflx = "srflx" - case prflx = "prflx" - case relay = "relay" + case host + case srflx + case prflx + case relay } public enum IceServerTransportProtocol: String { - case udp = "udp" - case tcp = "tcp" - case tls = "tls" + case udp + case tcp + case tls } public enum IceTcpCandidateType: String { - case active = "active" - case passive = "passive" - case so = "so" + case active + case passive + case so } // Base class @objc public class Statistics: NSObject, Identifiable { - public let id: String public let type: StatisticsType public let timestamp: Double @@ -118,8 +117,8 @@ public class Statistics: NSObject, Identifiable { init?(id: String, type: StatisticsType, timestamp: Double, - rawValues: [String: NSObject]) { - + rawValues: [String: NSObject]) + { self.id = id self.type = type self.timestamp = timestamp @@ -130,7 +129,6 @@ public class Statistics: NSObject, Identifiable { // type: codec @objc public class CodecStatistics: Statistics { - public let payloadType: UInt? public let transportId: String? public let mimeType: String? @@ -140,14 +138,14 @@ public class CodecStatistics: Statistics { init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.payloadType = rawValues.readOptional("payloadType") - self.transportId = rawValues.readOptional("transportId") - self.mimeType = rawValues.readOptional("mimeType") - self.clockRate = rawValues.readOptional("clockRate") - self.channels = rawValues.readOptional("channels") - self.sdpFmtpLine = rawValues.readOptional("sdpFmtpLine") + rawValues: [String: NSObject]) + { + payloadType = rawValues.readOptional("payloadType") + transportId = rawValues.readOptional("transportId") + mimeType = rawValues.readOptional("mimeType") + clockRate = rawValues.readOptional("clockRate") + channels = rawValues.readOptional("channels") + sdpFmtpLine = rawValues.readOptional("sdpFmtpLine") super.init(id: id, type: .codec, @@ -158,16 +156,15 @@ public class CodecStatistics: Statistics { @objc public class MediaSourceStatistics: Statistics { - public let trackIdentifier: String? public let kind: String? init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.trackIdentifier = rawValues.readOptional("trackIdentifier") - self.kind = rawValues.readOptional("kind") + rawValues: [String: NSObject]) + { + trackIdentifier = rawValues.readOptional("trackIdentifier") + kind = rawValues.readOptional("kind") super.init(id: id, type: .mediaSource, @@ -178,7 +175,6 @@ public class MediaSourceStatistics: Statistics { @objc public class RtpStreamStatistics: Statistics { - public let ssrc: UInt? public let kind: String? public let transportId: String? @@ -187,12 +183,12 @@ public class RtpStreamStatistics: Statistics { override init?(id: String, type: StatisticsType, timestamp: Double, - rawValues: [String: NSObject]) { - - self.ssrc = rawValues.readOptional("ssrc") - self.kind = rawValues.readOptional("kind") - self.transportId = rawValues.readOptional("transportId") - self.codecId = rawValues.readOptional("codecId") + rawValues: [String: NSObject]) + { + ssrc = rawValues.readOptional("ssrc") + kind = rawValues.readOptional("kind") + transportId = rawValues.readOptional("transportId") + codecId = rawValues.readOptional("codecId") super.init(id: id, type: type, @@ -204,7 +200,6 @@ public class RtpStreamStatistics: Statistics { // type: media-playout @objc public class AudioPlayoutStatistics: Statistics { - public let kind: String? public let synthesizedSamplesDuration: Double? public let synthesizedSamplesEvents: UInt? @@ -214,14 +209,14 @@ public class AudioPlayoutStatistics: Statistics { init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.kind = rawValues.readOptional("kind") - self.synthesizedSamplesDuration = rawValues.readOptional("synthesizedSamplesDuration") - self.synthesizedSamplesEvents = rawValues.readOptional("synthesizedSamplesEvents") - self.totalSamplesDuration = rawValues.readOptional("totalSamplesDuration") - self.totalPlayoutDelay = rawValues.readOptional("totalPlayoutDelay") - self.totalSamplesCount = rawValues.readOptional("totalSamplesCount") + rawValues: [String: NSObject]) + { + kind = rawValues.readOptional("kind") + synthesizedSamplesDuration = rawValues.readOptional("synthesizedSamplesDuration") + synthesizedSamplesEvents = rawValues.readOptional("synthesizedSamplesEvents") + totalSamplesDuration = rawValues.readOptional("totalSamplesDuration") + totalPlayoutDelay = rawValues.readOptional("totalPlayoutDelay") + totalSamplesCount = rawValues.readOptional("totalSamplesCount") super.init(id: id, type: .mediaPlayout, @@ -233,16 +228,15 @@ public class AudioPlayoutStatistics: Statistics { // type: peer-connection @objc public class PeerConnectionStatistics: Statistics { - public let dataChannelsOpened: UInt? public let dataChannelsClosed: UInt? init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.dataChannelsOpened = rawValues.readOptional("dataChannelsOpened") - self.dataChannelsClosed = rawValues.readOptional("dataChannelsClosed") + rawValues: [String: NSObject]) + { + dataChannelsOpened = rawValues.readOptional("dataChannelsOpened") + dataChannelsClosed = rawValues.readOptional("dataChannelsClosed") super.init(id: id, type: .peerConnection, @@ -254,7 +248,6 @@ public class PeerConnectionStatistics: Statistics { // type: data-channel @objc public class DataChannelStatistics: Statistics { - public let label: String? public let `protocol`: String? public let dataChannelIdentifier: UInt16? @@ -266,16 +259,16 @@ public class DataChannelStatistics: Statistics { init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.label = rawValues.readOptional("label") + rawValues: [String: NSObject]) + { + label = rawValues.readOptional("label") self.protocol = rawValues.readOptional("protocol") - self.dataChannelIdentifier = rawValues.readOptional("dataChannelIdentifier") - self.state = DataChannelState(rawValue: rawValues.readNonOptional("state")) - self.messagesSent = rawValues.readOptional("messagesSent") - self.bytesSent = rawValues.readOptional("bytesSent") - self.messagesReceived = rawValues.readOptional("messagesReceived") - self.bytesReceived = rawValues.readOptional("bytesReceived") + dataChannelIdentifier = rawValues.readOptional("dataChannelIdentifier") + state = DataChannelState(rawValue: rawValues.readNonOptional("state")) + messagesSent = rawValues.readOptional("messagesSent") + bytesSent = rawValues.readOptional("bytesSent") + messagesReceived = rawValues.readOptional("messagesReceived") + bytesReceived = rawValues.readOptional("bytesReceived") super.init(id: id, type: .dataChannel, @@ -287,7 +280,6 @@ public class DataChannelStatistics: Statistics { // type: transport @objc public class TransportStatistics: Statistics { - public let packetsSent: UInt64? public let packetsReceived: UInt64? public let bytesSent: UInt64? @@ -307,24 +299,24 @@ public class TransportStatistics: Statistics { init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.packetsSent = rawValues.readOptional("packetsSent") - self.packetsReceived = rawValues.readOptional("packetsReceived") - self.bytesSent = rawValues.readOptional("bytesSent") - self.bytesReceived = rawValues.readOptional("bytesReceived") - self.iceRole = IceRole(rawValue: rawValues.readNonOptional("iceRole")) - self.iceLocalUsernameFragment = rawValues.readOptional("iceLocalUsernameFragment") - self.dtlsState = DtlsTransportState(rawValue: rawValues.readNonOptional("dtlsState")) - self.iceState = IceTransportState(rawValue: rawValues.readNonOptional("iceState")) - self.selectedCandidatePairId = rawValues.readOptional("selectedCandidatePairId") - self.localCertificateId = rawValues.readOptional("localCertificateId") - self.remoteCertificateId = rawValues.readOptional("remoteCertificateId") - self.tlsVersion = rawValues.readOptional("tlsVersion") - self.dtlsCipher = rawValues.readOptional("dtlsCipher") - self.dtlsRole = DtlsRole(rawValue: rawValues.readNonOptional("dtlsRole")) - self.srtpCipher = rawValues.readOptional("srtpCipher") - self.selectedCandidatePairChanges = rawValues.readOptional("selectedCandidatePairChanges") + rawValues: [String: NSObject]) + { + packetsSent = rawValues.readOptional("packetsSent") + packetsReceived = rawValues.readOptional("packetsReceived") + bytesSent = rawValues.readOptional("bytesSent") + bytesReceived = rawValues.readOptional("bytesReceived") + iceRole = IceRole(rawValue: rawValues.readNonOptional("iceRole")) + iceLocalUsernameFragment = rawValues.readOptional("iceLocalUsernameFragment") + dtlsState = DtlsTransportState(rawValue: rawValues.readNonOptional("dtlsState")) + iceState = IceTransportState(rawValue: rawValues.readNonOptional("iceState")) + selectedCandidatePairId = rawValues.readOptional("selectedCandidatePairId") + localCertificateId = rawValues.readOptional("localCertificateId") + remoteCertificateId = rawValues.readOptional("remoteCertificateId") + tlsVersion = rawValues.readOptional("tlsVersion") + dtlsCipher = rawValues.readOptional("dtlsCipher") + dtlsRole = DtlsRole(rawValue: rawValues.readNonOptional("dtlsRole")) + srtpCipher = rawValues.readOptional("srtpCipher") + selectedCandidatePairChanges = rawValues.readOptional("selectedCandidatePairChanges") super.init(id: id, type: .transport, @@ -336,7 +328,6 @@ public class TransportStatistics: Statistics { // type: local-candidate, remote-candidate @objc public class IceCandidateStatistics: Statistics { - public let transportId: String? public let address: String? public let port: Int? @@ -354,21 +345,21 @@ public class IceCandidateStatistics: Statistics { override init?(id: String, type: StatisticsType, timestamp: Double, - rawValues: [String: NSObject]) { - - self.transportId = rawValues.readOptional("transportId") - self.address = rawValues.readOptional("address") - self.port = rawValues.readOptional("port") + rawValues: [String: NSObject]) + { + transportId = rawValues.readOptional("transportId") + address = rawValues.readOptional("address") + port = rawValues.readOptional("port") self.protocol = rawValues.readOptional("protocol") - self.candidateType = IceCandidateType(rawValue: rawValues.readNonOptional("candidateType")) - self.priority = rawValues.readOptional("priority") - self.url = rawValues.readOptional("url") - self.relayProtocol = IceServerTransportProtocol(rawValue: rawValues.readNonOptional("relayProtocol")) - self.foundation = rawValues.readOptional("foundation") - self.relatedAddress = rawValues.readOptional("relatedAddress") - self.relatedPort = rawValues.readOptional("relatedPort") - self.usernameFragment = rawValues.readOptional("usernameFragment") - self.tcpType = IceTcpCandidateType(rawValue: rawValues.readNonOptional("tcpType")) + candidateType = IceCandidateType(rawValue: rawValues.readNonOptional("candidateType")) + priority = rawValues.readOptional("priority") + url = rawValues.readOptional("url") + relayProtocol = IceServerTransportProtocol(rawValue: rawValues.readNonOptional("relayProtocol")) + foundation = rawValues.readOptional("foundation") + relatedAddress = rawValues.readOptional("relatedAddress") + relatedPort = rawValues.readOptional("relatedPort") + usernameFragment = rawValues.readOptional("usernameFragment") + tcpType = IceTcpCandidateType(rawValue: rawValues.readNonOptional("tcpType")) super.init(id: id, type: type, @@ -379,11 +370,10 @@ public class IceCandidateStatistics: Statistics { @objc public class LocalIceCandidateStatistics: IceCandidateStatistics { - init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - + rawValues: [String: NSObject]) + { super.init(id: id, type: .localCandidate, timestamp: timestamp, @@ -393,11 +383,10 @@ public class LocalIceCandidateStatistics: IceCandidateStatistics { @objc public class RemoteIceCandidateStatistics: IceCandidateStatistics { - init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - + rawValues: [String: NSObject]) + { super.init(id: id, type: .remoteCandidate, timestamp: timestamp, @@ -408,7 +397,6 @@ public class RemoteIceCandidateStatistics: IceCandidateStatistics { // type: candidate-pair @objc public class IceCandidatePairStatistics: Statistics { - public let transportId: String? public let localCandidateId: String? public let remoteCandidateId: String? @@ -434,30 +422,30 @@ public class IceCandidatePairStatistics: Statistics { init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.transportId = rawValues.readOptional("transportId") - self.localCandidateId = rawValues.readOptional("localCandidateId") - self.remoteCandidateId = rawValues.readOptional("remoteCandidateId") - self.state = IceCandidatePairState(rawValue: rawValues.readNonOptional("state")) - self.nominated = rawValues.readOptional("nominated") - self.packetsSent = rawValues.readOptional("packetsSent") - self.packetsReceived = rawValues.readOptional("packetsReceived") - self.bytesSent = rawValues.readOptional("bytesSent") - self.bytesReceived = rawValues.readOptional("bytesReceived") - self.lastPacketSentTimestamp = rawValues.readOptional("lastPacketSentTimestamp") - self.lastPacketReceivedTimestamp = rawValues.readOptional("lastPacketReceivedTimestamp") - self.totalRoundTripTime = rawValues.readOptional("totalRoundTripTime") - self.currentRoundTripTime = rawValues.readOptional("currentRoundTripTime") - self.availableOutgoingBitrate = rawValues.readOptional("availableOutgoingBitrate") - self.availableIncomingBitrate = rawValues.readOptional("availableIncomingBitrate") - self.requestsReceived = rawValues.readOptional("requestsReceived") - self.requestsSent = rawValues.readOptional("requestsSent") - self.responsesReceived = rawValues.readOptional("responsesReceived") - self.responsesSent = rawValues.readOptional("responsesSent") - self.consentRequestsSent = rawValues.readOptional("consentRequestsSent") - self.packetsDiscardedOnSend = rawValues.readOptional("packetsDiscardedOnSend") - self.bytesDiscardedOnSend = rawValues.readOptional("bytesDiscardedOnSend") + rawValues: [String: NSObject]) + { + transportId = rawValues.readOptional("transportId") + localCandidateId = rawValues.readOptional("localCandidateId") + remoteCandidateId = rawValues.readOptional("remoteCandidateId") + state = IceCandidatePairState(rawValue: rawValues.readNonOptional("state")) + nominated = rawValues.readOptional("nominated") + packetsSent = rawValues.readOptional("packetsSent") + packetsReceived = rawValues.readOptional("packetsReceived") + bytesSent = rawValues.readOptional("bytesSent") + bytesReceived = rawValues.readOptional("bytesReceived") + lastPacketSentTimestamp = rawValues.readOptional("lastPacketSentTimestamp") + lastPacketReceivedTimestamp = rawValues.readOptional("lastPacketReceivedTimestamp") + totalRoundTripTime = rawValues.readOptional("totalRoundTripTime") + currentRoundTripTime = rawValues.readOptional("currentRoundTripTime") + availableOutgoingBitrate = rawValues.readOptional("availableOutgoingBitrate") + availableIncomingBitrate = rawValues.readOptional("availableIncomingBitrate") + requestsReceived = rawValues.readOptional("requestsReceived") + requestsSent = rawValues.readOptional("requestsSent") + responsesReceived = rawValues.readOptional("responsesReceived") + responsesSent = rawValues.readOptional("responsesSent") + consentRequestsSent = rawValues.readOptional("consentRequestsSent") + packetsDiscardedOnSend = rawValues.readOptional("packetsDiscardedOnSend") + bytesDiscardedOnSend = rawValues.readOptional("bytesDiscardedOnSend") super.init(id: id, type: .candidatePair, @@ -469,7 +457,6 @@ public class IceCandidatePairStatistics: Statistics { // type: certificate @objc public class CertificateStatistics: Statistics { - public let fingerprint: String? public let fingerprintAlgorithm: String? public let base64Certificate: String? @@ -477,12 +464,12 @@ public class CertificateStatistics: Statistics { init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.fingerprint = rawValues.readOptional("fingerprint") - self.fingerprintAlgorithm = rawValues.readOptional("fingerprintAlgorithm") - self.base64Certificate = rawValues.readOptional("base64Certificate") - self.issuerCertificateId = rawValues.readOptional("issuerCertificateId") + rawValues: [String: NSObject]) + { + fingerprint = rawValues.readOptional("fingerprint") + fingerprintAlgorithm = rawValues.readOptional("fingerprintAlgorithm") + base64Certificate = rawValues.readOptional("base64Certificate") + issuerCertificateId = rawValues.readOptional("issuerCertificateId") super.init(id: id, type: .certificate, @@ -493,7 +480,6 @@ public class CertificateStatistics: Statistics { @objc public class ReceivedRtpStreamStatistics: RtpStreamStatistics { - public let packetsReceived: UInt64? public let packetsLost: Int64? public let jitter: Double? @@ -501,11 +487,11 @@ public class ReceivedRtpStreamStatistics: RtpStreamStatistics { override init?(id: String, type: StatisticsType, timestamp: Double, - rawValues: [String: NSObject]) { - - self.packetsReceived = rawValues.readOptional("packetsReceived") - self.packetsLost = rawValues.readOptional("packetsLost") - self.jitter = rawValues.readOptional("jitter") + rawValues: [String: NSObject]) + { + packetsReceived = rawValues.readOptional("packetsReceived") + packetsLost = rawValues.readOptional("packetsLost") + jitter = rawValues.readOptional("jitter") super.init(id: id, type: type, @@ -516,17 +502,16 @@ public class ReceivedRtpStreamStatistics: RtpStreamStatistics { @objc public class SentRtpStreamStatistics: RtpStreamStatistics { - public let packetsSent: UInt64? public let bytesSent: UInt64? override init?(id: String, type: StatisticsType, timestamp: Double, - rawValues: [String: NSObject]) { - - self.packetsSent = rawValues.readOptional("packetsSent") - self.bytesSent = rawValues.readOptional("bytesSent") + rawValues: [String: NSObject]) + { + packetsSent = rawValues.readOptional("packetsSent") + bytesSent = rawValues.readOptional("bytesSent") super.init(id: id, type: type, @@ -538,7 +523,6 @@ public class SentRtpStreamStatistics: RtpStreamStatistics { // type: inbound-rtp @objc public class InboundRtpStreamStatistics: ReceivedRtpStreamStatistics { - public let trackIdentifier: String? // let kind: String public let mid: String? @@ -596,59 +580,59 @@ public class InboundRtpStreamStatistics: ReceivedRtpStreamStatistics { init?(id: String, timestamp: Double, rawValues: [String: NSObject], - previous: InboundRtpStreamStatistics?) { - - self.trackIdentifier = rawValues.readOptional("trackIdentifier") + previous: InboundRtpStreamStatistics?) + { + trackIdentifier = rawValues.readOptional("trackIdentifier") // self.kind = kind - self.mid = rawValues.readOptional("mid") - self.remoteId = rawValues.readOptional("remoteId") - self.framesDecoded = rawValues.readOptional("framesDecoded") - self.keyFramesDecoded = rawValues.readOptional("keyFramesDecoded") - self.framesRendered = rawValues.readOptional("framesRendered") - self.framesDropped = rawValues.readOptional("framesDropped") - self.frameWidth = rawValues.readOptional("frameWidth") - self.frameHeight = rawValues.readOptional("frameHeight") - self.framesPerSecond = rawValues.readOptional("framesPerSecond") - self.qpSum = rawValues.readOptional("qpSum") - self.totalDecodeTime = rawValues.readOptional("totalDecodeTime") - self.totalInterFrameDelay = rawValues.readOptional("totalInterFrameDelay") - self.totalSquaredInterFrameDelay = rawValues.readOptional("totalSquaredInterFrameDelay") - self.pauseCount = rawValues.readOptional("pauseCount") - self.totalPausesDuration = rawValues.readOptional("totalPausesDuration") - self.freezeCount = rawValues.readOptional("freezeCount") - self.totalFreezesDuration = rawValues.readOptional("totalFreezesDuration") - self.lastPacketReceivedTimestamp = rawValues.readOptional("lastPacketReceivedTimestamp") - self.headerBytesReceived = rawValues.readOptional("headerBytesReceived") - self.packetsDiscarded = rawValues.readOptional("packetsDiscarded") - self.fecPacketsReceived = rawValues.readOptional("fecPacketsReceived") - self.fecPacketsDiscarded = rawValues.readOptional("fecPacketsDiscarded") - self.bytesReceived = rawValues.readOptional("bytesReceived") - self.nackCount = rawValues.readOptional("nackCount") - self.firCount = rawValues.readOptional("firCount") - self.pliCount = rawValues.readOptional("pliCount") - self.totalProcessingDelay = rawValues.readOptional("totalProcessingDelay") - self.estimatedPlayoutTimestamp = rawValues.readOptional("estimatedPlayoutTimestamp") - self.jitterBufferDelay = rawValues.readOptional("jitterBufferDelay") - self.jitterBufferTargetDelay = rawValues.readOptional("jitterBufferTargetDelay") - self.jitterBufferEmittedCount = rawValues.readOptional("jitterBufferEmittedCount") - self.jitterBufferMinimumDelay = rawValues.readOptional("jitterBufferMinimumDelay") - self.totalSamplesReceived = rawValues.readOptional("totalSamplesReceived") - self.concealedSamples = rawValues.readOptional("concealedSamples") - self.silentConcealedSamples = rawValues.readOptional("silentConcealedSamples") - self.concealmentEvents = rawValues.readOptional("concealmentEvents") - self.insertedSamplesForDeceleration = rawValues.readOptional("insertedSamplesForDeceleration") - self.removedSamplesForAcceleration = rawValues.readOptional("removedSamplesForAcceleration") - self.audioLevel = rawValues.readOptional("audioLevel") - self.totalAudioEnergy = rawValues.readOptional("totalAudioEnergy") - self.totalSamplesDuration = rawValues.readOptional("totalSamplesDuration") - self.framesReceived = rawValues.readOptional("framesReceived") - self.decoderImplementation = rawValues.readOptional("decoderImplementation") - self.playoutId = rawValues.readOptional("playoutId") - self.powerEfficientDecoder = rawValues.readOptional("powerEfficientDecoder") - self.framesAssembledFromMultiplePackets = rawValues.readOptional("framesAssembledFromMultiplePackets") - self.totalAssemblyTime = rawValues.readOptional("totalAssemblyTime") - self.retransmittedPacketsReceived = rawValues.readOptional("retransmittedPacketsReceived") - self.retransmittedBytesReceived = rawValues.readOptional("retransmittedBytesReceived") + mid = rawValues.readOptional("mid") + remoteId = rawValues.readOptional("remoteId") + framesDecoded = rawValues.readOptional("framesDecoded") + keyFramesDecoded = rawValues.readOptional("keyFramesDecoded") + framesRendered = rawValues.readOptional("framesRendered") + framesDropped = rawValues.readOptional("framesDropped") + frameWidth = rawValues.readOptional("frameWidth") + frameHeight = rawValues.readOptional("frameHeight") + framesPerSecond = rawValues.readOptional("framesPerSecond") + qpSum = rawValues.readOptional("qpSum") + totalDecodeTime = rawValues.readOptional("totalDecodeTime") + totalInterFrameDelay = rawValues.readOptional("totalInterFrameDelay") + totalSquaredInterFrameDelay = rawValues.readOptional("totalSquaredInterFrameDelay") + pauseCount = rawValues.readOptional("pauseCount") + totalPausesDuration = rawValues.readOptional("totalPausesDuration") + freezeCount = rawValues.readOptional("freezeCount") + totalFreezesDuration = rawValues.readOptional("totalFreezesDuration") + lastPacketReceivedTimestamp = rawValues.readOptional("lastPacketReceivedTimestamp") + headerBytesReceived = rawValues.readOptional("headerBytesReceived") + packetsDiscarded = rawValues.readOptional("packetsDiscarded") + fecPacketsReceived = rawValues.readOptional("fecPacketsReceived") + fecPacketsDiscarded = rawValues.readOptional("fecPacketsDiscarded") + bytesReceived = rawValues.readOptional("bytesReceived") + nackCount = rawValues.readOptional("nackCount") + firCount = rawValues.readOptional("firCount") + pliCount = rawValues.readOptional("pliCount") + totalProcessingDelay = rawValues.readOptional("totalProcessingDelay") + estimatedPlayoutTimestamp = rawValues.readOptional("estimatedPlayoutTimestamp") + jitterBufferDelay = rawValues.readOptional("jitterBufferDelay") + jitterBufferTargetDelay = rawValues.readOptional("jitterBufferTargetDelay") + jitterBufferEmittedCount = rawValues.readOptional("jitterBufferEmittedCount") + jitterBufferMinimumDelay = rawValues.readOptional("jitterBufferMinimumDelay") + totalSamplesReceived = rawValues.readOptional("totalSamplesReceived") + concealedSamples = rawValues.readOptional("concealedSamples") + silentConcealedSamples = rawValues.readOptional("silentConcealedSamples") + concealmentEvents = rawValues.readOptional("concealmentEvents") + insertedSamplesForDeceleration = rawValues.readOptional("insertedSamplesForDeceleration") + removedSamplesForAcceleration = rawValues.readOptional("removedSamplesForAcceleration") + audioLevel = rawValues.readOptional("audioLevel") + totalAudioEnergy = rawValues.readOptional("totalAudioEnergy") + totalSamplesDuration = rawValues.readOptional("totalSamplesDuration") + framesReceived = rawValues.readOptional("framesReceived") + decoderImplementation = rawValues.readOptional("decoderImplementation") + playoutId = rawValues.readOptional("playoutId") + powerEfficientDecoder = rawValues.readOptional("powerEfficientDecoder") + framesAssembledFromMultiplePackets = rawValues.readOptional("framesAssembledFromMultiplePackets") + totalAssemblyTime = rawValues.readOptional("totalAssemblyTime") + retransmittedPacketsReceived = rawValues.readOptional("retransmittedPacketsReceived") + retransmittedBytesReceived = rawValues.readOptional("retransmittedBytesReceived") self.previous = previous @@ -662,7 +646,6 @@ public class InboundRtpStreamStatistics: ReceivedRtpStreamStatistics { // type: remote-inbound-rtp @objc public class RemoteInboundRtpStreamStatistics: ReceivedRtpStreamStatistics { - public let localId: String? public let roundTripTime: Double? public let totalRoundTripTime: Double? @@ -671,13 +654,13 @@ public class RemoteInboundRtpStreamStatistics: ReceivedRtpStreamStatistics { init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.localId = rawValues.readOptional("localId") - self.roundTripTime = rawValues.readOptional("roundTripTime") - self.totalRoundTripTime = rawValues.readOptional("totalRoundTripTime") - self.fractionLost = rawValues.readOptional("fractionLost") - self.roundTripTimeMeasurements = rawValues.readOptional("roundTripTimeMeasurements") + rawValues: [String: NSObject]) + { + localId = rawValues.readOptional("localId") + roundTripTime = rawValues.readOptional("roundTripTime") + totalRoundTripTime = rawValues.readOptional("totalRoundTripTime") + fractionLost = rawValues.readOptional("fractionLost") + roundTripTimeMeasurements = rawValues.readOptional("roundTripTimeMeasurements") super.init(id: id, type: .remoteInboundRtp, @@ -689,19 +672,17 @@ public class RemoteInboundRtpStreamStatistics: ReceivedRtpStreamStatistics { // type: outbound-rtp @objc public class OutboundRtpStreamStatistics: SentRtpStreamStatistics { - public class QualityLimitationDurations { - public let none: Double? public let cpu: Double? public let bandwidth: Double? public let other: Double? init?(rawValues: [String: NSObject]) { - self.none = rawValues.readOptional("none") - self.cpu = rawValues.readOptional("cpu") - self.bandwidth = rawValues.readOptional("bandwidth") - self.other = rawValues.readOptional("other") + none = rawValues.readOptional("none") + cpu = rawValues.readOptional("cpu") + bandwidth = rawValues.readOptional("bandwidth") + other = rawValues.readOptional("other") if none == nil, cpu == nil, bandwidth == nil, other == nil { return nil @@ -744,37 +725,37 @@ public class OutboundRtpStreamStatistics: SentRtpStreamStatistics { init?(id: String, timestamp: Double, rawValues: [String: NSObject], - previous: OutboundRtpStreamStatistics?) { - - self.mid = rawValues.readOptional("mid") - self.mediaSourceId = rawValues.readOptional("mediaSourceId") - self.remoteId = rawValues.readOptional("remoteId") - self.rid = rawValues.readOptional("rid") - self.headerBytesSent = rawValues.readOptional("headerBytesSent") - self.retransmittedPacketsSent = rawValues.readOptional("retransmittedPacketsSent") - self.retransmittedBytesSent = rawValues.readOptional("retransmittedBytesSent") - self.targetBitrate = rawValues.readOptional("targetBitrate") - self.totalEncodedBytesTarget = rawValues.readOptional("totalEncodedBytesTarget") - self.frameWidth = rawValues.readOptional("frameWidth") - self.frameHeight = rawValues.readOptional("frameHeight") - self.framesPerSecond = rawValues.readOptional("framesPerSecond") - self.framesSent = rawValues.readOptional("framesSent") - self.hugeFramesSent = rawValues.readOptional("hugeFramesSent") - self.framesEncoded = rawValues.readOptional("framesEncoded") - self.keyFramesEncoded = rawValues.readOptional("keyFramesEncoded") - self.qpSum = rawValues.readOptional("qpSum") - self.totalEncodeTime = rawValues.readOptional("totalEncodeTime") - self.totalPacketSendDelay = rawValues.readOptional("totalPacketSendDelay") - self.qualityLimitationReason = QualityLimitationReason(rawValue: rawValues.readNonOptional("qualityLimitationReason")) - self.qualityLimitationDurations = QualityLimitationDurations(rawValues: rawValues.readNonOptional("qualityLimitationDurations")) - self.qualityLimitationResolutionChanges = rawValues.readOptional("qualityLimitationResolutionChanges") - self.nackCount = rawValues.readOptional("nackCount") - self.firCount = rawValues.readOptional("firCount") - self.pliCount = rawValues.readOptional("pliCount") - self.encoderImplementation = rawValues.readOptional("encoderImplementation") - self.powerEfficientEncoder = rawValues.readOptional("powerEfficientEncoder") - self.active = rawValues.readOptional("active") - self.scalabilityMode = rawValues.readOptional("scalabilityMode") + previous: OutboundRtpStreamStatistics?) + { + mid = rawValues.readOptional("mid") + mediaSourceId = rawValues.readOptional("mediaSourceId") + remoteId = rawValues.readOptional("remoteId") + rid = rawValues.readOptional("rid") + headerBytesSent = rawValues.readOptional("headerBytesSent") + retransmittedPacketsSent = rawValues.readOptional("retransmittedPacketsSent") + retransmittedBytesSent = rawValues.readOptional("retransmittedBytesSent") + targetBitrate = rawValues.readOptional("targetBitrate") + totalEncodedBytesTarget = rawValues.readOptional("totalEncodedBytesTarget") + frameWidth = rawValues.readOptional("frameWidth") + frameHeight = rawValues.readOptional("frameHeight") + framesPerSecond = rawValues.readOptional("framesPerSecond") + framesSent = rawValues.readOptional("framesSent") + hugeFramesSent = rawValues.readOptional("hugeFramesSent") + framesEncoded = rawValues.readOptional("framesEncoded") + keyFramesEncoded = rawValues.readOptional("keyFramesEncoded") + qpSum = rawValues.readOptional("qpSum") + totalEncodeTime = rawValues.readOptional("totalEncodeTime") + totalPacketSendDelay = rawValues.readOptional("totalPacketSendDelay") + qualityLimitationReason = QualityLimitationReason(rawValue: rawValues.readNonOptional("qualityLimitationReason")) + qualityLimitationDurations = QualityLimitationDurations(rawValues: rawValues.readNonOptional("qualityLimitationDurations")) + qualityLimitationResolutionChanges = rawValues.readOptional("qualityLimitationResolutionChanges") + nackCount = rawValues.readOptional("nackCount") + firCount = rawValues.readOptional("firCount") + pliCount = rawValues.readOptional("pliCount") + encoderImplementation = rawValues.readOptional("encoderImplementation") + powerEfficientEncoder = rawValues.readOptional("powerEfficientEncoder") + active = rawValues.readOptional("active") + scalabilityMode = rawValues.readOptional("scalabilityMode") self.previous = previous @@ -788,7 +769,6 @@ public class OutboundRtpStreamStatistics: SentRtpStreamStatistics { // type: remote-outbound-rtp @objc public class RemoteOutboundRtpStreamStatistics: SentRtpStreamStatistics { - public let localId: String? public let remoteTimestamp: Double? public let reportsSent: UInt64? @@ -798,14 +778,14 @@ public class RemoteOutboundRtpStreamStatistics: SentRtpStreamStatistics { init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.localId = rawValues.readOptional("localId") - self.remoteTimestamp = rawValues.readOptional("remoteTimestamp") - self.reportsSent = rawValues.readOptional("reportsSent") - self.roundTripTime = rawValues.readOptional("roundTripTime") - self.totalRoundTripTime = rawValues.readOptional("totalRoundTripTime") - self.roundTripTimeMeasurements = rawValues.readOptional("roundTripTimeMeasurements") + rawValues: [String: NSObject]) + { + localId = rawValues.readOptional("localId") + remoteTimestamp = rawValues.readOptional("remoteTimestamp") + reportsSent = rawValues.readOptional("reportsSent") + roundTripTime = rawValues.readOptional("roundTripTime") + totalRoundTripTime = rawValues.readOptional("totalRoundTripTime") + roundTripTimeMeasurements = rawValues.readOptional("roundTripTimeMeasurements") super.init(id: id, type: .remoteOutboundRtp, @@ -816,7 +796,6 @@ public class RemoteOutboundRtpStreamStatistics: SentRtpStreamStatistics { @objc public class AudioSourceStatistics: MediaSourceStatistics { - public let audioLevel: Double? public let totalAudioEnergy: Double? public let totalSamplesDuration: Double? @@ -829,17 +808,17 @@ public class AudioSourceStatistics: MediaSourceStatistics { override init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.audioLevel = rawValues.readOptional("audioLevel") - self.totalAudioEnergy = rawValues.readOptional("totalAudioEnergy") - self.totalSamplesDuration = rawValues.readOptional("totalSamplesDuration") - self.echoReturnLoss = rawValues.readOptional("echoReturnLoss") - self.echoReturnLossEnhancement = rawValues.readOptional("echoReturnLossEnhancement") - self.droppedSamplesDuration = rawValues.readOptional("droppedSamplesDuration") - self.droppedSamplesEvents = rawValues.readOptional("droppedSamplesEvents") - self.totalCaptureDelay = rawValues.readOptional("totalCaptureDelay") - self.totalSamplesCaptured = rawValues.readOptional("totalSamplesCaptured") + rawValues: [String: NSObject]) + { + audioLevel = rawValues.readOptional("audioLevel") + totalAudioEnergy = rawValues.readOptional("totalAudioEnergy") + totalSamplesDuration = rawValues.readOptional("totalSamplesDuration") + echoReturnLoss = rawValues.readOptional("echoReturnLoss") + echoReturnLossEnhancement = rawValues.readOptional("echoReturnLossEnhancement") + droppedSamplesDuration = rawValues.readOptional("droppedSamplesDuration") + droppedSamplesEvents = rawValues.readOptional("droppedSamplesEvents") + totalCaptureDelay = rawValues.readOptional("totalCaptureDelay") + totalSamplesCaptured = rawValues.readOptional("totalSamplesCaptured") super.init(id: id, timestamp: timestamp, @@ -849,7 +828,6 @@ public class AudioSourceStatistics: MediaSourceStatistics { @objc public class VideoSourceStatistics: MediaSourceStatistics { - public let width: UInt? public let height: UInt? public let frames: UInt? @@ -857,12 +835,12 @@ public class VideoSourceStatistics: MediaSourceStatistics { override init?(id: String, timestamp: Double, - rawValues: [String: NSObject]) { - - self.width = rawValues.readOptional("width") - self.height = rawValues.readOptional("height") - self.frames = rawValues.readOptional("frames") - self.framesPerSecond = rawValues.readOptional("framesPerSecond") + rawValues: [String: NSObject]) + { + width = rawValues.readOptional("width") + height = rawValues.readOptional("height") + frames = rawValues.readOptional("frames") + framesPerSecond = rawValues.readOptional("framesPerSecond") super.init(id: id, timestamp: timestamp, @@ -870,8 +848,7 @@ public class VideoSourceStatistics: MediaSourceStatistics { } } -internal extension Dictionary where Key == String, Value == NSObject { - +extension [String: NSObject] { func readOptional(_ key: String) -> Int? { self[key] as? Int } diff --git a/Sources/LiveKit/Types/TrackPublishState.swift b/Sources/LiveKit/Types/TrackPublishState.swift index ff18c5181..d4e8f36b5 100644 --- a/Sources/LiveKit/Types/TrackPublishState.swift +++ b/Sources/LiveKit/Types/TrackPublishState.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Sources/LiveKit/Types/TrackSettings.swift b/Sources/LiveKit/Types/TrackSettings.swift index 941666186..8d45bfa50 100644 --- a/Sources/LiveKit/Types/TrackSettings.swift +++ b/Sources/LiveKit/Types/TrackSettings.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,7 @@ import Foundation -internal struct TrackSettings: Equatable, Hashable { - +struct TrackSettings: Equatable, Hashable { let enabled: Bool let dimensions: Dimensions let videoQuality: VideoQuality @@ -26,8 +25,8 @@ internal struct TrackSettings: Equatable, Hashable { init(enabled: Bool = false, dimensions: Dimensions = .zero, videoQuality: VideoQuality = .low, - preferredFPS: UInt = 0) { - + preferredFPS: UInt = 0) + { self.enabled = enabled self.dimensions = dimensions self.videoQuality = videoQuality @@ -37,7 +36,8 @@ internal struct TrackSettings: Equatable, Hashable { func copyWith(enabled: Bool? = nil, dimensions: Dimensions? = nil, videoQuality: VideoQuality? = nil, - preferredFPS: UInt? = nil) -> TrackSettings { + preferredFPS: UInt? = nil) -> TrackSettings + { TrackSettings(enabled: enabled ?? self.enabled, dimensions: dimensions ?? self.dimensions, videoQuality: videoQuality ?? self.videoQuality, diff --git a/Sources/LiveKit/Types/TrackSource.swift b/Sources/LiveKit/Types/TrackSource.swift index 0381b7cb7..8fbc96bdc 100644 --- a/Sources/LiveKit/Types/TrackSource.swift +++ b/Sources/LiveKit/Types/TrackSource.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import Foundation extension Livekit_TrackSource { - func toLKType() -> Track.Source { switch self { case .camera: return .camera @@ -30,7 +29,6 @@ extension Livekit_TrackSource { } extension Track.Source { - func toPBType() -> Livekit_TrackSource { switch self { case .camera: return .camera diff --git a/Sources/LiveKit/Types/TrackStatistics.swift b/Sources/LiveKit/Types/TrackStatistics.swift index 958a1cd92..c67c3cbce 100644 --- a/Sources/LiveKit/Types/TrackStatistics.swift +++ b/Sources/LiveKit/Types/TrackStatistics.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import Foundation @objc public class TrackStatistics: NSObject { - public let codec: [CodecStatistics] public let transportStats: TransportStatistics? public let videoSource: [VideoSourceStatistics] @@ -38,34 +37,32 @@ public class TrackStatistics: NSObject { public let remoteOutboundRtpStream: [RemoteOutboundRtpStreamStatistics] init(from stats: [LKRTCStatistics], prevStatistics: TrackStatistics?) { - let stats = stats.map { $0.toLKType(prevStatistics: prevStatistics) }.compactMap { $0 } - self.codec = stats.compactMap { $0 as? CodecStatistics } - self.videoSource = stats.compactMap { $0 as? VideoSourceStatistics } - self.certificate = stats.compactMap { $0 as? CertificateStatistics } - self.iceCandidatePair = stats.compactMap { $0 as? IceCandidatePairStatistics } - self.inboundRtpStream = stats.compactMap { $0 as? InboundRtpStreamStatistics } - self.outboundRtpStream = stats.compactMap { $0 as? OutboundRtpStreamStatistics } - self.remoteInboundRtpStream = stats.compactMap { $0 as? RemoteInboundRtpStreamStatistics } - self.remoteOutboundRtpStream = stats.compactMap { $0 as? RemoteOutboundRtpStreamStatistics } + codec = stats.compactMap { $0 as? CodecStatistics } + videoSource = stats.compactMap { $0 as? VideoSourceStatistics } + certificate = stats.compactMap { $0 as? CertificateStatistics } + iceCandidatePair = stats.compactMap { $0 as? IceCandidatePairStatistics } + inboundRtpStream = stats.compactMap { $0 as? InboundRtpStreamStatistics } + outboundRtpStream = stats.compactMap { $0 as? OutboundRtpStreamStatistics } + remoteInboundRtpStream = stats.compactMap { $0 as? RemoteInboundRtpStreamStatistics } + remoteOutboundRtpStream = stats.compactMap { $0 as? RemoteOutboundRtpStreamStatistics } let t = stats.compactMap { $0 as? TransportStatistics } assert(t.count <= 1, "More than 1 TransportStatistics exists") - self.transportStats = t.first + transportStats = t.first let l = stats.compactMap { $0 as? LocalIceCandidateStatistics } assert(l.count <= 1, "More than 1 LocalIceCandidateStatistics exists") - self.localIceCandidate = l.first + localIceCandidate = l.first let r = stats.compactMap { $0 as? RemoteIceCandidateStatistics } assert(r.count <= 1, "More than 1 RemoteIceCandidateStatistics exists") - self.remoteIceCandidate = r.first + remoteIceCandidate = r.first } } extension LKRTCStatistics { - func toLKType(prevStatistics: TrackStatistics?) -> Statistics? { switch type { case "codec": return CodecStatistics(id: id, timestamp: timestamp_us, rawValues: values) @@ -98,27 +95,24 @@ extension LKRTCStatistics { } } -extension TrackStatistics { - - public override var description: String { +public extension TrackStatistics { + override var description: String { "TrackStatistics(inboundRtpStream: \(String(describing: inboundRtpStream)))" } } extension OutboundRtpStreamStatistics { - /// Index of the rid. var ridIndex: Int { - guard let rid = rid, let idx = VideoQuality.rids.firstIndex(of: rid) else { + guard let rid, let idx = VideoQuality.rids.firstIndex(of: rid) else { return -1 } return idx } } -extension Sequence where Element == OutboundRtpStreamStatistics { - - public func sortedByRidIndex() -> [OutboundRtpStreamStatistics] { +public extension Sequence { + func sortedByRidIndex() -> [OutboundRtpStreamStatistics] { sorted { $0.ridIndex > $1.ridIndex } } } diff --git a/Sources/LiveKit/Types/TrackStreamState.swift b/Sources/LiveKit/Types/TrackStreamState.swift index 9f468be3c..efa8315bc 100644 --- a/Sources/LiveKit/Types/TrackStreamState.swift +++ b/Sources/LiveKit/Types/TrackStreamState.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ public enum StreamState: Int { } extension Livekit_StreamState { - func toLKType() -> StreamState { switch self { case .active: return .active diff --git a/Sources/LiveKit/Types/TrackType.swift b/Sources/LiveKit/Types/TrackType.swift index 1c15a7a87..4e6f147b2 100644 --- a/Sources/LiveKit/Types/TrackType.swift +++ b/Sources/LiveKit/Types/TrackType.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import Foundation extension Livekit_TrackType { - func toLKType() -> Track.Kind { switch self { case .audio: @@ -31,7 +30,6 @@ extension Livekit_TrackType { } extension Track.Kind { - func toPBType() -> Livekit_TrackType { switch self { case .audio: diff --git a/Sources/LiveKit/Types/VideoEncoding+Comparable.swift b/Sources/LiveKit/Types/VideoEncoding+Comparable.swift index 08dc977f9..d3c186e05 100644 --- a/Sources/LiveKit/Types/VideoEncoding+Comparable.swift +++ b/Sources/LiveKit/Types/VideoEncoding+Comparable.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,7 @@ import Foundation extension VideoEncoding: Comparable { - public static func < (lhs: VideoEncoding, rhs: VideoEncoding) -> Bool { - if lhs.maxBitrate == rhs.maxBitrate { return lhs.maxFps < rhs.maxFps } diff --git a/Sources/LiveKit/Types/VideoEncoding.swift b/Sources/LiveKit/Types/VideoEncoding.swift index 43fa5a936..5be0c8744 100644 --- a/Sources/LiveKit/Types/VideoEncoding.swift +++ b/Sources/LiveKit/Types/VideoEncoding.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import Foundation @objc public class VideoEncoding: NSObject, MediaEncoding { - @objc public var maxBitrate: Int @@ -33,13 +32,13 @@ public class VideoEncoding: NSObject, MediaEncoding { // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.maxBitrate == other.maxBitrate && - self.maxFps == other.maxFps + return maxBitrate == other.maxBitrate && + maxFps == other.maxFps } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(maxBitrate) hasher.combine(maxFps) diff --git a/Sources/LiveKit/Types/VideoFrame.swift b/Sources/LiveKit/Types/VideoFrame.swift index e682d1abb..c146a436d 100644 --- a/Sources/LiveKit/Types/VideoFrame.swift +++ b/Sources/LiveKit/Types/VideoFrame.swift @@ -14,24 +14,22 @@ * limitations under the License. */ -import Foundation import CoreMedia +import Foundation @_implementationOnly import WebRTC -public protocol VideoBuffer { +public protocol VideoBuffer {} -} - -internal protocol RTCCompatibleVideoBuffer { +protocol RTCCompatibleVideoBuffer { func toRTCType() -> LKRTCVideoFrameBuffer } public class CVPixelVideoBuffer: VideoBuffer, RTCCompatibleVideoBuffer { // Internal RTC type - internal let rtcType: LKRTCCVPixelBuffer - internal init(rtcCVPixelBuffer: LKRTCCVPixelBuffer) { - self.rtcType = rtcCVPixelBuffer + let rtcType: LKRTCCVPixelBuffer + init(rtcCVPixelBuffer: LKRTCCVPixelBuffer) { + rtcType = rtcCVPixelBuffer } func toRTCType() -> LKRTCVideoFrameBuffer { @@ -41,9 +39,9 @@ public class CVPixelVideoBuffer: VideoBuffer, RTCCompatibleVideoBuffer { public struct I420VideoBuffer: VideoBuffer, RTCCompatibleVideoBuffer { // Internal RTC type - internal let rtcType: LKRTCI420Buffer - internal init(rtcI420Buffer: LKRTCI420Buffer) { - self.rtcType = rtcI420Buffer + let rtcType: LKRTCI420Buffer + init(rtcI420Buffer: LKRTCI420Buffer) { + rtcType = rtcI420Buffer } func toRTCType() -> LKRTCVideoFrameBuffer { @@ -52,7 +50,6 @@ public struct I420VideoBuffer: VideoBuffer, RTCCompatibleVideoBuffer { } public class VideoFrame: NSObject { - let dimensions: Dimensions let rotation: VideoRotation let timeStampNs: Int64 @@ -63,8 +60,8 @@ public class VideoFrame: NSObject { public init(dimensions: Dimensions, rotation: VideoRotation, timeStampNs: Int64, - buffer: VideoBuffer) { - + buffer: VideoBuffer) + { self.dimensions = dimensions self.rotation = rotation self.timeStampNs = timeStampNs @@ -72,10 +69,8 @@ public class VideoFrame: NSObject { } } -internal extension LKRTCVideoFrame { - +extension LKRTCVideoFrame { func toLKType() -> VideoFrame? { - let lkBuffer: VideoBuffer if let rtcBuffer = buffer as? LKRTCCVPixelBuffer { @@ -94,8 +89,7 @@ internal extension LKRTCVideoFrame { } } -internal extension VideoFrame { - +extension VideoFrame { func toRTCType() -> LKRTCVideoFrame { // This should never happen guard let buffer = buffer as? RTCCompatibleVideoBuffer else { fatalError("Buffer must be a RTCCompatibleVideoBuffer") } diff --git a/Sources/LiveKit/Types/VideoParameters+Comparable.swift b/Sources/LiveKit/Types/VideoParameters+Comparable.swift index 394b0f706..b11916dea 100644 --- a/Sources/LiveKit/Types/VideoParameters+Comparable.swift +++ b/Sources/LiveKit/Types/VideoParameters+Comparable.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,7 @@ import Foundation extension VideoParameters: Comparable { - public static func < (lhs: VideoParameters, rhs: VideoParameters) -> Bool { - if lhs.dimensions.area == rhs.dimensions.area { return lhs.encoding < rhs.encoding } diff --git a/Sources/LiveKit/Types/VideoParameters.swift b/Sources/LiveKit/Types/VideoParameters.swift index 00a8e9682..4599df43e 100644 --- a/Sources/LiveKit/Types/VideoParameters.swift +++ b/Sources/LiveKit/Types/VideoParameters.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ import Foundation -extension Collection where Element == VideoParameters { - +extension Collection { func suggestedPresetIndex(dimensions: Dimensions? = nil, - videoEncoding: VideoEncoding? = nil) -> Int { + videoEncoding: VideoEncoding? = nil) -> Int + { // self must at lease have 1 element assert(!isEmpty) // dimensions or videoEndocing is required @@ -27,16 +27,16 @@ extension Collection where Element == VideoParameters { var result = 0 for preset in self { - if let dimensions = dimensions, + if let dimensions, dimensions.width >= preset.dimensions.width, - dimensions.height >= preset.dimensions.height { - + dimensions.height >= preset.dimensions.height + { result += 1 - } else if let videoEncoding = videoEncoding, - videoEncoding.maxBitrate >= preset.encoding.maxBitrate { + } else if let videoEncoding, + videoEncoding.maxBitrate >= preset.encoding.maxBitrate + { result += 1 } - } return result } @@ -44,7 +44,6 @@ extension Collection where Element == VideoParameters { @objc public class VideoParameters: NSObject { - @objc public let dimensions: Dimensions @@ -59,13 +58,13 @@ public class VideoParameters: NSObject { // MARK: - Equal - public override func isEqual(_ object: Any?) -> Bool { + override public func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } - return self.dimensions == other.dimensions && - self.encoding == other.encoding + return dimensions == other.dimensions && + encoding == other.encoding } - public override var hash: Int { + override public var hash: Int { var hasher = Hasher() hasher.combine(dimensions) hasher.combine(encoding) @@ -75,10 +74,8 @@ public class VideoParameters: NSObject { // MARK: - Computation -internal extension VideoParameters { - +extension VideoParameters { func defaultScreenShareSimulcastLayers() -> [VideoParameters] { - struct Layer { let scaleDownBy: Double let fps: Int @@ -110,8 +107,7 @@ internal extension VideoParameters { // MARK: - Presets @objc -extension VideoParameters { - +public extension VideoParameters { internal static let presets43 = [ presetH120_43, presetH180_43, @@ -121,7 +117,7 @@ extension VideoParameters { presetH540_43, presetH720_43, presetH1080_43, - presetH1440_43 + presetH1440_43, ] internal static let presets169 = [ @@ -133,7 +129,7 @@ extension VideoParameters { presetH720_169, presetH1080_169, presetH1440_169, - presetH2160_169 + presetH2160_169, ] internal static let presetsScreenShare = [ @@ -141,133 +137,133 @@ extension VideoParameters { presetScreenShareH720FPS5, presetScreenShareH720FPS15, presetScreenShareH1080FPS15, - presetScreenShareH1080FPS30 + presetScreenShareH1080FPS30, ] internal static let defaultSimulcastPresets169 = [ presetH180_169, - presetH360_169 + presetH360_169, ] internal static let defaultSimulcastPresets43 = [ presetH180_43, - presetH360_43 + presetH360_43, ] // 16:9 aspect ratio - public static let presetH90_169 = VideoParameters( + static let presetH90_169 = VideoParameters( dimensions: .h90_169, - encoding: VideoEncoding(maxBitrate: 90_000, maxFps: 15) + encoding: VideoEncoding(maxBitrate: 90000, maxFps: 15) ) - public static let presetH180_169 = VideoParameters( + static let presetH180_169 = VideoParameters( dimensions: .h180_169, encoding: VideoEncoding(maxBitrate: 160_000, maxFps: 15) ) - public static let presetH216_169 = VideoParameters( + static let presetH216_169 = VideoParameters( dimensions: .h216_169, encoding: VideoEncoding(maxBitrate: 180_000, maxFps: 15) ) - public static let presetH360_169 = VideoParameters( + static let presetH360_169 = VideoParameters( dimensions: .h360_169, encoding: VideoEncoding(maxBitrate: 450_000, maxFps: 20) ) - public static let presetH540_169 = VideoParameters( + static let presetH540_169 = VideoParameters( dimensions: .h540_169, encoding: VideoEncoding(maxBitrate: 800_000, maxFps: 25) ) - public static let presetH720_169 = VideoParameters( + static let presetH720_169 = VideoParameters( dimensions: .h720_169, encoding: VideoEncoding(maxBitrate: 1_700_000, maxFps: 30) ) - public static let presetH1080_169 = VideoParameters( + static let presetH1080_169 = VideoParameters( dimensions: .h1080_169, encoding: VideoEncoding(maxBitrate: 3_000_000, maxFps: 30) ) - public static let presetH1440_169 = VideoParameters( + static let presetH1440_169 = VideoParameters( dimensions: .h1440_169, encoding: VideoEncoding(maxBitrate: 5_000_000, maxFps: 30) ) - public static let presetH2160_169 = VideoParameters( + static let presetH2160_169 = VideoParameters( dimensions: .h2160_169, encoding: VideoEncoding(maxBitrate: 8_000_000, maxFps: 30) ) // 4:3 aspect ratio - public static let presetH120_43 = VideoParameters( + static let presetH120_43 = VideoParameters( dimensions: .h120_43, - encoding: VideoEncoding(maxBitrate: 70_000, maxFps: 15) + encoding: VideoEncoding(maxBitrate: 70000, maxFps: 15) ) - public static let presetH180_43 = VideoParameters( + static let presetH180_43 = VideoParameters( dimensions: .h180_43, encoding: VideoEncoding(maxBitrate: 125_000, maxFps: 15) ) - public static let presetH240_43 = VideoParameters( + static let presetH240_43 = VideoParameters( dimensions: .h240_43, encoding: VideoEncoding(maxBitrate: 140_000, maxFps: 15) ) - public static let presetH360_43 = VideoParameters( + static let presetH360_43 = VideoParameters( dimensions: .h360_43, encoding: VideoEncoding(maxBitrate: 330_000, maxFps: 20) ) - public static let presetH480_43 = VideoParameters( + static let presetH480_43 = VideoParameters( dimensions: .h480_43, encoding: VideoEncoding(maxBitrate: 500_000, maxFps: 20) ) - public static let presetH540_43 = VideoParameters( + static let presetH540_43 = VideoParameters( dimensions: .h540_43, encoding: VideoEncoding(maxBitrate: 600_000, maxFps: 25) ) - public static let presetH720_43 = VideoParameters( + static let presetH720_43 = VideoParameters( dimensions: .h720_43, encoding: VideoEncoding(maxBitrate: 1_300_000, maxFps: 30) ) - public static let presetH1080_43 = VideoParameters( + static let presetH1080_43 = VideoParameters( dimensions: .h1080_43, encoding: VideoEncoding(maxBitrate: 2_300_000, maxFps: 30) ) - public static let presetH1440_43 = VideoParameters( + static let presetH1440_43 = VideoParameters( dimensions: .h1440_43, encoding: VideoEncoding(maxBitrate: 3_800_000, maxFps: 30) ) // Screen share - public static let presetScreenShareH360FPS3 = VideoParameters( + static let presetScreenShareH360FPS3 = VideoParameters( dimensions: .h360_169, encoding: VideoEncoding(maxBitrate: 200_000, maxFps: 3) ) - public static let presetScreenShareH720FPS5 = VideoParameters( + static let presetScreenShareH720FPS5 = VideoParameters( dimensions: .h720_169, encoding: VideoEncoding(maxBitrate: 400_000, maxFps: 5) ) - public static let presetScreenShareH720FPS15 = VideoParameters( + static let presetScreenShareH720FPS15 = VideoParameters( dimensions: .h720_169, encoding: VideoEncoding(maxBitrate: 1_500_000, maxFps: 15) ) - public static let presetScreenShareH1080FPS15 = VideoParameters( + static let presetScreenShareH1080FPS15 = VideoParameters( dimensions: .h1080_169, encoding: VideoEncoding(maxBitrate: 2_500_000, maxFps: 15) ) - public static let presetScreenShareH1080FPS30 = VideoParameters( + static let presetScreenShareH1080FPS30 = VideoParameters( dimensions: .h1080_169, encoding: VideoEncoding(maxBitrate: 4_000_000, maxFps: 30) ) diff --git a/Sources/LiveKit/Types/VideoQuality.swift b/Sources/LiveKit/Types/VideoQuality.swift index 3aa0ce635..842322a52 100644 --- a/Sources/LiveKit/Types/VideoQuality.swift +++ b/Sources/LiveKit/Types/VideoQuality.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,40 +16,37 @@ import Foundation -internal enum VideoQuality { +enum VideoQuality { case low case medium case high } -internal extension VideoQuality { - +extension VideoQuality { static let rids = ["q", "h", "f"] } -internal extension VideoQuality { - +extension VideoQuality { private static let toPBTypeMap: [VideoQuality: Livekit_VideoQuality] = [ .low: .low, .medium: .medium, - .high: .high + .high: .high, ] func toPBType() -> Livekit_VideoQuality { - return Self.toPBTypeMap[self] ?? .low + Self.toPBTypeMap[self] ?? .low } } -internal extension Livekit_VideoQuality { - +extension Livekit_VideoQuality { private static let toSDKTypeMap: [Livekit_VideoQuality: VideoQuality] = [ .low: .low, .medium: .medium, - .high: .high + .high: .high, ] func toSDKType() -> VideoQuality { - return Self.toSDKTypeMap[self] ?? .low + Self.toSDKTypeMap[self] ?? .low } static func from(rid: String?) -> Livekit_VideoQuality { diff --git a/Sources/LiveKit/Types/VideoRotation.swift b/Sources/LiveKit/Types/VideoRotation.swift index 0e13eaad9..74549edd3 100644 --- a/Sources/LiveKit/Types/VideoRotation.swift +++ b/Sources/LiveKit/Types/VideoRotation.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,15 +25,13 @@ public enum VideoRotation: Int { case _270 = 270 } -internal extension RTCVideoRotation { - +extension RTCVideoRotation { func toLKType() -> VideoRotation { VideoRotation(rawValue: rawValue)! } } -internal extension VideoRotation { - +extension VideoRotation { func toRTCType() -> RTCVideoRotation { RTCVideoRotation(rawValue: rawValue)! } diff --git a/Sources/LiveKit/Views/InternalSampleBufferVideoRenderer.swift b/Sources/LiveKit/Views/InternalSampleBufferVideoRenderer.swift index 489e15b04..1f2880955 100644 --- a/Sources/LiveKit/Views/InternalSampleBufferVideoRenderer.swift +++ b/Sources/LiveKit/Views/InternalSampleBufferVideoRenderer.swift @@ -18,8 +18,7 @@ import Foundation @_implementationOnly import WebRTC -internal class InternalSampleBufferVideoRenderer: NativeView, Loggable { - +class InternalSampleBufferVideoRenderer: NativeView, Loggable { public let sampleBufferDisplayLayer: AVSampleBufferDisplayLayer override init(frame: CGRect) { @@ -27,17 +26,18 @@ internal class InternalSampleBufferVideoRenderer: NativeView, Loggable { super.init(frame: frame) sampleBufferDisplayLayer.videoGravity = .resizeAspectFill #if os(macOS) - // this is required for macOS - wantsLayer = true - layer?.insertSublayer(sampleBufferDisplayLayer, at: 0) + // this is required for macOS + wantsLayer = true + layer?.insertSublayer(sampleBufferDisplayLayer, at: 0) #elseif os(iOS) - layer.insertSublayer(sampleBufferDisplayLayer, at: 0) + layer.insertSublayer(sampleBufferDisplayLayer, at: 0) #else - fatalError("Unimplemented") + fatalError("Unimplemented") #endif } - required init?(coder: NSCoder) { + @available(*, unavailable) + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -48,14 +48,12 @@ internal class InternalSampleBufferVideoRenderer: NativeView, Loggable { } extension InternalSampleBufferVideoRenderer: LKRTCVideoRenderer { - - internal func setSize(_ size: CGSize) { + func setSize(_: CGSize) { // } - internal func renderFrame(_ frame: LKRTCVideoFrame?) { - - guard let frame = frame else { return } + func renderFrame(_ frame: LKRTCVideoFrame?) { + guard let frame else { return } var pixelBuffer: CVPixelBuffer? @@ -65,7 +63,7 @@ extension InternalSampleBufferVideoRenderer: LKRTCVideoRenderer { pixelBuffer = rtcI420Buffer.toPixelBuffer() } - guard let pixelBuffer = pixelBuffer else { + guard let pixelBuffer else { log("pixelBuffer is nil", .error) return } @@ -82,8 +80,7 @@ extension InternalSampleBufferVideoRenderer: LKRTCVideoRenderer { } extension InternalSampleBufferVideoRenderer: Mirrorable { - - internal func set(mirrored: Bool) { + func set(mirrored: Bool) { sampleBufferDisplayLayer.transform = mirrored ? VideoView.mirrorTransform : CATransform3DIdentity } } diff --git a/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift b/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift index 4a6a3c436..11dee64de 100644 --- a/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift +++ b/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift @@ -17,7 +17,6 @@ import Foundation extension VideoView: MulticastDelegateProtocol { - @objc(addDelegate:) public func add(delegate: VideoViewDelegate) { delegates.add(delegate: delegate) diff --git a/Sources/LiveKit/Views/VideoView.swift b/Sources/LiveKit/Views/VideoView.swift index be1257798..f02ce0b45 100644 --- a/Sources/LiveKit/Views/VideoView.swift +++ b/Sources/LiveKit/Views/VideoView.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,28 +14,27 @@ * limitations under the License. */ -import Foundation import AVFoundation +import Foundation import MetalKit @_implementationOnly import WebRTC /// A ``NativeViewType`` that conforms to ``RTCVideoRenderer``. -internal typealias NativeRendererView = NativeViewType & LKRTCVideoRenderer & Mirrorable -internal protocol Mirrorable { +typealias NativeRendererView = LKRTCVideoRenderer & Mirrorable & NativeViewType +protocol Mirrorable { func set(mirrored: Bool) } @objc public class VideoView: NativeView, Loggable { - // MARK: - MulticastDelegate - internal var delegates = MulticastDelegate() + var delegates = MulticastDelegate() // MARK: - Static - internal static let mirrorTransform = CATransform3DMakeScale(-1.0, 1.0, 1.0) + static let mirrorTransform = CATransform3DMakeScale(-1.0, 1.0, 1.0) private static let _freezeDetectThreshold = 2.0 /// Specifies how to render the video withing the ``VideoView``'s bounds. @@ -122,7 +121,7 @@ public class VideoView: NativeView, Loggable { } @objc - public override var isHidden: Bool { + override public var isHidden: Bool { get { _state.isHidden } set { _state.mutate { $0.isHidden = newValue } @@ -154,8 +153,7 @@ public class VideoView: NativeView, Loggable { // MARK: - Internal - internal struct State: Equatable { - + struct State: Equatable { weak var track: Track? var isEnabled: Bool = true var isHidden: Bool = false @@ -182,7 +180,7 @@ public class VideoView: NativeView, Loggable { } } - internal var _state: StateSync + var _state: StateSync // MARK: - Private @@ -196,8 +194,7 @@ public class VideoView: NativeView, Loggable { private var _currentFPS: Int = 0 private var _frameCount: Int = 0 - public override init(frame: CGRect = .zero) { - + override public init(frame: CGRect = .zero) { // should always be on main thread assert(Thread.current.isMainThread, "must be on the main thread") @@ -207,13 +204,13 @@ public class VideoView: NativeView, Loggable { super.init(frame: frame) #if os(iOS) - clipsToBounds = true + clipsToBounds = true #endif // trigger events when state mutates _state.onDidMutate = { [weak self] newState, oldState in - guard let self = self else { return } + guard let self else { return } let shouldRenderDidUpdate = newState.shouldRender != oldState.shouldRender let renderModeDidUpdate = newState.renderMode != oldState.renderMode @@ -223,16 +220,13 @@ public class VideoView: NativeView, Loggable { // Enter .main only if the following conditions are met... if trackDidUpdate || shouldRenderDidUpdate || renderModeDidUpdate { - Task.detached { @MainActor in var didReCreateNativeRenderer = false if trackDidUpdate || shouldRenderDidUpdate { - // clean up old track if let track = oldState.track as? VideoTrack { - track.remove(videoRenderer: self) if let nr = self.nativeRenderer { @@ -247,15 +241,14 @@ public class VideoView: NativeView, Loggable { } // notify detach - track.delegates.notify(label: { "track.didDetach videoView: \(self)" }) { [weak self, weak track] (delegate) -> Void in - guard let self = self, let track = track else { return } + track.delegates.notify(label: { "track.didDetach videoView: \(self)" }) { [weak self, weak track] delegate in + guard let self, let track else { return } delegate.track?(track, didDetach: self) } } // set new track if let track = newState.track as? VideoTrack, newState.shouldRender { - // re-create renderer on main thread let nr = self.reCreateNativeRenderer() didReCreateNativeRenderer = true @@ -274,8 +267,8 @@ public class VideoView: NativeView, Loggable { } // notify attach - track.delegates.notify(label: { "track.didAttach videoView: \(self)" }) { [weak self, weak track] (delegate) -> Void in - guard let self = self, let track = track else { return } + track.delegates.notify(label: { "track.didAttach videoView: \(self)" }) { [weak self, weak track] delegate in + guard let self, let track else { return } delegate.track?(track, didAttach: self) } } @@ -289,7 +282,6 @@ public class VideoView: NativeView, Loggable { // isRendering updated if newState.isRendering != oldState.isRendering { - self.log("isRendering \(oldState.isRendering) -> \(newState.isRendering)") if newState.isRendering { @@ -322,8 +314,8 @@ public class VideoView: NativeView, Loggable { newState.renderMode != oldState.renderMode || newState.rotationOverride != oldState.rotationOverride || newState.didRenderFirstFrame != oldState.didRenderFirstFrame || - shouldRenderDidUpdate || trackDidUpdate { - + shouldRenderDidUpdate || trackDidUpdate + { // must be on main Task.detached { @MainActor in self.setNeedsLayout() @@ -342,7 +334,7 @@ public class VideoView: NativeView, Loggable { _renderTimer.handler = { [weak self] in - guard let self = self else { return } + guard let self else { return } if self._state.isRendering, let renderDate = self._state.renderDate { let diff = Date().timeIntervalSince(renderDate) @@ -354,7 +346,7 @@ public class VideoView: NativeView, Loggable { _fpsTimer.handler = { [weak self] in - guard let self = self else { return } + guard let self else { return } self._currentFPS = self._frameCount self._frameCount = 0 @@ -363,7 +355,8 @@ public class VideoView: NativeView, Loggable { } } - required init?(coder: NSCoder) { + @available(*, unavailable) + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -402,12 +395,12 @@ public class VideoView: NativeView, Loggable { debugView.text = "#\(hashValue)\n" + "\(_trackSid)\n" + "\(_dimensions.width)x\(_dimensions.height)\n" + "enabled: \(isEnabled)\n" + "firstFrame: \(_didRenderFirstFrame)\n" + "isRendering: \(_isRendering)\n" + "renderMode: \(_renderMode)\n" + "viewCount: \(_viewCount)\n" + "FPS: \(_currentFPS)\n" debugView.frame = bounds #if os(iOS) - debugView.layer.borderColor = (state.shouldRender ? UIColor.green : UIColor.red).withAlphaComponent(0.5).cgColor - debugView.layer.borderWidth = 3 + debugView.layer.borderColor = (state.shouldRender ? UIColor.green : UIColor.red).withAlphaComponent(0.5).cgColor + debugView.layer.borderWidth = 3 #elseif os(macOS) - debugView.wantsLayer = true - debugView.layer!.borderColor = (state.shouldRender ? NSColor.green : NSColor.red).withAlphaComponent(0.5).cgColor - debugView.layer!.borderWidth = 3 + debugView.wantsLayer = true + debugView.layer!.borderColor = (state.shouldRender ? NSColor.green : NSColor.red).withAlphaComponent(0.5).cgColor + debugView.layer!.borderWidth = 3 #endif } else { if let debugView = _debugTextView { @@ -433,9 +426,9 @@ public class VideoView: NativeView, Loggable { let wRatio = size.width / wDim let hRatio = size.height / hDim - if .fill == state.layoutMode ? hRatio > wRatio : hRatio < wRatio { + if state.layoutMode == .fill ? hRatio > wRatio : hRatio < wRatio { size.width = size.height / hDim * wDim - } else if .fill == state.layoutMode ? wRatio > hRatio : wRatio < hRatio { + } else if state.layoutMode == .fill ? wRatio > hRatio : wRatio < hRatio { size.height = size.width / wDim * hDim } @@ -453,7 +446,7 @@ public class VideoView: NativeView, Loggable { // nativeRenderer.layer!.borderColor = NSColor.red.cgColor // nativeRenderer.layer!.borderWidth = 3 - guard let nativeRenderer = nativeRenderer else { return } + guard let nativeRenderer else { return } nativeRenderer.frame = rendererFrame @@ -472,7 +465,6 @@ public class VideoView: NativeView, Loggable { // MARK: - Private private extension VideoView { - private func ensureDebugTextView() -> TextView { if let view = _debugTextView { return view } let view = TextView() @@ -494,7 +486,7 @@ private extension VideoView { let oldView = nativeRenderer nativeRenderer = newView - if let oldView = oldView { + if let oldView { // copy frame from old renderer newView.frame = oldView.frame // remove if existed @@ -525,7 +517,6 @@ private extension VideoView { // MARK: - RTCVideoRenderer extension VideoView: VideoRenderer { - public var adaptiveStreamIsEnabled: Bool { _state.read { $0.didLayout && !$0.isHidden && $0.isEnabled } } @@ -540,7 +531,6 @@ extension VideoView: VideoRenderer { } public func render(frame: VideoFrame) { - let state = _state.copy() // prevent any extra rendering if already !isEnabled etc. @@ -593,8 +583,7 @@ extension VideoView: VideoRenderer { // MARK: - VideoCapturerDelegate extension VideoView: VideoCapturerDelegate { - - public func capturer(_ capturer: VideoCapturer, didUpdate state: VideoCapturer.CapturerState) { + public func capturer(_: VideoCapturer, didUpdate state: VideoCapturer.CapturerState) { if case .started = state { Task.detached { @MainActor in self.setNeedsLayout() @@ -605,13 +594,12 @@ extension VideoView: VideoCapturerDelegate { // MARK: - Internal -internal extension VideoView { - +extension VideoView { static func track(_ track1: VideoTrack?, isEqualWith track2: VideoTrack?) -> Bool { // equal if both tracks are nil if track1 == nil, track2 == nil { return true } // not equal if a single track is nil - guard let track1 = track1, let track2 = track2 else { return false } + guard let track1, let track2 else { return false } // use isEqual return track1.isEqual(track2) } @@ -620,17 +608,16 @@ internal extension VideoView { // MARK: - Static helper methods extension VideoView { - public static func isMetalAvailable() -> Bool { #if os(iOS) - MTLCreateSystemDefaultDevice() != nil + MTLCreateSystemDefaultDevice() != nil #elseif os(macOS) - // same method used with WebRTC - !MTLCopyAllDevices().isEmpty + // same method used with WebRTC + !MTLCopyAllDevices().isEmpty #endif } - internal static func createNativeRendererView(for renderMode: VideoView.RenderMode) -> NativeRendererView { + static func createNativeRendererView(for renderMode: VideoView.RenderMode) -> NativeRendererView { if case .sampleBuffer = renderMode { logger.log("Using AVSampleBufferDisplayLayer for VideoView's Renderer", type: VideoView.self) return InternalSampleBufferVideoRenderer() @@ -639,16 +626,16 @@ extension VideoView { let result = LKRTCMTLVideoView() #if os(iOS) - result.contentMode = .scaleAspectFit - result.videoContentMode = .scaleAspectFit + result.contentMode = .scaleAspectFit + result.videoContentMode = .scaleAspectFit #endif // extra checks for MTKView if let mtkView = result.findMTKView() { #if os(iOS) - mtkView.contentMode = .scaleAspectFit + mtkView.contentMode = .scaleAspectFit #elseif os(macOS) - mtkView.layerContentsPlacement = .scaleProportionallyToFit + mtkView.layerContentsPlacement = .scaleProportionallyToFit #endif // ensure it's capable of rendering 60fps // https://developer.apple.com/documentation/metalkit/mtkview/1536027-preferredframespersecond @@ -663,62 +650,59 @@ extension VideoView { // MARK: - Access MTKView -internal extension NativeViewType { - +extension NativeViewType { func findMTKView() -> MTKView? { subviews.compactMap { $0 as? MTKView }.first } } #if os(macOS) -extension NSView { - // - // Converted to Swift + NSView from: - // http://stackoverflow.com/a/10700737 - // - func set(anchorPoint: CGPoint) { - if let layer = self.layer { - var newPoint = CGPoint(x: self.bounds.size.width * anchorPoint.x, - y: self.bounds.size.height * anchorPoint.y) - var oldPoint = CGPoint(x: self.bounds.size.width * layer.anchorPoint.x, - y: self.bounds.size.height * layer.anchorPoint.y) - - newPoint = newPoint.applying(layer.affineTransform()) - oldPoint = oldPoint.applying(layer.affineTransform()) - - var position = layer.position - - position.x -= oldPoint.x - position.x += newPoint.x - - position.y -= oldPoint.y - position.y += newPoint.y - - layer.position = position - layer.anchorPoint = anchorPoint + extension NSView { + // + // Converted to Swift + NSView from: + // http://stackoverflow.com/a/10700737 + // + func set(anchorPoint: CGPoint) { + if let layer { + var newPoint = CGPoint(x: bounds.size.width * anchorPoint.x, + y: bounds.size.height * anchorPoint.y) + var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, + y: bounds.size.height * layer.anchorPoint.y) + + newPoint = newPoint.applying(layer.affineTransform()) + oldPoint = oldPoint.applying(layer.affineTransform()) + + var position = layer.position + + position.x -= oldPoint.x + position.x += newPoint.x + + position.y -= oldPoint.y + position.y += newPoint.y + + layer.position = position + layer.anchorPoint = anchorPoint + } } } -} #endif extension LKRTCMTLVideoView: Mirrorable { - - internal func set(mirrored: Bool) { - + func set(mirrored: Bool) { if mirrored { #if os(macOS) - // This is required for macOS - wantsLayer = true - set(anchorPoint: CGPoint(x: 0.5, y: 0.5)) - layer!.sublayerTransform = VideoView.mirrorTransform + // This is required for macOS + wantsLayer = true + set(anchorPoint: CGPoint(x: 0.5, y: 0.5)) + layer!.sublayerTransform = VideoView.mirrorTransform #elseif os(iOS) - layer.transform = VideoView.mirrorTransform + layer.transform = VideoView.mirrorTransform #endif } else { #if os(macOS) - layer?.sublayerTransform = CATransform3DIdentity + layer?.sublayerTransform = CATransform3DIdentity #elseif os(iOS) - layer.transform = CATransform3DIdentity + layer.transform = CATransform3DIdentity #endif } } diff --git a/Tests/LiveKitTests/AsyncRetryTests.swift b/Tests/LiveKitTests/AsyncRetryTests.swift index b794dcd05..fc8496eeb 100644 --- a/Tests/LiveKitTests/AsyncRetryTests.swift +++ b/Tests/LiveKitTests/AsyncRetryTests.swift @@ -18,17 +18,11 @@ import XCTest class AsyncRetryTests: XCTestCase { + override func setUpWithError() throws {} - override func setUpWithError() throws { - - } - - override func tearDown() async throws { - - } + override func tearDown() async throws {} func testRetry1() async throws { - let test = Task.retrying(maxRetryCount: 1) { totalAttempts, currentAttempt in print("[TEST] Retrying with remaining attemps: \(currentAttempt)/\(totalAttempts)...") throw EngineError.state(message: "Test error") diff --git a/Tests/LiveKitTests/CompleterTests.swift b/Tests/LiveKitTests/CompleterTests.swift index eb685b1e4..22d778875 100644 --- a/Tests/LiveKitTests/CompleterTests.swift +++ b/Tests/LiveKitTests/CompleterTests.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,16 +18,9 @@ import XCTest class CompleterTests: XCTestCase { + override func setUpWithError() throws {} - override func setUpWithError() throws { + override func tearDown() async throws {} - } - - override func tearDown() async throws { - - } - - func testCompleter() async throws { - - } + func testCompleter() async throws {} } diff --git a/Tests/LiveKitTests/FunctionTests.swift b/Tests/LiveKitTests/FunctionTests.swift index 03cc9e4b7..ec2b5ca3b 100644 --- a/Tests/LiveKitTests/FunctionTests.swift +++ b/Tests/LiveKitTests/FunctionTests.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,13 +21,12 @@ import XCTest // For testing state-less functions // class FunctionTests: XCTestCase { - func testRangeMerge() async throws { - let range1 = 10...20 - let range2 = 5...15 + let range1 = 10 ... 20 + let range2 = 5 ... 15 let merged = merge(range: range1, with: range2) print("merged: \(merged)") - XCTAssert(merged == 5...20) + XCTAssert(merged == 5 ... 20) } } diff --git a/Tests/LiveKitTests/LiveKitTests.swift b/Tests/LiveKitTests/LiveKitTests.swift index 680005bbb..521898c65 100644 --- a/Tests/LiveKitTests/LiveKitTests.swift +++ b/Tests/LiveKitTests/LiveKitTests.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import XCTest final class LiveKitTests: XCTestCase { - func testConnect() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct @@ -32,6 +31,6 @@ final class LiveKitTests: XCTestCase { } static var allTests = [ - ("testExample", testConnect) + ("testExample", testConnect), ] } diff --git a/Tests/LiveKitTests/ThreadSafetyTests.swift b/Tests/LiveKitTests/ThreadSafetyTests.swift index 12c4b8e2a..60925d536 100644 --- a/Tests/LiveKitTests/ThreadSafetyTests.swift +++ b/Tests/LiveKitTests/ThreadSafetyTests.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import XCTest class ThreadSafetyTests: XCTestCase { - struct TestState: Equatable { var dictionary = [String: String]() var counter = 0 @@ -26,17 +25,17 @@ class ThreadSafetyTests: XCTestCase { let queueCount = 100 let blockCount = 1000 - + let safeState = StateSync(TestState()) var unsafeState = TestState() let group = DispatchGroup() var concurrentQueues = [DispatchQueue]() - + override func setUpWithError() throws { - concurrentQueues = Array(1...queueCount).map { DispatchQueue(label: "testQueue_\($0)", attributes: [.concurrent]) } + concurrentQueues = Array(1 ... queueCount).map { DispatchQueue(label: "testQueue_\($0)", attributes: [.concurrent]) } } - + override func tearDown() async throws { // concurrentQueues = [] @@ -47,22 +46,21 @@ class ThreadSafetyTests: XCTestCase { // this should never crash func testSafe() async throws { - for queue in concurrentQueues { - for i in 1...blockCount { + for i in 1 ... blockCount { // perform write queue.async(group: group) { // random sleep - let interval = 0.1 / Double.random(in: 1...100) + let interval = 0.1 / Double.random(in: 1 ... 100) // print("sleeping for \(interval)") Thread.sleep(forTimeInterval: interval) - + self.safeState.mutate { $0.dictionary["key"] = "\(i)" $0.counter += 1 } } - + // perform read queue.async(group: group) { // expected to be out-of-order since concurrent queue and random sleep @@ -70,36 +68,35 @@ class ThreadSafetyTests: XCTestCase { } } } - + await withCheckedContinuation { continuation in group.notify(queue: .main) { continuation.resume() } } - + print("state \(safeState)") - + let totalBlocks = queueCount * blockCount XCTAssert(safeState.counter == totalBlocks, "counter must be \(totalBlocks)") } // this will crash func testUnsafe() async throws { - for queue in concurrentQueues { - for i in 1...blockCount { + for i in 1 ... blockCount { // perform write queue.async(group: group) { // random sleep - let interval = 0.1 / Double.random(in: 1...100) + let interval = 0.1 / Double.random(in: 1 ... 100) // print("sleeping for \(interval)") Thread.sleep(forTimeInterval: interval) - + // high possibility it will crash here self.unsafeState.dictionary["key"] = "\(i)" self.unsafeState.counter += 1 } - + // perform read queue.async(group: group) { // expected to be out-of-order since concurrent queue and random sleep @@ -107,15 +104,15 @@ class ThreadSafetyTests: XCTestCase { } } } - + await withCheckedContinuation { continuation in group.notify(queue: .main) { continuation.resume() } } - + print("state \(unsafeState)") - + let totalBlocks = queueCount * blockCount XCTAssert(unsafeState.counter == totalBlocks, "counter must be \(totalBlocks)") } diff --git a/Tests/LiveKitTests/TimerTests.swift b/Tests/LiveKitTests/TimerTests.swift index a8c70f8ba..5c057b9b4 100644 --- a/Tests/LiveKitTests/TimerTests.swift +++ b/Tests/LiveKitTests/TimerTests.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,23 +15,21 @@ */ @testable import LiveKit -import XCTest import Promises +import XCTest class TimerTests: XCTestCase { - let timer = DispatchQueueTimer(timeInterval: 1) var counter = 0 func testSuspendRestart() async throws { - timer.resume() - - await withCheckedContinuation({ (continuation: CheckedContinuation) in + + await withCheckedContinuation { (continuation: CheckedContinuation) in // timer.handler = { print("onTimer count: \(self.counter)") - + self.counter += 1 if self.counter == 3 { @@ -42,11 +40,11 @@ class TimerTests: XCTestCase { self.timer.restart() } } - + if self.counter == 5 { continuation.resume() } } - }) + } } } diff --git a/Tests/LiveKitTests/WebSocketTests.swift b/Tests/LiveKitTests/WebSocketTests.swift index 4aa9cd124..d8bbe7200 100644 --- a/Tests/LiveKitTests/WebSocketTests.swift +++ b/Tests/LiveKitTests/WebSocketTests.swift @@ -1,5 +1,5 @@ /* - * Copyright 2022 LiveKit + * Copyright 2023 LiveKit * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,19 +18,14 @@ import XCTest class WebSocketTests: XCTestCase { - lazy var socket: WebSocket = { let url = URL(string: "wss://socketsbay.com/wss/v2/1/demo/")! return WebSocket(url: url) }() - override func setUpWithError() throws { - - } + override func setUpWithError() throws {} - override func tearDown() async throws { - - } + override func tearDown() async throws {} func testCompleter1() async throws { // Read messages