├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── banner_dark.png ├── banner_light.png └── workflows │ ├── bump.yaml │ ├── ci.yaml │ ├── cocoapods-lint.yaml │ ├── cocoapods-push.yaml │ └── publish-docs.yaml ├── .gitignore ├── .nanpa └── .keep ├── .nanparc ├── .periphery.yml ├── .swift-version ├── .swiftformat ├── CHANGELOG.md ├── Docs ├── Resources │ ├── broadcast-dialog.png │ ├── in-app-dialog.png │ └── new-target-options.png ├── cocoapods.md └── ios-screen-sharing.md ├── LICENSE ├── LiveKit.xctestplan ├── LiveKitClient.podspec ├── Makefile ├── NOTICE ├── Package.swift ├── Package@swift-5.9.swift ├── Package@swift-6.0.swift ├── README.md ├── Sources ├── LKObjCHelpers │ ├── LKObjCHelpers.m │ └── include │ │ └── LKObjCHelpers.h └── LiveKit │ ├── Agent │ ├── AgentState.swift │ ├── Participant+Agent.swift │ └── Room+Agent.swift │ ├── Audio │ ├── AudioDeviceModuleDelegateAdapter.swift │ ├── AudioEngineObserver.swift │ ├── AudioSessionEngineObserver.swift │ ├── Manager │ │ ├── AudioManager+ModuleType.swift │ │ ├── AudioManager+MuteMode.swift │ │ ├── AudioManager+Testing.swift │ │ └── AudioManager.swift │ └── MixerEngineObserver.swift │ ├── Broadcast │ ├── BroadcastBundleInfo.swift │ ├── BroadcastManager.swift │ ├── BroadcastScreenCapturer.swift │ ├── IPC │ │ ├── BroadcastAudioCodec.swift │ │ ├── BroadcastIPCHeader.swift │ │ ├── BroadcastImageCodec.swift │ │ ├── BroadcastReceiver.swift │ │ ├── BroadcastUploader.swift │ │ ├── IPCChannel.swift │ │ ├── IPCProtocol.swift │ │ └── SocketPath.swift │ ├── LKSampleHandler.swift │ ├── NOTICE │ └── Support │ │ ├── BundleInfo.swift │ │ ├── DarwinNotificationCenter.swift │ │ └── OSLogHandler.swift │ ├── Convenience │ └── AudioProcessing.swift │ ├── Core │ ├── DataChannelPair.swift │ ├── PreConnectAudioBuffer.swift │ ├── RPC.swift │ ├── RTC.swift │ ├── Room+Convenience.swift │ ├── Room+DataStream.swift │ ├── Room+Debug.swift │ ├── Room+Engine.swift │ ├── Room+EngineDelegate.swift │ ├── Room+MulticastDelegate.swift │ ├── Room+PreConnect.swift │ ├── Room+RPC.swift │ ├── Room+SignalClientDelegate.swift │ ├── Room+TransportDelegate.swift │ ├── Room.swift │ ├── SignalClient.swift │ └── Transport.swift │ ├── DataStream │ ├── FileInfo.swift │ ├── Incoming │ │ ├── ByteStreamReader.swift │ │ ├── IncomingStreamManager.swift │ │ ├── StreamReaderSource.swift │ │ └── TextStreamReader.swift │ ├── Outgoing │ │ ├── ByteStreamWriter.swift │ │ ├── OutgoingStreamManager.swift │ │ ├── StreamData.swift │ │ ├── StreamWriterDestination.swift │ │ └── TextStreamWriter.swift │ ├── StreamError.swift │ ├── StreamInfo.swift │ └── StreamOptions.swift │ ├── E2EE │ ├── E2EEManager.swift │ ├── KeyProvider.swift │ ├── Options.swift │ └── State.swift │ ├── Errors.swift │ ├── Extensions │ ├── AVAudioPCMBuffer.swift │ ├── AVCaptureDevice.swift │ ├── AsyncSequence.swift │ ├── CoreImage.swift │ ├── CustomStringConvertible.swift │ ├── DispatchQueue.swift │ ├── LKRTCRtpSender.swift │ ├── Logger.swift │ ├── PixelBuffer.swift │ ├── Primitives.swift │ ├── RTCConfiguration.swift │ ├── RTCDataChannel+Util.swift │ ├── RTCI420Buffer.swift │ ├── RTCMediaConstraints.swift │ ├── RTCRtpTransceiver.swift │ ├── RTCVideoCapturerDelegate+Buffer.swift │ ├── Sendable.swift │ ├── String.swift │ ├── TimeInterval.swift │ └── URL.swift │ ├── LiveKit+DeviceHelpers.swift │ ├── LiveKit.docc │ ├── LiveKit.md │ └── Resources │ │ └── .keep │ ├── LiveKit.swift │ ├── Participant │ ├── LocalParticipant+DataStream.swift │ ├── LocalParticipant+RPC.swift │ ├── LocalParticipant.swift │ ├── Participant+Convenience.swift │ ├── Participant+Equatable.swift │ ├── Participant+Identifiable.swift │ ├── Participant+Kind.swift │ ├── Participant+MulticastDelegate.swift │ ├── Participant.swift │ └── RemoteParticipant.swift │ ├── PrivacyInfo.xcprivacy │ ├── Protocols │ ├── AudioCustomProcessingDelegate.swift │ ├── AudioRenderer.swift │ ├── MediaEncoding.swift │ ├── Mirrorable.swift │ ├── NextInvokable.swift │ ├── ParticipantDelegate.swift │ ├── RoomDelegate.swift │ ├── SignalClientDelegate.swift │ ├── TrackDelegate.swift │ ├── TransportDelegate.swift │ ├── VideoProcessor.swift │ ├── VideoRenderer.swift │ └── VideoViewDelegate.swift │ ├── Protos │ ├── livekit_metrics.pb.swift │ ├── livekit_models.pb.swift │ └── livekit_rtc.pb.swift │ ├── Support │ ├── AppStateListener.swift │ ├── AsyncCompleter.swift │ ├── AsyncDebounce.swift │ ├── AsyncFileStream.swift │ ├── AsyncRetry.swift │ ├── AsyncSerialDelegate.swift │ ├── AsyncTimer.swift │ ├── AsyncTryMapSequence.swift │ ├── Audio │ │ ├── AVAudioPCMRingBuffer.swift │ │ └── AudioConverter.swift │ ├── AudioMixRecorder.swift │ ├── ConnectivityListener.swift │ ├── DeviceManager.swift │ ├── FFTProcessor.swift │ ├── Global.swift │ ├── HTTP.swift │ ├── Locks.swift │ ├── MulticastDelegate.swift │ ├── NativeView.swift │ ├── NativeViewRepresentable.swift │ ├── QueueActor.swift │ ├── RingBuffer.swift │ ├── SerialRunnerActor.swift │ ├── StateSync.swift │ ├── Stopwatch.swift │ ├── TextView.swift │ ├── Utils.swift │ ├── ValueOrAbsent.swift │ └── WebSocket.swift │ ├── SwiftUI │ ├── SwiftUIAudioRoutePickerButton.swift │ ├── SwiftUIVideoView.swift │ └── TrackDelegateObserver.swift │ ├── Track │ ├── AudioTrack.swift │ ├── Capturers │ │ ├── ARCameraCapturer.swift │ │ ├── BufferCapturer.swift │ │ ├── CameraCapturer.swift │ │ ├── InAppCapturer.swift │ │ ├── MacOSScreenCapturer.swift │ │ ├── VideoCapturer+MulticastDelegate.swift │ │ └── VideoCapturer.swift │ ├── Local │ │ ├── LocalAudioTrack.swift │ │ ├── LocalTrack.swift │ │ └── LocalVideoTrack.swift │ ├── Metrics │ │ └── MetricsManager.swift │ ├── Recorders │ │ └── LocalAudioTrackRecorder.swift │ ├── Remote │ │ ├── RemoteAudioTrack.swift │ │ ├── RemoteTrack.swift │ │ └── RemoteVideoTrack.swift │ ├── Support │ │ └── Extensions.swift │ ├── Track+Equatable.swift │ ├── Track+MulticastDelegate.swift │ ├── Track.swift │ └── VideoTrack.swift │ ├── TrackPublications │ ├── LocalTrackPublication.swift │ ├── RemoteTrackPublication.swift │ ├── TrackPublication+Equatable.swift │ ├── TrackPublication+Identifiable.swift │ └── TrackPublication.swift │ ├── Types │ ├── AudioBuffer.swift │ ├── AudioDevice.swift │ ├── AudioDuckingLevel.swift │ ├── AudioEncoding+Comparable.swift │ ├── AudioEncoding.swift │ ├── AudioSessionConfiguration.swift │ ├── Codec │ │ ├── Codec.swift │ │ └── VideoCodec.swift │ ├── ConnectionQuality.swift │ ├── ConnectionState.swift │ ├── DegradationPreference.swift │ ├── Dimensions.swift │ ├── IceCandidate.swift │ ├── IceServer.swift │ ├── IceTransportPolicy.swift │ ├── MediaDevice.swift │ ├── Options │ │ ├── ARCameraCaptureOptions.swift │ │ ├── AudioCaptureOptions.swift │ │ ├── AudioPublishOptions.swift │ │ ├── BufferCaptureOptions.swift │ │ ├── CameraCaptureOptions+Copy.swift │ │ ├── CameraCaptureOptions.swift │ │ ├── CaptureOptions.swift │ │ ├── ConnectOptions+Copy.swift │ │ ├── ConnectOptions.swift │ │ ├── DataPublishOptions.swift │ │ ├── PublishOptions.swift │ │ ├── RoomOptions.swift │ │ ├── ScreenShareCaptureOptions.swift │ │ ├── VideoCaptureOptions.swift │ │ └── VideoPublishOptions.swift │ ├── Participant+Types.swift │ ├── ParticipantPermissions.swift │ ├── ParticipantState.swift │ ├── ParticipantTrackPermission.swift │ ├── ProtocolVersion.swift │ ├── Room+Types.swift │ ├── ScalabilityMode.swift │ ├── SessionDescription.swift │ ├── SpeechActivityEvent.swift │ ├── Statistics.swift │ ├── Track+Types.swift │ ├── TrackSettings.swift │ ├── TrackSource.swift │ ├── TrackStatistics.swift │ ├── TrackStreamState.swift │ ├── TrackType.swift │ ├── TranscriptionSegment.swift │ ├── VideoEncoding+Comparable.swift │ ├── VideoEncoding.swift │ ├── VideoFrame.swift │ ├── VideoParameters+Comparable.swift │ ├── VideoParameters.swift │ ├── VideoQuality.swift │ └── VideoRotation.swift │ ├── VideoProcessors │ └── BackgroundBlurVideoProcessor.swift │ └── Views │ ├── SampleBufferVideoRenderer.swift │ ├── VideoView+MulticastDelegate.swift │ ├── VideoView+PinchToZoom.swift │ └── VideoView.swift ├── Tests ├── LKTestHost │ ├── LKTestHost.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ ├── LKTestHost │ │ ├── Assets.xcassets │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LKTestHost.entitlements │ │ └── LKTestHostApp.swift │ ├── README.md │ └── TestPlan.xctestplan ├── LiveKitTests │ ├── AVAudioPCMRingBufferTests.swift │ ├── AsyncFileStreamTests.swift │ ├── AsyncRetryTests.swift │ ├── Audio │ │ └── AudioManagerTests.swift │ ├── AudioConverterTests.swift │ ├── AudioEngineTests.swift │ ├── AudioMixRecorderTests.swift │ ├── AudioProcessingTests.swift │ ├── Broadcast │ │ ├── BroadcastAudioCodecTests.swift │ │ ├── BroadcastImageCodecTests.swift │ │ ├── IPCChannelTests.swift │ │ └── SocketPathTests.swift │ ├── BroadcastManagerTests.swift │ ├── CodecTests.swift │ ├── CompleterTests.swift │ ├── DarwinNotificationCenterTests.swift │ ├── DataStream │ │ ├── ByteStreamInfoTests.swift │ │ ├── ByteStreamReaderTests.swift │ │ ├── DataStreamTests.swift │ │ ├── FileInfoTests.swift │ │ ├── IncomingStreamManagerTests.swift │ │ ├── OutgoingStreamManagerTests.swift │ │ ├── StreamDataTests.swift │ │ ├── TextStreamInfoTests.swift │ │ └── TextStreamReaderTests.swift │ ├── DeviceManager.swift │ ├── E2EE │ │ └── Thread.swift │ ├── Experimental │ │ └── PublishDeviceOptimization.swift │ ├── Extensions │ │ ├── AVAudioPCMBufferTests.swift │ │ └── StringTests.swift │ ├── FunctionTests.swift │ ├── LKTestCase.swift │ ├── MuteTests.swift │ ├── ObjCHelpersTests.swift │ ├── ParticipantTests.swift │ ├── PreConnectAudioBufferTests.swift │ ├── PublishBufferCapturerTests.swift │ ├── PublishDataTests.swift │ ├── PublishMicrophoneTests.swift │ ├── PublishTrackTests.swift │ ├── QueueActorTests.swift │ ├── Room │ │ └── RoomTests.swift │ ├── RpcTests.swift │ ├── SDKTests.swift │ ├── SerialRunnerActor.swift │ ├── Support │ │ ├── ConcurrentCounter.swift │ │ ├── MockDataChannelPair.swift │ │ ├── Room.swift │ │ ├── SinWaveSourceNode.swift │ │ ├── TestAudioRecorder.swift │ │ ├── TokenGenerator.swift │ │ ├── Tracks.swift │ │ └── Xcode14.2Backport.swift │ ├── ThreadSafetyTests.swift │ ├── TimeIntervalTests.swift │ ├── Track │ │ ├── LocalAudioTrackRecorderTests.swift │ │ └── TrackTests.swift │ └── VideoViewTests.swift └── LiveKitTestsObjC │ └── Basic.m ├── livekit_ipc.proto └── scripts ├── build_docs.sh └── replace_version.sh /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/banner_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livekit/client-sdk-swift/f6f2978d634eb98b686327af3986aed2df64f778/.github/banner_dark.png -------------------------------------------------------------------------------- /.github/banner_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livekit/client-sdk-swift/f6f2978d634eb98b686327af3986aed2df64f778/.github/banner_light.png -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.nanparc: -------------------------------------------------------------------------------- 1 | version 2.6.0 2 | name client-sdk-swift 3 | custom ./scripts/replace_version.sh 4 | -------------------------------------------------------------------------------- /.periphery.yml: -------------------------------------------------------------------------------- 1 | retain_public: true 2 | targets: 3 | - LiveKit 4 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.7 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Docs/Resources/broadcast-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livekit/client-sdk-swift/f6f2978d634eb98b686327af3986aed2df64f778/Docs/Resources/broadcast-dialog.png -------------------------------------------------------------------------------- /Docs/Resources/in-app-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livekit/client-sdk-swift/f6f2978d634eb98b686327af3986aed2df64f778/Docs/Resources/in-app-dialog.png -------------------------------------------------------------------------------- /Docs/Resources/new-target-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livekit/client-sdk-swift/f6f2978d634eb98b686327af3986aed2df64f778/Docs/Resources/new-target-options.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.32") 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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: LKRTCAudioEngineState { 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/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 | -------------------------------------------------------------------------------- /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/Broadcast/NOTICE: -------------------------------------------------------------------------------- 1 | Inspired by the implementation from https://github.com/react-native-webrtc/react-native-webrtc (MIT License). Rewritten in Swift. 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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 LKRTCFrameCryptorState { 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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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": kLKRTCMediaConstraintsValueTrue] 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /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/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/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/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/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/LiveKit.docc/LiveKit.md: -------------------------------------------------------------------------------- 1 | # ``LiveKit`` 2 | -------------------------------------------------------------------------------- /Sources/LiveKit/LiveKit.docc/Resources/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livekit/client-sdk-swift/f6f2978d634eb98b686327af3986aed2df64f778/Sources/LiveKit/LiveKit.docc/Resources/.keep -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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: LKRTCPeerConnectionState) 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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/Track/Track+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 | // MARK: - Equatable for NSObject 20 | 21 | public extension Track { 22 | override func isEqual(_ object: Any?) -> Bool { 23 | guard let other = object as? Self else { return false } 24 | return mediaTrack.trackId == other.mediaTrack.trackId 25 | } 26 | 27 | override var hash: Int { 28 | var hasher = Hasher() 29 | hasher.combine(mediaTrack.trackId) 30 | return hasher.finalize() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LiveKit/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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() -> LKRTCDegradationPreference? { 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/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/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 | -------------------------------------------------------------------------------- /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() -> LKRTCIceTransportPolicy { 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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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: LKRTCSdpType 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/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 LKRTCSpeechActivityEvent { 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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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 LKRTCVideoRotation { 31 | func toLKType() -> VideoRotation { 32 | VideoRotation(rawValue: rawValue)! 33 | } 34 | } 35 | 36 | extension VideoRotation { 37 | func toRTCType() -> LKRTCVideoRotation { 38 | LKRTCVideoRotation(rawValue: rawValue)! 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Tests/LKTestHost/LKTestHost/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Tests/LKTestHost/LKTestHost/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIBackgroundModes 6 | 7 | audio 8 | voip 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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" -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | LKRTCSetMinDebugLogLevel(.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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 <