├── .swift-version ├── .periphery.yml ├── .github ├── banner_dark.png ├── banner_light.png ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── publish-docs.yaml │ └── testing-matrix.yaml ├── Sources └── LiveKit │ ├── Broadcast │ ├── NOTICE │ └── Uploader │ │ ├── Atomic.swift │ │ └── DarwinNotificationCenter.swift │ ├── PrivacyInfo.xcprivacy │ ├── Types │ ├── Options │ │ ├── CaptureOptions.swift │ │ ├── VideoCaptureOptions.swift │ │ ├── PublishOptions.swift │ │ ├── ConnectOptions+Copy.swift │ │ ├── CameraCaptureOptions+Copy.swift │ │ ├── BufferCaptureOptions.swift │ │ ├── AudioPublishOptions.swift │ │ ├── ScreenShareCaptureOptions.swift │ │ ├── DataPublishOptions.swift │ │ ├── CameraCaptureOptions.swift │ │ └── AudioCaptureOptions.swift │ ├── MediaDevice.swift │ ├── AudioEncoding+Comparable.swift │ ├── VideoEncoding+Comparable.swift │ ├── VideoParameters+Comparable.swift │ ├── TrackStreamState.swift │ ├── ConnectionState.swift │ ├── VideoRotation.swift │ ├── AudioDevice.swift │ ├── TrackType.swift │ ├── ProtocolVersion.swift │ ├── ConnectionQuality.swift │ ├── TrackSource.swift │ ├── Room+Types.swift │ ├── Track+Types.swift │ ├── VideoEncoding.swift │ ├── DegradationPreference.swift │ ├── ScalabilityMode.swift │ ├── SessionDescription.swift │ ├── TrackSettings.swift │ ├── ParticipantTrackPermission.swift │ ├── IceServer.swift │ ├── AudioEncoding.swift │ ├── IceCandidate.swift │ ├── Participant+Types.swift │ ├── VideoQuality.swift │ ├── ParticipantPermissions.swift │ └── VideoCodec.swift │ ├── Track │ ├── Remote │ │ ├── RemoteTrack.swift │ │ ├── RemoteVideoTrack.swift │ │ └── RemoteAudioTrack.swift │ ├── AudioTrack.swift │ ├── Local │ │ ├── LocalTrack.swift │ │ └── LocalVideoTrack.swift │ ├── Track+Equatable.swift │ ├── Track+MulticastDelegate.swift │ ├── Capturers │ │ ├── VideoCapturer+MulticastDelegate.swift │ │ ├── InAppCapturer.swift │ │ └── BufferCapturer.swift │ └── VideoTrack.swift │ ├── Protocols │ ├── Mirrorable.swift │ ├── MediaEncoding.swift │ ├── VideoViewDelegate.swift │ ├── EngineDelegate.swift │ ├── TransportDelegate.swift │ ├── AudioRenderer.swift │ ├── TrackDelegate.swift │ ├── VideoRenderer.swift │ └── SignalClientDelegate.swift │ ├── Extensions │ ├── String.swift │ ├── DispatchQueue.swift │ ├── RTCDataChannel+Util.swift │ ├── RTCConfiguration.swift │ ├── RTCMediaConstraints.swift │ ├── TimeInterval.swift │ ├── AVCaptureDevice.swift │ ├── Primitives.swift │ ├── RTCRtpTransceiver.swift │ ├── Logger.swift │ └── LKRTCRtpSender.swift │ ├── Participant │ ├── Participant+Identifiable.swift │ ├── Participant+Equatable.swift │ ├── Participant+MulticastDelegate.swift │ └── Participant+Convenience.swift │ ├── Support │ ├── Global.swift │ ├── ValueOrAbsent.swift │ ├── SerialRunnerActor.swift │ ├── UnfairLock.swift │ ├── AsyncDebounce.swift │ ├── AsyncSerialDelegate.swift │ ├── HTTP.swift │ ├── AsyncRetry.swift │ ├── Stopwatch.swift │ ├── NativeView.swift │ ├── AppStateListener.swift │ ├── AsyncTimer.swift │ ├── NativeViewRepresentable.swift │ ├── QueueActor.swift │ ├── StateSync.swift │ ├── TextView.swift │ └── MulticastDelegate.swift │ ├── TrackPublications │ ├── TrackPublication+Identifiable.swift │ └── TrackPublication+Equatable.swift │ ├── Core │ ├── Room+MulticastDelegate.swift │ ├── Room+Convenience.swift │ └── Room+Debug.swift │ ├── Views │ ├── VideoView+MulticastDelegate.swift │ └── SampleBufferVideoRenderer.swift │ ├── SwiftUI │ ├── SwiftUIAudioRoutePickerButton.swift │ └── TrackDelegateObserver.swift │ ├── LiveKit+DeviceHelpers.swift │ ├── E2EE │ ├── State.swift │ └── Options.swift │ └── LiveKit.swift ├── .gitignore ├── NOTICE ├── livekit_ipc.proto ├── .swiftformat ├── Makefile ├── Tests ├── LiveKitTests │ ├── Basic.swift │ ├── FunctionTests.swift │ ├── AsyncRetryTests.swift │ ├── VideoView.swift │ ├── QueueActorTests.swift │ ├── WebSocketTests.swift │ ├── AudioProcessing.swift │ ├── Support │ │ └── Xcode14.2Backport.swift │ └── E2EE │ │ └── Thread.swift └── LiveKitTestsObjC │ └── Basic.m ├── LiveKitClient.podspec ├── LiveKit.xctestplan └── Package.swift /.swift-version: -------------------------------------------------------------------------------- 1 | 5.7 2 | -------------------------------------------------------------------------------- /.periphery.yml: -------------------------------------------------------------------------------- 1 | retain_public: true 2 | targets: 3 | - LiveKit 4 | -------------------------------------------------------------------------------- /.github/banner_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bcherry/client-sdk-swift/main/.github/banner_dark.png -------------------------------------------------------------------------------- /.github/banner_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bcherry/client-sdk-swift/main/.github/banner_light.png -------------------------------------------------------------------------------- /Sources/LiveKit/Broadcast/NOTICE: -------------------------------------------------------------------------------- 1 | Inspired by the implementation from https://github.com/react-native-webrtc/react-native-webrtc (MIT License). Rewritten in Swift. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | .swiftpm/ 6 | xcuserdata/ 7 | xcshareddata/ 8 | Package.resolved 9 | Documentation 10 | # DocC 11 | /build_docs/ 12 | *.doccarchive 13 | # Local WebRTC 14 | LiveKitWebRTC.xcframework 15 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2023 LiveKit, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /livekit_ipc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message IPCMessage { 4 | oneof type { 5 | Buffer buffer = 1; 6 | } 7 | 8 | message Buffer { 9 | uint64 timestampNs = 1; 10 | bytes buffer = 2; 11 | oneof type { 12 | Video video = 3; 13 | AudioApp audioApp = 4; 14 | AudioMic audioMic = 5; 15 | } 16 | 17 | message Video { 18 | uint32 format = 1; // CVPixelBuffer's FormatType 19 | uint32 rotation = 2; // RTCVideoRotation 20 | uint32 width = 3; 21 | uint32 height = 4; 22 | } 23 | 24 | message AudioApp {} 25 | message AudioMic {} 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/LiveKit/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | NSPrivacyAccessedAPITypes 8 | 9 | 10 | NSPrivacyAccessedAPIType 11 | NSPrivacyAccessedAPICategorySystemBootTime 12 | NSPrivacyAccessedAPITypeReasons 13 | 14 | 35F9.1 15 | 8FFB.1 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --exclude Sources/LiveKit/Protos 2 | --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 */" 3 | --ifdef no-indent 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: hiroshihorie 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **SDK Version** 14 | Please provide the SDK version. 15 | 16 | **iOS/macOS Version** 17 | The OS version which the issue occurs. 18 | 19 | **Steps to Reproduce** 20 | Steps to reproduce the behavior. 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Screenshots** 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **Logs** 29 | Please provide logs if you can. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: hiroshihorie 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROTO_SOURCE=../protocol/protobufs 2 | 3 | proto: protoc protoc-swift 4 | protoc --swift_out=Sources/LiveKit/protos -I=${PROTO_SOURCE} \ 5 | ${PROTO_SOURCE}/livekit_models.proto \ 6 | ${PROTO_SOURCE}/livekit_rtc.proto 7 | 8 | docs: swift-docs 9 | swift doc generate Sources/LiveKit \ 10 | --module-name "LiveKit Swift Client SDK" \ 11 | --output Documentation \ 12 | --format html \ 13 | --base-url /client-sdk-swift 14 | 15 | protoc-swift: 16 | ifeq (, $(shell which protoc-gen-swift)) 17 | brew install swift-protobuf 18 | endif 19 | 20 | protoc: 21 | ifeq (, $(shell which protoc)) 22 | brew install protobuf 23 | endif 24 | 25 | swift-docs: 26 | ifeq (, $(shell which swift-doc)) 27 | brew install swiftdocorg/formulae/swift-doc 28 | endif 29 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Options/CaptureOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public protocol CaptureOptions {} 21 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/Remote/RemoteTrack.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public protocol RemoteTrack where Self: Track {} 21 | -------------------------------------------------------------------------------- /Sources/LiveKit/Protocols/Mirrorable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // Internal only 20 | protocol Mirrorable { 21 | func set(mirrored: Bool) 22 | } 23 | -------------------------------------------------------------------------------- /Sources/LiveKit/Protocols/MediaEncoding.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public protocol MediaEncoding { 21 | // 22 | var maxBitrate: Int { get } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/AudioTrack.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public protocol AudioTrack where Self: Track {} 23 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/MediaDevice.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public protocol MediaDevice: AnyObject { 21 | var deviceId: String { get } 22 | var name: String { get } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/LiveKit/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension String { 20 | /// Simply return nil if String is empty 21 | var nilIfEmpty: String? { 22 | isEmpty ? nil : self 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Options/VideoCaptureOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public protocol VideoCaptureOptions: CaptureOptions { 21 | var dimensions: Dimensions { get } 22 | var fps: Int { get } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/LiveKitTests/Basic.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @testable import LiveKit 18 | import XCTest 19 | 20 | class Basic: XCTestCase { 21 | func testReadVersion() { 22 | print("LiveKitSDK.version: \(LiveKitSDK.version)") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/AudioEncoding+Comparable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension AudioEncoding: Comparable { 20 | public static func < (lhs: AudioEncoding, rhs: AudioEncoding) -> Bool { 21 | lhs.maxBitrate < rhs.maxBitrate 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/LiveKit/Extensions/DispatchQueue.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public extension DispatchQueue { 20 | // The queue which SDK uses to invoke WebRTC methods 21 | static let liveKitWebRTC = DispatchQueue(label: "LiveKitSDK.webRTC", qos: .default) 22 | } 23 | -------------------------------------------------------------------------------- /Sources/LiveKit/Participant/Participant+Identifiable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // Identify by sid 20 | 21 | extension Participant: Identifiable { 22 | public var id: String { 23 | "\(type(of: self))-\(sid?.stringValue ?? String(hash))" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/Global.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // merge a ClosedRange 18 | func merge(range range1: ClosedRange, with range2: ClosedRange) -> ClosedRange where T: Comparable { 19 | min(range1.lowerBound, range2.lowerBound) ... max(range1.upperBound, range2.upperBound) 20 | } 21 | -------------------------------------------------------------------------------- /Sources/LiveKit/TrackPublications/TrackPublication+Identifiable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // Identify by sid 20 | 21 | extension TrackPublication: Identifiable { 22 | public typealias ID = Track.Sid 23 | 24 | public var id: Track.Sid { 25 | _state.sid 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/LiveKitTestsObjC/Basic.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @import XCTest; 18 | @import LiveKit; 19 | 20 | // Simple ObjC test just to ensure ObjC SDK code compiles. 21 | @interface Basic : XCTestCase 22 | @end 23 | 24 | @implementation Basic 25 | 26 | - (void)testSdkVersion { 27 | NSLog(@"%@", LiveKitSDK.sdkVersion); 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/VideoEncoding+Comparable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension VideoEncoding: Comparable { 20 | public static func < (lhs: VideoEncoding, rhs: VideoEncoding) -> Bool { 21 | if lhs.maxBitrate == rhs.maxBitrate { 22 | return lhs.maxFps < rhs.maxFps 23 | } 24 | 25 | return lhs.maxBitrate < rhs.maxBitrate 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/Local/LocalTrack.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public protocol LocalTrack where Self: Track { 21 | @objc 22 | var publishOptions: TrackPublishOptions? { get } 23 | 24 | @objc 25 | var publishState: PublishState { get } 26 | 27 | @objc 28 | func mute() async throws 29 | 30 | @objc 31 | func unmute() async throws 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/ValueOrAbsent.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /// Allows distinguishing between setting nil and no-op in copyWith operations. 18 | public enum ValueOrAbsent { 19 | case value(T) 20 | case absent 21 | 22 | func value(ifAbsent other: T) -> T { 23 | switch self { 24 | case let .value(t): return t 25 | case .absent: return other 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/VideoParameters+Comparable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension VideoParameters: Comparable { 20 | public static func < (lhs: VideoParameters, rhs: VideoParameters) -> Bool { 21 | if lhs.dimensions.area == rhs.dimensions.area { 22 | return lhs.encoding < rhs.encoding 23 | } 24 | 25 | return lhs.dimensions.area < rhs.dimensions.area 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LiveKitClient.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "LiveKitClient" 3 | spec.version = "2.0.9" 4 | spec.summary = "LiveKit Swift Client SDK. Easily build live audio or video experiences into your mobile app, game or website." 5 | spec.homepage = "https://github.com/livekit/client-sdk-swift" 6 | spec.license = {:type => "Apache 2.0", :file => "LICENSE"} 7 | spec.author = "LiveKit" 8 | 9 | spec.ios.deployment_target = "13.0" 10 | spec.osx.deployment_target = "10.15" 11 | 12 | spec.swift_versions = ["5.7"] 13 | spec.source = {:git => "https://github.com/livekit/client-sdk-swift.git", :tag => "2.0.9"} 14 | 15 | spec.source_files = "Sources/**/*" 16 | 17 | spec.dependency("LiveKitWebRTC", "~> 114.5735.18") 18 | spec.dependency("SwiftProtobuf") 19 | spec.dependency("Logging") 20 | # spec.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' } 21 | # spec.pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' } 22 | 23 | spec.resource_bundles = {"Privacy" => ["Sources/LiveKit/PrivacyInfo.xcprivacy"]} 24 | end 25 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/TrackStreamState.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public enum StreamState: Int { 23 | case paused 24 | case active 25 | } 26 | 27 | extension Livekit_StreamState { 28 | func toLKType() -> StreamState { 29 | switch self { 30 | case .active: return .active 31 | default: return .paused 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/ConnectionState.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public enum ReconnectMode: Int { 21 | case quick 22 | case full 23 | } 24 | 25 | @objc 26 | public enum ConnectionState: Int { 27 | case disconnected 28 | case connecting 29 | case reconnecting 30 | case connected 31 | } 32 | 33 | extension ConnectionState: Identifiable { 34 | public var id: Int { 35 | rawValue 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/LiveKitTests/FunctionTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @testable import LiveKit 18 | import XCTest 19 | 20 | // 21 | // For testing state-less functions 22 | // 23 | class FunctionTests: XCTestCase { 24 | func testRangeMerge() async throws { 25 | let range1 = 10 ... 20 26 | let range2 = 5 ... 15 27 | 28 | let merged = merge(range: range1, with: range2) 29 | print("merged: \(merged)") 30 | XCTAssert(merged == 5 ... 20) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LiveKit/Extensions/RTCDataChannel+Util.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | extension LKRTCDataChannel { 22 | enum labels { 23 | static let reliable = "_reliable" 24 | static let lossy = "_lossy" 25 | } 26 | 27 | func toLKInfoType() -> Livekit_DataChannelInfo { 28 | Livekit_DataChannelInfo.with { 29 | $0.id = UInt32(max(0, channelId)) 30 | $0.label = label 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/Track+Equatable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // MARK: - Equatable for NSObject 20 | 21 | public extension Track { 22 | override func isEqual(_ object: Any?) -> Bool { 23 | guard let other = object as? Self else { return false } 24 | return mediaTrack.trackId == other.mediaTrack.trackId 25 | } 26 | 27 | override var hash: Int { 28 | var hasher = Hasher() 29 | hasher.combine(mediaTrack.trackId) 30 | return hasher.finalize() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LiveKit/Core/Room+MulticastDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension Room: MulticastDelegateProtocol { 20 | @objc(addDelegate:) 21 | public func add(delegate: RoomDelegate) { 22 | delegates.add(delegate: delegate) 23 | } 24 | 25 | @objc(removeDelegate:) 26 | public func remove(delegate: RoomDelegate) { 27 | delegates.remove(delegate: delegate) 28 | } 29 | 30 | @objc 31 | public func removeAllDelegates() { 32 | delegates.removeAllDelegates() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/Track+MulticastDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension Track: MulticastDelegateProtocol { 20 | @objc(addDelegate:) 21 | public func add(delegate: TrackDelegate) { 22 | delegates.add(delegate: delegate) 23 | } 24 | 25 | @objc(removeDelegate:) 26 | public func remove(delegate: TrackDelegate) { 27 | delegates.remove(delegate: delegate) 28 | } 29 | 30 | @objc 31 | public func removeAllDelegates() { 32 | delegates.removeAllDelegates() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/LiveKit/Core/Room+Convenience.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public extension Room { 20 | /// Returns a dictionary containing both local and remote participants. 21 | var allParticipants: [Participant.Identity: Participant] { 22 | var result: [Participant.Identity: Participant] = remoteParticipants 23 | if let localParticipantIdentity = localParticipant.identity { 24 | result.updateValue(localParticipant, forKey: localParticipantIdentity) 25 | } 26 | return result 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/LiveKit/Participant/Participant+Equatable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // Objects are considered equal if both states are equal 20 | 21 | public extension Participant { 22 | override var hash: Int { 23 | var hasher = Hasher() 24 | hasher.combine(_state.copy()) 25 | return hasher.finalize() 26 | } 27 | 28 | override func isEqual(_ object: Any?) -> Bool { 29 | guard let other = object as? Self else { return false } 30 | return _state.copy() == other._state.copy() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/VideoRotation.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | public enum VideoRotation: Int { 22 | case _0 = 0 23 | case _90 = 90 24 | case _180 = 180 25 | case _270 = 270 26 | } 27 | 28 | extension RTCVideoRotation { 29 | func toLKType() -> VideoRotation { 30 | VideoRotation(rawValue: rawValue)! 31 | } 32 | } 33 | 34 | extension VideoRotation { 35 | func toRTCType() -> RTCVideoRotation { 36 | RTCVideoRotation(rawValue: rawValue)! 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/LiveKit/Views/VideoView+MulticastDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension VideoView: MulticastDelegateProtocol { 20 | @objc(addDelegate:) 21 | public func add(delegate: VideoViewDelegate) { 22 | delegates.add(delegate: delegate) 23 | } 24 | 25 | @objc(removeDelegate:) 26 | public func remove(delegate: VideoViewDelegate) { 27 | delegates.remove(delegate: delegate) 28 | } 29 | 30 | @objc 31 | public func removeAllDelegates() { 32 | delegates.removeAllDelegates() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/LiveKit/Protocols/VideoViewDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public protocol VideoViewDelegate: AnyObject { 23 | /// Dimensions of the VideoView itself has updated 24 | @objc(videoView:didUpdateSize:) optional 25 | func videoView(_ videoView: VideoView, didUpdate size: CGSize) 26 | /// VideoView updated the isRendering property 27 | @objc(videoView:didUpdateIsRendering:) optional 28 | func videoView(_ videoView: VideoView, didUpdate isRendering: Bool) 29 | } 30 | -------------------------------------------------------------------------------- /Sources/LiveKit/TrackPublications/TrackPublication+Equatable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // Objects are considered equal if both states are equal 20 | 21 | public extension TrackPublication { 22 | override var hash: Int { 23 | var hasher = Hasher() 24 | hasher.combine(_state.copy()) 25 | return hasher.finalize() 26 | } 27 | 28 | override func isEqual(_ object: Any?) -> Bool { 29 | guard let other = object as? Self else { return false } 30 | return _state.copy() == other._state.copy() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LiveKit/Participant/Participant+MulticastDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension Participant: MulticastDelegateProtocol { 20 | @objc(addDelegate:) 21 | public func add(delegate: ParticipantDelegate) { 22 | delegates.add(delegate: delegate) 23 | } 24 | 25 | @objc(removeDelegate:) 26 | public func remove(delegate: ParticipantDelegate) { 27 | delegates.remove(delegate: delegate) 28 | } 29 | 30 | @objc 31 | public func removeAllDelegates() { 32 | delegates.removeAllDelegates() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/Capturers/VideoCapturer+MulticastDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension VideoCapturer: MulticastDelegateProtocol { 20 | @objc(addDelegate:) 21 | public func add(delegate: VideoCapturerDelegate) { 22 | delegates.add(delegate: delegate) 23 | } 24 | 25 | @objc(removeDelegate:) 26 | public func remove(delegate: VideoCapturerDelegate) { 27 | delegates.remove(delegate: delegate) 28 | } 29 | 30 | @objc 31 | public func removeAllDelegates() { 32 | delegates.removeAllDelegates() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/AudioDevice.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public class AudioDevice: NSObject, MediaDevice { 23 | public var deviceId: String { _ioDevice.deviceId } 24 | public var name: String { _ioDevice.name } 25 | public var isDefault: Bool { _ioDevice.isDefault } 26 | 27 | let _ioDevice: LKRTCIODevice 28 | 29 | init(ioDevice: LKRTCIODevice) { 30 | _ioDevice = ioDevice 31 | } 32 | } 33 | 34 | extension AudioDevice: Identifiable { 35 | public var id: String { deviceId } 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Docs 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - "*" 8 | 9 | jobs: 10 | publish: 11 | runs-on: macos-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Build DocC 17 | run: | 18 | swift package --allow-writing-to-directory ./docs generate-documentation \ 19 | --target LiveKit \ 20 | --output-path ./docs \ 21 | --transform-for-static-hosting \ 22 | --hosting-base-path client-sdk-swift/ 23 | 24 | - name: S3 Upload 25 | run: aws s3 cp docs/ s3://livekit-docs/client-sdk-swift --recursive 26 | env: 27 | AWS_ACCESS_KEY_ID: ${{ secrets.DOCS_DEPLOY_AWS_ACCESS_KEY }} 28 | AWS_SECRET_ACCESS_KEY: ${{ secrets.DOCS_DEPLOY_AWS_API_SECRET }} 29 | AWS_DEFAULT_REGION: "us-east-1" 30 | 31 | - name: Invalidate cache 32 | run: aws cloudfront create-invalidation --distribution-id EJJ40KLJ3TRY9 --paths "/*" 33 | env: 34 | AWS_ACCESS_KEY_ID: ${{ secrets.DOCS_DEPLOY_AWS_ACCESS_KEY }} 35 | AWS_SECRET_ACCESS_KEY: ${{ secrets.DOCS_DEPLOY_AWS_API_SECRET }} 36 | AWS_DEFAULT_REGION: "us-east-1" 37 | -------------------------------------------------------------------------------- /Sources/LiveKit/Extensions/RTCConfiguration.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | extension LKRTCConfiguration { 22 | static func liveKitDefault() -> LKRTCConfiguration { 23 | let result = DispatchQueue.liveKitWebRTC.sync { LKRTCConfiguration() } 24 | result.sdpSemantics = .unifiedPlan 25 | result.continualGatheringPolicy = .gatherContinually 26 | result.candidateNetworkPolicy = .all 27 | result.tcpCandidatePolicy = .enabled 28 | result.iceTransportPolicy = .all 29 | 30 | return result 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LiveKit.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "C13DBD7E-A26D-4166-987B-8BB0E3A8A56F", 5 | "name" : "Test Scheme Action", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "environmentVariableEntries" : [ 13 | { 14 | "key" : "LIVEKIT_TESTING_URL", 15 | "value" : "$(LIVEKIT_TESTING_URL)" 16 | }, 17 | { 18 | "key" : "LIVEKIT_TESTING_API_KEY", 19 | "value" : "$(LIVEKIT_TESTING_API_KEY)" 20 | }, 21 | { 22 | "key" : "LIVEKIT_TESTING_API_SECRET", 23 | "value" : "$(LIVEKIT_TESTING_API_SECRET)" 24 | } 25 | ], 26 | "targetForVariableExpansion" : { 27 | "containerPath" : "container:", 28 | "identifier" : "LiveKit", 29 | "name" : "LiveKit" 30 | } 31 | }, 32 | "testTargets" : [ 33 | { 34 | "target" : { 35 | "containerPath" : "container:", 36 | "identifier" : "LiveKitTests", 37 | "name" : "LiveKitTests" 38 | } 39 | }, 40 | { 41 | "target" : { 42 | "containerPath" : "container:", 43 | "identifier" : "LiveKitTestsObjC", 44 | "name" : "LiveKitTestsObjC" 45 | } 46 | } 47 | ], 48 | "version" : 1 49 | } 50 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/SerialRunnerActor.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | actor SerialRunnerActor { 20 | private var previousTask: Task? 21 | 22 | func run(block: @Sendable @escaping () async throws -> Value) async throws -> Value { 23 | let task = Task { [previousTask] in 24 | let _ = try? await previousTask?.value 25 | return try await block() 26 | } 27 | 28 | previousTask = task 29 | 30 | return try await withTaskCancellationHandler { 31 | try await task.value 32 | } onCancel: { 33 | task.cancel() 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/TrackType.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension Livekit_TrackType { 20 | func toLKType() -> Track.Kind { 21 | switch self { 22 | case .audio: 23 | return .audio 24 | case .video: 25 | return .video 26 | default: 27 | return .none 28 | } 29 | } 30 | } 31 | 32 | extension Track.Kind { 33 | func toPBType() -> Livekit_TrackType { 34 | switch self { 35 | case .audio: 36 | return .audio 37 | case .video: 38 | return .video 39 | default: 40 | return .UNRECOGNIZED(10) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/LiveKit/Protocols/EngineDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | protocol EngineDelegate: AnyObject { 22 | func engine(_ engine: Engine, didMutateState state: Engine.State, oldState: Engine.State) async 23 | func engine(_ engine: Engine, didUpdateSpeakers speakers: [Livekit_SpeakerInfo]) async 24 | func engine(_ engine: Engine, didAddTrack track: LKRTCMediaStreamTrack, rtpReceiver: LKRTCRtpReceiver, stream: LKRTCMediaStream) async 25 | func engine(_ engine: Engine, didRemoveTrack track: LKRTCMediaStreamTrack) async 26 | func engine(_ engine: Engine, didReceiveUserPacket packet: Livekit_UserPacket) async 27 | } 28 | -------------------------------------------------------------------------------- /Sources/LiveKit/Broadcast/Uploader/Atomic.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @propertyWrapper 20 | struct Atomic { 21 | private var value: Value 22 | private let lock = NSLock() 23 | 24 | init(wrappedValue value: Value) { 25 | self.value = value 26 | } 27 | 28 | var wrappedValue: Value { 29 | get { load() } 30 | set { store(newValue: newValue) } 31 | } 32 | 33 | func load() -> Value { 34 | lock.lock() 35 | defer { lock.unlock() } 36 | return value 37 | } 38 | 39 | mutating func store(newValue: Value) { 40 | lock.lock() 41 | defer { lock.unlock() } 42 | value = newValue 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/LiveKitTests/AsyncRetryTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @testable import LiveKit 18 | import XCTest 19 | 20 | class AsyncRetryTests: XCTestCase { 21 | override func setUpWithError() throws {} 22 | 23 | override func tearDown() async throws {} 24 | 25 | // func testRetry1() async throws { 26 | // let test = Task.retrying(totalAttempts: 3) { currentAttempt, totalAttempts in 27 | // print("[TEST] Retrying with remaining attemps: \(currentAttempt)/\(totalAttempts)...") 28 | // throw LiveKitError(.invalidState, message: "Test error") 29 | // } 30 | // 31 | // let value: () = try await test.value 32 | // print("[TEST] Ended with value: '\(value)'...") 33 | // } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/LiveKitTests/VideoView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @testable import LiveKit 18 | import XCTest 19 | 20 | class VideoViewTests: XCTestCase { 21 | override class func setUp() { 22 | LiveKitSDK.setLoggerStandardOutput() 23 | } 24 | 25 | /// Test if avSampleBufferDisplayLayer is available immediately after creating VideoView. 26 | @MainActor 27 | func testAVSampleBufferDisplayLayer() { 28 | let track = LocalVideoTrack.createCameraTrack() 29 | let view = VideoView() 30 | view.renderMode = .sampleBuffer 31 | view.track = track 32 | // avSampleBufferDisplayLayer should not be nil at this point 33 | XCTAssert(view.avSampleBufferDisplayLayer != nil) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/ProtocolVersion.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public enum ProtocolVersion: Int { 21 | case v8 = 8 22 | case v9 = 9 23 | case v10 = 10 /// Sync stream id 24 | case v11 = 11 /// Supports ``ConnectionQuality/lost`` 25 | case v12 = 12 /// Faster room join (delayed ``Room/sid``) 26 | } 27 | 28 | // MARK: - Comparable 29 | 30 | extension ProtocolVersion: Comparable { 31 | public static func < (lhs: Self, rhs: Self) -> Bool { 32 | lhs.rawValue < rhs.rawValue 33 | } 34 | } 35 | 36 | // MARK: - CustomStringConvertible 37 | 38 | extension ProtocolVersion: CustomStringConvertible { 39 | public var description: String { 40 | String(rawValue) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Options/PublishOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | /// Base protocol for ``DataPublishOptions`` and ``MediaPublishOptions``. 20 | @objc 21 | public protocol PublishOptions {} 22 | 23 | /// Base protocol for both ``VideoPublishOptions`` and ``AudioPublishOptions``. 24 | @objc 25 | public protocol TrackPublishOptions: PublishOptions { 26 | var name: String? { get } 27 | /// Set stream name for the track. Audio and video tracks with the same stream name 28 | /// will be placed in the same `MediaStream` and offer better synchronization. 29 | /// By default, camera and microphone will be placed in a stream; as would screen_share and screen_share_audio 30 | var streamName: String? { get } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/LiveKit/Broadcast/Uploader/DarwinNotificationCenter.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | enum DarwinNotification: String { 20 | case broadcastStarted = "iOS_BroadcastStarted" 21 | case broadcastStopped = "iOS_BroadcastStopped" 22 | } 23 | 24 | class DarwinNotificationCenter { 25 | static let shared = DarwinNotificationCenter() 26 | 27 | private let notificationCenter: CFNotificationCenter 28 | 29 | init() { 30 | notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() 31 | } 32 | 33 | func postNotification(_ name: DarwinNotification) { 34 | CFNotificationCenterPostNotification(notificationCenter, CFNotificationName(rawValue: name.rawValue as CFString), nil, nil, true) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/UnfairLock.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // 20 | // Read http://www.russbishop.net/the-law for more information on why this is necessary 21 | // 22 | class UnfairLock { 23 | private var _lock: UnsafeMutablePointer 24 | 25 | init() { 26 | _lock = UnsafeMutablePointer.allocate(capacity: 1) 27 | _lock.initialize(to: os_unfair_lock()) 28 | } 29 | 30 | deinit { 31 | _lock.deinitialize(count: 1) 32 | _lock.deallocate() 33 | } 34 | 35 | func sync(_ fnc: () throws -> Result) rethrows -> Result { 36 | os_unfair_lock_lock(_lock) 37 | defer { os_unfair_lock_unlock(_lock) } 38 | return try fnc() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/LiveKitTests/QueueActorTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @testable import LiveKit 18 | import XCTest 19 | 20 | class QueueActorTests: XCTestCase { 21 | private lazy var queue = QueueActor { print($0) } 22 | 23 | override func setUpWithError() throws {} 24 | 25 | override func tearDown() async throws {} 26 | 27 | func testQueueActor01() async throws { 28 | await queue.processIfResumed("Value 0") 29 | await queue.suspend() 30 | await queue.processIfResumed("Value 1") 31 | await queue.processIfResumed("Value 2") 32 | await queue.processIfResumed("Value 3") 33 | await print("Count: \(queue.count)") 34 | await queue.resume() 35 | await print("Count: \(queue.count)") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/AsyncDebounce.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | actor Debounce { 20 | private var _task: Task? 21 | private let _delay: TimeInterval 22 | 23 | init(delay: TimeInterval) { 24 | _delay = delay 25 | } 26 | 27 | deinit { 28 | _task?.cancel() 29 | } 30 | 31 | func cancel() { 32 | _task?.cancel() 33 | } 34 | 35 | func schedule(_ action: @escaping () async throws -> Void) { 36 | _task?.cancel() 37 | _task = Task.detached(priority: .utility) { 38 | try? await Task.sleep(nanoseconds: UInt64(self._delay * 1_000_000_000)) 39 | if !Task.isCancelled { 40 | try? await action() 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/ConnectionQuality.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public enum ConnectionQuality: Int { 21 | case unknown 22 | /// Indicates that a participant has temporarily (or permanently) lost connection to LiveKit. 23 | /// For permanent disconnection, ``RoomDelegate/room(_:participantDidLeave:)`` will be invoked after a timeout. 24 | case lost 25 | case poor 26 | case good 27 | case excellent 28 | } 29 | 30 | extension Livekit_ConnectionQuality { 31 | func toLKType() -> ConnectionQuality { 32 | switch self { 33 | case .poor: return .poor 34 | case .good: return .good 35 | case .excellent: return .excellent 36 | case .lost: return .lost 37 | default: return .unknown 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/LiveKit/Extensions/RTCMediaConstraints.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | extension LKRTCMediaConstraints { 22 | // static let defaultOfferConstraints = RTCMediaConstraints( 23 | // mandatoryConstraints: [ 24 | // kRTCMediaConstraintsOfferToReceiveAudio: kRTCMediaConstraintsValueFalse, 25 | // kRTCMediaConstraintsOfferToReceiveVideo: kRTCMediaConstraintsValueFalse, 26 | // ], 27 | // optionalConstraints: nil 28 | // ) 29 | 30 | static let defaultPCConstraints = DispatchQueue.liveKitWebRTC.sync { LKRTCMediaConstraints( 31 | mandatoryConstraints: nil, 32 | optionalConstraints: ["DtlsSrtpKeyAgreement": kRTCMediaConstraintsValueTrue] 33 | ) } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/LiveKit/Protocols/TransportDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | protocol TransportDelegate: AnyObject { 22 | func transport(_ transport: Transport, didUpdateState state: RTCPeerConnectionState) async 23 | func transport(_ transport: Transport, didGenerateIceCandidate iceCandidate: LKRTCIceCandidate) async 24 | func transport(_ transport: Transport, didOpenDataChannel dataChannel: LKRTCDataChannel) async 25 | func transport(_ transport: Transport, didAddTrack track: LKRTCMediaStreamTrack, rtpReceiver: LKRTCRtpReceiver, streams: [LKRTCMediaStream]) async 26 | func transport(_ transport: Transport, didRemoveTrack track: LKRTCMediaStreamTrack) async 27 | func transportShouldNegotiate(_ transport: Transport) async 28 | } 29 | -------------------------------------------------------------------------------- /Sources/LiveKit/SwiftUI/SwiftUIAudioRoutePickerButton.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import AVKit 18 | import Foundation 19 | import SwiftUI 20 | 21 | @_implementationOnly import LiveKitWebRTC 22 | 23 | public struct SwiftUIAudioRoutePickerButton: NativeViewRepresentable { 24 | public init() {} 25 | 26 | public func makeView(context _: Context) -> AVRoutePickerView { 27 | let routePickerView = AVRoutePickerView() 28 | 29 | #if os(iOS) 30 | routePickerView.prioritizesVideoDevices = false 31 | #elseif os(macOS) 32 | routePickerView.isRoutePickerButtonBordered = false 33 | #endif 34 | 35 | return routePickerView 36 | } 37 | 38 | public func updateView(_: AVRoutePickerView, context _: Context) {} 39 | public static func dismantleView(_: AVRoutePickerView, coordinator _: ()) {} 40 | } 41 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/TrackSource.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension Livekit_TrackSource { 20 | func toLKType() -> Track.Source { 21 | switch self { 22 | case .camera: return .camera 23 | case .microphone: return .microphone 24 | case .screenShare: return .screenShareVideo 25 | case .screenShareAudio: return .screenShareAudio 26 | default: return .unknown 27 | } 28 | } 29 | } 30 | 31 | extension Track.Source { 32 | func toPBType() -> Livekit_TrackSource { 33 | switch self { 34 | case .camera: return .camera 35 | case .microphone: return .microphone 36 | case .screenShareVideo: return .screenShare 37 | case .screenShareAudio: return .screenShareAudio 38 | default: return .unknown 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Room+Types.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public extension Room { 20 | @objc(RoomSid) 21 | class Sid: NSObject, Codable { 22 | @objc 23 | public let stringValue: String 24 | 25 | init(from stringValue: String) { 26 | self.stringValue = stringValue 27 | } 28 | 29 | override public func isEqual(_ object: Any?) -> Bool { 30 | guard let other = object as? Self else { return false } 31 | return stringValue == other.stringValue 32 | } 33 | 34 | override public var hash: Int { 35 | var hasher = Hasher() 36 | stringValue.hash(into: &hasher) 37 | return hasher.finalize() 38 | } 39 | 40 | override public var description: String { 41 | stringValue 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Track+Types.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public extension Track { 20 | @objc(TrackSid) 21 | class Sid: NSObject, Codable { 22 | @objc 23 | public let stringValue: String 24 | 25 | init(from stringValue: String) { 26 | self.stringValue = stringValue 27 | } 28 | 29 | override public func isEqual(_ object: Any?) -> Bool { 30 | guard let other = object as? Self else { return false } 31 | return stringValue == other.stringValue 32 | } 33 | 34 | override public var hash: Int { 35 | var hasher = Hasher() 36 | stringValue.hash(into: &hasher) 37 | return hasher.finalize() 38 | } 39 | 40 | override public var description: String { 41 | stringValue 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/VideoEncoding.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public class VideoEncoding: NSObject, MediaEncoding { 21 | @objc 22 | public var maxBitrate: Int 23 | 24 | @objc 25 | public var maxFps: Int 26 | 27 | @objc 28 | public init(maxBitrate: Int, maxFps: Int) { 29 | self.maxBitrate = maxBitrate 30 | self.maxFps = maxFps 31 | } 32 | 33 | // MARK: - Equal 34 | 35 | override public func isEqual(_ object: Any?) -> Bool { 36 | guard let other = object as? Self else { return false } 37 | return maxBitrate == other.maxBitrate && 38 | maxFps == other.maxFps 39 | } 40 | 41 | override public var hash: Int { 42 | var hasher = Hasher() 43 | hasher.combine(maxBitrate) 44 | hasher.combine(maxFps) 45 | return hasher.finalize() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/Remote/RemoteVideoTrack.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public class RemoteVideoTrack: Track, RemoteTrack, VideoTrack { 23 | init(name: String, 24 | source: Track.Source, 25 | track: LKRTCMediaStreamTrack, 26 | reportStatistics: Bool) 27 | { 28 | super.init(name: name, 29 | kind: .video, 30 | source: source, 31 | track: track, 32 | reportStatistics: reportStatistics) 33 | } 34 | } 35 | 36 | public extension RemoteVideoTrack { 37 | func add(videoRenderer: VideoRenderer) { 38 | super._add(videoRenderer: videoRenderer) 39 | } 40 | 41 | func remove(videoRenderer: VideoRenderer) { 42 | super._remove(videoRenderer: videoRenderer) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Options/ConnectOptions+Copy.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public extension ConnectOptions { 20 | func copyWith(autoSubscribe: ValueOrAbsent = .absent, 21 | reconnectAttempts: ValueOrAbsent = .absent, 22 | reconnectAttemptDelay: ValueOrAbsent = .absent, 23 | protocolVersion: ValueOrAbsent = .absent) -> ConnectOptions 24 | { 25 | ConnectOptions(autoSubscribe: autoSubscribe.value(ifAbsent: self.autoSubscribe), 26 | reconnectAttempts: reconnectAttempts.value(ifAbsent: self.reconnectAttempts), 27 | reconnectAttemptDelay: reconnectAttemptDelay.value(ifAbsent: self.reconnectAttemptDelay), 28 | protocolVersion: protocolVersion.value(ifAbsent: self.protocolVersion)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/AsyncSerialDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | class AsyncSerialDelegate { 20 | private struct State { 21 | weak var delegate: AnyObject? 22 | } 23 | 24 | private let _state = StateSync(State()) 25 | private let _serialRunner = SerialRunnerActor() 26 | 27 | public func set(delegate: T) { 28 | _state.mutate { $0.delegate = delegate as AnyObject } 29 | } 30 | 31 | public func notifyAsync(_ fnc: @escaping (T) async -> Void) async throws { 32 | guard let delegate = _state.read({ $0.delegate }) as? T else { return } 33 | try await _serialRunner.run { 34 | await fnc(delegate) 35 | } 36 | } 37 | 38 | public func notifyDetached(_ fnc: @escaping (T) async -> Void) { 39 | Task.detached { 40 | try await self.notifyAsync(fnc) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/LiveKit/Core/Room+Debug.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public enum SimulateScenario { 20 | // Client 21 | case quickReconnect 22 | case fullReconnect 23 | // Server 24 | case nodeFailure 25 | case migration 26 | case serverLeave 27 | case speakerUpdate(seconds: Int) 28 | case forceTCP 29 | case forceTLS 30 | } 31 | 32 | public extension Room { 33 | /// Simulate a scenario for debuggin 34 | func debug_simulate(scenario: SimulateScenario) async throws { 35 | if case .quickReconnect = scenario { 36 | try await engine.startReconnect(reason: .debug) 37 | } else if case .fullReconnect = scenario { 38 | try await engine.startReconnect(reason: .debug, nextReconnectMode: .full) 39 | } else { 40 | try await engine.signalClient.sendSimulate(scenario: scenario) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/DegradationPreference.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @_implementationOnly import LiveKitWebRTC 18 | 19 | @objc 20 | public enum DegradationPreference: Int { 21 | /// The SDK will decide which preference is suitable or will use WebRTC's default implementation. 22 | case auto 23 | case disabled 24 | /// Prefer to maintain FPS rather than resolution. 25 | case maintainFramerate 26 | /// Prefer to maintain resolution rather than FPS. 27 | case maintainResolution 28 | case balanced 29 | } 30 | 31 | extension DegradationPreference { 32 | func toRTCType() -> RTCDegradationPreference? { 33 | switch self { 34 | case .auto: return nil 35 | case .disabled: return .disabled 36 | case .maintainFramerate: return .maintainFramerate 37 | case .maintainResolution: return .maintainResolution 38 | case .balanced: return .balanced 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/LiveKit/Extensions/TimeInterval.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | /// Default timeout `TimeInterval`s used throughout the SDK. 20 | public extension TimeInterval { 21 | static let defaultReconnectAttemptDelay: Self = 2 22 | // the following 3 timeouts are used for a typical connect sequence 23 | static let defaultSocketConnect: Self = 10 24 | // used for validation mode 25 | static let defaultHTTPConnect: Self = 5 26 | 27 | static let defaultJoinResponse: Self = 7 28 | static let defaultTransportState: Self = 10 29 | static let defaultPublisherDataChannelOpen: Self = 7 30 | static let resolveSid: Self = 7 + 5 // Join response + 5 31 | static let defaultPublish: Self = 10 32 | static let defaultCaptureStart: Self = 5 33 | } 34 | 35 | extension TimeInterval { 36 | var toDispatchTimeInterval: DispatchTimeInterval { 37 | .milliseconds(Int(self * 1000)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/LiveKit/Extensions/AVCaptureDevice.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import AVFoundation 18 | 19 | public extension AVCaptureDevice { 20 | /// Helper extension to return the acual direction the camera is facing. 21 | /// In macOS, the Facetime camera's position is .unspecified but this property will return .front for such cases. 22 | var facingPosition: AVCaptureDevice.Position { 23 | if deviceType == .builtInWideAngleCamera, position == .unspecified { 24 | return .front 25 | } 26 | 27 | return position 28 | } 29 | } 30 | 31 | public extension Collection where Element: AVCaptureDevice { 32 | /// Helper extension to return only a single suggested device for each position. 33 | func singleDeviceforEachPosition() -> [AVCaptureDevice] { 34 | let front = first { $0.facingPosition == .front } 35 | let back = first { $0.facingPosition == .back } 36 | return [front, back].compactMap { $0 } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Options/CameraCaptureOptions+Copy.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import AVFoundation 18 | 19 | public extension CameraCaptureOptions { 20 | func copyWith(device: ValueOrAbsent = .absent, 21 | position: ValueOrAbsent = .absent, 22 | preferredFormat: ValueOrAbsent = .absent, 23 | dimensions: ValueOrAbsent = .absent, 24 | fps: ValueOrAbsent = .absent) -> CameraCaptureOptions 25 | { 26 | CameraCaptureOptions(device: device.value(ifAbsent: self.device), 27 | position: position.value(ifAbsent: self.position), 28 | preferredFormat: preferredFormat.value(ifAbsent: self.preferredFormat), 29 | dimensions: dimensions.value(ifAbsent: self.dimensions), 30 | fps: fps.value(ifAbsent: self.fps)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LiveKit/Protocols/AudioRenderer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import CoreMedia 18 | import Foundation 19 | 20 | @_implementationOnly import LiveKitWebRTC 21 | 22 | @objc 23 | public protocol AudioRenderer { 24 | /// CMSampleBuffer for this track. 25 | func render(sampleBuffer: CMSampleBuffer) 26 | } 27 | 28 | class AudioRendererAdapter: NSObject, LKRTCAudioRenderer { 29 | private weak var target: AudioRenderer? 30 | 31 | init(target: AudioRenderer) { 32 | self.target = target 33 | } 34 | 35 | func render(sampleBuffer: CMSampleBuffer) { 36 | target?.render(sampleBuffer: sampleBuffer) 37 | } 38 | 39 | // Proxy the equality operators 40 | 41 | override func isEqual(_ object: Any?) -> Bool { 42 | guard let other = object as? AudioRendererAdapter else { return false } 43 | return target === other.target 44 | } 45 | 46 | override var hash: Int { 47 | guard let target else { return 0 } 48 | return ObjectIdentifier(target).hashValue 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/LiveKit/Protocols/TrackDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public protocol TrackDelegate: AnyObject { 23 | /// Dimensions of the video track has updated 24 | @objc(track:didUpdateDimensions:) optional 25 | func track(_ track: VideoTrack, didUpdateDimensions dimensions: Dimensions?) 26 | 27 | /// Statistics for the track has been generated (v2). 28 | @objc(track:didUpdateStatistics:simulcastStatistics:) optional 29 | func track(_ track: Track, didUpdateStatistics: TrackStatistics, simulcastStatistics: [VideoCodec: TrackStatistics]) 30 | } 31 | 32 | protocol TrackDelegateInternal: TrackDelegate { 33 | /// Notify RemoteTrackPublication to send isMuted state to server. 34 | func track(_ track: Track, didUpdateIsMuted isMuted: Bool, shouldSendSignal: Bool) 35 | 36 | /// Used to report track state mutation to TrackPublication if attached. 37 | func track(_ track: Track, didMutateState newState: Track.State, oldState: Track.State) 38 | } 39 | -------------------------------------------------------------------------------- /Tests/LiveKitTests/WebSocketTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @testable import LiveKit 18 | import XCTest 19 | 20 | class WebSocketTests: XCTestCase { 21 | override func setUpWithError() throws {} 22 | 23 | override func tearDown() async throws {} 24 | 25 | func testWebSocket01() async throws { 26 | // print("Connecting...") 27 | // let socket = try await WebSocket(url: URL(string: "wss://socketsbay.com/wss/v2/1/demo/")!) 28 | // 29 | // print("Connected, waiting for messages...") 30 | // do { 31 | // for try await message in socket { 32 | // switch message { 33 | // case let .string(string): print("Received String: \(string)") 34 | // case let .data(data): print("Received Data: \(data)") 35 | // @unknown default: print("Received unknown message") 36 | // } 37 | // } 38 | // } catch { 39 | // print("Error: \(error)") 40 | // throw error 41 | // } 42 | // 43 | // print("Completed") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/ScalabilityMode.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public enum ScalabilityMode: Int { 21 | case L3T3 = 1 22 | case L3T3_KEY = 2 23 | case L3T3_KEY_SHIFT = 3 24 | } 25 | 26 | public extension ScalabilityMode { 27 | static func fromString(_ rawString: String?) -> ScalabilityMode? { 28 | switch rawString { 29 | case "L3T3": return .L3T3 30 | case "L3T3_KEY": return .L3T3_KEY 31 | case "L3T3_KEY_SHIFT": return .L3T3_KEY_SHIFT 32 | default: return nil 33 | } 34 | } 35 | 36 | var rawStringValue: String { 37 | switch self { 38 | case .L3T3: return "L3T3" 39 | case .L3T3_KEY: return "L3T3_KEY" 40 | case .L3T3_KEY_SHIFT: return "L3T3_KEY_SHIFT" 41 | } 42 | } 43 | 44 | var spatial: Int { 3 } 45 | 46 | var temporal: Int { 3 } 47 | } 48 | 49 | // MARK: - CustomStringConvertible 50 | 51 | extension ScalabilityMode: CustomStringConvertible { 52 | public var description: String { 53 | "ScalabilityMode(\(rawStringValue))" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/SessionDescription.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | extension LKRTCSessionDescription { 22 | func toPBType() -> Livekit_SessionDescription { 23 | var sd = Livekit_SessionDescription() 24 | sd.sdp = sdp 25 | 26 | switch type { 27 | case .answer: sd.type = "answer" 28 | case .offer: sd.type = "offer" 29 | case .prAnswer: sd.type = "pranswer" 30 | default: fatalError("Unknown state \(type)") // This should never happen 31 | } 32 | 33 | return sd 34 | } 35 | } 36 | 37 | extension Livekit_SessionDescription { 38 | func toRTCType() -> LKRTCSessionDescription { 39 | var sdpType: RTCSdpType 40 | switch type { 41 | case "answer": sdpType = .answer 42 | case "offer": sdpType = .offer 43 | case "pranswer": sdpType = .prAnswer 44 | default: fatalError("Unknown state \(type)") // This should never happen 45 | } 46 | 47 | return Engine.createSessionDescription(type: sdpType, sdp: sdp) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Options/BufferCaptureOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public class BufferCaptureOptions: NSObject, VideoCaptureOptions { 23 | @objc 24 | public let dimensions: Dimensions 25 | 26 | @objc 27 | public let fps: Int 28 | 29 | public init(dimensions: Dimensions = .h1080_169, 30 | fps: Int = 15) 31 | { 32 | self.dimensions = dimensions 33 | self.fps = fps 34 | } 35 | 36 | public init(from options: ScreenShareCaptureOptions) { 37 | dimensions = options.dimensions 38 | fps = options.fps 39 | } 40 | 41 | // MARK: - Equal 42 | 43 | override public func isEqual(_ object: Any?) -> Bool { 44 | guard let other = object as? Self else { return false } 45 | return dimensions == other.dimensions && 46 | fps == other.fps 47 | } 48 | 49 | override public var hash: Int { 50 | var hasher = Hasher() 51 | hasher.combine(dimensions) 52 | hasher.combine(fps) 53 | return hasher.finalize() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/LiveKit/LiveKit+DeviceHelpers.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import AVFoundation 18 | 19 | public extension LiveKitSDK { 20 | /// Helper method to ensure authorization for video(camera) / audio(microphone) permissions in a single call. 21 | static func ensureDeviceAccess(for types: Set) async -> Bool { 22 | for type in types { 23 | if ![.video, .audio].contains(type) { 24 | logger.log("types must be .video or .audio", .error, type: LiveKitSDK.self) 25 | } 26 | 27 | let status = AVCaptureDevice.authorizationStatus(for: type) 28 | switch status { 29 | case .notDetermined: 30 | if await !(AVCaptureDevice.requestAccess(for: type)) { 31 | return false 32 | } 33 | case .restricted, .denied: return false 34 | case .authorized: continue // No action needed for authorized status. 35 | @unknown default: 36 | logger.error("Unknown AVAuthorizationStatus") 37 | return false 38 | } 39 | } 40 | 41 | return true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/HTTP.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | class HTTP: NSObject { 20 | private static let operationQueue = OperationQueue() 21 | 22 | private static let session: URLSession = .init(configuration: .default, 23 | delegate: nil, 24 | delegateQueue: operationQueue) 25 | 26 | public static func requestData(from url: URL) async throws -> Data { 27 | let request = URLRequest(url: url, 28 | cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, 29 | timeoutInterval: .defaultHTTPConnect) 30 | let (data, _) = try await session.data(for: request) 31 | return data 32 | } 33 | 34 | public static func requestString(from url: URL) async throws -> String { 35 | let data = try await requestData(from: url) 36 | guard let string = String(data: data, encoding: .utf8) else { 37 | throw LiveKitError(.failedToConvertData, message: "Failed to convert string") 38 | } 39 | return string 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/testing-matrix.yaml: -------------------------------------------------------------------------------- 1 | name: Testing Matrix 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | run_all_tests: 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | xcode-version: [14.2, 15.2] 20 | destination: 21 | [ 22 | "platform=iOS Simulator,OS=17.2,name=iPhone 14 Pro", 23 | "platform=macOS", 24 | "platform=macOS,variant=Mac Catalyst", 25 | ] 26 | 27 | runs-on: macos-13 28 | timeout-minutes: 30 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - name: Install LiveKit Server 34 | run: brew install livekit 35 | 36 | - name: Run LiveKit Server 37 | run: livekit-server --dev & 38 | 39 | - uses: maxim-lobanov/setup-xcode@v1 40 | with: 41 | xcode-version: ${{ matrix.xcode-version }} 42 | 43 | - name: Xcode Version 44 | run: xcodebuild -version 45 | 46 | - name: Show SDKs 47 | run: xcodebuild -showsdks 48 | 49 | - name: Download iOS platforms 50 | run: xcodebuild -downloadPlatform iOS 51 | 52 | # TODO: Add step to install iOS 13 53 | # - name: Install iOS 13 54 | # run: xcversion simulators --install='iOS 13.0' 55 | 56 | # - name: Download iOS platforms 57 | # run: xcodebuild -downloadPlatform iOS 58 | 59 | - name: Show Destinations 60 | run: xcodebuild -scheme LiveKit -showdestinations 61 | 62 | - name: Run All Tests 63 | run: xcodebuild test -scheme LiveKit -destination '${{ matrix.destination }}' 64 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/TrackSettings.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | struct TrackSettings: Equatable, Hashable { 20 | let isEnabled: Bool 21 | let dimensions: Dimensions 22 | let videoQuality: VideoQuality 23 | let preferredFPS: UInt 24 | 25 | init(enabled: Bool = false, 26 | dimensions: Dimensions = .zero, 27 | videoQuality: VideoQuality = .low, 28 | preferredFPS: UInt = 0) 29 | { 30 | isEnabled = enabled 31 | self.dimensions = dimensions 32 | self.videoQuality = videoQuality 33 | self.preferredFPS = preferredFPS 34 | } 35 | 36 | func copyWith(isEnabled: ValueOrAbsent = .absent, 37 | dimensions: ValueOrAbsent = .absent, 38 | videoQuality: ValueOrAbsent = .absent, 39 | preferredFPS: ValueOrAbsent = .absent) -> TrackSettings 40 | { 41 | TrackSettings(enabled: isEnabled.value(ifAbsent: self.isEnabled), 42 | dimensions: dimensions.value(ifAbsent: self.dimensions), 43 | videoQuality: videoQuality.value(ifAbsent: self.videoQuality), 44 | preferredFPS: preferredFPS.value(ifAbsent: self.preferredFPS)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/AsyncRetry.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension Task where Failure == Error { 20 | static func retrying( 21 | priority: TaskPriority? = nil, 22 | totalAttempts: Int = 3, 23 | retryDelay: TimeInterval = 1, 24 | @_implicitSelfCapture operation: @escaping (_ currentAttempt: Int, _ totalAttempts: Int) async throws -> Success 25 | ) -> Task { 26 | Task(priority: priority) { 27 | for currentAttempt in 1 ..< max(1, totalAttempts) { 28 | print("[Retry] Attempt \(currentAttempt) of \(totalAttempts), delay: \(retryDelay)") 29 | do { 30 | return try await operation(currentAttempt, totalAttempts) 31 | } catch { 32 | let oneSecond = TimeInterval(1_000_000_000) 33 | let delayNS = UInt64(oneSecond * retryDelay) 34 | print("[Retry] Waiting for \(retryDelay) seconds...") 35 | try await Task.sleep(nanoseconds: delayNS) 36 | continue 37 | } 38 | } 39 | 40 | try Task.checkCancellation() 41 | return try await operation(totalAttempts, totalAttempts) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.7 2 | // (Xcode14.0+) 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "LiveKit", 8 | platforms: [ 9 | .iOS(.v13), 10 | .macOS(.v10_15), 11 | .macCatalyst(.v14), 12 | ], 13 | products: [ 14 | .library( 15 | name: "LiveKit", 16 | targets: ["LiveKit"] 17 | ), 18 | ], 19 | dependencies: [ 20 | // LK-Prefixed Dynamic WebRTC XCFramework 21 | .package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "114.5735.18"), 22 | .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.26.0"), 23 | .package(url: "https://github.com/apple/swift-log.git", from: "1.5.4"), 24 | // Only used for DocC generation 25 | .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"), 26 | // Only used for Testing 27 | .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.13.4"), 28 | ], 29 | targets: [ 30 | .target( 31 | name: "LiveKit", 32 | dependencies: [ 33 | .product(name: "LiveKitWebRTC", package: "webrtc-xcframework"), 34 | .product(name: "SwiftProtobuf", package: "swift-protobuf"), 35 | .product(name: "Logging", package: "swift-log"), 36 | ], 37 | resources: [ 38 | .process("PrivacyInfo.xcprivacy"), 39 | ] 40 | ), 41 | .testTarget( 42 | name: "LiveKitTests", 43 | dependencies: [ 44 | "LiveKit", 45 | .product(name: "JWTKit", package: "jwt-kit"), 46 | ] 47 | ), 48 | .testTarget( 49 | name: "LiveKitTestsObjC", 50 | dependencies: [ 51 | "LiveKit", 52 | .product(name: "JWTKit", package: "jwt-kit"), 53 | ] 54 | ), 55 | ] 56 | ) 57 | -------------------------------------------------------------------------------- /Sources/LiveKit/Extensions/Primitives.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | struct ParseStreamIdResult { 20 | let participantSid: Participant.Sid 21 | let streamId: String? 22 | let trackId: Track.Sid? 23 | } 24 | 25 | func parse(streamId: String) -> ParseStreamIdResult { 26 | let parts = streamId.split(separator: "|") 27 | if parts.count >= 2 { 28 | let p1String = String(parts[1]) 29 | let p1IsTrackId = p1String.starts(with: "TR_") 30 | return ParseStreamIdResult(participantSid: Participant.Sid(from: String(parts[0])), 31 | streamId: p1IsTrackId ? nil : p1String, 32 | trackId: p1IsTrackId ? Track.Sid(from: p1String) : nil) 33 | } 34 | return ParseStreamIdResult(participantSid: Participant.Sid(from: streamId), 35 | streamId: nil, 36 | trackId: nil) 37 | } 38 | 39 | extension Bool { 40 | func toString() -> String { 41 | self ? "true" : "false" 42 | } 43 | } 44 | 45 | extension URL { 46 | var isSecure: Bool { 47 | scheme == "https" || scheme == "wss" 48 | } 49 | } 50 | 51 | public extension Double { 52 | func rounded(to places: Int) -> Double { 53 | let divisor = pow(10.0, Double(places)) 54 | return (self * divisor).rounded() / divisor 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Options/AudioPublishOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public class AudioPublishOptions: NSObject, TrackPublishOptions { 21 | @objc 22 | public let name: String? 23 | 24 | /// preferred encoding parameters 25 | @objc 26 | public let encoding: AudioEncoding? 27 | 28 | @objc 29 | public let dtx: Bool 30 | 31 | @objc 32 | public let streamName: String? 33 | 34 | public init(name: String? = nil, 35 | encoding: AudioEncoding? = nil, 36 | dtx: Bool = true, 37 | streamName: String? = nil) 38 | { 39 | self.name = name 40 | self.encoding = encoding 41 | self.dtx = dtx 42 | self.streamName = streamName 43 | } 44 | 45 | // MARK: - Equal 46 | 47 | override public func isEqual(_ object: Any?) -> Bool { 48 | guard let other = object as? Self else { return false } 49 | return name == other.name && 50 | encoding == other.encoding && 51 | dtx == other.dtx && 52 | streamName == other.streamName 53 | } 54 | 55 | override public var hash: Int { 56 | var hasher = Hasher() 57 | hasher.combine(name) 58 | hasher.combine(encoding) 59 | hasher.combine(dtx) 60 | hasher.combine(streamName) 61 | return hasher.finalize() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/ParticipantTrackPermission.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public class ParticipantTrackPermission: NSObject { 21 | /** 22 | * The participant id this permission applies to. 23 | */ 24 | @objc 25 | public let participantSid: String 26 | 27 | /** 28 | * If set to true, the target participant can subscribe to all tracks from the local participant. 29 | * 30 | * Takes precedence over ``allowedTrackSids``. 31 | */ 32 | @objc 33 | let allTracksAllowed: Bool 34 | 35 | /** 36 | * The list of track ids that the target participant can subscribe to. 37 | */ 38 | @objc 39 | let allowedTrackSids: [String] 40 | 41 | @objc 42 | public init(participantSid: String, 43 | allTracksAllowed: Bool, 44 | allowedTrackSids: [String] = [String]()) 45 | { 46 | self.participantSid = participantSid 47 | self.allTracksAllowed = allTracksAllowed 48 | self.allowedTrackSids = allowedTrackSids 49 | } 50 | } 51 | 52 | extension ParticipantTrackPermission { 53 | func toPBType() -> Livekit_TrackPermission { 54 | Livekit_TrackPermission.with { 55 | $0.participantSid = self.participantSid 56 | $0.allTracks = self.allTracksAllowed 57 | $0.trackSids = self.allowedTrackSids 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/LiveKit/E2EE/State.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public enum E2EEState: Int { 23 | case new 24 | case ok 25 | case key_ratcheted 26 | case missing_key 27 | case encryption_failed 28 | case decryption_failed 29 | case internal_error 30 | } 31 | 32 | public extension E2EEState { 33 | func toString() -> String { 34 | switch self { 35 | case .new: return "new" 36 | case .ok: return "ok" 37 | case .key_ratcheted: return "key_ratcheted" 38 | case .missing_key: return "missing_key" 39 | case .encryption_failed: return "encryption_failed" 40 | case .decryption_failed: return "decryption_failed" 41 | case .internal_error: return "internal_error" 42 | default: return "internal_error" 43 | } 44 | } 45 | } 46 | 47 | extension FrameCryptionState { 48 | func toLKType() -> E2EEState { 49 | switch self { 50 | case .new: return .new 51 | case .ok: return .ok 52 | case .keyRatcheted: return .key_ratcheted 53 | case .missingKey: return .missing_key 54 | case .encryptionFailed: return .encryption_failed 55 | case .decryptionFailed: return .decryption_failed 56 | case .internalError: return .internal_error 57 | default: return .internal_error 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/IceServer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | /// Options used when establishing a connection. 22 | @objc 23 | public class IceServer: NSObject { 24 | public let urls: [String] 25 | public let username: String? 26 | public let credential: String? 27 | 28 | public init(urls: [String], 29 | username: String?, 30 | credential: String?) 31 | { 32 | self.urls = urls 33 | self.username = username 34 | self.credential = credential 35 | } 36 | } 37 | 38 | extension IceServer { 39 | func toRTCType() -> LKRTCIceServer { 40 | DispatchQueue.liveKitWebRTC.sync { LKRTCIceServer(urlStrings: urls, 41 | username: username, 42 | credential: credential) } 43 | } 44 | } 45 | 46 | extension Livekit_ICEServer { 47 | func toRTCType() -> LKRTCIceServer { 48 | let rtcUsername = !username.isEmpty ? username : nil 49 | let rtcCredential = !credential.isEmpty ? credential : nil 50 | return DispatchQueue.liveKitWebRTC.sync { LKRTCIceServer(urlStrings: urls, 51 | username: rtcUsername, 52 | credential: rtcCredential) } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/LiveKit/Extensions/RTCRtpTransceiver.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | extension LKRTCRtpTransceiver: Loggable { 22 | /// Attempts to set preferred video codec. 23 | func set(preferredVideoCodec codec: VideoCodec, exceptCodec: VideoCodec? = nil) { 24 | // Get list of supported codecs... 25 | let allVideoCodecs = Engine.videoSenderCapabilities.codecs 26 | 27 | // Get the RTCRtpCodecCapability of the preferred codec 28 | let preferredCodecCapability = allVideoCodecs.first { $0.name.lowercased() == codec.id } 29 | 30 | // Get list of capabilities other than the preferred one 31 | let otherCapabilities = allVideoCodecs.filter { 32 | $0.name.lowercased() != codec.id && $0.name.lowercased() != exceptCodec?.id 33 | } 34 | 35 | // Bring preferredCodecCapability to the front and combine all capabilities 36 | let combinedCapabilities = [preferredCodecCapability] + otherCapabilities 37 | 38 | // Codecs not set in codecPreferences will not be negotiated in the offer 39 | codecPreferences = combinedCapabilities.compactMap { $0 } 40 | 41 | log("codecPreferences set: \(codecPreferences.map { String(describing: $0) }.joined(separator: ", "))") 42 | 43 | if codecPreferences.first?.name.lowercased() != codec.id { 44 | log("Preferred codec is not first of codecPreferences", .error) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/LiveKit/LiveKit.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | @_implementationOnly import Logging 21 | 22 | let logger = Logger(label: "LiveKitSDK") 23 | 24 | /// The open source platform for real-time communication. 25 | /// 26 | /// See [LiveKit's Online Docs](https://docs.livekit.io/) for more information. 27 | /// 28 | /// Comments are written in [DocC](https://developer.apple.com/documentation/docc) compatible format. 29 | /// With Xcode 13 and above you can build documentation right into your Xcode documentation viewer by chosing 30 | /// **Product** > **Build Documentation** from Xcode's menu. 31 | /// 32 | /// Download the [Multiplatform SwiftUI Example](https://github.com/livekit/multiplatform-swiftui-example) 33 | /// to try out the features. 34 | @objc 35 | public class LiveKitSDK: NSObject { 36 | @objc(sdkVersion) 37 | public static let version = "2.0.9" 38 | 39 | @objc 40 | public static func setLoggerStandardOutput() { 41 | LoggingSystem.bootstrap { 42 | var logHandler = StreamLogHandler.standardOutput(label: $0) 43 | logHandler.logLevel = .debug 44 | return logHandler 45 | } 46 | } 47 | 48 | /// Notify the SDK to start initializing for faster connection/publishing later on. This is non-blocking. 49 | @objc 50 | public static func prepare() { 51 | // TODO: Add RTC related initializations 52 | DeviceManager.prepare() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/Stopwatch.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public struct Stopwatch { 20 | public struct Entry: Equatable { 21 | let label: String 22 | let time: TimeInterval 23 | } 24 | 25 | public let label: String 26 | public private(set) var start: TimeInterval 27 | public private(set) var splits = [Entry]() 28 | 29 | init(label: String) { 30 | self.label = label 31 | start = ProcessInfo.processInfo.systemUptime 32 | } 33 | 34 | mutating func split(label: String = "") { 35 | splits.append(Entry(label: label, time: ProcessInfo.processInfo.systemUptime)) 36 | } 37 | 38 | public func total() -> TimeInterval { 39 | guard let last = splits.last else { return 0 } 40 | return last.time - start 41 | } 42 | } 43 | 44 | extension Stopwatch: Equatable { 45 | public static func == (lhs: Stopwatch, rhs: Stopwatch) -> Bool { 46 | lhs.start == rhs.start && 47 | lhs.splits == rhs.splits 48 | } 49 | } 50 | 51 | extension Stopwatch: CustomStringConvertible { 52 | public var description: String { 53 | var e = [String]() 54 | var s = start 55 | for x in splits { 56 | let diff = x.time - s 57 | s = x.time 58 | e.append("\(x.label) +\(diff.rounded(to: 2))s") 59 | } 60 | 61 | e.append("total \((s - start).rounded(to: 2))s") 62 | return "Stopwatch(\(label), \(e.joined(separator: ", ")))" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/AudioEncoding.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public class AudioEncoding: NSObject, MediaEncoding { 23 | @objc 24 | public var maxBitrate: Int 25 | 26 | @objc 27 | public init(maxBitrate: Int) { 28 | self.maxBitrate = maxBitrate 29 | } 30 | 31 | // MARK: - Equal 32 | 33 | override public func isEqual(_ object: Any?) -> Bool { 34 | guard let other = object as? Self else { return false } 35 | return maxBitrate == other.maxBitrate 36 | } 37 | 38 | override public var hash: Int { 39 | var hasher = Hasher() 40 | hasher.combine(maxBitrate) 41 | return hasher.finalize() 42 | } 43 | } 44 | 45 | // MARK: - Presets 46 | 47 | @objc 48 | public extension AudioEncoding { 49 | internal static let presets = [ 50 | presetTelephone, 51 | presetSpeech, 52 | presetMusic, 53 | presetMusicStereo, 54 | presetMusicHighQuality, 55 | presetMusicHighQualityStereo, 56 | ] 57 | 58 | static let presetTelephone = AudioEncoding(maxBitrate: 12000) 59 | static let presetSpeech = AudioEncoding(maxBitrate: 20000) 60 | static let presetMusic = AudioEncoding(maxBitrate: 32000) 61 | static let presetMusicStereo = AudioEncoding(maxBitrate: 48000) 62 | static let presetMusicHighQuality = AudioEncoding(maxBitrate: 64000) 63 | static let presetMusicHighQualityStereo = AudioEncoding(maxBitrate: 96000) 64 | } 65 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/IceCandidate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | struct IceCandidate: Codable { 22 | let sdp: String 23 | let sdpMLineIndex: Int32 24 | let sdpMid: String? 25 | 26 | enum CodingKeys: String, CodingKey { 27 | case sdpMLineIndex, sdpMid 28 | case sdp = "candidate" 29 | } 30 | 31 | func toJsonString() throws -> String { 32 | let data = try JSONEncoder().encode(self) 33 | guard let string = String(data: data, encoding: .utf8) else { 34 | throw LiveKitError(.failedToConvertData, message: "Failed to convert Data to String") 35 | } 36 | return string 37 | } 38 | } 39 | 40 | extension LKRTCIceCandidate { 41 | func toLKType() -> IceCandidate { 42 | IceCandidate(sdp: sdp, 43 | sdpMLineIndex: sdpMLineIndex, 44 | sdpMid: sdpMid) 45 | } 46 | 47 | convenience init(fromJsonString string: String) throws { 48 | // String to Data 49 | guard let data = string.data(using: .utf8) else { 50 | throw LiveKitError(.failedToConvertData, message: "Failed to convert String to Data") 51 | } 52 | // Decode JSON 53 | let iceCandidate: IceCandidate = try JSONDecoder().decode(IceCandidate.self, from: data) 54 | 55 | self.init(sdp: iceCandidate.sdp, 56 | sdpMLineIndex: iceCandidate.sdpMLineIndex, 57 | sdpMid: iceCandidate.sdpMid) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/LiveKit/SwiftUI/TrackDelegateObserver.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | /// Helper class to observer ``TrackDelegate`` from Swift UI. 20 | public class TrackDelegateObserver: ObservableObject, TrackDelegate { 21 | private let track: Track 22 | 23 | @Published public var dimensions: Dimensions? 24 | @Published public var statistics: TrackStatistics? 25 | @Published public var simulcastStatistics: [VideoCodec: TrackStatistics] 26 | 27 | public var allStatisticts: [TrackStatistics] { 28 | var result: [TrackStatistics] = [] 29 | if let statistics { 30 | result.append(statistics) 31 | } 32 | result.append(contentsOf: simulcastStatistics.values) 33 | return result 34 | } 35 | 36 | public init(track: Track) { 37 | self.track = track 38 | 39 | dimensions = track.dimensions 40 | statistics = track.statistics 41 | simulcastStatistics = track.simulcastStatistics 42 | 43 | track.add(delegate: self) 44 | } 45 | 46 | // MARK: - TrackDelegate 47 | 48 | public func track(_: VideoTrack, didUpdateDimensions dimensions: Dimensions?) { 49 | Task.detached { @MainActor in 50 | self.dimensions = dimensions 51 | } 52 | } 53 | 54 | public func track(_: Track, didUpdateStatistics statistics: TrackStatistics, simulcastStatistics: [VideoCodec: TrackStatistics]) { 55 | Task.detached { @MainActor in 56 | self.statistics = statistics 57 | self.simulcastStatistics = simulcastStatistics 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/NativeView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | #if canImport(UIKit) 20 | import UIKit 21 | #elseif canImport(AppKit) 22 | import AppKit 23 | #endif 24 | 25 | #if os(iOS) 26 | public typealias NativeViewType = UIView 27 | #elseif os(macOS) 28 | public typealias NativeViewType = NSView 29 | #endif 30 | 31 | /// A simple abstraction of a View that is native to the platform. 32 | /// When built for iOS this will be a UIView. 33 | /// When built for macOS this will be a NSView. 34 | open class NativeView: NativeViewType { 35 | override public init(frame: CGRect) { 36 | super.init(frame: frame) 37 | } 38 | 39 | @available(*, unavailable) 40 | public required init?(coder _: NSCoder) { 41 | fatalError("init(coder:) has not been implemented") 42 | } 43 | 44 | #if os(iOS) 45 | override public func layoutSubviews() { 46 | super.layoutSubviews() 47 | performLayout() 48 | } 49 | #else 50 | override public func layout() { 51 | super.layout() 52 | performLayout() 53 | } 54 | #endif 55 | 56 | #if os(macOS) 57 | // for compatibility with macOS 58 | public func setNeedsLayout() { 59 | needsLayout = true 60 | } 61 | #endif 62 | 63 | #if os(macOS) 64 | public func bringSubviewToFront(_ view: NSView) { 65 | addSubview(view) 66 | } 67 | 68 | public func insertSubview(_ view: NSView, belowSubview: NSView) { 69 | addSubview(view, positioned: .below, relativeTo: belowSubview) 70 | } 71 | #endif 72 | 73 | open func performLayout() { 74 | // 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/AppStateListener.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | #if canImport(UIKit) 20 | import UIKit 21 | #endif 22 | 23 | protocol AppStateDelegate: AnyObject { 24 | func appDidEnterBackground() 25 | func appWillEnterForeground() 26 | func appWillTerminate() 27 | } 28 | 29 | class AppStateListener: MulticastDelegate { 30 | static let shared = AppStateListener() 31 | 32 | private init() { 33 | super.init(label: "AppStateDelegate") 34 | 35 | let center = NotificationCenter.default 36 | 37 | #if os(iOS) 38 | center.addObserver(forName: UIApplication.didEnterBackgroundNotification, 39 | object: nil, 40 | queue: OperationQueue.main) 41 | { _ in 42 | 43 | self.log("UIApplication.didEnterBackground") 44 | self.notify { $0.appDidEnterBackground() } 45 | } 46 | 47 | center.addObserver(forName: UIApplication.willEnterForegroundNotification, 48 | object: nil, 49 | queue: OperationQueue.main) 50 | { _ in 51 | 52 | self.log("UIApplication.willEnterForeground") 53 | self.notify { $0.appWillEnterForeground() } 54 | } 55 | 56 | center.addObserver(forName: UIApplication.willTerminateNotification, 57 | object: nil, 58 | queue: OperationQueue.main) 59 | { _ in 60 | 61 | self.log("UIApplication.willTerminate") 62 | self.notify { $0.appWillTerminate() } 63 | } 64 | #endif 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Participant+Types.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public extension Participant { 20 | @objc(ParticipantSid) 21 | class Sid: NSObject, Codable { 22 | @objc 23 | public let stringValue: String 24 | 25 | init(from stringValue: String) { 26 | self.stringValue = stringValue 27 | } 28 | 29 | override public func isEqual(_ object: Any?) -> Bool { 30 | guard let other = object as? Self else { return false } 31 | return stringValue == other.stringValue 32 | } 33 | 34 | override public var hash: Int { 35 | var hasher = Hasher() 36 | stringValue.hash(into: &hasher) 37 | return hasher.finalize() 38 | } 39 | 40 | override public var description: String { 41 | stringValue 42 | } 43 | } 44 | 45 | @objc(ParticipantIdentity) 46 | class Identity: NSObject, Codable { 47 | @objc 48 | public let stringValue: String 49 | 50 | init(from stringValue: String) { 51 | self.stringValue = stringValue 52 | } 53 | 54 | override public func isEqual(_ object: Any?) -> Bool { 55 | guard let other = object as? Self else { return false } 56 | return stringValue == other.stringValue 57 | } 58 | 59 | override public var hash: Int { 60 | var hasher = Hasher() 61 | stringValue.hash(into: &hasher) 62 | return hasher.finalize() 63 | } 64 | 65 | override public var description: String { 66 | stringValue 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/LiveKit/Participant/Participant+Convenience.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public extension Participant { 20 | var firstCameraPublication: TrackPublication? { 21 | videoTracks.first(where: { $0.source == .camera }) 22 | } 23 | 24 | var firstScreenSharePublication: TrackPublication? { 25 | videoTracks.first(where: { $0.source == .screenShareVideo }) 26 | } 27 | 28 | var firstAudioPublication: TrackPublication? { 29 | audioTracks.first 30 | } 31 | 32 | var firstTrackEncryptionType: EncryptionType { 33 | if let pub = firstCameraPublication { 34 | return pub.encryptionType 35 | } else if let pub = firstScreenSharePublication { 36 | return pub.encryptionType 37 | } else if let pub = firstAudioPublication { 38 | return pub.encryptionType 39 | } else { 40 | return .none 41 | } 42 | } 43 | 44 | var firstCameraVideoTrack: VideoTrack? { 45 | guard let pub = firstCameraPublication, !pub.isMuted, pub.isSubscribed, 46 | let track = pub.track else { return nil } 47 | return track as? VideoTrack 48 | } 49 | 50 | var firstScreenShareVideoTrack: VideoTrack? { 51 | guard let pub = firstScreenSharePublication, !pub.isMuted, pub.isSubscribed, 52 | let track = pub.track else { return nil } 53 | return track as? VideoTrack 54 | } 55 | 56 | var firstAudioTrack: AudioTrack? { 57 | guard let pub = firstAudioPublication, !pub.isMuted, 58 | let track = pub.track else { return nil } 59 | return track as? AudioTrack 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/LiveKit/E2EE/Options.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public enum EncryptionType: Int { 21 | case none 22 | case gcm 23 | case custom 24 | } 25 | 26 | extension EncryptionType { 27 | func toPBType() -> Livekit_Encryption.TypeEnum { 28 | switch self { 29 | case .none: return .none 30 | case .gcm: return .gcm 31 | case .custom: return .custom 32 | default: return .custom 33 | } 34 | } 35 | } 36 | 37 | extension Livekit_Encryption.TypeEnum { 38 | func toLKType() -> EncryptionType { 39 | switch self { 40 | case .none: return .none 41 | case .gcm: return .gcm 42 | case .custom: return .custom 43 | default: return .custom 44 | } 45 | } 46 | } 47 | 48 | @objc 49 | public class E2EEOptions: NSObject { 50 | @objc 51 | public let keyProvider: BaseKeyProvider 52 | 53 | @objc 54 | public let encryptionType: EncryptionType 55 | 56 | public init(keyProvider: BaseKeyProvider, 57 | encryptionType: EncryptionType = .gcm) 58 | { 59 | self.keyProvider = keyProvider 60 | self.encryptionType = encryptionType 61 | } 62 | 63 | // MARK: - Equal 64 | 65 | override public func isEqual(_ object: Any?) -> Bool { 66 | guard let other = object as? Self else { return false } 67 | return keyProvider == other.keyProvider && 68 | encryptionType == other.encryptionType 69 | } 70 | 71 | override public var hash: Int { 72 | var hasher = Hasher() 73 | hasher.combine(keyProvider) 74 | hasher.combine(encryptionType) 75 | return hasher.finalize() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/AsyncTimer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | actor AsyncTimer: Loggable { 20 | // MARK: - Public types 21 | 22 | typealias TimerBlock = () async throws -> Void 23 | 24 | // MARK: - Private 25 | 26 | private var _interval: TimeInterval 27 | private var _task: Task? 28 | private var _block: TimerBlock? 29 | public var isStarted: Bool = false 30 | 31 | init(interval: TimeInterval) { 32 | _interval = interval 33 | } 34 | 35 | deinit { 36 | isStarted = false 37 | _task?.cancel() 38 | log(nil, .trace) 39 | } 40 | 41 | func cancel() { 42 | isStarted = false 43 | _task?.cancel() 44 | } 45 | 46 | /// Block must not retain self 47 | func setTimerBlock(block: @escaping TimerBlock) { 48 | _block = block 49 | } 50 | 51 | /// Update timer interval 52 | func setTimerInterval(_ timerInterval: TimeInterval) { 53 | _interval = timerInterval 54 | } 55 | 56 | private func _invoke() async { 57 | if !isStarted { return } 58 | _task = Task.detached(priority: .utility) { [weak self] in 59 | guard let self else { return } 60 | try? await Task.sleep(nanoseconds: UInt64(self._interval * 1_000_000_000)) 61 | if await !(self.isStarted) || Task.isCancelled { return } 62 | try? await self._block?() 63 | await self._invoke() 64 | } 65 | } 66 | 67 | func restart() async { 68 | _task?.cancel() 69 | isStarted = true 70 | await _invoke() 71 | } 72 | 73 | func startIfStopped() async { 74 | if isStarted { return } 75 | await restart() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Tests/LiveKitTests/AudioProcessing.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Accelerate 18 | import AVFoundation 19 | import Foundation 20 | 21 | @testable import LiveKit 22 | import XCTest 23 | 24 | class AudioProcessingTests: XCTestCase, AudioCustomProcessingDelegate { 25 | var _initSampleRate: Double = 0.0 26 | var _initChannels: Int = 0 27 | 28 | func audioProcessingInitialize(sampleRate: Int, channels: Int) { 29 | // 48000, 1 30 | print("sampleRate: \(sampleRate), channels: \(channels)") 31 | _initSampleRate = Double(sampleRate) 32 | _initChannels = channels 33 | } 34 | 35 | func audioProcessingProcess(audioBuffer: LiveKit.LKAudioBuffer) { 36 | guard let pcm = audioBuffer.toAVAudioPCMBuffer() else { 37 | XCTFail("Failed to convert audio buffer to AVAudioPCMBuffer") 38 | return 39 | } 40 | 41 | print("pcm: \(pcm), " + "sampleRate: \(pcm.format.sampleRate), " + "channels: \(pcm.format.channelCount), " + "frameLength: \(pcm.frameLength), " + "frameCapacity: \(pcm.frameCapacity)") 42 | 43 | XCTAssert(pcm.format.sampleRate == _initSampleRate) 44 | XCTAssert(pcm.format.channelCount == _initChannels) 45 | } 46 | 47 | func audioProcessingRelease() { 48 | // 49 | } 50 | 51 | func testConvertAudioBufferToPCM() async throws { 52 | try await with2Rooms { room1, _ in 53 | // ... 54 | AudioManager.shared.capturePostProcessingDelegate = self 55 | 56 | // Publish mic 57 | try await room1.localParticipant.setMicrophone(enabled: true) 58 | 59 | // 3 secs... 60 | let ns = UInt64(5 * 1_000_000_000) 61 | try await Task.sleep(nanoseconds: ns) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/LiveKitTests/Support/Xcode14.2Backport.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import XCTest 19 | 20 | // Support iOS 13 21 | public extension URLSession { 22 | func downloadBackport(from url: URL) async throws -> (URL, URLResponse) { 23 | if #available(iOS 15.0, macOS 12.0, *) { 24 | return try await download(from: url) 25 | } else { 26 | return try await withCheckedThrowingContinuation { continuation in 27 | let task = downloadTask(with: url) { url, response, error in 28 | if let url, let response { 29 | continuation.resume(returning: (url, response)) 30 | } else if let error { 31 | continuation.resume(throwing: error) 32 | } else { 33 | fatalError("Unknown state") 34 | } 35 | } 36 | task.resume() 37 | } 38 | } 39 | } 40 | } 41 | 42 | // Support for Xcode 14.2 43 | #if !compiler(>=5.8) 44 | extension XCTestCase { 45 | func fulfillment(of expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false) async { 46 | await withCheckedContinuation { continuation in 47 | // This function operates by blocking a background thread instead of one owned by libdispatch or by the 48 | // Swift runtime (as used by Swift concurrency.) To ensure we use a thread owned by neither subsystem, use 49 | // Foundation's Thread.detachNewThread(_:). 50 | Thread.detachNewThread { [self] in 51 | wait(for: expectations, timeout: timeout, enforceOrder: enforceOrder) 52 | continuation.resume() 53 | } 54 | } 55 | } 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/NativeViewRepresentable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import SwiftUI 19 | 20 | #if canImport(UIKit) 21 | import UIKit 22 | #elseif canImport(AppKit) 23 | import AppKit 24 | #endif 25 | 26 | #if os(iOS) 27 | public typealias NativeViewRepresentableType = UIViewRepresentable 28 | #elseif os(macOS) 29 | public typealias NativeViewRepresentableType = NSViewRepresentable 30 | #endif 31 | 32 | // multiplatform version of UI/NSViewRepresentable 33 | public protocol NativeViewRepresentable: NativeViewRepresentableType { 34 | /// The type of view to present. 35 | associatedtype ViewType: NativeViewType 36 | 37 | func makeView(context: Self.Context) -> Self.ViewType 38 | func updateView(_ nsView: Self.ViewType, context: Self.Context) 39 | static func dismantleView(_ nsView: Self.ViewType, coordinator: Self.Coordinator) 40 | } 41 | 42 | public extension NativeViewRepresentable { 43 | #if os(iOS) 44 | func makeUIView(context: Context) -> Self.ViewType { 45 | makeView(context: context) 46 | } 47 | 48 | func updateUIView(_ view: Self.ViewType, context: Context) { 49 | updateView(view, context: context) 50 | } 51 | 52 | static func dismantleUIView(_ view: Self.ViewType, coordinator: Self.Coordinator) { 53 | dismantleView(view, coordinator: coordinator) 54 | } 55 | 56 | #elseif os(macOS) 57 | func makeNSView(context: Context) -> Self.ViewType { 58 | makeView(context: context) 59 | } 60 | 61 | func updateNSView(_ view: Self.ViewType, context: Context) { 62 | updateView(view, context: context) 63 | } 64 | 65 | static func dismantleNSView(_ view: Self.ViewType, coordinator: Self.Coordinator) { 66 | dismantleView(view, coordinator: coordinator) 67 | } 68 | #endif 69 | } 70 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Options/ScreenShareCaptureOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public class ScreenShareCaptureOptions: NSObject, VideoCaptureOptions { 21 | @objc 22 | public let dimensions: Dimensions 23 | 24 | @objc 25 | public let fps: Int 26 | 27 | /// Only used for macOS 28 | @objc 29 | public let showCursor: Bool 30 | 31 | @objc 32 | public let useBroadcastExtension: Bool 33 | 34 | @objc 35 | public let includeCurrentApplication: Bool 36 | 37 | public init(dimensions: Dimensions = .h1080_169, 38 | fps: Int = 30, 39 | showCursor: Bool = true, 40 | useBroadcastExtension: Bool = false, 41 | includeCurrentApplication: Bool = false) 42 | { 43 | self.dimensions = dimensions 44 | self.fps = fps 45 | self.showCursor = showCursor 46 | self.useBroadcastExtension = useBroadcastExtension 47 | self.includeCurrentApplication = includeCurrentApplication 48 | } 49 | 50 | // MARK: - Equal 51 | 52 | override public func isEqual(_ object: Any?) -> Bool { 53 | guard let other = object as? Self else { return false } 54 | return dimensions == other.dimensions && 55 | fps == other.fps && 56 | showCursor == other.showCursor && 57 | useBroadcastExtension == other.useBroadcastExtension && 58 | includeCurrentApplication == other.includeCurrentApplication 59 | } 60 | 61 | override public var hash: Int { 62 | var hasher = Hasher() 63 | hasher.combine(dimensions) 64 | hasher.combine(fps) 65 | hasher.combine(showCursor) 66 | hasher.combine(useBroadcastExtension) 67 | hasher.combine(includeCurrentApplication) 68 | return hasher.finalize() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/VideoQuality.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public enum VideoQuality: Int { 21 | case low 22 | case medium 23 | case high 24 | } 25 | 26 | extension VideoQuality { 27 | static let RIDs = ["q", "h", "f"] 28 | } 29 | 30 | // Make convertible between protobuf type. 31 | extension VideoQuality { 32 | private static let toPBTypeMap: [VideoQuality: Livekit_VideoQuality] = [ 33 | .low: .low, 34 | .medium: .medium, 35 | .high: .high, 36 | ] 37 | 38 | func toPBType() -> Livekit_VideoQuality { 39 | Self.toPBTypeMap[self] ?? .low 40 | } 41 | } 42 | 43 | // Make convertible between RIDs. 44 | extension Livekit_VideoQuality { 45 | static func from(rid: String?) -> Livekit_VideoQuality? { 46 | switch rid { 47 | case "q": return Livekit_VideoQuality.low 48 | case "h": return Livekit_VideoQuality.medium 49 | case "f": return Livekit_VideoQuality.high 50 | default: return nil 51 | } 52 | } 53 | 54 | var asRID: String? { 55 | switch self { 56 | case .low: return "q" 57 | case .medium: return "h" 58 | case .high: return "f" 59 | default: return nil 60 | } 61 | } 62 | } 63 | 64 | // Make comparable by the real quality index since the raw protobuf values are not in order. 65 | // E.g. value of `.off` is `3` which is larger than `.high`. 66 | extension Livekit_VideoQuality: Comparable { 67 | private var _weightIndex: Int { 68 | switch self { 69 | case .low: return 1 70 | case .medium: return 2 71 | case .high: return 3 72 | default: return 0 73 | } 74 | } 75 | 76 | static func < (lhs: Livekit_VideoQuality, rhs: Livekit_VideoQuality) -> Bool { 77 | lhs._weightIndex < rhs._weightIndex 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Options/DataPublishOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public class DataPublishOptions: NSObject, PublishOptions { 21 | @objc 22 | public let name: String? 23 | 24 | /// The identities of participants who will receive the message, will be sent to every one if empty. 25 | @objc 26 | public let destinationIdentities: [Participant.Identity] 27 | 28 | /// The topic under which the message gets published. 29 | @objc 30 | public let topic: String? 31 | 32 | /// Whether to send this as reliable or lossy. 33 | /// For data that you need delivery guarantee (such as chat messages) set to true (reliable). 34 | /// For data that should arrive as quickly as possible, but you are ok with dropped packets, set to false (lossy). 35 | @objc 36 | public let reliable: Bool 37 | 38 | public init(name: String? = nil, 39 | destinationIdentities: [Participant.Identity] = [], 40 | topic: String? = nil, 41 | reliable: Bool = false) 42 | { 43 | self.name = name 44 | self.destinationIdentities = destinationIdentities 45 | self.topic = topic 46 | self.reliable = reliable 47 | } 48 | 49 | // MARK: - Equal 50 | 51 | override public func isEqual(_ object: Any?) -> Bool { 52 | guard let other = object as? Self else { return false } 53 | return name == other.name && 54 | destinationIdentities == other.destinationIdentities && 55 | topic == other.topic && 56 | reliable == other.reliable 57 | } 58 | 59 | override public var hash: Int { 60 | var hasher = Hasher() 61 | hasher.combine(name) 62 | hasher.combine(destinationIdentities) 63 | hasher.combine(topic) 64 | hasher.combine(reliable) 65 | return hasher.finalize() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/QueueActor.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | actor QueueActor: Loggable { 20 | typealias OnProcess = (T) async -> Void 21 | 22 | // MARK: - Public 23 | 24 | public enum State { 25 | case resumed 26 | case suspended 27 | } 28 | 29 | public private(set) var state: State = .suspended 30 | 31 | public var count: Int { queue.count } 32 | 33 | // MARK: - Private 34 | 35 | private var queue = [T]() 36 | private let onProcess: OnProcess 37 | 38 | init(onProcess: @escaping OnProcess) { 39 | self.onProcess = onProcess 40 | } 41 | 42 | /// Mark as `.suspended`. 43 | func suspend() { 44 | state = .suspended 45 | } 46 | 47 | /// Only process if `.resumed` state, otherwise enqueue. 48 | func processIfResumed(_ value: T, or condition: Bool = false, elseEnqueue: Bool = true) async { 49 | await process(value, if: state == .resumed || condition, elseEnqueue: elseEnqueue) 50 | } 51 | 52 | /// Only process if `condition` is true, otherwise enqueue. 53 | func process(_ value: T, if condition: Bool, elseEnqueue: Bool = true) async { 54 | if condition { 55 | await onProcess(value) 56 | } else if elseEnqueue { 57 | queue.append(value) 58 | } 59 | } 60 | 61 | func clear() { 62 | if !queue.isEmpty { 63 | log("Clearing queue which is not empty", .warning) 64 | } 65 | 66 | queue.removeAll() 67 | state = .suspended 68 | } 69 | 70 | /// Mark as `.resumed` and process each element with an async `block`. 71 | func resume() async { 72 | state = .resumed 73 | if queue.isEmpty { return } 74 | for element in queue { 75 | // Check cancellation before processing next block... 76 | // try Task.checkCancellation() 77 | await onProcess(element) 78 | } 79 | queue.removeAll() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/LiveKit/Extensions/Logger.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import Logging 20 | 21 | /// Allows to extend with custom `log` method which automatically captures current type (class name). 22 | public protocol Loggable {} 23 | 24 | public typealias ScopedMetadata = CustomStringConvertible 25 | typealias ScopedMetadataContainer = [String: ScopedMetadata] 26 | 27 | extension Loggable { 28 | /// Automatically captures current type (class name) to ``Logger.Metadata`` 29 | func log(_ message: Logger.Message? = nil, 30 | _ level: Logger.Level = .debug, 31 | file: String = #fileID, 32 | type type_: Any.Type? = nil, 33 | function: String = #function, 34 | line: UInt = #line) 35 | { 36 | logger.log(message ?? "", 37 | level, 38 | file: file, 39 | type: type_ ?? type(of: self), 40 | function: function, 41 | line: line) 42 | } 43 | } 44 | 45 | extension Logger { 46 | /// Adds `type` param to capture current type (usually class) 47 | func log(_ message: Logger.Message, 48 | _ level: Logger.Level = .debug, 49 | source _: @autoclosure () -> String? = nil, 50 | file: String = #fileID, 51 | type: Any.Type, 52 | function: String = #function, 53 | line: UInt = #line, 54 | metaData: ScopedMetadataContainer = ScopedMetadataContainer()) 55 | { 56 | func _buildScopedMetadataString() -> String { 57 | guard !metaData.isEmpty else { return "" } 58 | return " [\(metaData.map { "\($0): \($1)" }.joined(separator: ", "))]" 59 | } 60 | 61 | log(level: level, 62 | "\(String(describing: type)).\(function) \(message)\(_buildScopedMetadataString())", 63 | file: file, 64 | function: function, 65 | line: line) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Tests/LiveKitTests/E2EE/Thread.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @testable import LiveKit 18 | import LiveKitWebRTC 19 | import XCTest 20 | 21 | class E2EEThreadTests: XCTestCase { 22 | // Attempt to crash LKRTCFrameCryptor initialization 23 | func testCreateFrameCryptor() async throws { 24 | // Run Tasks concurrently 25 | let result = try await withThrowingTaskGroup(of: LKRTCFrameCryptor.self, returning: [LKRTCFrameCryptor].self) { group in 26 | for _ in 1 ... 10000 { 27 | group.addTask { 28 | let ns = UInt64(Double.random(in: 1 ..< 3) * 1_000_000) 29 | try await Task.sleep(nanoseconds: ns) 30 | 31 | let pc = Engine.peerConnectionFactory.peerConnection(with: .liveKitDefault(), 32 | constraints: .defaultPCConstraints, 33 | delegate: nil) 34 | 35 | guard let transceiver = pc?.addTransceiver(of: .audio) else { 36 | XCTFail("Failed to create transceiver") 37 | throw fatalError() 38 | } 39 | 40 | let keyprovider = LKRTCFrameCryptorKeyProvider() 41 | 42 | return LKRTCFrameCryptor(factory: Engine.peerConnectionFactory, 43 | rtpReceiver: transceiver.receiver, 44 | participantId: "dummy", 45 | algorithm: RTCCyrptorAlgorithm.aesGcm, 46 | keyProvider: keyprovider) 47 | } 48 | } 49 | 50 | var result: [LKRTCFrameCryptor] = [] 51 | for try await e in group { 52 | result.append(e) 53 | } 54 | return result 55 | } 56 | 57 | print("frameCryptors: \(result)") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/VideoTrack.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public protocol VideoTrack where Self: Track { 23 | @objc(addVideoRenderer:) 24 | func add(videoRenderer: VideoRenderer) 25 | 26 | @objc(removeVideoRenderer:) 27 | func remove(videoRenderer: VideoRenderer) 28 | } 29 | 30 | // Directly add/remove renderers for better performance 31 | protocol VideoTrack_Internal where Self: Track { 32 | func add(rtcVideoRenderer: LKRTCVideoRenderer) 33 | 34 | func remove(rtcVideoRenderer: LKRTCVideoRenderer) 35 | } 36 | 37 | extension VideoTrack { 38 | // Update a single SubscribedCodec 39 | func _set(subscribedCodec: Livekit_SubscribedCodec) throws -> Bool { 40 | // ... 41 | let videoCodec = try VideoCodec.from(id: subscribedCodec.codec) 42 | 43 | // Check if main sender is sending the codec... 44 | if let rtpSender = _state.rtpSender, videoCodec == _state.videoCodec { 45 | rtpSender._set(subscribedQualities: subscribedCodec.qualities) 46 | return true 47 | } 48 | 49 | // Find simulcast sender for codec... 50 | if let rtpSender = _state.rtpSenderForCodec[videoCodec] { 51 | rtpSender._set(subscribedQualities: subscribedCodec.qualities) 52 | return true 53 | } 54 | 55 | return false 56 | } 57 | 58 | // Update an array of SubscribedCodecs 59 | func _set(subscribedCodecs: [Livekit_SubscribedCodec]) throws -> [Livekit_SubscribedCodec] { 60 | // ... 61 | var missingCodecs: [Livekit_SubscribedCodec] = [] 62 | 63 | for subscribedCodec in subscribedCodecs { 64 | let didUpdate = try _set(subscribedCodec: subscribedCodec) 65 | if !didUpdate { 66 | log("Sender for codec \(subscribedCodec.codec) not found", .info) 67 | missingCodecs.append(subscribedCodec) 68 | } 69 | } 70 | 71 | return missingCodecs 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/LiveKit/Extensions/LKRTCRtpSender.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | extension LKRTCRtpSender: Loggable { 22 | // ... 23 | func _set(subscribedQualities qualities: [Livekit_SubscribedQuality]) { 24 | let _parameters = parameters 25 | let encodings = _parameters.encodings 26 | 27 | var didUpdate = false 28 | 29 | // For SVC mode... 30 | if let firstEncoding = encodings.first, 31 | let _ = ScalabilityMode.fromString(firstEncoding.scalabilityMode) 32 | { 33 | let _enabled = qualities.highest != .off 34 | if firstEncoding.isActive != _enabled { 35 | firstEncoding.isActive = _enabled 36 | didUpdate = true 37 | } 38 | } else { 39 | // For Simulcast... 40 | for e in qualities { 41 | guard let rid = e.quality.asRID else { continue } 42 | guard let encodingforRID = encodings.first(where: { $0.rid == rid }) else { continue } 43 | 44 | if encodingforRID.isActive != e.enabled { 45 | didUpdate = true 46 | encodingforRID.isActive = e.enabled 47 | log("Setting layer \(e.quality) to \(e.enabled)", .info) 48 | } 49 | } 50 | 51 | // Non simulcast streams don't have RIDs, handle here. 52 | if encodings.count == 1, qualities.count >= 1 { 53 | let firstEncoding = encodings.first! 54 | let firstQuality = qualities.first! 55 | 56 | if firstEncoding.isActive != firstQuality.enabled { 57 | didUpdate = true 58 | firstEncoding.isActive = firstQuality.enabled 59 | log("Setting layer \(firstQuality.quality) to \(firstQuality.enabled)", .info) 60 | } 61 | } 62 | } 63 | 64 | if didUpdate { 65 | parameters = _parameters 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/StateSync.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Combine 18 | import Foundation 19 | 20 | @dynamicMemberLookup 21 | public final class StateSync { 22 | // MARK: - Types 23 | 24 | public typealias OnDidMutate = (_ newState: State, _ oldState: State) -> Void 25 | 26 | // MARK: - Public 27 | 28 | public var onDidMutate: OnDidMutate? { 29 | get { _lock.sync { _onDidMutate } } 30 | set { _lock.sync { _onDidMutate = newValue } } 31 | } 32 | 33 | // MARK: - Private 34 | 35 | private var _state: State 36 | private let _lock = UnfairLock() 37 | private var _onDidMutate: OnDidMutate? 38 | 39 | public init(_ state: State, onDidMutate: OnDidMutate? = nil) { 40 | _state = state 41 | _onDidMutate = onDidMutate 42 | } 43 | 44 | // mutate sync 45 | @discardableResult 46 | public func mutate(_ block: (inout State) throws -> Result) rethrows -> Result { 47 | try _lock.sync { 48 | let oldState = _state 49 | let result = try block(&_state) 50 | let newState = _state 51 | 52 | // Always invoke onDidMutate within the lock (sync) since 53 | // logic following the state mutation may depend on this. 54 | // Invoke on async queue within _onDidMutate if necessary. 55 | _onDidMutate?(newState, oldState) 56 | 57 | return result 58 | } 59 | } 60 | 61 | // read sync and return copy 62 | public func copy() -> State { 63 | _lock.sync { _state } 64 | } 65 | 66 | // read with block 67 | public func read(_ block: (State) throws -> Result) rethrows -> Result { 68 | try _lock.sync { try block(_state) } 69 | } 70 | 71 | // property read sync 72 | public subscript(dynamicMember keyPath: KeyPath) -> Property { 73 | _lock.sync { _state[keyPath: keyPath] } 74 | } 75 | } 76 | 77 | extension StateSync: CustomStringConvertible { 78 | public var description: String { 79 | "StateSync(\(String(describing: copy()))" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/LiveKit/Protocols/VideoRenderer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import AVFoundation 18 | import Foundation 19 | 20 | @_implementationOnly import LiveKitWebRTC 21 | 22 | @objc 23 | public protocol VideoRenderer { 24 | /// Whether this ``VideoRenderer`` should be considered visible or not for AdaptiveStream. 25 | /// This will be invoked on the .main thread. 26 | @objc 27 | var isAdaptiveStreamEnabled: Bool { get } 28 | /// The size used for AdaptiveStream computation. Return .zero if size is unknown yet. 29 | /// This will be invoked on the .main thread. 30 | @objc 31 | var adaptiveStreamSize: CGSize { get } 32 | 33 | /// Size of the frame. 34 | @objc optional 35 | func set(size: CGSize) 36 | 37 | @objc optional 38 | func render(frame: VideoFrame) 39 | 40 | // Only invoked for local tracks, provides additional capture time options 41 | @objc optional 42 | func render(frame: VideoFrame, captureDevice: AVCaptureDevice?, captureOptions: VideoCaptureOptions?) 43 | } 44 | 45 | class VideoRendererAdapter: NSObject, LKRTCVideoRenderer { 46 | private weak var target: VideoRenderer? 47 | private weak var localVideoTrack: LocalVideoTrack? 48 | 49 | init(target: VideoRenderer, localVideoTrack: LocalVideoTrack?) { 50 | self.target = target 51 | self.localVideoTrack = localVideoTrack 52 | } 53 | 54 | func setSize(_ size: CGSize) { 55 | target?.set?(size: size) 56 | } 57 | 58 | func renderFrame(_ frame: LKRTCVideoFrame?) { 59 | guard let frame = frame?.toLKType() else { return } 60 | target?.render?(frame: frame) 61 | 62 | let cameraCapturer = localVideoTrack?.capturer as? CameraCapturer 63 | target?.render?(frame: frame, captureDevice: cameraCapturer?.device, captureOptions: cameraCapturer?.options) 64 | } 65 | 66 | // Proxy the equality operators 67 | 68 | override func isEqual(_ object: Any?) -> Bool { 69 | guard let other = object as? VideoRendererAdapter else { return false } 70 | return target === other.target 71 | } 72 | 73 | override var hash: Int { 74 | guard let target else { return 0 } 75 | return ObjectIdentifier(target).hashValue 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/TextView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | #if canImport(UIKit) 20 | import UIKit 21 | #elseif canImport(AppKit) 22 | import AppKit 23 | #endif 24 | 25 | class TextView: NativeView { 26 | #if os(iOS) 27 | private class DebugUILabel: UILabel { 28 | override func drawText(in _: CGRect) { 29 | let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines) 30 | super.drawText(in: textRect) 31 | } 32 | } 33 | 34 | private let _textView: DebugUILabel 35 | #elseif os(macOS) 36 | private let _textView: NSTextField 37 | #endif 38 | 39 | var text: String? { 40 | get { 41 | #if os(iOS) 42 | _textView.text 43 | #elseif os(macOS) 44 | _textView.stringValue 45 | #endif 46 | } 47 | set { 48 | #if os(iOS) 49 | _textView.text = newValue 50 | #elseif os(macOS) 51 | _textView.stringValue = newValue ?? "" 52 | #endif 53 | } 54 | } 55 | 56 | override init(frame: CGRect) { 57 | #if os(iOS) 58 | _textView = DebugUILabel(frame: .zero) 59 | _textView.numberOfLines = 0 60 | _textView.adjustsFontSizeToFitWidth = false 61 | _textView.lineBreakMode = .byWordWrapping 62 | _textView.textColor = .white 63 | _textView.font = .systemFont(ofSize: 11) 64 | _textView.backgroundColor = .clear 65 | _textView.textAlignment = .right 66 | #elseif os(macOS) 67 | _textView = NSTextField() 68 | _textView.drawsBackground = false 69 | _textView.isBordered = false 70 | _textView.isEditable = false 71 | _textView.isSelectable = false 72 | _textView.font = .systemFont(ofSize: 11) 73 | _textView.alignment = .right 74 | #endif 75 | 76 | super.init(frame: frame) 77 | addSubview(_textView) 78 | } 79 | 80 | @available(*, unavailable) 81 | required init?(coder _: NSCoder) { 82 | fatalError("init(coder:) has not been implemented") 83 | } 84 | 85 | override func performLayout() { 86 | super.performLayout() 87 | _textView.frame = bounds 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/ParticipantPermissions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public class ParticipantPermissions: NSObject { 21 | /// ``Participant`` can subscribe to tracks in the room 22 | @objc 23 | public let canSubscribe: Bool 24 | 25 | /// ``Participant`` can publish new tracks to room 26 | @objc 27 | public let canPublish: Bool 28 | 29 | /// ``Participant`` can publish data 30 | @objc 31 | public let canPublishData: Bool 32 | 33 | /// ``Participant`` is hidden to others 34 | @objc 35 | public let hidden: Bool 36 | 37 | /// Indicates it's a recorder instance 38 | @objc 39 | public let recorder: Bool 40 | 41 | init(canSubscribe: Bool = false, 42 | canPublish: Bool = false, 43 | canPublishData: Bool = false, 44 | hidden: Bool = false, 45 | recorder: Bool = false) 46 | { 47 | self.canSubscribe = canSubscribe 48 | self.canPublish = canPublish 49 | self.canPublishData = canPublishData 50 | self.hidden = hidden 51 | self.recorder = recorder 52 | } 53 | 54 | // MARK: - Equal 55 | 56 | override public func isEqual(_ object: Any?) -> Bool { 57 | guard let other = object as? Self else { return false } 58 | return canSubscribe == other.canSubscribe && 59 | canPublish == other.canPublish && 60 | canPublishData == other.canPublishData && 61 | hidden == other.hidden && 62 | recorder == other.recorder 63 | } 64 | 65 | override public var hash: Int { 66 | var hasher = Hasher() 67 | hasher.combine(canSubscribe) 68 | hasher.combine(canPublish) 69 | hasher.combine(canPublishData) 70 | hasher.combine(hidden) 71 | hasher.combine(recorder) 72 | return hasher.finalize() 73 | } 74 | } 75 | 76 | extension Livekit_ParticipantPermission { 77 | func toLKType() -> ParticipantPermissions { 78 | ParticipantPermissions(canSubscribe: canSubscribe, 79 | canPublish: canPublish, 80 | canPublishData: canPublishData, 81 | hidden: hidden, 82 | recorder: recorder) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/Local/LocalVideoTrack.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public class LocalVideoTrack: Track, LocalTrack, VideoTrack { 23 | @objc 24 | public internal(set) var capturer: VideoCapturer 25 | 26 | var videoSource: LKRTCVideoSource 27 | 28 | init(name: String, 29 | source: Track.Source, 30 | capturer: VideoCapturer, 31 | videoSource: LKRTCVideoSource, 32 | reportStatistics: Bool) 33 | { 34 | let rtcTrack = Engine.createVideoTrack(source: videoSource) 35 | rtcTrack.isEnabled = true 36 | 37 | self.capturer = capturer 38 | self.videoSource = videoSource 39 | 40 | super.init(name: name, 41 | kind: .video, 42 | source: source, 43 | track: rtcTrack, 44 | reportStatistics: reportStatistics) 45 | } 46 | 47 | public func mute() async throws { 48 | try await super._mute() 49 | } 50 | 51 | public func unmute() async throws { 52 | try await super._unmute() 53 | } 54 | 55 | // MARK: - Internal 56 | 57 | override func startCapture() async throws { 58 | try await capturer.startCapture() 59 | } 60 | 61 | override func stopCapture() async throws { 62 | try await capturer.stopCapture() 63 | } 64 | } 65 | 66 | public extension LocalVideoTrack { 67 | func add(videoRenderer: VideoRenderer) { 68 | capturer.rendererDelegates.add(delegate: videoRenderer) 69 | } 70 | 71 | func remove(videoRenderer: VideoRenderer) { 72 | capturer.rendererDelegates.remove(delegate: videoRenderer) 73 | } 74 | } 75 | 76 | public extension LocalVideoTrack { 77 | var publishOptions: TrackPublishOptions? { super._state.lastPublishOptions } 78 | var publishState: Track.PublishState { super._state.publishState } 79 | } 80 | 81 | public extension LocalVideoTrack { 82 | /// Clone with same ``VideoCapturer``. 83 | func clone() -> LocalVideoTrack { 84 | LocalVideoTrack(name: name, 85 | source: source, 86 | capturer: capturer, 87 | videoSource: videoSource, 88 | reportStatistics: _state.reportStatistics) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Options/CameraCaptureOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import AVFoundation 18 | import Foundation 19 | 20 | @objc 21 | public class CameraCaptureOptions: NSObject, VideoCaptureOptions { 22 | @objc 23 | public let device: AVCaptureDevice? 24 | 25 | @objc 26 | public let position: AVCaptureDevice.Position 27 | 28 | @objc 29 | public let preferredFormat: AVCaptureDevice.Format? 30 | 31 | /// preferred dimensions for capturing, the SDK may override with a recommended value. 32 | @objc 33 | public let dimensions: Dimensions 34 | 35 | /// preferred fps to use for capturing, the SDK may override with a recommended value. 36 | @objc 37 | public let fps: Int 38 | 39 | @objc 40 | override public init() { 41 | device = nil 42 | position = .unspecified 43 | preferredFormat = nil 44 | dimensions = .h720_169 45 | fps = 30 46 | } 47 | 48 | @objc 49 | public init(device: AVCaptureDevice? = nil, 50 | position: AVCaptureDevice.Position = .unspecified, 51 | preferredFormat: AVCaptureDevice.Format? = nil, 52 | dimensions: Dimensions = .h720_169, 53 | fps: Int = 30) 54 | { 55 | self.device = device 56 | self.position = position 57 | self.preferredFormat = preferredFormat 58 | self.dimensions = dimensions 59 | self.fps = fps 60 | } 61 | 62 | // MARK: - Equal 63 | 64 | override public func isEqual(_ object: Any?) -> Bool { 65 | guard let other = object as? Self else { return false } 66 | return device == other.device && 67 | position == other.position && 68 | preferredFormat == other.preferredFormat && 69 | dimensions == other.dimensions && 70 | fps == other.fps 71 | } 72 | 73 | override public var hash: Int { 74 | var hasher = Hasher() 75 | hasher.combine(device) 76 | hasher.combine(position) 77 | hasher.combine(preferredFormat) 78 | hasher.combine(dimensions) 79 | hasher.combine(fps) 80 | return hasher.finalize() 81 | } 82 | 83 | // MARK: - CustomStringConvertible 84 | 85 | override public var description: String { 86 | "CameraCaptureOptions(position: \(String(describing: position))" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/LiveKit/Protocols/SignalClientDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | protocol SignalClientDelegate: AnyObject { 22 | func signalClient(_ signalClient: SignalClient, didUpdateConnectionState newState: ConnectionState, oldState: ConnectionState, disconnectError: LiveKitError?) async 23 | func signalClient(_ signalClient: SignalClient, didReceiveConnectResponse connectResponse: SignalClient.ConnectResponse) async 24 | func signalClient(_ signalClient: SignalClient, didReceiveAnswer answer: LKRTCSessionDescription) async 25 | func signalClient(_ signalClient: SignalClient, didReceiveOffer offer: LKRTCSessionDescription) async 26 | func signalClient(_ signalClient: SignalClient, didReceiveIceCandidate iceCandidate: LKRTCIceCandidate, target: Livekit_SignalTarget) async 27 | func signalClient(_ signalClient: SignalClient, didPublishLocalTrack localTrack: Livekit_TrackPublishedResponse) async 28 | func signalClient(_ signalClient: SignalClient, didUnpublishLocalTrack localTrack: Livekit_TrackUnpublishedResponse) async 29 | func signalClient(_ signalClient: SignalClient, didUpdateParticipants participants: [Livekit_ParticipantInfo]) async 30 | func signalClient(_ signalClient: SignalClient, didUpdateRoom room: Livekit_Room) async 31 | func signalClient(_ signalClient: SignalClient, didUpdateSpeakers speakers: [Livekit_SpeakerInfo]) async 32 | func signalClient(_ signalClient: SignalClient, didUpdateConnectionQuality connectionQuality: [Livekit_ConnectionQualityInfo]) async 33 | func signalClient(_ signalClient: SignalClient, didUpdateRemoteMute trackSid: Track.Sid, muted: Bool) async 34 | func signalClient(_ signalClient: SignalClient, didUpdateTrackStreamStates streamStates: [Livekit_StreamStateInfo]) async 35 | func signalClient(_ signalClient: SignalClient, didUpdateSubscribedCodecs codecs: [Livekit_SubscribedCodec], qualities: [Livekit_SubscribedQuality], forTrackSid sid: String) async 36 | func signalClient(_ signalClient: SignalClient, didUpdateSubscriptionPermission permission: Livekit_SubscriptionPermissionUpdate) async 37 | func signalClient(_ signalClient: SignalClient, didUpdateToken token: String) async 38 | func signalClient(_ signalClient: SignalClient, didReceiveLeave canReconnect: Bool, reason: Livekit_DisconnectReason) async 39 | } 40 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/Options/AudioCaptureOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | @objc 22 | public class AudioCaptureOptions: NSObject, CaptureOptions { 23 | @objc 24 | public let echoCancellation: Bool 25 | 26 | @objc 27 | public let noiseSuppression: Bool 28 | 29 | @objc 30 | public let autoGainControl: Bool 31 | 32 | @objc 33 | public let typingNoiseDetection: Bool 34 | 35 | @objc 36 | public let highpassFilter: Bool 37 | 38 | @objc 39 | public let experimentalNoiseSuppression: Bool = false 40 | 41 | @objc 42 | public let experimentalAutoGainControl: Bool = false 43 | 44 | public init(echoCancellation: Bool = true, 45 | noiseSuppression: Bool = true, 46 | autoGainControl: Bool = true, 47 | typingNoiseDetection: Bool = true, 48 | highpassFilter: Bool = true) 49 | { 50 | self.echoCancellation = echoCancellation 51 | self.noiseSuppression = noiseSuppression 52 | self.autoGainControl = autoGainControl 53 | self.typingNoiseDetection = typingNoiseDetection 54 | self.highpassFilter = highpassFilter 55 | } 56 | 57 | // MARK: - Equatable 58 | 59 | override public func isEqual(_ object: Any?) -> Bool { 60 | guard let other = object as? Self else { return false } 61 | return echoCancellation == other.echoCancellation && 62 | noiseSuppression == other.noiseSuppression && 63 | autoGainControl == other.autoGainControl && 64 | typingNoiseDetection == other.typingNoiseDetection && 65 | highpassFilter == other.highpassFilter && 66 | experimentalNoiseSuppression == other.experimentalNoiseSuppression && 67 | experimentalAutoGainControl == other.experimentalAutoGainControl 68 | } 69 | 70 | override public var hash: Int { 71 | var hasher = Hasher() 72 | hasher.combine(echoCancellation) 73 | hasher.combine(noiseSuppression) 74 | hasher.combine(autoGainControl) 75 | hasher.combine(typingNoiseDetection) 76 | hasher.combine(highpassFilter) 77 | hasher.combine(experimentalNoiseSuppression) 78 | hasher.combine(experimentalAutoGainControl) 79 | return hasher.finalize() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/LiveKit/Types/VideoCodec.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @objc 20 | public class VideoCodec: NSObject, Identifiable { 21 | public static func from(id: String) throws -> VideoCodec { 22 | // Try to find codec from id... 23 | guard let codec = all.first(where: { $0.id == id }) else { 24 | throw LiveKitError(.invalidState, message: "Failed to create VideoCodec from id") 25 | } 26 | 27 | return codec 28 | } 29 | 30 | public static func from(mimeType: String) throws -> VideoCodec { 31 | let parts = mimeType.lowercased().split(separator: "/") 32 | var id = String(parts.first!) 33 | if parts.count > 1 { 34 | if parts[0] != "video" { throw LiveKitError(.invalidState, message: "MIME type must be video") } 35 | id = String(parts[1]) 36 | } 37 | return try from(id: id) 38 | } 39 | 40 | public static let h264 = VideoCodec(id: "h264", backup: true) 41 | public static let vp8 = VideoCodec(id: "vp8", backup: true) 42 | public static let vp9 = VideoCodec(id: "vp9", isSVC: true) 43 | public static let av1 = VideoCodec(id: "av1", isSVC: true) 44 | 45 | public static let all: [VideoCodec] = [.h264, .vp8, .vp9, .av1] 46 | public static let allBackup: [VideoCodec] = [.h264, .vp8] 47 | 48 | // codec Id 49 | public let id: String 50 | // Whether the codec can be used as `backup` 51 | public let isBackup: Bool 52 | // Whether the codec can be used as `backup` 53 | public let isSVC: Bool 54 | 55 | // Internal only 56 | init(id: String, 57 | backup: Bool = false, 58 | isSVC: Bool = false) 59 | { 60 | self.id = id 61 | isBackup = backup 62 | self.isSVC = isSVC 63 | } 64 | 65 | // MARK: - Equal 66 | 67 | override public func isEqual(_ object: Any?) -> Bool { 68 | guard let other = object as? Self else { return false } 69 | return id == other.id 70 | } 71 | 72 | override public var hash: Int { 73 | var hasher = Hasher() 74 | hasher.combine(id) 75 | return hasher.finalize() 76 | } 77 | 78 | override public var description: String { 79 | "VideoCodec(id: \(id))" 80 | } 81 | } 82 | 83 | extension Livekit_SubscribedCodec { 84 | func toVideoCodec() throws -> VideoCodec { 85 | try VideoCodec.from(id: codec) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Sources/LiveKit/Views/SampleBufferVideoRenderer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | @_implementationOnly import LiveKitWebRTC 20 | 21 | class SampleBufferVideoRenderer: NativeView, Loggable { 22 | public let sampleBufferDisplayLayer: AVSampleBufferDisplayLayer 23 | 24 | override init(frame: CGRect) { 25 | sampleBufferDisplayLayer = AVSampleBufferDisplayLayer() 26 | super.init(frame: frame) 27 | sampleBufferDisplayLayer.videoGravity = .resizeAspectFill 28 | #if os(macOS) 29 | // this is required for macOS 30 | wantsLayer = true 31 | layer?.insertSublayer(sampleBufferDisplayLayer, at: 0) 32 | #elseif os(iOS) 33 | layer.insertSublayer(sampleBufferDisplayLayer, at: 0) 34 | #else 35 | fatalError("Unimplemented") 36 | #endif 37 | } 38 | 39 | @available(*, unavailable) 40 | required init?(coder _: NSCoder) { 41 | fatalError("init(coder:) has not been implemented") 42 | } 43 | 44 | override func performLayout() { 45 | super.performLayout() 46 | sampleBufferDisplayLayer.frame = bounds 47 | sampleBufferDisplayLayer.removeAllAnimations() 48 | } 49 | } 50 | 51 | extension SampleBufferVideoRenderer: LKRTCVideoRenderer { 52 | func setSize(_: CGSize) { 53 | // 54 | } 55 | 56 | func renderFrame(_ frame: LKRTCVideoFrame?) { 57 | guard let frame else { return } 58 | 59 | var pixelBuffer: CVPixelBuffer? 60 | 61 | if let rtcPixelBuffer = frame.buffer as? LKRTCCVPixelBuffer { 62 | pixelBuffer = rtcPixelBuffer.pixelBuffer 63 | } else if let rtcI420Buffer = frame.buffer as? LKRTCI420Buffer { 64 | pixelBuffer = rtcI420Buffer.toPixelBuffer() 65 | } 66 | 67 | guard let pixelBuffer else { 68 | log("pixelBuffer is nil", .error) 69 | return 70 | } 71 | 72 | guard let sampleBuffer = CMSampleBuffer.from(pixelBuffer) else { 73 | log("Failed to convert CVPixelBuffer to CMSampleBuffer", .error) 74 | return 75 | } 76 | 77 | Task.detached { @MainActor in 78 | self.sampleBufferDisplayLayer.enqueue(sampleBuffer) 79 | } 80 | } 81 | } 82 | 83 | extension SampleBufferVideoRenderer: Mirrorable { 84 | func set(mirrored: Bool) { 85 | sampleBufferDisplayLayer.transform = mirrored ? VideoView.mirrorTransform : CATransform3DIdentity 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/Capturers/InAppCapturer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | #if canImport(ReplayKit) 20 | import ReplayKit 21 | #endif 22 | 23 | @_implementationOnly import LiveKitWebRTC 24 | 25 | @available(macOS 11.0, iOS 11.0, *) 26 | public class InAppScreenCapturer: VideoCapturer { 27 | private let capturer = Engine.createVideoCapturer() 28 | private let options: ScreenShareCaptureOptions 29 | 30 | init(delegate: LKRTCVideoCapturerDelegate, options: ScreenShareCaptureOptions) { 31 | self.options = options 32 | super.init(delegate: delegate) 33 | } 34 | 35 | override public func startCapture() async throws -> Bool { 36 | let didStart = try await super.startCapture() 37 | 38 | // Already started 39 | guard didStart else { return false } 40 | 41 | // TODO: force pixel format kCVPixelFormatType_420YpCbCr8BiPlanarFullRange 42 | try await RPScreenRecorder.shared().startCapture { [weak self] sampleBuffer, type, _ in 43 | guard let self else { return } 44 | // Only process .video 45 | if type == .video { 46 | self.capture(sampleBuffer: sampleBuffer, capturer: self.capturer, options: self.options) 47 | } 48 | } 49 | 50 | return true 51 | } 52 | 53 | override public func stopCapture() async throws -> Bool { 54 | let didStop = try await super.stopCapture() 55 | 56 | // Already stopped 57 | guard didStop else { return false } 58 | 59 | RPScreenRecorder.shared().stopCapture() 60 | 61 | return true 62 | } 63 | } 64 | 65 | public extension LocalVideoTrack { 66 | /// Creates a track that captures in-app screen only (due to limitation of ReplayKit) 67 | @available(macOS 11.0, iOS 11.0, *) 68 | static func createInAppScreenShareTrack(name: String = Track.screenShareVideoName, 69 | options: ScreenShareCaptureOptions = ScreenShareCaptureOptions(), 70 | reportStatistics: Bool = false) -> LocalVideoTrack 71 | { 72 | let videoSource = Engine.createVideoSource(forScreenShare: true) 73 | let capturer = InAppScreenCapturer(delegate: videoSource, options: options) 74 | return LocalVideoTrack(name: name, 75 | source: .screenShareVideo, 76 | capturer: capturer, 77 | videoSource: videoSource, 78 | reportStatistics: reportStatistics) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/Capturers/BufferCapturer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import CoreMedia 18 | import Foundation 19 | 20 | @_implementationOnly import LiveKitWebRTC 21 | 22 | /// A ``VideoCapturer`` that can capture ``CMSampleBuffer``s. 23 | /// 24 | /// Repeatedly call ``capture(_:)`` to capture a stream of ``CMSampleBuffer``s. 25 | /// The pixel format must be one of ``VideoCapturer/supportedPixelFormats``. If an unsupported pixel format is used, the SDK will skip the capture. 26 | /// ``BufferCapturer`` can be used to provide video buffers from ReplayKit. 27 | /// 28 | /// > Note: At least one frame must be captured before publishing the track or the publish will timeout, 29 | /// since dimensions must be resolved at the time of publishing (to compute video parameters). 30 | /// 31 | public class BufferCapturer: VideoCapturer { 32 | private let capturer = Engine.createVideoCapturer() 33 | 34 | /// The ``BufferCaptureOptions`` used for this capturer. 35 | public let options: BufferCaptureOptions 36 | 37 | init(delegate: LKRTCVideoCapturerDelegate, options: BufferCaptureOptions) { 38 | self.options = options 39 | super.init(delegate: delegate) 40 | } 41 | 42 | /// Capture a ``CMSampleBuffer``. 43 | public func capture(_ sampleBuffer: CMSampleBuffer) { 44 | capture(sampleBuffer: sampleBuffer, capturer: capturer, options: options) 45 | } 46 | 47 | /// Capture a ``CVPixelBuffer``. 48 | public func capture(_ pixelBuffer: CVPixelBuffer, timeStampNs: Int64 = VideoCapturer.createTimeStampNs(), rotation: VideoRotation = ._0) { 49 | capture(pixelBuffer: pixelBuffer, capturer: capturer, timeStampNs: timeStampNs, rotation: rotation, options: options) 50 | } 51 | } 52 | 53 | public extension LocalVideoTrack { 54 | /// Creates a track that can directly capture `CVPixelBuffer` or `CMSampleBuffer` for convienience 55 | static func createBufferTrack(name: String = Track.screenShareVideoName, 56 | source: VideoTrack.Source = .screenShareVideo, 57 | options: BufferCaptureOptions = BufferCaptureOptions(), 58 | reportStatistics: Bool = false) -> LocalVideoTrack 59 | { 60 | let videoSource = Engine.createVideoSource(forScreenShare: source == .screenShareVideo) 61 | let capturer = BufferCapturer(delegate: videoSource, options: options) 62 | return LocalVideoTrack(name: name, 63 | source: source, 64 | capturer: capturer, 65 | videoSource: videoSource, 66 | reportStatistics: reportStatistics) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/LiveKit/Support/MulticastDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // Workaround for Swift-ObjC limitation around generics. 20 | public protocol MulticastDelegateProtocol { 21 | associatedtype Delegate 22 | func add(delegate: Delegate) 23 | func remove(delegate: Delegate) 24 | func removeAllDelegates() 25 | } 26 | 27 | /// A class that allows to have multiple delegates instead of one. 28 | /// 29 | /// Uses `NSHashTable` internally to maintain a set of weak delegates. 30 | /// 31 | public class MulticastDelegate: NSObject, Loggable { 32 | // MARK: - Public properties 33 | 34 | public var isDelegatesEmpty: Bool { countDelegates == 0 } 35 | 36 | public var isDelegatesNotEmpty: Bool { countDelegates != 0 } 37 | 38 | /// `NSHashTable` may not immediately deinit the un-referenced object, due to Apple's implementation, therefore ``countDelegates`` may be unreliable. 39 | public var countDelegates: Int { 40 | _state.read { $0.delegates.allObjects.count } 41 | } 42 | 43 | public var allDelegates: [T] { 44 | _state.read { $0.delegates.allObjects.compactMap { $0 as? T } } 45 | } 46 | 47 | // MARK: - Private properties 48 | 49 | private struct State { 50 | let delegates = NSHashTable.weakObjects() 51 | } 52 | 53 | private let _queue: DispatchQueue 54 | 55 | private let _state = StateSync(State()) 56 | 57 | init(label: String, qos: DispatchQoS = .default) { 58 | _queue = DispatchQueue(label: "LiveKitSDK.Multicast.\(label)", qos: qos, attributes: []) 59 | } 60 | 61 | /// Add a single delegate. 62 | public func add(delegate: T) { 63 | guard let delegate = delegate as AnyObject? else { 64 | log("MulticastDelegate: delegate is not an AnyObject") 65 | return 66 | } 67 | 68 | _state.mutate { $0.delegates.add(delegate) } 69 | } 70 | 71 | /// Remove a single delegate. 72 | /// 73 | /// In most cases this is not required to be called explicitly since all delegates are weak. 74 | public func remove(delegate: T) { 75 | guard let delegate = delegate as AnyObject? else { 76 | log("MulticastDelegate: delegate is not an AnyObject") 77 | return 78 | } 79 | 80 | _state.mutate { $0.delegates.remove(delegate) } 81 | } 82 | 83 | /// Remove all delegates. 84 | public func removeAllDelegates() { 85 | _state.mutate { $0.delegates.removeAllObjects() } 86 | } 87 | 88 | /// Notify delegates inside the queue. 89 | func notify(label _: (() -> String)? = nil, _ fnc: @escaping (T) -> Void) { 90 | let delegates = _state.read { $0.delegates.allObjects.compactMap { $0 as? T } } 91 | 92 | _queue.async { 93 | for delegate in delegates { 94 | fnc(delegate) 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/LiveKit/Track/Remote/RemoteAudioTrack.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LiveKit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import CoreMedia 18 | import Foundation 19 | 20 | @_implementationOnly import LiveKitWebRTC 21 | 22 | @objc 23 | public class RemoteAudioTrack: Track, RemoteTrack, AudioTrack { 24 | // State used to manage AudioRenderers 25 | private struct RendererState { 26 | var didAttacheAudioRendererAdapter: Bool = false 27 | let audioRenderers = MulticastDelegate(label: "AudioRenderer") 28 | } 29 | 30 | private lazy var _audioRendererAdapter = AudioRendererAdapter(target: self) 31 | private let _rendererState = StateSync(RendererState()) 32 | 33 | /// Volume with range 0.0 - 1.0 34 | public var volume: Double { 35 | get { 36 | guard let audioTrack = mediaTrack as? LKRTCAudioTrack else { return 0 } 37 | return audioTrack.source.volume / 10 38 | } 39 | set { 40 | guard let audioTrack = mediaTrack as? LKRTCAudioTrack else { return } 41 | audioTrack.source.volume = newValue * 10 42 | } 43 | } 44 | 45 | init(name: String, 46 | source: Track.Source, 47 | track: LKRTCMediaStreamTrack, 48 | reportStatistics: Bool) 49 | { 50 | super.init(name: name, 51 | kind: .audio, 52 | source: source, 53 | track: track, 54 | reportStatistics: reportStatistics) 55 | } 56 | 57 | public func add(audioRenderer: AudioRenderer) { 58 | guard let audioTrack = mediaTrack as? LKRTCAudioTrack else { return } 59 | 60 | _rendererState.mutate { 61 | $0.audioRenderers.add(delegate: audioRenderer) 62 | if !$0.didAttacheAudioRendererAdapter { 63 | audioTrack.add(_audioRendererAdapter) 64 | $0.didAttacheAudioRendererAdapter = true 65 | } 66 | } 67 | } 68 | 69 | public func remove(audioRenderer: AudioRenderer) { 70 | guard let audioTrack = mediaTrack as? LKRTCAudioTrack else { return } 71 | 72 | _rendererState.mutate { 73 | $0.audioRenderers.remove(delegate: audioRenderer) 74 | if $0.audioRenderers.allDelegates.isEmpty { 75 | audioTrack.remove(_audioRendererAdapter) 76 | $0.didAttacheAudioRendererAdapter = false 77 | } 78 | } 79 | } 80 | 81 | // MARK: - Internal 82 | 83 | override func startCapture() async throws { 84 | AudioManager.shared.trackDidStart(.remote) 85 | } 86 | 87 | override func stopCapture() async throws { 88 | AudioManager.shared.trackDidStop(.remote) 89 | } 90 | } 91 | 92 | extension RemoteAudioTrack: AudioRenderer { 93 | public func render(sampleBuffer: CMSampleBuffer) { 94 | _rendererState.audioRenderers.notify { audioRenderer in 95 | audioRenderer.render(sampleBuffer: sampleBuffer) 96 | } 97 | } 98 | } 99 | --------------------------------------------------------------------------------