├── .swift-version
├── Sources
├── LiveKit
│ ├── LiveKit.docc
│ │ ├── Resources
│ │ │ └── .keep
│ │ └── LiveKit.md
│ ├── Broadcast
│ │ ├── NOTICE
│ │ ├── IPC
│ │ │ ├── BroadcastIPCHeader.swift
│ │ │ └── SocketPath.swift
│ │ └── Support
│ │ │ └── BundleInfo.swift
│ ├── PrivacyInfo.xcprivacy
│ ├── Track
│ │ ├── Remote
│ │ │ ├── RemoteTrack.swift
│ │ │ ├── RemoteVideoTrack.swift
│ │ │ └── RemoteAudioTrack.swift
│ │ ├── AudioTrack.swift
│ │ ├── Local
│ │ │ └── LocalTrack.swift
│ │ ├── Track+Equatable.swift
│ │ ├── Track+MulticastDelegate.swift
│ │ └── Capturers
│ │ │ └── VideoCapturer+MulticastDelegate.swift
│ ├── Types
│ │ ├── Options
│ │ │ ├── CaptureOptions.swift
│ │ │ ├── VideoCaptureOptions.swift
│ │ │ ├── PublishOptions.swift
│ │ │ ├── CameraCaptureOptions+Copy.swift
│ │ │ ├── ConnectOptions+Copy.swift
│ │ │ ├── BufferCaptureOptions.swift
│ │ │ └── ARCameraCaptureOptions.swift
│ │ ├── AudioDuckingLevel.swift
│ │ ├── MediaDevice.swift
│ │ ├── AudioEncoding+Comparable.swift
│ │ ├── VideoEncoding+Comparable.swift
│ │ ├── VideoParameters+Comparable.swift
│ │ ├── Codec
│ │ │ └── Codec.swift
│ │ ├── TrackStreamState.swift
│ │ ├── SpeechActivityEvent.swift
│ │ ├── VideoRotation.swift
│ │ ├── IceTransportPolicy.swift
│ │ ├── AudioDevice.swift
│ │ ├── TrackType.swift
│ │ ├── ProtocolVersion.swift
│ │ ├── ConnectionQuality.swift
│ │ ├── TrackSource.swift
│ │ ├── Track+Types.swift
│ │ ├── Room+Types.swift
│ │ ├── VideoEncoding.swift
│ │ ├── ConnectionState.swift
│ │ ├── DegradationPreference.swift
│ │ ├── ScalabilityMode.swift
│ │ ├── SessionDescription.swift
│ │ ├── AudioBuffer.swift
│ │ ├── TranscriptionSegment.swift
│ │ ├── TrackSettings.swift
│ │ ├── ParticipantTrackPermission.swift
│ │ ├── IceServer.swift
│ │ ├── AudioEncoding.swift
│ │ ├── Participant+Types.swift
│ │ ├── IceCandidate.swift
│ │ └── VideoQuality.swift
│ ├── Protocols
│ │ ├── MediaEncoding.swift
│ │ ├── Mirrorable.swift
│ │ ├── NextInvokable.swift
│ │ ├── VideoProcessor.swift
│ │ ├── VideoViewDelegate.swift
│ │ ├── TransportDelegate.swift
│ │ ├── AudioRenderer.swift
│ │ └── TrackDelegate.swift
│ ├── DataStream
│ │ ├── Incoming
│ │ │ └── StreamReaderSource.swift
│ │ ├── Outgoing
│ │ │ ├── StreamWriterDestination.swift
│ │ │ └── StreamData.swift
│ │ └── StreamError.swift
│ ├── Extensions
│ │ ├── AsyncSequence.swift
│ │ ├── DispatchQueue.swift
│ │ ├── URL.swift
│ │ ├── RTCMediaConstraints.swift
│ │ ├── RTCDataChannel+Util.swift
│ │ ├── RTCConfiguration.swift
│ │ ├── String.swift
│ │ ├── AVCaptureDevice.swift
│ │ ├── Primitives.swift
│ │ ├── RTCRtpTransceiver.swift
│ │ └── Sendable.swift
│ ├── Participant
│ │ ├── Participant+Identifiable.swift
│ │ ├── Participant+Equatable.swift
│ │ ├── Participant+MulticastDelegate.swift
│ │ ├── Participant+Kind.swift
│ │ └── Participant+Convenience.swift
│ ├── TrackPublications
│ │ ├── TrackPublication+Identifiable.swift
│ │ └── TrackPublication+Equatable.swift
│ ├── Support
│ │ ├── ValueOrAbsent.swift
│ │ ├── AsyncDebounce.swift
│ │ ├── AsyncSerialDelegate.swift
│ │ ├── AsyncTryMapSequence.swift
│ │ ├── RingBuffer.swift
│ │ ├── HTTP.swift
│ │ ├── SerialRunnerActor.swift
│ │ ├── Global.swift
│ │ ├── Stopwatch.swift
│ │ └── NativeView.swift
│ ├── Core
│ │ ├── Room+MulticastDelegate.swift
│ │ ├── Room+Convenience.swift
│ │ └── Room+Debug.swift
│ ├── Views
│ │ └── VideoView+MulticastDelegate.swift
│ ├── Agent
│ │ ├── Room+Agent.swift
│ │ ├── Participant+Agent.swift
│ │ └── AgentState.swift
│ ├── SwiftUI
│ │ ├── SwiftUIAudioRoutePickerButton.swift
│ │ └── TrackDelegateObserver.swift
│ ├── LiveKit+DeviceHelpers.swift
│ ├── E2EE
│ │ ├── State.swift
│ │ └── Options.swift
│ ├── LiveKit.swift
│ └── Audio
│ │ └── Manager
│ │ └── AudioManager+Testing.swift
└── LKObjCHelpers
│ ├── include
│ └── LKObjCHelpers.h
│ └── LKObjCHelpers.m
├── .periphery.yml
├── .nanparc
├── .github
├── banner_dark.png
├── banner_light.png
├── workflows
│ ├── bump.yaml
│ ├── publish-docs.yaml
│ ├── cocoapods-lint.yaml
│ └── cocoapods-push.yaml
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── Docs
├── Resources
│ ├── in-app-dialog.png
│ ├── broadcast-dialog.png
│ └── new-target-options.png
└── cocoapods.md
├── Tests
├── LKTestHost
│ ├── LKTestHost
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ ├── LKTestHost.entitlements
│ │ └── LKTestHostApp.swift
│ ├── LKTestHost.xcodeproj
│ │ └── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ ├── TestPlan.xctestplan
│ └── README.md
├── LiveKitTests
│ ├── ObjCHelpersTests.swift
│ ├── SDKTests.swift
│ ├── Support
│ │ ├── ConcurrentCounter.swift
│ │ ├── MockDataChannelPair.swift
│ │ └── TestAudioRecorder.swift
│ ├── Broadcast
│ │ └── SocketPathTests.swift
│ ├── LKTestCase.swift
│ ├── CodecTests.swift
│ ├── VideoViewTests.swift
│ ├── DeviceManager.swift
│ ├── AsyncRetryTests.swift
│ ├── QueueActorTests.swift
│ ├── DarwinNotificationCenterTests.swift
│ ├── Extensions
│ │ └── StringTests.swift
│ ├── ParticipantTests.swift
│ ├── DataStream
│ │ └── ByteStreamInfoTests.swift
│ ├── FunctionTests.swift
│ ├── PreConnectAudioBufferTests.swift
│ └── PublishTrackTests.swift
└── LiveKitTestsObjC
│ └── Basic.m
├── .nanpa
└── .keep
├── .gitignore
├── scripts
├── build_docs.sh
└── replace_version.sh
├── NOTICE
├── livekit_ipc.proto
├── .swiftformat
├── Makefile
├── LiveKit.xctestplan
└── LiveKitClient.podspec
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.7
2 |
--------------------------------------------------------------------------------
/Sources/LiveKit/LiveKit.docc/Resources/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Sources/LiveKit/LiveKit.docc/LiveKit.md:
--------------------------------------------------------------------------------
1 | # ``LiveKit``
2 |
--------------------------------------------------------------------------------
/.periphery.yml:
--------------------------------------------------------------------------------
1 | retain_public: true
2 | targets:
3 | - LiveKit
4 |
--------------------------------------------------------------------------------
/.nanparc:
--------------------------------------------------------------------------------
1 | version 2.6.0
2 | name client-sdk-swift
3 | custom ./scripts/replace_version.sh
4 |
--------------------------------------------------------------------------------
/.github/banner_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afgarcia86/client-sdk-swift/main/.github/banner_dark.png
--------------------------------------------------------------------------------
/.github/banner_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afgarcia86/client-sdk-swift/main/.github/banner_light.png
--------------------------------------------------------------------------------
/Docs/Resources/in-app-dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afgarcia86/client-sdk-swift/main/Docs/Resources/in-app-dialog.png
--------------------------------------------------------------------------------
/Docs/Resources/broadcast-dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afgarcia86/client-sdk-swift/main/Docs/Resources/broadcast-dialog.png
--------------------------------------------------------------------------------
/Docs/Resources/new-target-options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afgarcia86/client-sdk-swift/main/Docs/Resources/new-target-options.png
--------------------------------------------------------------------------------
/Tests/LKTestHost/LKTestHost/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.nanpa/.keep:
--------------------------------------------------------------------------------
1 | Add changeset files in this directory.
2 |
3 | See nanpa documentation for more info:
4 | https://github.com/nbsp/nanpa/blob/trunk/doc/nanpa-changeset.5.scd
--------------------------------------------------------------------------------
/Tests/LKTestHost/LKTestHost.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Tests/LKTestHost/LKTestHost/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/scripts/build_docs.sh:
--------------------------------------------------------------------------------
1 | cd "$(dirname "$0")"
2 |
3 | xcodebuild docbuild \
4 | -scheme LiveKit \
5 | -destination generic/platform=iOS \
6 | -sdk iphoneos \
7 | -derivedDataPath ./build_docs/
8 |
9 | rsync -a --delete -vh build_docs/Build/Products/Debug-iphoneos/LiveKit.doccarchive \
10 | ./
11 |
--------------------------------------------------------------------------------
/Sources/LKObjCHelpers/include/LKObjCHelpers.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface LKObjCHelpers : NSObject
5 |
6 | + (void)finishBroadcastWithoutError:(RPBroadcastSampleHandler *)handler API_AVAILABLE(ios(10.0), macCatalyst(13.1), macos(11.0), tvos(10.0));
7 |
8 | @end
9 |
--------------------------------------------------------------------------------
/Tests/LKTestHost/LKTestHost/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIBackgroundModes
6 |
7 | audio
8 | voip
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.github/workflows/bump.yaml:
--------------------------------------------------------------------------------
1 | name: Bump version
2 | on:
3 | workflow_dispatch:
4 | inputs:
5 | packages:
6 | description: "Packages to bump"
7 | type: string
8 | required: true
9 | jobs:
10 | bump:
11 | runs-on: ubuntu-latest
12 | permissions:
13 | contents: write
14 | steps:
15 | - uses: actions/checkout@v3
16 | - uses: nbsp/ilo@v1
17 | with:
18 | packages: ${{ github.event.inputs.packages }}
19 |
--------------------------------------------------------------------------------
/Tests/LKTestHost/TestPlan.xctestplan:
--------------------------------------------------------------------------------
1 | {
2 | "configurations" : [
3 | {
4 | "id" : "4083664C-CBD4-4DC9-A23B-AD78C697A794",
5 | "name" : "Configuration 1",
6 | "options" : {
7 |
8 | }
9 | }
10 | ],
11 | "defaultOptions" : {
12 | "testTimeoutsEnabled" : true
13 | },
14 | "testTargets" : [
15 | {
16 | "target" : {
17 | "containerPath" : "container:LKTestHost.xcodeproj",
18 | "identifier" : "7B7356772D9CB604003BB6F4",
19 | "name" : "LiveKitTests"
20 | }
21 | }
22 | ],
23 | "version" : 1
24 | }
25 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/LKTestHost/LKTestHost/LKTestHost.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.networking.multipath
6 |
7 | com.apple.security.app-sandbox
8 |
9 | com.apple.security.device.audio-input
10 |
11 | com.apple.security.device.camera
12 |
13 | com.apple.security.files.user-selected.read-only
14 |
15 | com.apple.security.network.client
16 |
17 | com.apple.security.network.server
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Track/Remote/RemoteTrack.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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/Types/Options/CaptureOptions.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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: Sendable {}
21 |
--------------------------------------------------------------------------------
/Docs/cocoapods.md:
--------------------------------------------------------------------------------
1 | # CocoaPods Installation
2 |
3 | To install LiveKit using CocoaPods, add the LiveKit podspec source to your
4 | Podfile in addition to adding the pod:
5 |
6 | ```ruby
7 | source "https://cdn.cocoapods.org/"
8 | source "https://github.com/livekit/podspecs.git" # <-
9 |
10 | platform :ios, "18.0"
11 |
12 | target "YourApp" do
13 | pod "LiveKitClient", "~> 2.2.0"
14 |
15 | # Other dependencies...
16 | end
17 | ```
18 |
19 | The LiveKit source is necessary as some of this library's dependencies
20 | no longer officially support Cocoapods; the LiveKit source defines
21 | podspecs for such dependencies.
22 |
23 | ## Platform support
24 |
25 | Currently, only iOS and macOS are supported through Cocoapods. To use
26 | LiveKit in a tvOS or visionOS app, please install using SPM.
27 |
--------------------------------------------------------------------------------
/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 | ${PROTO_SOURCE}/livekit_metrics.proto
8 |
9 | docs: swift-docs
10 | swift doc generate Sources/LiveKit \
11 | --module-name "LiveKit Swift Client SDK" \
12 | --output Documentation \
13 | --format html \
14 | --base-url /client-sdk-swift
15 |
16 | protoc-swift:
17 | ifeq (, $(shell which protoc-gen-swift))
18 | brew install swift-protobuf
19 | endif
20 |
21 | protoc:
22 | ifeq (, $(shell which protoc))
23 | brew install protobuf
24 | endif
25 |
26 | swift-docs:
27 | ifeq (, $(shell which swift-doc))
28 | brew install swiftdocorg/formulae/swift-doc
29 | endif
30 |
--------------------------------------------------------------------------------
/Sources/LKObjCHelpers/LKObjCHelpers.m:
--------------------------------------------------------------------------------
1 | #import "LKObjCHelpers.h"
2 |
3 | @implementation LKObjCHelpers
4 |
5 | NS_ASSUME_NONNULL_BEGIN
6 |
7 | + (void)finishBroadcastWithoutError:(RPBroadcastSampleHandler *)handler API_AVAILABLE(ios(10.0), macCatalyst(13.1), macos(11.0), tvos(10.0)) {
8 | // Call finishBroadcastWithError with nil error, which ends the broadcast without an error popup
9 | // This is unsupported/undocumented but appears to work and is preferable to an error dialog with a cryptic default message
10 | // See https://stackoverflow.com/a/63402492 for more discussion
11 | #pragma clang diagnostic push
12 | #pragma clang diagnostic ignored "-Wnonnull"
13 | [handler finishBroadcastWithError:nil];
14 | #pragma clang diagnostic pop
15 | }
16 |
17 | NS_ASSUME_NONNULL_END
18 |
19 | @end
20 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Protocols/MediaEncoding.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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/Protocols/Mirrorable.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | @MainActor
21 | protocol Mirrorable {
22 | func set(isMirrored: Bool)
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Protocols/NextInvokable.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 protocol NextInvokable {
20 | associatedtype Next
21 | var next: Next? { get set }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Protocols/VideoProcessor.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 VideoProcessor {
21 | func process(frame: VideoFrame) -> VideoFrame?
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/AudioDuckingLevel.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | public enum AudioDuckingLevel: Int {
18 | case `default` = 0
19 | case min = 10
20 | case mid = 20
21 | case max = 30
22 | }
23 |
--------------------------------------------------------------------------------
/.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 | **Xcode Version**
20 | The Xcode version which the issue occurs.
21 | (Please provide the Swift version if you know it)
22 |
23 | **Steps to Reproduce**
24 | Steps to reproduce the behavior.
25 |
26 | **Expected behavior**
27 | A clear and concise description of what you expected to happen.
28 |
29 | **Screenshots**
30 | If applicable, add screenshots to help explain your problem.
31 |
32 | **Logs**
33 | Please provide logs if you can.
34 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/MediaDevice.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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/DataStream/Incoming/StreamReaderSource.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | /// Upstream asynchronous sequence from which raw chunk data is read.
20 | typealias StreamReaderSource = AsyncThrowingStream
21 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Extensions/AsyncSequence.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | extension AsyncSequence where Element: RangeReplaceableCollection {
18 | func collect() async throws -> Element {
19 | try await reduce(Element()) { $0 + $1 }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/ObjCHelpersTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 LKObjCHelpers
18 | import XCTest
19 |
20 | class ObjCHelperTests: LKTestCase {
21 | func testHelper() {
22 | LKObjCHelpers.finishBroadcastWithoutError(nil)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/SDKTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 SDKTests: LKTestCase {
21 | func testReadVersion() {
22 | print("LiveKitSDK.version: \(LiveKitSDK.version)")
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/Options/VideoCaptureOptions.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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, Sendable {
21 | var dimensions: Dimensions { get }
22 | var fps: Int { get }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/AudioEncoding+Comparable.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 2025 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 2025 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/DataStream/Outgoing/StreamWriterDestination.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | protocol StreamWriterDestination: Sendable {
20 | var isOpen: Bool { get async }
21 | func write(_ data: some StreamData) async throws
22 | func close(reason: String?) async throws
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/LiveKit/TrackPublications/TrackPublication+Identifiable.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Track/AudioTrack.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 AudioTrack where Self: Track {
21 | @objc(addAudioRenderer:)
22 | func add(audioRenderer: AudioRenderer)
23 |
24 | @objc(removeAudioRenderer:)
25 | func remove(audioRenderer: AudioRenderer)
26 | }
27 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/Support/ConcurrentCounter.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 ConcurrentCounter {
20 | private var count: Int = 0
21 |
22 | func increment() -> Int {
23 | count += 1
24 | return count - 1
25 | }
26 |
27 | func getCount() -> Int {
28 | count
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/LKTestHost/README.md:
--------------------------------------------------------------------------------
1 | # LKTestHost
2 |
3 | Minimal application for running unit tests on device.
4 |
5 | ## Environment
6 |
7 | | Key | Default |
8 | | ---------------------------- | --------------------- |
9 | | `LIVEKIT_TESTING_URL` | `ws://localhost:7880` |
10 | | `LIVEKIT_TESTING_API_KEY` | `devkey` |
11 | | `LIVEKIT_TESTING_API_SECRET` | `secret` |
12 |
13 | Set these values by specifying them under the arguments tab for the "LKTestHost" scheme.
14 |
15 | Some tests require access to a LiveKit server. For testing on-device, you can run a development server on your Mac and set `LIVEKIT_TESTING_URL` accordingly:
16 |
17 | ```sh
18 | livekit-server --dev --bind 0.0.0.0
19 | # Set `LIVEKIT_TESTING_URL` to `ws://:7880`
20 | ```
21 |
22 | Make sure to allow local network access when prompted.
23 |
24 | ## Usage
25 |
26 | 1. Open the Xcode Project
27 | 2. Select your device
28 | 3. Click the icon for "Build and then test current scheme"
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/VideoEncoding+Comparable.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 2025 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 2025 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: Sendable {
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 2025 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 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Extensions/URL.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 URL {
20 | var isValidForConnect: Bool {
21 | host != nil && (scheme == "ws" || scheme == "wss" || scheme == "https" || scheme == "http")
22 | }
23 |
24 | var isValidForSocket: Bool {
25 | host != nil && (scheme == "ws" || scheme == "wss")
26 | }
27 |
28 | var isSecure: Bool {
29 | scheme == "https" || scheme == "wss"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Extensions/RTCMediaConstraints.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | extension LKRTCMediaConstraints {
26 | static let defaultPCConstraints = LKRTCMediaConstraints(
27 | mandatoryConstraints: nil,
28 | optionalConstraints: ["DtlsSrtpKeyAgreement": kRTCMediaConstraintsValueTrue]
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/Codec/Codec.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | public protocol Codec: Identifiable, Sendable {
18 | var name: String { get }
19 | var mediaType: String { get }
20 | static func from(name: String) -> Self?
21 | static func from(mimeType: String) -> Self?
22 | }
23 |
24 | public extension Codec {
25 | // Identifiable by mimeString
26 | var id: String {
27 | mimeType
28 | }
29 |
30 | var mimeType: String {
31 | "\(mediaType)/\(name)"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Broadcast/IPC/BroadcastIPCHeader.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if os(iOS)
18 |
19 | /// Message header for communication between uploader and receiver.
20 | enum BroadcastIPCHeader: Codable {
21 | /// Image sample sent by uploader.
22 | case image(BroadcastImageCodec.Metadata, VideoRotation)
23 |
24 | /// Audio sample sent by uploader.
25 | case audio(BroadcastAudioCodec.Metadata)
26 |
27 | /// Request sent by receiver to set audio demand.
28 | case wantsAudio(Bool)
29 | }
30 |
31 | #endif
32 |
--------------------------------------------------------------------------------
/scripts/replace_version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # This script is invoked by nanpa with the new version set in VERSION.
3 | # ed is used instead of sed to make this script POSIX compliant (run on Mac or Linux runner).
4 |
5 | if [ -z "${VERSION}" ]; then
6 | echo "Error: VERSION is not set. Exiting..."
7 | exit 1
8 | fi
9 |
10 | replace() {
11 | ed -s "$1" >/dev/null
12 | if [ $? -ne 0 ]; then
13 | echo "Error: unable to replace version in $1" >&2
14 | exit 1
15 | fi
16 | }
17 |
18 | # 1. Podspec version & tag
19 | # -----------------------------------------
20 | replace ./LiveKitClient.podspec < 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/Types/TrackStreamState.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | @objc
26 | public enum StreamState: Int, Sendable {
27 | case paused
28 | case active
29 | }
30 |
31 | extension Livekit_StreamState {
32 | func toLKType() -> StreamState {
33 | switch self {
34 | case .active: return .active
35 | default: return .paused
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Core/Room+MulticastDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 2025 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 2025 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/Types/SpeechActivityEvent.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if swift(>=5.9)
18 | internal import LiveKitWebRTC
19 | #else
20 | @_implementationOnly import LiveKitWebRTC
21 | #endif
22 |
23 | public enum SpeechActivityEvent {
24 | case started
25 | case ended
26 | }
27 |
28 | extension RTCSpeechActivityEvent {
29 | func toLKType() -> SpeechActivityEvent {
30 | switch self {
31 | case .started: return .started
32 | case .ended: return .ended
33 | @unknown default: return .ended
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/Broadcast/SocketPathTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if os(iOS)
18 |
19 | @testable import LiveKit
20 | import XCTest
21 |
22 | final class SocketPathTests: LKTestCase {
23 | func testValid() throws {
24 | let path = "/tmp/a.sock"
25 | let socketPath = try XCTUnwrap(SocketPath(path))
26 | XCTAssertEqual(socketPath.path, path)
27 | }
28 |
29 | func testInvalid() {
30 | let longPath = String(repeating: "a", count: 104)
31 | XCTAssertNil(SocketPath(longPath))
32 | }
33 | }
34 |
35 | #endif
36 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Participant/Participant+Equatable.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/Support/MockDataChannelPair.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 |
19 | /// Mock ``DataChannelPair`` to intercept outgoing packets.
20 | class MockDataChannelPair: DataChannelPair, @unchecked Sendable {
21 | var packetHandler: (Livekit_DataPacket) -> Void
22 |
23 | init(packetHandler: @escaping (Livekit_DataPacket) -> Void) {
24 | self.packetHandler = packetHandler
25 | }
26 |
27 | override func send(dataPacket packet: Livekit_DataPacket) async throws {
28 | packetHandler(packet)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/LiveKit/TrackPublications/TrackPublication+Equatable.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 |
--------------------------------------------------------------------------------
/Tests/LKTestHost/LKTestHost/LKTestHostApp.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 SwiftUI
18 |
19 | @main
20 | struct LKTestHostApp: App {
21 | var body: some Scene {
22 | WindowGroup {
23 | ContentView()
24 | }
25 | }
26 | }
27 |
28 | private struct ContentView: View {
29 | var body: some View {
30 | VStack {
31 | Text("LiveKit Test Host")
32 | .font(.title)
33 | }
34 | .scenePadding()
35 | .preferredColorScheme(.dark)
36 | }
37 | }
38 |
39 | #Preview {
40 | ContentView()
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Participant/Participant+MulticastDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 2025 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/Extensions/RTCDataChannel+Util.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | extension LKRTCDataChannel {
26 | enum labels {
27 | static let reliable = "_reliable"
28 | static let lossy = "_lossy"
29 | }
30 |
31 | func toLKInfoType() -> Livekit_DataChannelInfo {
32 | Livekit_DataChannelInfo.with {
33 | $0.id = UInt32(max(0, channelId))
34 | $0.label = label
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 nonisolated func add(delegate: VideoViewDelegate) {
22 | delegates.add(delegate: delegate)
23 | }
24 |
25 | @objc(removeDelegate:)
26 | public nonisolated func remove(delegate: VideoViewDelegate) {
27 | delegates.remove(delegate: delegate)
28 | }
29 |
30 | @objc
31 | public nonisolated func removeAllDelegates() {
32 | delegates.removeAllDelegates()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Broadcast/Support/BundleInfo.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | /// A property wrapper type that reflects a value from a bundle's info dictionary.
20 | @propertyWrapper
21 | struct BundleInfo: Sendable {
22 | private let key: String
23 | private let bundle: Bundle
24 |
25 | init(_ key: String, bundle: Bundle = .main) {
26 | self.key = key
27 | self.bundle = bundle
28 | }
29 |
30 | var wrappedValue: Value? {
31 | guard let value = bundle.infoDictionary?[key] as? Value else { return nil }
32 | return value
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/LKTestCase.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | /// Subclass of XCTestCase that performs global initialization.
22 | class LKTestCase: XCTestCase {
23 | private static let _globalSetup: Bool = {
24 | LiveKitSDK.setLoggerStandardOutput()
25 | RTCSetMinDebugLogLevel(.info)
26 | return true
27 | }()
28 |
29 | override func setUp() {
30 | assert(Self._globalSetup, "Global initialization failed")
31 | continueAfterFailure = false // Fail early
32 | super.setUp()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/CodecTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 CodecTests: LKTestCase {
21 | func testParseCodec() throws {
22 | // Video codecs
23 | let vp8 = VideoCodec.from(mimeType: "video/vp8")
24 | XCTAssert(vp8 == .vp8)
25 |
26 | let vp9 = VideoCodec.from(mimeType: "video/vp9")
27 | XCTAssert(vp9 == .vp9)
28 |
29 | let h264 = VideoCodec.from(mimeType: "video/h264")
30 | XCTAssert(h264 == .h264)
31 |
32 | let av1 = VideoCodec.from(mimeType: "video/av1")
33 | XCTAssert(av1 == .av1)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/VideoViewTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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: LKTestCase {
21 | /// Test if avSampleBufferDisplayLayer is available immediately after creating VideoView.
22 | @MainActor
23 | func testAVSampleBufferDisplayLayer() {
24 | let track = LocalVideoTrack.createCameraTrack()
25 | let view = VideoView()
26 | view.renderMode = .sampleBuffer
27 | view.track = track
28 | // avSampleBufferDisplayLayer should not be nil at this point
29 | XCTAssert(view.avSampleBufferDisplayLayer != nil)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/VideoRotation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if swift(>=5.9)
18 | internal import LiveKitWebRTC
19 | #else
20 | @_implementationOnly import LiveKitWebRTC
21 | #endif
22 |
23 | public enum VideoRotation: Int, Sendable, Codable {
24 | case _0 = 0
25 | case _90 = 90
26 | case _180 = 180
27 | case _270 = 270
28 | }
29 |
30 | extension RTCVideoRotation {
31 | func toLKType() -> VideoRotation {
32 | VideoRotation(rawValue: rawValue)!
33 | }
34 | }
35 |
36 | extension VideoRotation {
37 | func toRTCType() -> RTCVideoRotation {
38 | RTCVideoRotation(rawValue: rawValue)!
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Extensions/RTCConfiguration.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | extension LKRTCConfiguration {
26 | static func liveKitDefault() -> LKRTCConfiguration {
27 | let result = DispatchQueue.liveKitWebRTC.sync { LKRTCConfiguration() }
28 | result.sdpSemantics = .unifiedPlan
29 | result.continualGatheringPolicy = .gatherContinually
30 | result.candidateNetworkPolicy = .all
31 | result.tcpCandidatePolicy = .enabled
32 |
33 | return result
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Protocols/VideoViewDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | @objc
26 | public protocol VideoViewDelegate: AnyObject, Sendable {
27 | /// Dimensions of the VideoView itself has updated
28 | @objc(videoView:didUpdateSize:) optional
29 | func videoView(_ videoView: VideoView, didUpdate size: CGSize)
30 | /// VideoView updated the isRendering property
31 | @objc(videoView:didUpdateIsRendering:) optional
32 | func videoView(_ videoView: VideoView, didUpdate isRendering: Bool)
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/IceTransportPolicy.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | @objc
26 | public enum IceTransportPolicy: Int, Sendable {
27 | case none
28 | case relay
29 | case noHost
30 | case all
31 | }
32 |
33 | extension IceTransportPolicy {
34 | func toRTCType() -> RTCIceTransportPolicy {
35 | switch self {
36 | case .none: return .none
37 | case .relay: return .relay
38 | case .noHost: return .noHost
39 | case .all: return .all
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/AudioDevice.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | @objc
26 | public class AudioDevice: NSObject, MediaDevice {
27 | public var deviceId: String { _ioDevice.deviceId }
28 | public var name: String { _ioDevice.name }
29 | public var isDefault: Bool { _ioDevice.isDefault }
30 |
31 | let _ioDevice: LKRTCIODevice
32 |
33 | init(ioDevice: LKRTCIODevice) {
34 | _ioDevice = ioDevice
35 | }
36 | }
37 |
38 | extension AudioDevice: Identifiable {
39 | public var id: String { deviceId }
40 | }
41 |
--------------------------------------------------------------------------------
/LiveKitClient.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = "LiveKitClient"
3 | spec.version = "2.6.0"
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 => spec.version.to_s}
14 |
15 | spec.source_files = "Sources/**/*"
16 |
17 | spec.dependency("LiveKitWebRTC", "= 125.6422.29")
18 | spec.dependency("SwiftProtobuf")
19 | spec.dependency("Logging", "= 1.5.4")
20 | spec.dependency("DequeModule", "= 1.1.4")
21 | spec.dependency("OrderedCollections", " = 1.1.4")
22 |
23 | spec.resource_bundles = {"Privacy" => ["Sources/LiveKit/PrivacyInfo.xcprivacy"]}
24 |
25 | xcode_output = `xcodebuild -version`.strip
26 | major_version = xcode_output =~ /Xcode\s+(\d+)/ ? $1.to_i : 15
27 |
28 | spec.pod_target_xcconfig = {
29 | "OTHER_SWIFT_FLAGS" => major_version >=15 ?
30 | "-enable-experimental-feature AccessLevelOnImport" : ""
31 | }
32 | end
33 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/TrackType.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/DeviceManager.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 DeviceManagerTests: LKTestCase {
21 | func testListDevices() async throws {
22 | let devices = try await DeviceManager.shared.devices()
23 | print("Devices: \(devices.map { "(facingPosition: \(String(describing: $0.facingPosition)))" }.joined(separator: ", "))")
24 | XCTAssert(devices.count > 0, "No capture devices found.")
25 |
26 | // visionOS will return 0 formats.
27 | guard let firstDevice = devices.first else { return }
28 | let formats = firstDevice.formats
29 | XCTAssert(formats.count > 0, "No formats found for device.")
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/AsyncRetryTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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: LKTestCase {
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 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/ProtocolVersion.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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, Sendable {
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 2025 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, Sendable {
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 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/QueueActorTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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: LKTestCase {
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 2025 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: @Sendable @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 2025 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, Sendable {
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/Protocols/TransportDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if swift(>=5.9)
18 | internal import LiveKitWebRTC
19 | #else
20 | @_implementationOnly import LiveKitWebRTC
21 | #endif
22 |
23 | protocol TransportDelegate: AnyObject, Sendable {
24 | func transport(_ transport: Transport, didUpdateState state: RTCPeerConnectionState)
25 | func transport(_ transport: Transport, didGenerateIceCandidate iceCandidate: IceCandidate)
26 | func transport(_ transport: Transport, didOpenDataChannel dataChannel: LKRTCDataChannel)
27 | func transport(_ transport: Transport, didAddTrack track: LKRTCMediaStreamTrack, rtpReceiver: LKRTCRtpReceiver, streams: [LKRTCMediaStream])
28 | func transport(_ transport: Transport, didRemoveTrack track: LKRTCMediaStreamTrack)
29 | func transportShouldNegotiate(_ transport: Transport)
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Agent/Room+Agent.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | let publishOnBehalfAttributeKey = "lk.publish_on_behalf"
20 |
21 | public extension Room {
22 | /// A dictionary containing all agent participants.
23 | ///
24 | /// - Note: This will not include participants that are publishing on behalf of another participant
25 | /// e.g. avatar workers. To access them directly use ``Participant/avatarWorker`` property of `agentParticipant`.
26 | @objc
27 | var agentParticipants: [Participant.Identity: Participant] {
28 | allParticipants.filter { $0.value.isAgent && $0.value.attributes[publishOnBehalfAttributeKey] == nil }
29 | }
30 |
31 | /// The first agent participant.
32 | @objc
33 | var agentParticipant: Participant? {
34 | agentParticipants.values.first
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/TrackSource.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 |
--------------------------------------------------------------------------------
/.github/workflows/publish-docs.yaml:
--------------------------------------------------------------------------------
1 | name: Publish Docs
2 | on:
3 | workflow_dispatch:
4 | inputs:
5 | dry_run:
6 | description: Dry run (only list files, don't upload)
7 | default: true
8 | type: boolean
9 | release:
10 | types: [published]
11 | jobs:
12 | publish:
13 | name: Publish Docs
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Download Archive
17 | uses: dawidd6/action-download-artifact@v9
18 | with:
19 | workflow: ci.yaml
20 | workflow_search: false
21 | branch: main
22 | name: docs
23 |
24 | - name: Unzip Archive
25 | run: unzip docs.zip
26 |
27 | - name: List Files
28 | run: ls -la docs
29 |
30 | - name: S3 Upload
31 | if: inputs.dry_run != true
32 | run: aws s3 cp docs/ s3://livekit-docs/client-sdk-swift --recursive
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 |
38 | - name: Invalidate cache
39 | if: inputs.dry_run != true
40 | run: aws cloudfront create-invalidation --distribution-id EJJ40KLJ3TRY9 --paths "/*"
41 | env:
42 | AWS_ACCESS_KEY_ID: ${{ secrets.DOCS_DEPLOY_AWS_ACCESS_KEY }}
43 | AWS_SECRET_ACCESS_KEY: ${{ secrets.DOCS_DEPLOY_AWS_API_SECRET }}
44 | AWS_DEFAULT_REGION: "us-east-1"
45 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/Track+Types.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | final class Sid: NSObject, Codable, Sendable {
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/Room+Types.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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, @unchecked Sendable, 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/Core/Room+Debug.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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: Sendable {
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 startReconnect(reason: .debug)
37 | } else if case .fullReconnect = scenario {
38 | try await startReconnect(reason: .debug, nextReconnectMode: .full)
39 | } else {
40 | try await signalClient.sendSimulate(scenario: scenario)
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/VideoEncoding.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 final class VideoEncoding: NSObject, MediaEncoding, Sendable {
21 | @objc
22 | public let maxBitrate: Int
23 |
24 | @objc
25 | public let 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/SwiftUI/SwiftUIAudioRoutePickerButton.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 SwiftUI
19 |
20 | #if swift(>=5.9)
21 | internal import LiveKitWebRTC
22 | #else
23 | @_implementationOnly import LiveKitWebRTC
24 | #endif
25 |
26 | #if os(iOS) || os(macOS)
27 | public struct SwiftUIAudioRoutePickerButton: NativeViewRepresentable {
28 | public init() {}
29 |
30 | public func makeView(context _: Context) -> AVRoutePickerView {
31 | let routePickerView = AVRoutePickerView()
32 |
33 | #if os(iOS)
34 | routePickerView.prioritizesVideoDevices = false
35 | #elseif os(macOS)
36 | routePickerView.isRoutePickerButtonBordered = false
37 | #endif
38 |
39 | return routePickerView
40 | }
41 |
42 | public func updateView(_: AVRoutePickerView, context _: Context) {}
43 | public static func dismantleView(_: AVRoutePickerView, coordinator _: ()) {}
44 | }
45 | #endif
46 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Support/AsyncSerialDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | final class AsyncSerialDelegate: Sendable {
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: @Sendable @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: @Sendable @escaping (T) async -> Void) {
39 | Task.detached {
40 | try await self.notifyAsync(fnc)
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/ConnectionState.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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, Sendable {
21 | /// Quick reconnection mode attempts to maintain the same session, reusing existing
22 | /// transport connections and published tracks. This is faster but may not succeed
23 | /// in all network conditions.
24 | case quick
25 |
26 | /// Full reconnection mode performs a complete new connection to the LiveKit server,
27 | /// closing existing connections and re-publishing all tracks. This is slower but
28 | /// more reliable for recovering from severe connection issues.
29 | case full
30 | }
31 |
32 | @objc
33 | public enum ConnectionState: Int, Sendable {
34 | case disconnected
35 | case connecting
36 | case reconnecting
37 | case connected
38 | }
39 |
40 | extension ConnectionState: Identifiable {
41 | public var id: Int {
42 | rawValue
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/Options/CameraCaptureOptions+Copy.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | @preconcurrency 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/Types/DegradationPreference.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if swift(>=5.9)
18 | internal import LiveKitWebRTC
19 | #else
20 | @_implementationOnly import LiveKitWebRTC
21 | #endif
22 |
23 | @objc
24 | public enum DegradationPreference: Int, Sendable {
25 | /// The SDK will decide which preference is suitable or will use WebRTC's default implementation.
26 | case auto
27 | case disabled
28 | /// Prefer to maintain FPS rather than resolution.
29 | case maintainFramerate
30 | /// Prefer to maintain resolution rather than FPS.
31 | case maintainResolution
32 | case balanced
33 | }
34 |
35 | extension DegradationPreference {
36 | func toRTCType() -> RTCDegradationPreference? {
37 | switch self {
38 | case .auto: return nil
39 | case .disabled: return .disabled
40 | case .maintainFramerate: return .maintainFramerate
41 | case .maintainResolution: return .maintainResolution
42 | case .balanced: return .balanced
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Protocols/AudioRenderer.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | @preconcurrency import AVFoundation
18 | import CoreMedia
19 |
20 | #if swift(>=5.9)
21 | internal import LiveKitWebRTC
22 | #else
23 | @_implementationOnly import LiveKitWebRTC
24 | #endif
25 |
26 | /// Used to observe audio buffers before playback, e.g. for visualization, recording, etc
27 | /// - Note: AudioRenderer is not suitable for buffer modification. If you need to modify the buffer, use `AudioCustomProcessingDelegate` instead.
28 | @objc
29 | public protocol AudioRenderer: Sendable {
30 | @objc
31 | func render(pcmBuffer: AVAudioPCMBuffer)
32 | }
33 |
34 | class AudioRendererAdapter: MulticastDelegate, @unchecked Sendable, LKRTCAudioRenderer {
35 | //
36 | typealias Delegate = AudioRenderer
37 |
38 | init() {
39 | super.init(label: "AudioRendererAdapter")
40 | }
41 |
42 | // MARK: - LKRTCAudioRenderer
43 |
44 | func render(pcmBuffer: AVAudioPCMBuffer) {
45 | notify { $0.render(pcmBuffer: pcmBuffer) }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/Options/ConnectOptions+Copy.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | reconnectMaxDelay: ValueOrAbsent = .absent,
24 | protocolVersion: ValueOrAbsent = .absent) -> ConnectOptions
25 | {
26 | ConnectOptions(autoSubscribe: autoSubscribe.value(ifAbsent: self.autoSubscribe),
27 | reconnectAttempts: reconnectAttempts.value(ifAbsent: self.reconnectAttempts),
28 | reconnectAttemptDelay: reconnectAttemptDelay.value(ifAbsent: self.reconnectAttemptDelay),
29 | reconnectMaxDelay: reconnectMaxDelay.value(ifAbsent: self.reconnectMaxDelay),
30 | protocolVersion: protocolVersion.value(ifAbsent: self.protocolVersion))
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/LiveKit/DataStream/StreamError.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | public enum StreamError: Error, Equatable {
18 | /// Unable to open a stream with the same ID more than once.
19 | case alreadyOpened
20 |
21 | /// Stream closed abnormally by remote participant.
22 | case abnormalEnd(reason: String)
23 |
24 | /// Incoming chunk data could not be decoded.
25 | case decodeFailed
26 |
27 | /// Read length exceeded total length specified in stream header.
28 | case lengthExceeded
29 |
30 | /// Read length less than total length specified in stream header.
31 | case incomplete
32 |
33 | /// Stream terminated before completion.
34 | case terminated
35 |
36 | /// Cannot perform operations on an unknown stream.
37 | case unknownStream
38 |
39 | /// Unable to register a stream handler more than once.
40 | case handlerAlreadyRegistered
41 |
42 | /// Given destination URL is not a directory.
43 | case notDirectory
44 |
45 | /// Unable to read information about the file to send.
46 | case fileInfoUnavailable
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Support/AsyncTryMapSequence.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | extension AsyncSequence {
18 | func tryMap(
19 | _ transform: @escaping (Element) async throws -> T
20 | ) -> AsyncTryMapSequence {
21 | AsyncTryMapSequence(base: self, transform: transform)
22 | }
23 | }
24 |
25 | struct AsyncTryMapSequence: AsyncSequence {
26 | fileprivate let base: Base
27 | fileprivate let transform: (Base.Element) async throws -> Element
28 |
29 | struct Iterator: AsyncIteratorProtocol {
30 | var baseIterator: Base.AsyncIterator
31 | let transform: (Base.Element) async throws -> Element
32 |
33 | mutating func next() async throws -> Element? {
34 | guard let nextElement = try await baseIterator.next() else {
35 | return nil
36 | }
37 | return try await transform(nextElement)
38 | }
39 | }
40 |
41 | func makeAsyncIterator() -> Iterator {
42 | Iterator(baseIterator: base.makeAsyncIterator(), transform: transform)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Support/RingBuffer.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | // Simple ring-buffer used for internal audio processing. Not thread-safe.
20 | class RingBuffer {
21 | private var _isFull = false
22 | private var _buffer: [T]
23 | private var _head: Int = 0
24 |
25 | init(size: Int) {
26 | _buffer = [T](repeating: 0, count: size)
27 | }
28 |
29 | func write(_ value: T) {
30 | _buffer[_head] = value
31 | _head = (_head + 1) % _buffer.count
32 | if _head == 0 { _isFull = true }
33 | }
34 |
35 | func write(_ sequence: [T]) {
36 | for value in sequence {
37 | write(value)
38 | }
39 | }
40 |
41 | func read() -> [T]? {
42 | guard _isFull else { return nil }
43 |
44 | if _head == 0 {
45 | return _buffer // Return the entire buffer if _head is at the start
46 | } else {
47 | // Return the buffer in the correct order
48 | return Array(_buffer[_head ..< _buffer.count] + _buffer[0 ..< _head])
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/ScalabilityMode.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 |
--------------------------------------------------------------------------------
/.github/workflows/cocoapods-lint.yaml:
--------------------------------------------------------------------------------
1 | name: CocoaPods Lint
2 | on:
3 | workflow_dispatch:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 | concurrency:
9 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
10 | cancel-in-progress: true
11 | jobs:
12 | lint:
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | include:
17 | - os: macos-13
18 | xcode: 14.2
19 | - os: macos-14
20 | xcode: 15.4
21 | - os: macos-15
22 | xcode: 16.2
23 | runs-on: ${{ matrix.os }}
24 | timeout-minutes: 30
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@v3
28 | - name: Setup CocoaPods
29 | uses: maxim-lobanov/setup-cocoapods@v1
30 | with:
31 | version: latest
32 | - name: Setup Xcode
33 | uses: maxim-lobanov/setup-xcode@v1
34 | with:
35 | xcode-version: ${{ matrix.xcode }}
36 | - name: Library Lint
37 | id: lib-lint
38 | run: |
39 | validation_dir=$(mktemp -d)
40 | echo "validation_dir=${validation_dir}" >> $GITHUB_OUTPUT
41 | pod lib lint \
42 | --validation-dir="${validation_dir}" \
43 | --no-clean \
44 | --allow-warnings \
45 | --verbose \
46 | --sources=https://github.com/livekit/podspecs.git/,https://cdn.cocoapods.org/
47 | - name: Upload Validation Directory (Failure Only)
48 | if: failure()
49 | uses: actions/upload-artifact@v4
50 | with:
51 | path: ${{ steps.lib-lint.outputs.validation_dir }}
52 | name: validation-xcode${{ matrix.xcode }}
53 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Agent/Participant+Agent.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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
21 | var isAgent: Bool {
22 | switch kind {
23 | case .agent: return true
24 | default: return false
25 | }
26 | }
27 |
28 | @objc
29 | var agentState: AgentState {
30 | guard isAgent else { return .unknown }
31 | guard let attrString = attributes[agentStateAttributeKey] else { return .unknown }
32 | guard let state = AgentState.fromString(attrString) else { return .unknown }
33 | return state
34 | }
35 | }
36 |
37 | public extension Participant {
38 | private var publishingOnBehalf: [Participant.Identity: Participant] {
39 | guard let _room else { return [:] }
40 | return _room.allParticipants.filter { $0.value.attributes[publishOnBehalfAttributeKey] == identity?.stringValue }
41 | }
42 |
43 | /// The avatar worker participant associated with the agent.
44 | @objc
45 | var avatarWorker: Participant? {
46 | publishingOnBehalf.values.first
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Protocols/TrackDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | @objc
26 | public protocol TrackDelegate: AnyObject, Sendable {
27 | /// Dimensions of the video track has updated
28 | @objc(track:didUpdateDimensions:) optional
29 | func track(_ track: VideoTrack, didUpdateDimensions dimensions: Dimensions?)
30 |
31 | /// Statistics for the track has been generated (v2).
32 | @objc(track:didUpdateStatistics:simulcastStatistics:) optional
33 | func track(_ track: Track, didUpdateStatistics: TrackStatistics, simulcastStatistics: [VideoCodec: TrackStatistics])
34 | }
35 |
36 | protocol TrackDelegateInternal: TrackDelegate {
37 | /// Notify RemoteTrackPublication to send isMuted state to server.
38 | func track(_ track: Track, didUpdateIsMuted isMuted: Bool, shouldSendSignal: Bool)
39 |
40 | /// Used to report track state mutation to TrackPublication if attached.
41 | func track(_ track: Track, didMutateState newState: Track.State, oldState: Track.State)
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Extensions/String.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | var byteLength: Int {
26 | data(using: .utf8)?.count ?? 0
27 | }
28 |
29 | func truncate(maxBytes: Int) -> String {
30 | if byteLength <= maxBytes {
31 | return self
32 | }
33 |
34 | var low = 0
35 | var high = count
36 |
37 | while low < high {
38 | let mid = (low + high + 1) / 2
39 | let substring = String(prefix(mid))
40 | if substring.byteLength <= maxBytes {
41 | low = mid
42 | } else {
43 | high = mid - 1
44 | }
45 | }
46 |
47 | return String(prefix(low))
48 | }
49 |
50 | /// The path extension, if any, of the string as interpreted as a path.
51 | var pathExtension: String? {
52 | let pathExtension = (self as NSString).pathExtension
53 | return pathExtension.isEmpty ? nil : pathExtension
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Sources/LiveKit/LiveKit+DeviceHelpers.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 2025 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 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/SessionDescription.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if swift(>=5.9)
18 | internal import LiveKitWebRTC
19 | #else
20 | @_implementationOnly import LiveKitWebRTC
21 | #endif
22 |
23 | extension LKRTCSessionDescription {
24 | func toPBType() -> Livekit_SessionDescription {
25 | var sd = Livekit_SessionDescription()
26 | sd.sdp = sdp
27 |
28 | switch type {
29 | case .answer: sd.type = "answer"
30 | case .offer: sd.type = "offer"
31 | case .prAnswer: sd.type = "pranswer"
32 | default: fatalError("Unknown state \(type)") // This should never happen
33 | }
34 |
35 | return sd
36 | }
37 | }
38 |
39 | extension Livekit_SessionDescription {
40 | func toRTCType() -> LKRTCSessionDescription {
41 | var sdpType: RTCSdpType
42 | switch type {
43 | case "answer": sdpType = .answer
44 | case "offer": sdpType = .offer
45 | case "pranswer": sdpType = .prAnswer
46 | default: fatalError("Unknown state \(type)") // This should never happen
47 | }
48 |
49 | return RTC.createSessionDescription(type: sdpType, sdp: sdp)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Support/SerialRunnerActor.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | // Wait for the previous task to complete, but cancel it if needed
25 | if let previousTask, !Task.isCancelled {
26 | // If previous task is still running, wait for it
27 | _ = try? await previousTask.value
28 | }
29 |
30 | // Check for cancellation before running the block
31 | try Task.checkCancellation()
32 |
33 | // Run the new block
34 | return try await block()
35 | }
36 |
37 | previousTask = task
38 |
39 | return try await withTaskCancellationHandler {
40 | // Await the current task's result
41 | try await task.value
42 | } onCancel: {
43 | // Ensure the task is canceled when requested
44 | task.cancel()
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/AudioBuffer.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if swift(>=5.9)
18 | internal import LiveKitWebRTC
19 | #else
20 | @_implementationOnly import LiveKitWebRTC
21 | #endif
22 |
23 | import Foundation
24 |
25 | // Wrapper for LKRTCAudioBuffer
26 | @objc
27 | public class LKAudioBuffer: NSObject {
28 | private let _audioBuffer: LKRTCAudioBuffer
29 |
30 | @objc
31 | public var channels: Int { _audioBuffer.channels }
32 |
33 | @objc
34 | public var frames: Int { _audioBuffer.frames }
35 |
36 | @objc
37 | public var framesPerBand: Int { _audioBuffer.framesPerBand }
38 |
39 | @objc
40 | public var bands: Int { _audioBuffer.bands }
41 |
42 | @objc
43 | @available(*, deprecated, renamed: "rawBuffer(forChannel:)")
44 | public func rawBuffer(for channel: Int) -> UnsafeMutablePointer {
45 | _audioBuffer.rawBuffer(forChannel: channel)
46 | }
47 |
48 | @objc
49 | public func rawBuffer(forChannel channel: Int) -> UnsafeMutablePointer {
50 | _audioBuffer.rawBuffer(forChannel: channel)
51 | }
52 |
53 | init(audioBuffer: LKRTCAudioBuffer) {
54 | _audioBuffer = audioBuffer
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/DarwinNotificationCenterTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | @testable import LiveKit
19 | import XCTest
20 |
21 | class DarwinNotificationCenterTests: LKTestCase {
22 | func testPublisher() throws {
23 | let receiveFirst = XCTestExpectation(description: "Receive from 1st subscriber")
24 | let receiveSecond = XCTestExpectation(description: "Receive from 2nd subscriber")
25 |
26 | let name = DarwinNotification.broadcastStarted
27 |
28 | var cancellable = Set()
29 | DarwinNotificationCenter.shared
30 | .publisher(for: name)
31 | .sink {
32 | XCTAssertEqual($0, name)
33 | receiveFirst.fulfill()
34 | }
35 | .store(in: &cancellable)
36 | DarwinNotificationCenter.shared
37 | .publisher(for: name)
38 | .sink {
39 | XCTAssertEqual($0, name)
40 | receiveSecond.fulfill()
41 | }
42 | .store(in: &cancellable)
43 |
44 | DarwinNotificationCenter.shared.postNotification(name)
45 | wait(for: [receiveFirst, receiveSecond], timeout: 10.0)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/TranscriptionSegment.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 final class TranscriptionSegment: NSObject, Sendable {
21 | public let id: String
22 | public let text: String
23 | public let language: String
24 | public let firstReceivedTime: Date
25 | public let lastReceivedTime: Date
26 | public let isFinal: Bool
27 |
28 | init(id: String,
29 | text: String,
30 | language: String,
31 | firstReceivedTime: Date,
32 | lastReceivedTime: Date,
33 | isFinal: Bool)
34 | {
35 | self.id = id
36 | self.text = text
37 | self.language = language
38 | self.firstReceivedTime = firstReceivedTime
39 | self.lastReceivedTime = lastReceivedTime
40 | self.isFinal = isFinal
41 | }
42 |
43 | // MARK: - Equal
44 |
45 | override public func isEqual(_ object: Any?) -> Bool {
46 | guard let other = object as? Self else { return false }
47 | return id == other.id
48 | }
49 |
50 | override public var hash: Int {
51 | var hasher = Hasher()
52 | hasher.combine(id)
53 | return hasher.finalize()
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Agent/AgentState.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | let agentStateAttributeKey = "lk.agent.state"
18 |
19 | @objc
20 | public enum AgentState: Int {
21 | case unknown
22 | case disconnected
23 | case connecting
24 | case initializing
25 | case listening
26 | case thinking
27 | case speaking
28 | }
29 |
30 | extension AgentState {
31 | static func fromString(_ rawString: String?) -> AgentState? {
32 | switch rawString {
33 | case "initializing": return .initializing
34 | case "listening": return .listening
35 | case "thinking": return .thinking
36 | case "speaking": return .speaking
37 | default: return unknown
38 | }
39 | }
40 | }
41 |
42 | extension AgentState: CustomStringConvertible {
43 | public var description: String {
44 | switch self {
45 | case .unknown: return "Unknown"
46 | case .disconnected: return "Disconnected"
47 | case .connecting: return "Connecting"
48 | case .initializing: return "Initializing"
49 | case .listening: return "Listening"
50 | case .thinking: return "Thinking"
51 | case .speaking: return "Speaking"
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/Options/BufferCaptureOptions.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | @objc
26 | public final class BufferCaptureOptions: NSObject, VideoCaptureOptions, Sendable {
27 | @objc
28 | public let dimensions: Dimensions
29 |
30 | @objc
31 | public let fps: Int
32 |
33 | public init(dimensions: Dimensions = .h1080_169,
34 | fps: Int = 15)
35 | {
36 | self.dimensions = dimensions
37 | self.fps = fps
38 | }
39 |
40 | public init(from options: ScreenShareCaptureOptions) {
41 | dimensions = options.dimensions
42 | fps = options.fps
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 dimensions == other.dimensions &&
50 | fps == other.fps
51 | }
52 |
53 | override public var hash: Int {
54 | var hasher = Hasher()
55 | hasher.combine(dimensions)
56 | hasher.combine(fps)
57 | return hasher.finalize()
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Tests/LKTestHost/LKTestHost/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "idiom" : "universal",
16 | "platform" : "ios",
17 | "size" : "1024x1024"
18 | },
19 | {
20 | "appearances" : [
21 | {
22 | "appearance" : "luminosity",
23 | "value" : "tinted"
24 | }
25 | ],
26 | "idiom" : "universal",
27 | "platform" : "ios",
28 | "size" : "1024x1024"
29 | },
30 | {
31 | "idiom" : "mac",
32 | "scale" : "1x",
33 | "size" : "16x16"
34 | },
35 | {
36 | "idiom" : "mac",
37 | "scale" : "2x",
38 | "size" : "16x16"
39 | },
40 | {
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "32x32"
44 | },
45 | {
46 | "idiom" : "mac",
47 | "scale" : "2x",
48 | "size" : "32x32"
49 | },
50 | {
51 | "idiom" : "mac",
52 | "scale" : "1x",
53 | "size" : "128x128"
54 | },
55 | {
56 | "idiom" : "mac",
57 | "scale" : "2x",
58 | "size" : "128x128"
59 | },
60 | {
61 | "idiom" : "mac",
62 | "scale" : "1x",
63 | "size" : "256x256"
64 | },
65 | {
66 | "idiom" : "mac",
67 | "scale" : "2x",
68 | "size" : "256x256"
69 | },
70 | {
71 | "idiom" : "mac",
72 | "scale" : "1x",
73 | "size" : "512x512"
74 | },
75 | {
76 | "idiom" : "mac",
77 | "scale" : "2x",
78 | "size" : "512x512"
79 | }
80 | ],
81 | "info" : {
82 | "author" : "xcode",
83 | "version" : 1
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/Options/ARCameraCaptureOptions.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if os(visionOS)
18 | import Foundation
19 |
20 | #if swift(>=5.9)
21 | internal import LiveKitWebRTC
22 | #else
23 | @_implementationOnly import LiveKitWebRTC
24 | #endif
25 |
26 | @objc
27 | public final class ARCameraCaptureOptions: NSObject, VideoCaptureOptions, Sendable {
28 | @objc
29 | public let dimensions: Dimensions
30 |
31 | @objc
32 | public let fps: Int
33 |
34 | public init(dimensions: Dimensions = .h1080_169, fps: Int = 30) {
35 | self.dimensions = dimensions
36 | self.fps = fps
37 | }
38 |
39 | public init(from options: ScreenShareCaptureOptions) {
40 | dimensions = options.dimensions
41 | fps = options.fps
42 | }
43 |
44 | // MARK: - Equal
45 |
46 | override public func isEqual(_ object: Any?) -> Bool {
47 | guard let other = object as? Self else { return false }
48 | return dimensions == other.dimensions &&
49 | fps == other.fps
50 | }
51 |
52 | override public var hash: Int {
53 | var hasher = Hasher()
54 | hasher.combine(dimensions)
55 | hasher.combine(fps)
56 | return hasher.finalize()
57 | }
58 | }
59 | #endif
60 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Support/Global.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | let msecPerSec = 1000
20 |
21 | // merge a ClosedRange
22 | func merge(range range1: ClosedRange, with range2: ClosedRange) -> ClosedRange where T: Comparable {
23 | min(range1.lowerBound, range2.lowerBound) ... max(range1.upperBound, range2.upperBound)
24 | }
25 |
26 | // throws a timeout if the operation takes longer than the given timeout
27 | func withThrowingTimeout(timeout: TimeInterval,
28 | operation: @Sendable @escaping () async throws -> T) async throws -> T
29 | {
30 | try await withThrowingTaskGroup(of: T.self) { group in
31 | group.addTask {
32 | try await operation()
33 | }
34 |
35 | group.addTask {
36 | try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))
37 | throw LiveKitError(.timedOut)
38 | }
39 |
40 | let result = try await group.next()
41 |
42 | group.cancelAll()
43 |
44 | guard let result else {
45 | // This should never happen since we know we added tasks
46 | throw LiveKitError(.invalidState)
47 | }
48 |
49 | return result
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Extensions/AVCaptureDevice.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | var facingPosition: AVCaptureDevice.Position {
22 | #if os(macOS)
23 | /// In macOS, the Facetime camera's position is .unspecified but this property will return .front for such cases.
24 | if deviceType == .builtInWideAngleCamera, position == .unspecified {
25 | return .front
26 | }
27 | #elseif os(visionOS)
28 | /// In visionOS, the Persona camera's position is .unspecified but this property will return .front for such cases.
29 | if position == .unspecified {
30 | return .front
31 | }
32 | #endif
33 |
34 | return position
35 | }
36 | }
37 |
38 | public extension Collection where Element: AVCaptureDevice {
39 | /// Helper extension to return only a single suggested device for each position.
40 | func singleDeviceforEachPosition() -> [AVCaptureDevice] {
41 | let front = first { $0.facingPosition == .front }
42 | let back = first { $0.facingPosition == .back }
43 | return [front, back].compactMap { $0 }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Extensions/Primitives.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | public extension Double {
46 | func rounded(to places: Int) -> Double {
47 | let divisor = pow(10.0, Double(places))
48 | return (self * divisor).rounded() / divisor
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/TrackSettings.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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, Sendable {
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 |
--------------------------------------------------------------------------------
/.github/workflows/cocoapods-push.yaml:
--------------------------------------------------------------------------------
1 | name: CocoaPods Push
2 | on:
3 | workflow_dispatch:
4 | inputs:
5 | dry_run:
6 | description: Dry run (only lint spec, don't push)
7 | default: true
8 | type: boolean
9 | release:
10 | types: [published]
11 | env:
12 | PODSPEC_FILE: LiveKitClient.podspec
13 | jobs:
14 | push:
15 | runs-on: macos-latest
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v3
19 | - name: Setup CocoaPods
20 | uses: maxim-lobanov/setup-cocoapods@v1
21 | with:
22 | version: latest
23 | - name: Setup Xcode
24 | uses: maxim-lobanov/setup-xcode@v1
25 | with:
26 | xcode-version: latest-stable
27 | - name: Add Repo
28 | run: |
29 | pod repo add livekit https://github.com/livekit/podspecs.git
30 | - name: Spec Lint
31 | id: spec-lint
32 | run: |
33 | validation_dir=$(mktemp -d)
34 | echo "validation_dir=${validation_dir}" >> $GITHUB_OUTPUT
35 | pod spec lint \
36 | --platforms=macos \
37 | --validation-dir="${validation_dir}" \
38 | --no-clean \
39 | --allow-warnings \
40 | --verbose \
41 | --sources=livekit,https://cdn.cocoapods.org/
42 | - name: Upload Validation Directory (Failure Only)
43 | if: failure()
44 | uses: actions/upload-artifact@v4
45 | with:
46 | path: ${{ steps.spec-lint.outputs.validation_dir }}
47 | name: validation
48 | - name: Push to CocoaPods
49 | if: ${{ inputs.dry_run != true && success() }}
50 | run: |
51 | pod trunk push ${{ env.PODSPEC_FILE }} \
52 | --allow-warnings \
53 | --verbose \
54 | --sources=livekit,https://cdn.cocoapods.org/
55 | env:
56 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
57 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Broadcast/IPC/SocketPath.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if os(iOS)
18 |
19 | import Network
20 |
21 | /// A UNIX domain path valid on this system.
22 | struct SocketPath {
23 | let path: String
24 |
25 | /// Creates a socket path or returns nil if the given path string is not valid.
26 | init?(_ path: String) {
27 | guard Self.isValid(path) else {
28 | logger.error("Invalid socket path: \(path)")
29 | return nil
30 | }
31 | self.path = path
32 | }
33 |
34 | /// Whether or not the given socket path is valid on this system.
35 | ///
36 | /// Proper path validation is essential; as of writing, the Network framework
37 | /// does not perform such validation internally, and attempting to connect to a
38 | /// socket with an invalid path results in a crash.
39 | ///
40 | private static func isValid(_ path: String) -> Bool {
41 | path.utf8.count <= addressMaxLength
42 | }
43 |
44 | /// The maximum supported length (in bytes) for socket paths on this system.
45 | private static let addressMaxLength: Int = MemoryLayout.size(ofValue: sockaddr_un().sun_path) - 1
46 | }
47 |
48 | extension NWEndpoint {
49 | init(_ socketPath: SocketPath) {
50 | self = .unix(path: socketPath.path)
51 | }
52 | }
53 |
54 | #endif
55 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Participant/Participant+Kind.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | // MARK: - Public
18 |
19 | public extension Participant {
20 | @objc
21 | enum Kind: Int, Sendable {
22 | case unknown
23 | /// Standard participants, e.g. web clients.
24 | case standard
25 | /// Only ingests streams.
26 | case ingress
27 | /// Only consumes streams.
28 | case egress
29 | /// SIP participants.
30 | case sip
31 | /// LiveKit agents.
32 | case agent
33 | }
34 | }
35 |
36 | extension Participant.Kind: CustomStringConvertible {
37 | public var description: String {
38 | switch self {
39 | case .unknown: return "unknown"
40 | case .standard: return "standard"
41 | case .ingress: return "ingress"
42 | case .egress: return "egress"
43 | case .sip: return "sip"
44 | case .agent: return "agent"
45 | }
46 | }
47 | }
48 |
49 | // MARK: - Internal
50 |
51 | extension Livekit_ParticipantInfo.Kind {
52 | func toLKType() -> Participant.Kind {
53 | switch self {
54 | case .standard: return .standard
55 | case .ingress: return .ingress
56 | case .egress: return .egress
57 | case .sip: return .sip
58 | case .agent: return .agent
59 | default: return .unknown
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/Extensions/StringTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | final class StringTests: LKTestCase {
21 | func testByteLength() {
22 | // ASCII characters (1 byte each)
23 | XCTAssertEqual("hello".byteLength, 5)
24 | XCTAssertEqual("".byteLength, 0)
25 |
26 | // Unicode characters (variable bytes)
27 | XCTAssertEqual("👋".byteLength, 4) // Emoji (4 bytes)
28 | XCTAssertEqual("ñ".byteLength, 2) // Spanish n with tilde (2 bytes)
29 | XCTAssertEqual("你好".byteLength, 6) // Chinese characters (3 bytes each)
30 | }
31 |
32 | func testTruncate() {
33 | // Test ASCII strings
34 | XCTAssertEqual("hello".truncate(maxBytes: 5), "hello")
35 | XCTAssertEqual("hello".truncate(maxBytes: 3), "hel")
36 | XCTAssertEqual("".truncate(maxBytes: 5), "")
37 |
38 | // Test Unicode strings
39 | XCTAssertEqual("👋hello".truncate(maxBytes: 4), "👋") // Emoji is 4 bytes
40 | XCTAssertEqual("hi👋".truncate(maxBytes: 5), "hi") // Won't cut in middle of emoji
41 | XCTAssertEqual("你好world".truncate(maxBytes: 6), "你好") // Chinese characters are 3 bytes each
42 |
43 | // Test edge cases
44 | XCTAssertEqual("hello".truncate(maxBytes: 0), "")
45 | XCTAssertEqual("hello".truncate(maxBytes: 100), "hello")
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/ParticipantTrackPermission.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 final class ParticipantTrackPermission: NSObject, Sendable {
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/DataStream/Outgoing/StreamData.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | protocol StreamData: Sendable {
20 | func chunks(of size: Int) -> [Data]
21 | }
22 |
23 | extension Data: StreamData {
24 | func chunks(of size: Int) -> [Data] {
25 | guard size > 0, !isEmpty else { return [] }
26 | return stride(from: startIndex, to: endIndex, by: size).map {
27 | let end = index($0, offsetBy: size, limitedBy: endIndex) ?? endIndex
28 | return self[$0 ..< end]
29 | }
30 | }
31 | }
32 |
33 | extension String: StreamData {
34 | /// Chunk along valid UTF-8 bounderies.
35 | ///
36 | /// Uses the same algorithm as in the LiveKit JS SDK.
37 | ///
38 | func chunks(of size: Int) -> [Data] {
39 | guard size > 0, !isEmpty else { return [] }
40 |
41 | var chunks: [Data] = []
42 | var encoded = Data(utf8)[...]
43 |
44 | while encoded.count > size {
45 | var k = size
46 | while k > 0 {
47 | guard encoded.indices.contains(k),
48 | encoded[k] & 0xC0 == 0x80 else { break }
49 | k -= 1
50 | }
51 | chunks.append(encoded.subdata(in: 0 ..< k))
52 | encoded = encoded.subdata(in: k ..< encoded.count)
53 | }
54 | if !encoded.isEmpty {
55 | chunks.append(encoded)
56 | }
57 | return chunks
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/LiveKit/E2EE/State.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | @objc
26 | public enum E2EEState: Int, Sendable {
27 | case new
28 | case ok
29 | case key_ratcheted
30 | case missing_key
31 | case encryption_failed
32 | case decryption_failed
33 | case internal_error
34 | }
35 |
36 | public extension E2EEState {
37 | func toString() -> String {
38 | switch self {
39 | case .new: return "new"
40 | case .ok: return "ok"
41 | case .key_ratcheted: return "key_ratcheted"
42 | case .missing_key: return "missing_key"
43 | case .encryption_failed: return "encryption_failed"
44 | case .decryption_failed: return "decryption_failed"
45 | case .internal_error: return "internal_error"
46 | default: return "internal_error"
47 | }
48 | }
49 | }
50 |
51 | extension FrameCryptionState {
52 | func toLKType() -> E2EEState {
53 | switch self {
54 | case .new: return .new
55 | case .ok: return .ok
56 | case .keyRatcheted: return .key_ratcheted
57 | case .missingKey: return .missing_key
58 | case .encryptionFailed: return .encryption_failed
59 | case .decryptionFailed: return .decryption_failed
60 | case .internalError: return .internal_error
61 | default: return .internal_error
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Support/Stopwatch.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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: Sendable {
20 | public struct Entry: Equatable, Sendable {
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/IceServer.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | /// Options used when establishing a connection.
26 | @objc
27 | public final class IceServer: NSObject, Sendable {
28 | public let urls: [String]
29 | public let username: String?
30 | public let credential: String?
31 |
32 | public init(urls: [String],
33 | username: String?,
34 | credential: String?)
35 | {
36 | self.urls = urls
37 | self.username = username
38 | self.credential = credential
39 | }
40 | }
41 |
42 | extension IceServer {
43 | func toRTCType() -> LKRTCIceServer {
44 | DispatchQueue.liveKitWebRTC.sync { LKRTCIceServer(urlStrings: urls,
45 | username: username,
46 | credential: credential) }
47 | }
48 | }
49 |
50 | extension Livekit_ICEServer {
51 | func toRTCType() -> LKRTCIceServer {
52 | let rtcUsername = !username.isEmpty ? username : nil
53 | let rtcCredential = !credential.isEmpty ? credential : nil
54 | return DispatchQueue.liveKitWebRTC.sync { LKRTCIceServer(urlStrings: urls,
55 | username: rtcUsername,
56 | credential: rtcCredential) }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/ParticipantTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 ParticipantTests: LKTestCase {
21 | func testLocalParticipantIdentity() async throws {
22 | try await withRooms([RoomTestingOptions()]) { rooms in
23 | // Alias to Room
24 | let room1 = rooms[0]
25 |
26 | XCTAssert(room1.localParticipant.identity != nil, "LocalParticipant's identity is nil")
27 |
28 | print("room1.localParticipant.identity: \(String(describing: room1.localParticipant.identity))")
29 | }
30 | }
31 |
32 | func testRemoteParticipants() async throws {
33 | try await withRooms([RoomTestingOptions(), RoomTestingOptions(), RoomTestingOptions()]) { rooms in
34 | // Alias to Room
35 | let room1 = rooms[0]
36 | let room2 = rooms[1]
37 | let room3 = rooms[2]
38 |
39 | XCTAssert(room1.remoteParticipants.count == 2, "Remote participant count must be 2")
40 | XCTAssert(room2.remoteParticipants.count == 2, "Remote participant count must be 2")
41 | XCTAssert(room3.remoteParticipants.count == 2, "Remote participant count must be 2")
42 |
43 | print("room1.remoteParticipants: \(String(describing: room1.remoteParticipants))")
44 | print("room2.remoteParticipants: \(String(describing: room2.remoteParticipants))")
45 | print("room2.remoteParticipants: \(String(describing: room3.remoteParticipants))")
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/DataStream/ByteStreamInfoTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 ByteStreamInfoTests: LKTestCase {
21 | func testProtocolTypeConversion() {
22 | let info = ByteStreamInfo(
23 | id: "id",
24 | topic: "topic",
25 | timestamp: Date(timeIntervalSince1970: 100),
26 | totalLength: 128,
27 | attributes: ["key": "value"],
28 | mimeType: "image/jpeg",
29 | name: "filename.bin"
30 | )
31 | let header = Livekit_DataStream.Header(info)
32 | XCTAssertEqual(header.streamID, info.id)
33 | XCTAssertEqual(header.mimeType, info.mimeType)
34 | XCTAssertEqual(header.topic, info.topic)
35 | XCTAssertEqual(header.timestamp, Int64(info.timestamp.timeIntervalSince1970 * TimeInterval(1000)))
36 | XCTAssertEqual(header.totalLength, UInt64(info.totalLength ?? -1))
37 | XCTAssertEqual(header.attributes, info.attributes)
38 | XCTAssertEqual(header.byteHeader.name, info.name)
39 |
40 | let newInfo = ByteStreamInfo(header, header.byteHeader)
41 | XCTAssertEqual(newInfo.id, info.id)
42 | XCTAssertEqual(newInfo.mimeType, info.mimeType)
43 | XCTAssertEqual(newInfo.topic, info.topic)
44 | XCTAssertEqual(newInfo.timestamp, info.timestamp)
45 | XCTAssertEqual(newInfo.totalLength, info.totalLength)
46 | XCTAssertEqual(newInfo.attributes, info.attributes)
47 | XCTAssertEqual(newInfo.name, info.name)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Extensions/RTCRtpTransceiver.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | extension LKRTCRtpTransceiver: Loggable {
26 | /// Attempts to set preferred video codec.
27 | func set(preferredVideoCodec codec: VideoCodec, exceptCodec: VideoCodec? = nil) {
28 | // Get list of supported codecs...
29 | let allVideoCodecs = RTC.videoSenderCapabilities.codecs
30 |
31 | // Get the RTCRtpCodecCapability of the preferred codec
32 | let preferredCodecCapability = allVideoCodecs.first { $0.name.lowercased() == codec.name }
33 |
34 | // Get list of capabilities other than the preferred one
35 | let otherCapabilities = allVideoCodecs.filter {
36 | $0.name.lowercased() != codec.name && $0.name.lowercased() != exceptCodec?.name
37 | }
38 |
39 | // Bring preferredCodecCapability to the front and combine all capabilities
40 | let combinedCapabilities = [preferredCodecCapability] + otherCapabilities
41 |
42 | // Codecs not set in codecPreferences will not be negotiated in the offer
43 | codecPreferences = combinedCapabilities.compactMap { $0 }
44 |
45 | log("codecPreferences set: \(codecPreferences.map { String(describing: $0) }.joined(separator: ", "))")
46 |
47 | if codecPreferences.first?.name.lowercased() != codec.name {
48 | log("Preferred codec is not first of codecPreferences", .error)
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/LiveKit/LiveKit.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | internal import Logging
22 | #else
23 | @_implementationOnly import LiveKitWebRTC
24 | @_implementationOnly import Logging
25 | #endif
26 |
27 | let logger = Logger(label: "LiveKitSDK")
28 |
29 | /// The open source platform for real-time communication.
30 | ///
31 | /// See [LiveKit's Online Docs](https://docs.livekit.io/) for more information.
32 | ///
33 | /// Comments are written in [DocC](https://developer.apple.com/documentation/docc) compatible format.
34 | /// With Xcode 13 and above you can build documentation right into your Xcode documentation viewer by chosing
35 | /// **Product** > **Build Documentation** from Xcode's menu.
36 | ///
37 | /// Download the [Multiplatform SwiftUI Example](https://github.com/livekit/multiplatform-swiftui-example)
38 | /// to try out the features.
39 | @objc
40 | public class LiveKitSDK: NSObject {
41 | @objc(sdkVersion)
42 | public static let version = "2.6.0"
43 |
44 | @objc
45 | public static func setLoggerStandardOutput() {
46 | LoggingSystem.bootstrap {
47 | var logHandler = StreamLogHandler.standardOutput(label: $0)
48 | logHandler.logLevel = .debug
49 | return logHandler
50 | }
51 | }
52 |
53 | /// Notify the SDK to start initializing for faster connection/publishing later on. This is non-blocking.
54 | @objc
55 | public static func prepare() {
56 | // TODO: Add RTC related initializations
57 | DeviceManager.prepare()
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/LiveKit/SwiftUI/TrackDelegateObserver.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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, @unchecked Sendable {
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 { @MainActor in
50 | self.dimensions = dimensions
51 | }
52 | }
53 |
54 | public func track(_: Track, didUpdateStatistics statistics: TrackStatistics, simulcastStatistics: [VideoCodec: TrackStatistics]) {
55 | Task { @MainActor in
56 | self.statistics = statistics
57 | self.simulcastStatistics = simulcastStatistics
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Track/Remote/RemoteVideoTrack.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if swift(>=5.9)
18 | internal import LiveKitWebRTC
19 | #else
20 | @_implementationOnly import LiveKitWebRTC
21 | #endif
22 |
23 | @objc
24 | public class RemoteVideoTrack: Track, RemoteTrack, @unchecked Sendable {
25 | init(name: String,
26 | source: Track.Source,
27 | track: LKRTCMediaStreamTrack,
28 | reportStatistics: Bool)
29 | {
30 | super.init(name: name,
31 | kind: .video,
32 | source: source,
33 | track: track,
34 | reportStatistics: reportStatistics)
35 | }
36 | }
37 |
38 | // MARK: - VideoTrack Protocol
39 |
40 | extension RemoteVideoTrack: VideoTrack {
41 | public func add(videoRenderer: VideoRenderer) {
42 | guard let rtcVideoTrack = mediaTrack as? LKRTCVideoTrack else {
43 | log("mediaTrack is not a RTCVideoTrack", .error)
44 | return
45 | }
46 |
47 | _state.mutate {
48 | $0.videoRenderers.add(videoRenderer)
49 | }
50 |
51 | rtcVideoTrack.add(VideoRendererAdapter(target: videoRenderer))
52 | }
53 |
54 | public func remove(videoRenderer: VideoRenderer) {
55 | guard let rtcVideoTrack = mediaTrack as? LKRTCVideoTrack else {
56 | log("mediaTrack is not a RTCVideoTrack", .error)
57 | return
58 | }
59 |
60 | _state.mutate {
61 | $0.videoRenderers.remove(videoRenderer)
62 | }
63 |
64 | rtcVideoTrack.remove(VideoRendererAdapter(target: videoRenderer))
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/AudioEncoding.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | @objc
26 | public final class AudioEncoding: NSObject, MediaEncoding, Sendable {
27 | @objc
28 | public let maxBitrate: Int
29 |
30 | @objc
31 | public init(maxBitrate: Int) {
32 | self.maxBitrate = maxBitrate
33 | }
34 |
35 | // MARK: - Equal
36 |
37 | override public func isEqual(_ object: Any?) -> Bool {
38 | guard let other = object as? Self else { return false }
39 | return maxBitrate == other.maxBitrate
40 | }
41 |
42 | override public var hash: Int {
43 | var hasher = Hasher()
44 | hasher.combine(maxBitrate)
45 | return hasher.finalize()
46 | }
47 | }
48 |
49 | // MARK: - Presets
50 |
51 | @objc
52 | public extension AudioEncoding {
53 | internal static let presets = [
54 | presetTelephone,
55 | presetSpeech,
56 | presetMusic,
57 | presetMusicStereo,
58 | presetMusicHighQuality,
59 | presetMusicHighQualityStereo,
60 | ]
61 |
62 | static let presetTelephone = AudioEncoding(maxBitrate: 12000)
63 | static let presetSpeech = AudioEncoding(maxBitrate: 24000)
64 | static let presetMusic = AudioEncoding(maxBitrate: 48000)
65 | static let presetMusicStereo = AudioEncoding(maxBitrate: 64000)
66 | static let presetMusicHighQuality = AudioEncoding(maxBitrate: 96000)
67 | static let presetMusicHighQualityStereo = AudioEncoding(maxBitrate: 128_000)
68 | }
69 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/FunctionTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 FunctionTests: LKTestCase {
21 | func testRangeMerge() async throws {
22 | let range1 = 10 ... 20
23 | let range2 = 5 ... 15
24 |
25 | let merged = merge(range: range1, with: range2)
26 | print("merged: \(merged)")
27 | XCTAssert(merged == 5 ... 20)
28 | }
29 |
30 | func testAttributesUpdated() {
31 | let oldValues: [String: String] = ["a": "value", "b": "initial", "c": "value"]
32 | let newValues: [String: String] = ["a": "value", "b": "updated", "c": "value"]
33 |
34 | let diff = computeAttributesDiff(oldValues: oldValues, newValues: newValues)
35 | XCTAssertEqual(diff.count, 1)
36 | XCTAssertEqual(diff["b"], "updated")
37 | }
38 |
39 | func testAttributesNew() {
40 | let newValues: [String: String] = ["a": "value", "b": "value", "c": "value"]
41 | let oldValues: [String: String] = ["a": "value", "b": "value"]
42 |
43 | let diff = computeAttributesDiff(oldValues: oldValues, newValues: newValues)
44 | XCTAssertEqual(diff.count, 1)
45 | XCTAssertEqual(diff["c"], "value")
46 | }
47 |
48 | func testAttributesRemoved() {
49 | let newValues: [String: String] = ["a": "value", "b": "value"]
50 | let oldValues: [String: String] = ["a": "value", "b": "value", "c": "value"]
51 |
52 | let diff = computeAttributesDiff(oldValues: oldValues, newValues: newValues)
53 | XCTAssertEqual(diff.count, 1)
54 | XCTAssertEqual(diff["c"], "")
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Audio/Manager/AudioManager+Testing.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if swift(>=5.9)
18 | internal import LiveKitWebRTC
19 | #else
20 | @_implementationOnly import LiveKitWebRTC
21 | #endif
22 |
23 | // Only internal testing.
24 | extension AudioManager {
25 | var engineState: RTCAudioEngineState {
26 | get { RTC.audioDeviceModule.engineState }
27 | set { RTC.audioDeviceModule.engineState = newValue }
28 | }
29 |
30 | var isPlayoutInitialized: Bool {
31 | RTC.audioDeviceModule.isPlayoutInitialized
32 | }
33 |
34 | var isPlaying: Bool {
35 | RTC.audioDeviceModule.isPlaying
36 | }
37 |
38 | var isRecordingInitialized: Bool {
39 | RTC.audioDeviceModule.isRecordingInitialized
40 | }
41 |
42 | var isRecording: Bool {
43 | RTC.audioDeviceModule.isRecording
44 | }
45 |
46 | @discardableResult
47 | func initPlayout() -> Int {
48 | RTC.audioDeviceModule.initPlayout()
49 | }
50 |
51 | @discardableResult
52 | func startPlayout() -> Int {
53 | RTC.audioDeviceModule.startPlayout()
54 | }
55 |
56 | @discardableResult
57 | func stopPlayout() -> Int {
58 | RTC.audioDeviceModule.stopPlayout()
59 | }
60 |
61 | @discardableResult
62 | func initRecording() -> Int {
63 | RTC.audioDeviceModule.initRecording()
64 | }
65 |
66 | @discardableResult
67 | func startRecording() -> Int {
68 | RTC.audioDeviceModule.startRecording()
69 | }
70 |
71 | @discardableResult
72 | func stopRecording() -> Int {
73 | RTC.audioDeviceModule.stopRecording()
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Participant/Participant+Convenience.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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/Support/NativeView.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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) || os(visionOS) || os(tvOS)
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) || os(visionOS) || os(tvOS)
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 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/PreConnectAudioBufferTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 PreConnectAudioBufferTests: LKTestCase {
21 | func testParticipantActiveStateSendsAudioData() async throws {
22 | let receiveExpectation = expectation(description: "Receives audio data")
23 |
24 | try await withRooms([RoomTestingOptions(canSubscribe: true), RoomTestingOptions(canPublish: true, canPublishData: true)]) { rooms in
25 | let subscriberRoom = rooms[0]
26 | let publisherRoom = rooms[1]
27 |
28 | try await subscriberRoom.registerByteStreamHandler(for: PreConnectAudioBuffer.dataTopic) { reader, participant in
29 | XCTAssertEqual(participant, publisherRoom.localParticipant.identity)
30 | do {
31 | let data = try await reader.readAll()
32 | XCTAssertFalse(data.isEmpty, "Received audio data should not be empty")
33 | receiveExpectation.fulfill()
34 | } catch {
35 | XCTFail("Read failed: \(error.localizedDescription)")
36 | }
37 | }
38 |
39 | let buffer = PreConnectAudioBuffer(room: publisherRoom)
40 |
41 | try await buffer.startRecording()
42 | try await Task.sleep(nanoseconds: NSEC_PER_SEC / 2)
43 |
44 | subscriberRoom.localParticipant._state.mutate { $0.kind = .agent } // override kind
45 | buffer.room(publisherRoom, participant: subscriberRoom.localParticipant, didUpdateState: .active)
46 |
47 | await self.fulfillment(of: [receiveExpectation], timeout: 10)
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/Participant+Types.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | final class Sid: NSObject, Codable, Sendable {
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 | final class Identity: NSObject, Codable, Sendable {
47 | @objc
48 | public let stringValue: String
49 |
50 | public 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/Extensions/Sendable.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 swift(>=5.9)
20 | internal import LiveKitWebRTC
21 | #else
22 | @_implementationOnly import LiveKitWebRTC
23 | #endif
24 |
25 | // MARK: Immutable classes
26 |
27 | extension LKRTCDataBuffer: @unchecked Swift.Sendable {}
28 | extension LKRTCDataChannel: @unchecked Swift.Sendable {}
29 | extension LKRTCFrameCryptorKeyProvider: @unchecked Swift.Sendable {}
30 | extension LKRTCIceCandidate: @unchecked Swift.Sendable {}
31 | extension LKRTCMediaConstraints: @unchecked Swift.Sendable {}
32 | extension LKRTCMediaStream: @unchecked Swift.Sendable {}
33 | extension LKRTCMediaStreamTrack: @unchecked Swift.Sendable {}
34 | extension LKRTCPeerConnection: @unchecked Swift.Sendable {}
35 | extension LKRTCPeerConnectionFactory: @unchecked Swift.Sendable {}
36 | extension LKRTCRtpReceiver: @unchecked Swift.Sendable {}
37 | extension LKRTCRtpSender: @unchecked Swift.Sendable {}
38 | extension LKRTCRtpTransceiver: @unchecked Swift.Sendable {}
39 | extension LKRTCRtpTransceiverInit: @unchecked Swift.Sendable {}
40 | extension LKRTCSessionDescription: @unchecked Swift.Sendable {}
41 | extension LKRTCStatisticsReport: @unchecked Swift.Sendable {}
42 | extension LKRTCVideoCodecInfo: @unchecked Swift.Sendable {}
43 | extension LKRTCVideoFrame: @unchecked Swift.Sendable {}
44 |
45 | // MARK: Mutable classes - to be validated
46 |
47 | extension LKRTCConfiguration: @unchecked Swift.Sendable {}
48 | extension LKRTCVideoCapturer: @unchecked Swift.Sendable {}
49 |
50 | // MARK: Collections
51 |
52 | extension NSHashTable: @unchecked Swift.Sendable {} // cannot specify Obj-C generics
53 | extension Dictionary: Swift.Sendable where Key: Sendable, Value: Sendable {}
54 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/IceCandidate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | #if swift(>=5.9)
18 | internal import LiveKitWebRTC
19 | #else
20 | @_implementationOnly import LiveKitWebRTC
21 | #endif
22 |
23 | struct IceCandidate: Codable, Sendable {
24 | let sdp: String
25 | let sdpMLineIndex: Int32
26 | let sdpMid: String?
27 |
28 | enum CodingKeys: String, CodingKey {
29 | case sdpMLineIndex, sdpMid
30 | case sdp = "candidate"
31 | }
32 |
33 | func toJsonString() throws -> String {
34 | let data = try JSONEncoder().encode(self)
35 | guard let string = String(data: data, encoding: .utf8) else {
36 | throw LiveKitError(.failedToConvertData, message: "Failed to convert Data to String")
37 | }
38 | return string
39 | }
40 | }
41 |
42 | extension LKRTCIceCandidate {
43 | func toLKType() -> IceCandidate {
44 | IceCandidate(sdp: sdp, sdpMLineIndex: sdpMLineIndex, 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 |
61 | extension IceCandidate {
62 | func toRTCType() -> LKRTCIceCandidate {
63 | LKRTCIceCandidate(sdp: sdp, sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid)
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Sources/LiveKit/E2EE/Options.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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, Sendable {
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 final class E2EEOptions: NSObject, Sendable {
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/Track/Remote/RemoteAudioTrack.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 CoreMedia
19 |
20 | #if swift(>=5.9)
21 | internal import LiveKitWebRTC
22 | #else
23 | @_implementationOnly import LiveKitWebRTC
24 | #endif
25 |
26 | @objc
27 | public class RemoteAudioTrack: Track, RemoteTrack, AudioTrack, @unchecked Sendable {
28 | /// Volume with range 0.0 - 1.0
29 | public var volume: Double {
30 | get {
31 | guard let audioTrack = mediaTrack as? LKRTCAudioTrack else { return 0 }
32 | return audioTrack.source.volume / 10
33 | }
34 | set {
35 | guard let audioTrack = mediaTrack as? LKRTCAudioTrack else { return }
36 | audioTrack.source.volume = newValue * 10
37 | }
38 | }
39 |
40 | private lazy var _adapter = AudioRendererAdapter()
41 |
42 | init(name: String,
43 | source: Track.Source,
44 | track: LKRTCMediaStreamTrack,
45 | reportStatistics: Bool)
46 | {
47 | super.init(name: name,
48 | kind: .audio,
49 | source: source,
50 | track: track,
51 | reportStatistics: reportStatistics)
52 |
53 | if let audioTrack = mediaTrack as? LKRTCAudioTrack {
54 | audioTrack.add(_adapter)
55 | }
56 | }
57 |
58 | deinit {
59 | if let audioTrack = mediaTrack as? LKRTCAudioTrack {
60 | audioTrack.remove(_adapter)
61 | }
62 | }
63 |
64 | public func add(audioRenderer: AudioRenderer) {
65 | _adapter.add(delegate: audioRenderer)
66 | }
67 |
68 | public func remove(audioRenderer: AudioRenderer) {
69 | _adapter.remove(delegate: audioRenderer)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/Support/TestAudioRecorder.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 | @preconcurrency import AVFAudio
18 | @testable import LiveKit
19 |
20 | // Used to save audio data for inspecting the correct format, etc.
21 | class TestAudioRecorder: @unchecked Sendable {
22 | public let sampleRate: Double
23 | public let filePath: URL
24 | private var audioFile: AVAudioFile?
25 |
26 | init(sampleRate: Double = 48000, channels: Int = 1) throws {
27 | self.sampleRate = sampleRate
28 |
29 | let settings: [String: Any] = [
30 | AVFormatIDKey: kAudioFormatMPEG4AAC,
31 | AVSampleRateKey: sampleRate,
32 | AVNumberOfChannelsKey: channels,
33 | AVLinearPCMBitDepthKey: 16,
34 | AVLinearPCMIsFloatKey: false,
35 | AVLinearPCMIsNonInterleaved: false,
36 | AVLinearPCMIsBigEndianKey: false,
37 | ]
38 |
39 | let fileName = UUID().uuidString + ".aac"
40 | let filePath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
41 | self.filePath = filePath
42 |
43 | audioFile = try AVAudioFile(forWriting: filePath,
44 | settings: settings,
45 | commonFormat: .pcmFormatInt16,
46 | interleaved: true)
47 | }
48 |
49 | func write(pcmBuffer: AVAudioPCMBuffer) throws {
50 | guard let audioFile else { return }
51 | try audioFile.write(from: pcmBuffer)
52 | }
53 |
54 | func close() {
55 | audioFile = nil
56 | }
57 | }
58 |
59 | extension TestAudioRecorder: AudioRenderer {
60 | func render(pcmBuffer: AVAudioPCMBuffer) {
61 | try? write(pcmBuffer: pcmBuffer)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Tests/LiveKitTests/PublishTrackTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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 PublishTrackTests: LKTestCase {
21 | func testPublishWithoutPermissions() async throws {
22 | try await withRooms([RoomTestingOptions(canPublish: false)]) { rooms in
23 | let room = rooms[0]
24 | let audioTrack = LocalAudioTrack.createTrack()
25 |
26 | do {
27 | try await room.localParticipant.publish(audioTrack: audioTrack)
28 | XCTFail("Publishing without permissions should throw an error")
29 | } catch let error as LiveKitError {
30 | XCTAssertEqual(error.type, .insufficientPermissions)
31 | XCTAssertEqual(error.message, "Participant does not have permission to publish")
32 | } catch {
33 | XCTFail("Expected LiveKitError but got \(error)")
34 | }
35 | }
36 | }
37 |
38 | func testPublishWithDisallowedSource() async throws {
39 | try await withRooms([RoomTestingOptions(canPublish: true, canPublishSources: [.camera])]) { rooms in
40 | let room = rooms[0]
41 | let audioTrack = LocalAudioTrack.createTrack()
42 |
43 | do {
44 | try await room.localParticipant.publish(audioTrack: audioTrack)
45 | XCTFail("Publishing with disallowed source should throw an error")
46 | } catch let error as LiveKitError {
47 | XCTAssertEqual(error.type, .insufficientPermissions)
48 | XCTAssertEqual(error.message, "Participant does not have permission to publish tracks from this source")
49 | } catch {
50 | XCTFail("Expected LiveKitError but got \(error)")
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/LiveKit/Types/VideoQuality.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 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, Sendable {
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 |
--------------------------------------------------------------------------------