├── .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 |
--------------------------------------------------------------------------------