├── .swift-version
├── Shared
├── Assets.xcassets
│ ├── Contents.json
│ ├── iOS.appiconset
│ │ ├── Twitter Icon-20.png
│ │ ├── Twitter Icon-29.png
│ │ ├── Twitter Icon-40.png
│ │ ├── Twitter Icon-76.png
│ │ ├── Twitter Icon-1024.png
│ │ ├── Twitter Icon-20@2x.png
│ │ ├── Twitter Icon-20@3x.png
│ │ ├── Twitter Icon-29@2x.png
│ │ ├── Twitter Icon-29@3x.png
│ │ ├── Twitter Icon-40@2x.png
│ │ ├── Twitter Icon-40@3x.png
│ │ ├── Twitter Icon-60@2x.png
│ │ ├── Twitter Icon-60@3x.png
│ │ ├── Twitter Icon-76@2x.png
│ │ ├── Twitter Icon-83.5@2x.png
│ │ └── Contents.json
│ ├── logo.imageset
│ │ ├── Livekit Wordmark.png
│ │ ├── Livekit Wordmark@2x.png
│ │ ├── Livekit Wordmark@3x.png
│ │ └── Contents.json
│ ├── MacOS.appiconset
│ │ ├── Twitter Icon-128.png
│ │ ├── Twitter Icon-16.png
│ │ ├── Twitter Icon-256.png
│ │ ├── Twitter Icon-32.png
│ │ ├── Twitter Icon-512.png
│ │ ├── Twitter Icon-64.png
│ │ ├── Twitter Icon-1024.png
│ │ └── Contents.json
│ ├── LKBlue.colorset
│ │ └── Contents.json
│ ├── LKRed.colorset
│ │ └── Contents.json
│ ├── lkDarkRed.colorset
│ │ └── Contents.json
│ ├── lkGray1.colorset
│ │ └── Contents.json
│ ├── lkGray2.colorset
│ │ └── Contents.json
│ ├── lkGray3.colorset
│ │ └── Contents.json
│ ├── LKDarkBlue.colorset
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
├── Support
│ ├── Participant+Helpers.swift
│ ├── ExampleRoomMessage.swift
│ ├── Bundle.swift
│ ├── Binding+OptionSet.swift
│ ├── ConnectionHistory.swift
│ └── SecureStore.swift
├── Custom.swift
├── Controllers
│ ├── AppContext.swift
│ └── RoomContext.swift
├── Views
│ ├── PublishOptionsView.swift
│ └── ScreenShareSourcePickerView.swift
├── LiveKitExample.swift
├── ConnectView.swift
├── ParticipantView.swift
└── RoomView.swift
├── .periphery.yml
├── LiveKitExample.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── xcshareddata
│ └── xcschemes
│ ├── LiveKitExample (iOS).xcscheme
│ ├── LiveKitExample (macOS).xcscheme
│ └── BroadcastExt.xcscheme
├── LiveKitExample-main.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
├── xcshareddata
│ └── xcschemes
│ │ ├── LiveKitExample (macOS).xcscheme
│ │ └── BroadcastExt.xcscheme
└── project.pbxproj
├── LiveKitExample-dev.xcworkspace
├── xcshareddata
│ ├── WorkspaceSettings.xcsettings
│ ├── IDEWorkspaceChecks.plist
│ └── swiftpm
│ │ └── Package.resolved
└── contents.xcworkspacedata
├── .swiftformat
├── iOS
├── BroadcastExt
│ ├── BroadcastExt.entitlements
│ ├── SampleHandler.swift
│ ├── Info.plist
│ └── LoggingOSLog.swift
├── iOS.entitlements
└── Info.plist
├── macOS
├── macOS.entitlements
└── Info.plist
├── .gitignore
└── README.md
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.7 # Xcode 14
2 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-20.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-29.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-40.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-76.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/logo.imageset/Livekit Wordmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/logo.imageset/Livekit Wordmark.png
--------------------------------------------------------------------------------
/.periphery.yml:
--------------------------------------------------------------------------------
1 | retain_objc_accessible: true
2 | schemes:
3 | - Example (macOS Debug)
4 | targets:
5 | - LiveKitExample (macOS)
6 | workspace: LiveKitExample-dev.xcworkspace
7 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-128.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-16.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-256.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-32.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-512.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-64.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-1024.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-20@2x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-20@3x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-29@2x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-29@3x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-40@2x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-40@3x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-60@2x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-60@3x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-76@2x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/logo.imageset/Livekit Wordmark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/logo.imageset/Livekit Wordmark@2x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/logo.imageset/Livekit Wordmark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/logo.imageset/Livekit Wordmark@3x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/MacOS.appiconset/Twitter Icon-1024.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bcherry/swift-example/main/Shared/Assets.xcassets/iOS.appiconset/Twitter Icon-83.5@2x.png
--------------------------------------------------------------------------------
/LiveKitExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LiveKitExample-main.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LiveKitExample-dev.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LiveKitExample-dev.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LiveKitExample-dev.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LiveKitExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LiveKitExample-main.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/LKBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0x8B",
10 | "red" : "0x5A"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/LKRed.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x5D",
9 | "green" : "0x6D",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/lkDarkRed.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x40",
9 | "green" : "0x4B",
10 | "red" : "0xB0"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/lkGray1.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x19",
9 | "green" : "0x19",
10 | "red" : "0x19"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/lkGray2.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x32",
9 | "green" : "0x32",
10 | "red" : "0x32"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/lkGray3.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x4B",
9 | "green" : "0x4B",
10 | "red" : "0x4B"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/LKDarkBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x3C",
9 | "green" : "0x15",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Livekit Wordmark.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Livekit Wordmark@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Livekit Wordmark@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/iOS/BroadcastExt/BroadcastExt.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | group.io.livekit.example.SwiftSDK.1
10 |
11 | com.apple.security.device.audio-input
12 |
13 | com.apple.security.device.camera
14 |
15 | com.apple.security.network.client
16 |
17 | com.apple.security.network.server
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/iOS/iOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | group.io.livekit.example.SwiftSDK.1
10 |
11 | com.apple.security.device.audio-input
12 |
13 | com.apple.security.device.camera
14 |
15 | com.apple.security.network.client
16 |
17 | com.apple.security.network.server
18 |
19 | keychain-access-groups
20 |
21 | $(AppIdentifierPrefix)keychain-group.io.livekit.example.SwiftSDK.1
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "1.000",
27 | "green" : "1.000",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/macOS/macOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | group.io.livekit.example.SwiftSDK.1
10 |
11 | com.apple.security.device.audio-input
12 |
13 | com.apple.security.device.camera
14 |
15 | com.apple.security.files.user-selected.read-only
16 |
17 | com.apple.security.network.client
18 |
19 | com.apple.security.network.server
20 |
21 | keychain-access-groups
22 |
23 | $(AppIdentifierPrefix)keychain-group.io.livekit.example.SwiftSDK.1
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Shared/Support/Participant+Helpers.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import LiveKit
18 |
19 | public extension Participant {
20 | var mainVideoPublication: TrackPublication? {
21 | firstScreenSharePublication ?? firstCameraPublication
22 | }
23 |
24 | var mainVideoTrack: VideoTrack? {
25 | firstScreenShareVideoTrack ?? firstCameraVideoTrack
26 | }
27 |
28 | var subVideoTrack: VideoTrack? {
29 | firstScreenShareVideoTrack != nil ? firstCameraVideoTrack : nil
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/iOS/BroadcastExt/SampleHandler.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 | import LiveKit
19 | import Logging
20 | import OSLog
21 |
22 | private let broadcastLogger = OSLog(subsystem: "io.livekit.example.SwiftSDK", category: "Broadcast")
23 | class SampleHandler: LKSampleHandler {
24 | override public init() {
25 | // Turn on logging for the Broadcast Extension
26 | LoggingSystem.bootstrap { label in
27 | var logHandler = LoggingOSLog(label: label, log: broadcastLogger)
28 | logHandler.logLevel = .debug
29 | return logHandler
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Shared/Support/ExampleRoomMessage.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import LiveKit
18 |
19 | struct ExampleRoomMessage: Identifiable, Equatable, Hashable, Codable {
20 | // Identifiable protocol needs param named id
21 | var id: String {
22 | messageId
23 | }
24 |
25 | // message id
26 | let messageId: String
27 |
28 | let senderSid: Participant.Sid?
29 | let senderIdentity: Participant.Identity?
30 | let text: String
31 |
32 | static func == (lhs: Self, rhs: Self) -> Bool {
33 | lhs.messageId == rhs.messageId
34 | }
35 |
36 | func hash(into hasher: inout Hasher) {
37 | hasher.combine(messageId)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Shared/Support/Bundle.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 |
19 | public extension Bundle {
20 | var appName: String { getInfo("CFBundleName") }
21 | var displayName: String { getInfo("CFBundleDisplayName") }
22 | var language: String { getInfo("CFBundleDevelopmentRegion") }
23 | var identifier: String { getInfo("CFBundleIdentifier") }
24 |
25 | var appBuild: String { getInfo("CFBundleVersion") }
26 | var appVersionLong: String { getInfo("CFBundleShortVersionString") }
27 | var appVersionShort: String { getInfo("CFBundleShortVersion") }
28 |
29 | private func getInfo(_ str: String) -> String { infoDictionary?[str] as? String ?? "⚠️" }
30 | }
31 |
--------------------------------------------------------------------------------
/iOS/BroadcastExt/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | LiveKit Broadcast Example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSExtension
24 |
25 | NSExtensionPointIdentifier
26 | com.apple.broadcast-services-upload
27 | NSExtensionPrincipalClass
28 | $(PRODUCT_MODULE_NAME).SampleHandler
29 | RPBroadcastProcessMode
30 | RPBroadcastProcessModeSampleBuffer
31 |
32 | RTCAppGroupIdentifier
33 | group.io.livekit.example.SwiftSDK.1
34 |
35 |
36 |
--------------------------------------------------------------------------------
/Shared/Support/Binding+OptionSet.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import SwiftUI
18 |
19 | extension Binding where Value: OptionSet, Value == Value.Element {
20 | func bindedValue(_ options: Value) -> Bool {
21 | wrappedValue.contains(options)
22 | }
23 |
24 | func bind(
25 | _ options: Value,
26 | animate: Bool = false
27 | ) -> Binding {
28 | .init { () -> Bool in
29 | self.wrappedValue.contains(options)
30 | } set: { newValue in
31 | let body = {
32 | if newValue {
33 | self.wrappedValue.insert(options)
34 | } else {
35 | self.wrappedValue.remove(options)
36 | }
37 | }
38 | guard animate else {
39 | body()
40 | return
41 | }
42 | withAnimation {
43 | body()
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/MacOS.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Twitter Icon-16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "Twitter Icon-32.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "Twitter Icon-32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "Twitter Icon-64.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "Twitter Icon-128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "Twitter Icon-256.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "Twitter Icon-256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "Twitter Icon-512.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "Twitter Icon-512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "Twitter Icon-1024.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/macOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | LiveKit Example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIconFile
12 |
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
21 | CFBundleShortVersionString
22 | $(MARKETING_VERSION)
23 | CFBundleURLTypes
24 |
25 |
26 | CFBundleURLSchemes
27 |
28 | livekit
29 |
30 |
31 |
32 | CFBundleVersion
33 | $(CURRENT_PROJECT_VERSION)
34 | LSApplicationCategoryType
35 | public.app-category.developer-tools
36 | LSMinimumSystemVersion
37 | $(MACOSX_DEPLOYMENT_TARGET)
38 | NSAppTransportSecurity
39 |
40 | NSAllowsArbitraryLoads
41 |
42 |
43 | NSCameraUsageDescription
44 | Please allow for publishing your camera
45 | NSMicrophoneUsageDescription
46 | Please allow for publishing your microphone
47 |
48 |
49 |
--------------------------------------------------------------------------------
/LiveKitExample-main.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "bf59b79f5d4ea0a54ef7e929b035c5821f5094ee990a7a369ad0cb3713f6c62d",
3 | "pins" : [
4 | {
5 | "identity" : "client-sdk-swift",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/livekit/client-sdk-swift",
8 | "state" : {
9 | "branch" : "main",
10 | "revision" : "fceb4566aac45b8e0107c43c740a9e8149850d3b"
11 | }
12 | },
13 | {
14 | "identity" : "keychainaccess",
15 | "kind" : "remoteSourceControl",
16 | "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
17 | "state" : {
18 | "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
19 | "version" : "4.2.2"
20 | }
21 | },
22 | {
23 | "identity" : "sfsafesymbols",
24 | "kind" : "remoteSourceControl",
25 | "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
26 | "state" : {
27 | "revision" : "7cca2d60925876b5953a2cf7341cd80fbeac983c",
28 | "version" : "4.1.1"
29 | }
30 | },
31 | {
32 | "identity" : "swift-log",
33 | "kind" : "remoteSourceControl",
34 | "location" : "https://github.com/apple/swift-log.git",
35 | "state" : {
36 | "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
37 | "version" : "1.5.4"
38 | }
39 | },
40 | {
41 | "identity" : "swift-protobuf",
42 | "kind" : "remoteSourceControl",
43 | "location" : "https://github.com/apple/swift-protobuf.git",
44 | "state" : {
45 | "revision" : "65e8f29b2d63c4e38e736b25c27b83e012159be8",
46 | "version" : "1.25.2"
47 | }
48 | },
49 | {
50 | "identity" : "webrtc-xcframework",
51 | "kind" : "remoteSourceControl",
52 | "location" : "https://github.com/livekit/webrtc-xcframework.git",
53 | "state" : {
54 | "revision" : "56e5119f00daa2e42e10670d29ae2edbff6be961",
55 | "version" : "114.5735.13"
56 | }
57 | }
58 | ],
59 | "version" : 3
60 | }
61 |
--------------------------------------------------------------------------------
/LiveKitExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "bf59b79f5d4ea0a54ef7e929b035c5821f5094ee990a7a369ad0cb3713f6c62d",
3 | "pins" : [
4 | {
5 | "identity" : "client-sdk-swift",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/livekit/client-sdk-swift",
8 | "state" : {
9 | "revision" : "c7358137775ed6f6ac583d13c79f39f6244005d8",
10 | "version" : "2.0.9"
11 | }
12 | },
13 | {
14 | "identity" : "keychainaccess",
15 | "kind" : "remoteSourceControl",
16 | "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
17 | "state" : {
18 | "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
19 | "version" : "4.2.2"
20 | }
21 | },
22 | {
23 | "identity" : "sfsafesymbols",
24 | "kind" : "remoteSourceControl",
25 | "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
26 | "state" : {
27 | "revision" : "7cca2d60925876b5953a2cf7341cd80fbeac983c",
28 | "version" : "4.1.1"
29 | }
30 | },
31 | {
32 | "identity" : "swift-log",
33 | "kind" : "remoteSourceControl",
34 | "location" : "https://github.com/apple/swift-log.git",
35 | "state" : {
36 | "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
37 | "version" : "1.5.4"
38 | }
39 | },
40 | {
41 | "identity" : "swift-protobuf",
42 | "kind" : "remoteSourceControl",
43 | "location" : "https://github.com/apple/swift-protobuf.git",
44 | "state" : {
45 | "revision" : "9f0c76544701845ad98716f3f6a774a892152bcb",
46 | "version" : "1.26.0"
47 | }
48 | },
49 | {
50 | "identity" : "webrtc-xcframework",
51 | "kind" : "remoteSourceControl",
52 | "location" : "https://github.com/livekit/webrtc-xcframework.git",
53 | "state" : {
54 | "revision" : "f7222b7a8012fb96e87ae95f1075e27a927de7c5",
55 | "version" : "114.5735.18"
56 | }
57 | }
58 | ],
59 | "version" : 3
60 | }
61 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 | .DS_Store
92 |
--------------------------------------------------------------------------------
/iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | LiveKit Example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleURLTypes
22 |
23 |
24 | CFBundleURLSchemes
25 |
26 | livekit
27 |
28 |
29 |
30 | CFBundleVersion
31 | $(CURRENT_PROJECT_VERSION)
32 | ITSAppUsesNonExemptEncryption
33 |
34 | LSRequiresIPhoneOS
35 |
36 | NSAppTransportSecurity
37 |
38 | NSAllowsArbitraryLoads
39 |
40 |
41 | NSCameraUsageDescription
42 | uses your camera for video chat
43 | NSFaceIDUsageDescription
44 | Keychain is used to store all preferences.
45 | NSMicrophoneUsageDescription
46 | uses your microphone for video chat
47 | RTCAppGroupIdentifier
48 | group.io.livekit.example.SwiftSDK.1
49 | RTCScreenSharingExtension
50 | io.livekit.example.SwiftSDK.1.BroadcastExt
51 | UIApplicationSceneManifest
52 |
53 | UIApplicationSupportsMultipleScenes
54 |
55 |
56 | UIApplicationSupportsIndirectInputEvents
57 |
58 | UIBackgroundModes
59 |
60 | audio
61 | voip
62 |
63 | UILaunchScreen
64 |
65 | UIRequiredDeviceCapabilities
66 |
67 | armv7
68 |
69 | UISupportedInterfaceOrientations
70 |
71 | UIInterfaceOrientationLandscapeLeft
72 | UIInterfaceOrientationLandscapeRight
73 | UIInterfaceOrientationPortrait
74 | UIInterfaceOrientationPortraitUpsideDown
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/LiveKitExample-dev.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "jwt-kit",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/vapor/jwt-kit.git",
7 | "state" : {
8 | "revision" : "c2595b9ad7f512d7f334830b4df1fed6e917946a",
9 | "version" : "4.13.4"
10 | }
11 | },
12 | {
13 | "identity" : "keychainaccess",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
16 | "state" : {
17 | "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
18 | "version" : "4.2.2"
19 | }
20 | },
21 | {
22 | "identity" : "sfsafesymbols",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
25 | "state" : {
26 | "revision" : "7cca2d60925876b5953a2cf7341cd80fbeac983c",
27 | "version" : "4.1.1"
28 | }
29 | },
30 | {
31 | "identity" : "swift-crypto",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/apple/swift-crypto.git",
34 | "state" : {
35 | "revision" : "bc1c29221f6dfeb0ebbfbc98eb95cd3d4967868e",
36 | "version" : "3.4.0"
37 | }
38 | },
39 | {
40 | "identity" : "swift-docc-plugin",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/apple/swift-docc-plugin.git",
43 | "state" : {
44 | "revision" : "26ac5758409154cc448d7ab82389c520fa8a8247",
45 | "version" : "1.3.0"
46 | }
47 | },
48 | {
49 | "identity" : "swift-docc-symbolkit",
50 | "kind" : "remoteSourceControl",
51 | "location" : "https://github.com/apple/swift-docc-symbolkit",
52 | "state" : {
53 | "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
54 | "version" : "1.0.0"
55 | }
56 | },
57 | {
58 | "identity" : "swift-log",
59 | "kind" : "remoteSourceControl",
60 | "location" : "https://github.com/apple/swift-log.git",
61 | "state" : {
62 | "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
63 | "version" : "1.5.4"
64 | }
65 | },
66 | {
67 | "identity" : "swift-protobuf",
68 | "kind" : "remoteSourceControl",
69 | "location" : "https://github.com/apple/swift-protobuf.git",
70 | "state" : {
71 | "revision" : "9f0c76544701845ad98716f3f6a774a892152bcb",
72 | "version" : "1.26.0"
73 | }
74 | },
75 | {
76 | "identity" : "webrtc-xcframework",
77 | "kind" : "remoteSourceControl",
78 | "location" : "https://github.com/livekit/webrtc-xcframework.git",
79 | "state" : {
80 | "revision" : "f7222b7a8012fb96e87ae95f1075e27a927de7c5",
81 | "version" : "114.5735.18"
82 | }
83 | }
84 | ],
85 | "version" : 2
86 | }
87 |
--------------------------------------------------------------------------------
/Shared/Support/ConnectionHistory.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import LiveKit
18 | import SwiftUI
19 |
20 | struct ConnectionHistory: Codable {
21 | let updated: Date
22 | let url: String
23 | let token: String
24 | let e2ee: Bool
25 | let e2eeKey: String
26 | let roomSid: Room.Sid?
27 | let roomName: String?
28 | let participantSid: Participant.Sid?
29 | let participantIdentity: Participant.Identity?
30 | let participantName: String?
31 | }
32 |
33 | extension ConnectionHistory: CustomStringConvertible {
34 | var description: String {
35 | var segments: [String] = []
36 | if let roomName {
37 | segments.append(String(describing: roomName))
38 | }
39 | if let participantIdentity {
40 | segments.append(String(describing: participantIdentity))
41 | }
42 | segments.append(url)
43 | return segments.joined(separator: " ")
44 | }
45 | }
46 |
47 | extension ConnectionHistory: Identifiable {
48 | var id: Int {
49 | hashValue
50 | }
51 | }
52 |
53 | extension ConnectionHistory: Hashable, Equatable {
54 | func hash(into hasher: inout Hasher) {
55 | hasher.combine(url)
56 | hasher.combine(token)
57 | }
58 |
59 | static func == (lhs: ConnectionHistory, rhs: ConnectionHistory) -> Bool {
60 | lhs.url == rhs.url && lhs.token == rhs.token
61 | }
62 | }
63 |
64 | extension Sequence {
65 | var sortedByUpdated: [ConnectionHistory] {
66 | Array(self).sorted { $0.updated > $1.updated }
67 | }
68 | }
69 |
70 | extension Set {
71 | mutating func update(room: Room, e2ee: Bool, e2eeKey: String) {
72 | guard let url = room.url,
73 | let token = room.token else { return }
74 |
75 | let element = ConnectionHistory(
76 | updated: Date(),
77 | url: url,
78 | token: token,
79 | e2ee: e2ee,
80 | e2eeKey: e2eeKey,
81 | roomSid: room.sid,
82 | roomName: room.name,
83 | participantSid: room.localParticipant.sid,
84 | participantIdentity: room.localParticipant.identity,
85 | participantName: room.localParticipant.name
86 | )
87 |
88 | update(with: element)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/iOS.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Twitter Icon-20@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "Twitter Icon-20@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "Twitter Icon-29@2x.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "Twitter Icon-29@3x.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "Twitter Icon-40@2x.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "Twitter Icon-40@3x.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "Twitter Icon-60@2x.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "Twitter Icon-60@3x.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "Twitter Icon-20.png",
53 | "idiom" : "ipad",
54 | "scale" : "1x",
55 | "size" : "20x20"
56 | },
57 | {
58 | "filename" : "Twitter Icon-20@2x.png",
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "Twitter Icon-29.png",
65 | "idiom" : "ipad",
66 | "scale" : "1x",
67 | "size" : "29x29"
68 | },
69 | {
70 | "filename" : "Twitter Icon-29@2x.png",
71 | "idiom" : "ipad",
72 | "scale" : "2x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "Twitter Icon-40.png",
77 | "idiom" : "ipad",
78 | "scale" : "1x",
79 | "size" : "40x40"
80 | },
81 | {
82 | "filename" : "Twitter Icon-40@2x.png",
83 | "idiom" : "ipad",
84 | "scale" : "2x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "Twitter Icon-76.png",
89 | "idiom" : "ipad",
90 | "scale" : "1x",
91 | "size" : "76x76"
92 | },
93 | {
94 | "filename" : "Twitter Icon-76@2x.png",
95 | "idiom" : "ipad",
96 | "scale" : "2x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "Twitter Icon-83.5@2x.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "83.5x83.5"
104 | },
105 | {
106 | "filename" : "Twitter Icon-1024.png",
107 | "idiom" : "ios-marketing",
108 | "scale" : "1x",
109 | "size" : "1024x1024"
110 | }
111 | ],
112 | "info" : {
113 | "author" : "xcode",
114 | "version" : 1
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Shared/Support/SecureStore.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Combine
18 | import KeychainAccess
19 | import LiveKit
20 | import SwiftUI
21 |
22 | struct Preferences: Codable, Equatable {
23 | var url = ""
24 | var token = ""
25 | var e2eeKey = ""
26 | var isE2eeEnabled = false
27 |
28 | // Connect options
29 | var autoSubscribe = true
30 |
31 | // Room options
32 | var simulcast = true
33 | var adaptiveStream = true
34 | var dynacast = true
35 | var reportStats = true
36 |
37 | // Settings
38 | var videoViewVisible = true
39 | var showInformationOverlay = false
40 | var preferSampleBufferRendering = false
41 | var videoViewMode: VideoView.LayoutMode = .fit
42 | var videoViewMirrored = false
43 |
44 | var connectionHistory = Set()
45 | }
46 |
47 | let encoder = JSONEncoder()
48 | let decoder = JSONDecoder()
49 |
50 | class ValueStore: ObservableObject {
51 | private let store: Keychain
52 | private let key: String
53 | private let message = ""
54 | private weak var timer: Timer?
55 |
56 | public var value: T {
57 | didSet {
58 | guard oldValue != value else { return }
59 | lazySync()
60 | }
61 | }
62 |
63 | private var storeWithOptions: Keychain {
64 | store
65 | .accessibility(.whenUnlocked)
66 | .synchronizable(true)
67 | }
68 |
69 | public init(store: Keychain, key: String, default: T) {
70 | self.store = store
71 | self.key = key
72 | value = `default`
73 |
74 | if let data = try? storeWithOptions.getData(key),
75 | let result = try? decoder.decode(T.self, from: data)
76 | {
77 | value = result
78 | }
79 | }
80 |
81 | deinit {
82 | timer?.invalidate()
83 | }
84 |
85 | public func lazySync() {
86 | timer?.invalidate()
87 | timer = Timer.scheduledTimer(withTimeInterval: 1,
88 | repeats: false,
89 | block: { _ in self.sync() })
90 | }
91 |
92 | public func sync() {
93 | do {
94 | let data = try encoder.encode(value)
95 | try storeWithOptions.set(data, key: key)
96 | } catch {
97 | print("Failed to write in Keychain, error: \(error)")
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/LiveKitExample-main.xcodeproj/xcshareddata/xcschemes/LiveKitExample (macOS).xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
42 |
44 |
50 |
51 |
52 |
53 |
59 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/LiveKitExample.xcodeproj/xcshareddata/xcschemes/LiveKitExample (iOS).xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/LiveKitExample.xcodeproj/xcshareddata/xcschemes/LiveKitExample (macOS).xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
44 |
46 |
52 |
53 |
54 |
55 |
61 |
63 |
69 |
70 |
71 |
72 |
74 |
75 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LiveKit SDK Example App for iOS & macOS
2 |
3 | This app demonstrates the basic usage of [LiveKit Swift SDK (iOS/macOS)](https://github.com/livekit/client-sdk-swift). See [LiveKit Docs](https://docs.livekit.io/) for more information.
4 |
5 | ### Compiled version
6 |
7 | Precompiled macOS version is available from the [Releases page](https://github.com/livekit/client-example-swift/releases), so you can quickly try out features of the [LiveKit Swift SDK](https://github.com/livekit/client-sdk-swift) or the [LiveKit Server](https://github.com/livekit/livekit-server).
8 |
9 | Precompiled iOS version can be downloaded on [Apple TestFlight](https://testflight.apple.com/join/21F6ARiQ). Click on the link from an iOS device and follow the instructions.
10 |
11 | ### Screenshots
12 | **macOS**
13 | 
14 |
15 | # How to run the example
16 |
17 | ### Get the code
18 |
19 | 1. Clone this [LiveKit Swift Example](https://github.com/livekit/client-example-swift) repo.
20 | 2. Open `LiveKitExample.xcodeproj` (not the `-dev.xcworkspace`).
21 | 3. Wait for packages to sync.
22 |
23 | ### Change bundle id & code signing information
24 | 1. Select the `LiveKitExample` project from the left Navigator.
25 | 2. For each **Target**, select **Signing & Capabilities** tab and update your **Team** and **Bundle Identifier** to your preference.
26 |
27 | ### 🚀 Run
28 | 1. Select `LiveKitExample (iOS)` or `LiveKitExample (macOS)` from the **Scheme selector** at the top of Xcode.
29 | 2. **Run** the project from the menu **Product** → **Run** or by ⌘R.
30 |
31 | If you encounter code signing issues, make sure you change the **Team** and **bundle id** from the previous step.
32 |
33 | ### ⚡️ Connect
34 |
35 | 1. Prepare & Start [LiveKit Server](https://github.com/livekit/livekit-server). See the [Getting Started page](https://docs.livekit.io/guides/getting-started) for more information.
36 | 2. Generate an access token.
37 | 3. Enter the **Server URL** and **Access token** to the example app and tap **Connect**.
38 |
39 | Server URL would typically look like `ws://localhost:7880` depending on your configuration. It should start with `ws://` for *non-secure* and `wss://` for *secure* connections.
40 |
41 | ### ✅ Permissions
42 |
43 | iOS/macOS will ask you to grant permission when enabling **Camera**, **Microphone** and/or **Screen Share**. Simply allow this to continue publishing the track.
44 |
45 | #### macOS Screen Share
46 |
47 | Open **Settings** → **Security & Privacy** → **Screen Recording** and make sure **LiveKitExample** has a ✔️ mark. You will need to restart the app.
48 |
49 | # Troubleshooting
50 |
51 | ### Package errors
52 |
53 | If you get package syncing errors, try *resetting your package caches* by right clicking **Package Dependencies** and choosing **Reset Package Caches** from the **Navigator**.
54 |
55 | # Getting help / Contributing
56 |
57 | Please join us on [Slack](https://join.slack.com/t/livekit-users/shared_invite/zt-rrdy5abr-5pZ1wW8pXEkiQxBzFiXPUg) to get help from our [devs](https://github.com/orgs/livekit/teams/devs/members) / community members. We welcome your contributions(PRs) and details can be discussed there.
58 |
59 | # Development
60 |
61 | For development, open `LiveKitExample-dev.xcworkspace` instead. This workspace will compile with the local `../client-sdk-swift`.
62 |
--------------------------------------------------------------------------------
/Shared/Custom.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import SwiftUI
18 |
19 | extension Color {
20 | static let lkRed = Color("lkRed")
21 | static let lkDarkRed = Color("lkDarkRed")
22 | static let lkGray1 = Color("lkGray1")
23 | static let lkGray2 = Color("lkGray2")
24 | static let lkGray3 = Color("lkGray3")
25 | }
26 |
27 | struct LazyView: View {
28 | let build: () -> Content
29 | init(_ build: @autoclosure @escaping () -> Content) {
30 | self.build = build
31 | }
32 |
33 | var body: Content {
34 | build()
35 | }
36 | }
37 |
38 | // Default button style for this example
39 | struct LKButton: View {
40 | let title: String
41 | let action: () -> Void
42 |
43 | var body: some View {
44 | Button(action: action,
45 | label: {
46 | Text(title.uppercased())
47 | .fontWeight(.bold)
48 | .padding(.horizontal, 12)
49 | .padding(.vertical, 10)
50 | })
51 | .background(Color.lkRed)
52 | .cornerRadius(8)
53 | }
54 | }
55 |
56 | #if os(iOS)
57 | extension LKTextField.`Type` {
58 | func toiOSType() -> UIKeyboardType {
59 | switch self {
60 | case .default: return .default
61 | case .URL: return .URL
62 | case .ascii: return .asciiCapable
63 | }
64 | }
65 | }
66 | #endif
67 |
68 | #if os(macOS)
69 | // Avoid showing focus border around textfield for macOS
70 | extension NSTextField {
71 | override open var focusRingType: NSFocusRingType {
72 | get { .none }
73 | set {}
74 | }
75 | }
76 | #endif
77 |
78 | struct LKTextField: View {
79 | enum `Type` {
80 | case `default`
81 | case URL
82 | case ascii
83 | }
84 |
85 | let title: String
86 | @Binding var text: String
87 | var type: Type = .default
88 |
89 | var body: some View {
90 | VStack(alignment: .leading, spacing: 10.0) {
91 | Text(title)
92 | .fontWeight(.bold)
93 |
94 | TextField("", text: $text)
95 | .textFieldStyle(PlainTextFieldStyle())
96 | .disableAutocorrection(true)
97 | // TODO: add iOS unique view modifiers
98 | // #if os(iOS)
99 | // .autocapitalization(.none)
100 | // .keyboardType(type.toiOSType())
101 | // #endif
102 | .padding()
103 | .overlay(RoundedRectangle(cornerRadius: 10.0)
104 | .strokeBorder(Color.white.opacity(0.3),
105 | style: StrokeStyle(lineWidth: 1.0)))
106 |
107 | }.frame(maxWidth: .infinity)
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Shared/Controllers/AppContext.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Combine
18 | import LiveKit
19 | import SwiftUI
20 |
21 | // This class contains the logic to control behavior of the whole app.
22 | final class AppContext: ObservableObject {
23 | private let store: ValueStore
24 |
25 | @Published var videoViewVisible: Bool = true {
26 | didSet { store.value.videoViewVisible = videoViewVisible }
27 | }
28 |
29 | @Published var showInformationOverlay: Bool = false {
30 | didSet { store.value.showInformationOverlay = showInformationOverlay }
31 | }
32 |
33 | @Published var preferSampleBufferRendering: Bool = false {
34 | didSet { store.value.preferSampleBufferRendering = preferSampleBufferRendering }
35 | }
36 |
37 | @Published var videoViewMode: VideoView.LayoutMode = .fit {
38 | didSet { store.value.videoViewMode = videoViewMode }
39 | }
40 |
41 | @Published var videoViewMirrored: Bool = false {
42 | didSet { store.value.videoViewMirrored = videoViewMirrored }
43 | }
44 |
45 | @Published var videoViewPinchToZoomOptions: VideoView.PinchToZoomOptions = []
46 |
47 | @Published var connectionHistory: Set = [] {
48 | didSet { store.value.connectionHistory = connectionHistory }
49 | }
50 |
51 | @Published var outputDevice: AudioDevice = AudioManager.shared.defaultOutputDevice {
52 | didSet {
53 | print("didSet outputDevice: \(String(describing: outputDevice))")
54 | AudioManager.shared.outputDevice = outputDevice
55 | }
56 | }
57 |
58 | @Published var inputDevice: AudioDevice = AudioManager.shared.defaultInputDevice {
59 | didSet {
60 | print("didSet inputDevice: \(String(describing: inputDevice))")
61 | AudioManager.shared.inputDevice = inputDevice
62 | }
63 | }
64 |
65 | @Published var preferSpeakerOutput: Bool = true {
66 | didSet { AudioManager.shared.isSpeakerOutputPreferred = preferSpeakerOutput }
67 | }
68 |
69 | public init(store: ValueStore) {
70 | self.store = store
71 |
72 | videoViewVisible = store.value.videoViewVisible
73 | showInformationOverlay = store.value.showInformationOverlay
74 | preferSampleBufferRendering = store.value.preferSampleBufferRendering
75 | videoViewMode = store.value.videoViewMode
76 | videoViewMirrored = store.value.videoViewMirrored
77 | connectionHistory = store.value.connectionHistory
78 |
79 | AudioManager.shared.onDeviceUpdate = { [weak self] audioManager in
80 | guard let self else { return }
81 | print("devices did update")
82 | // force UI update for outputDevice / inputDevice
83 | Task.detached { @MainActor [weak self] in
84 | guard let self else { return }
85 | self.outputDevice = audioManager.outputDevice
86 | self.inputDevice = audioManager.inputDevice
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/iOS/BroadcastExt/LoggingOSLog.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 | import Logging
19 | import os
20 |
21 | public struct LoggingOSLog: LogHandler {
22 | public var logLevel: Logging.Logger.Level = .debug
23 | private let oslogger: OSLog
24 |
25 | public init(label: String) {
26 | oslogger = OSLog(subsystem: label, category: "")
27 | }
28 |
29 | public init(label _: String, log: OSLog) {
30 | oslogger = log
31 | }
32 |
33 | public func log(level _: Logging.Logger.Level, message: Logging.Logger.Message, metadata: Logging.Logger.Metadata?, file _: String, function _: String, line _: UInt) {
34 | var combinedPrettyMetadata = prettyMetadata
35 | if let metadataOverride = metadata, !metadataOverride.isEmpty {
36 | combinedPrettyMetadata = prettify(
37 | self.metadata.merging(metadataOverride) {
38 | $1
39 | }
40 | )
41 | }
42 |
43 | var formedMessage = message.description
44 | if combinedPrettyMetadata != nil {
45 | formedMessage += " -- " + combinedPrettyMetadata!
46 | }
47 | os_log("%{public}@", log: oslogger, type: OSLogType.from(loggerLevel: .info), formedMessage as NSString)
48 | }
49 |
50 | private var prettyMetadata: String?
51 | public var metadata = Logger.Metadata() {
52 | didSet {
53 | prettyMetadata = prettify(metadata)
54 | }
55 | }
56 |
57 | /// Add, remove, or change the logging metadata.
58 | /// - parameters:
59 | /// - metadataKey: the key for the metadata item.
60 | public subscript(metadataKey metadataKey: String) -> Logging.Logger.Metadata.Value? {
61 | get {
62 | metadata[metadataKey]
63 | }
64 | set {
65 | metadata[metadataKey] = newValue
66 | }
67 | }
68 |
69 | private func prettify(_ metadata: Logging.Logger.Metadata) -> String? {
70 | if metadata.isEmpty {
71 | return nil
72 | }
73 | return metadata.map {
74 | "\($0)=\($1)"
75 | }.joined(separator: " ")
76 | }
77 | }
78 |
79 | extension OSLogType {
80 | static func from(loggerLevel: Logging.Logger.Level) -> Self {
81 | switch loggerLevel {
82 | case .trace:
83 | /// `OSLog` doesn't have `trace`, so use `debug`
84 | return .debug
85 | case .debug:
86 | return .debug
87 | case .info:
88 | return .info
89 | case .notice:
90 | // https://developer.apple.com/documentation/os/logging/generating_log_messages_from_your_code
91 | // According to the documentation, `default` is `notice`.
92 | return .default
93 | case .warning:
94 | /// `OSLog` doesn't have `warning`, so use `info`
95 | return .info
96 | case .error:
97 | return .error
98 | case .critical:
99 | return .fault
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/LiveKitExample.xcodeproj/xcshareddata/xcschemes/BroadcastExt.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
9 |
10 |
16 |
22 |
23 |
24 |
30 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/LiveKitExample-main.xcodeproj/xcshareddata/xcschemes/BroadcastExt.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
9 |
10 |
16 |
22 |
23 |
24 |
30 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/Shared/Views/PublishOptionsView.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import AVFoundation
18 | import LiveKit
19 | import SwiftUI
20 |
21 | extension AVCaptureDevice: Identifiable {
22 | public var id: String { uniqueID }
23 | }
24 |
25 | struct PublishOptionsView: View {
26 | typealias OnPublish = (_ captureOptions: CameraCaptureOptions, _ publishOptions: VideoPublishOptions) -> Void
27 |
28 | @State private var devices: [AVCaptureDevice] = []
29 | @State private var device: AVCaptureDevice?
30 | @State private var simulcast: Bool
31 | @State private var preferredVideoCodec: VideoCodec?
32 | @State private var preferredBackupVideoCodec: VideoCodec?
33 | @State private var maxFPS: Int = 30
34 |
35 | private let providedPublishOptions: VideoPublishOptions
36 | private let onPublish: OnPublish
37 |
38 | init(publishOptions: VideoPublishOptions, _ onPublish: @escaping OnPublish) {
39 | providedPublishOptions = publishOptions
40 | self.onPublish = onPublish
41 |
42 | simulcast = publishOptions.simulcast
43 | preferredVideoCodec = publishOptions.preferredCodec
44 | preferredBackupVideoCodec = publishOptions.preferredBackupCodec
45 | }
46 |
47 | var body: some View {
48 | VStack(alignment: .center, spacing: 10) {
49 | Text("Publish options")
50 | .fontWeight(.bold)
51 | Form {
52 | Picker("Device", selection: $device) {
53 | Text("Auto").tag(nil as AVCaptureDevice?)
54 | ForEach(devices) {
55 | Text($0.localizedName).tag($0 as AVCaptureDevice?)
56 | }
57 | }
58 |
59 | Picker("Codec", selection: $preferredVideoCodec) {
60 | Text("Auto").tag(nil as VideoCodec?)
61 | ForEach(VideoCodec.all) {
62 | Text($0.id.uppercased()).tag($0 as VideoCodec?)
63 | }
64 | }.onChange(of: preferredVideoCodec) { newValue in
65 | if newValue?.isSVC ?? false {
66 | preferredBackupVideoCodec = .vp8
67 | } else {
68 | preferredBackupVideoCodec = nil
69 | }
70 | }
71 |
72 | Picker("Backup Codec", selection: $preferredBackupVideoCodec) {
73 | Text("Off").tag(nil as VideoCodec?)
74 | ForEach(VideoCodec.allBackup.filter { $0 != preferredVideoCodec }) {
75 | Text($0.id.uppercased()).tag($0 as VideoCodec?)
76 | }
77 | }.disabled(!(preferredVideoCodec?.isSVC ?? false))
78 |
79 | Picker("Max FPS", selection: $maxFPS) {
80 | ForEach(1 ... 30, id: \.self) {
81 | Text("\($0)").tag($0)
82 | }
83 | }
84 |
85 | Toggle(isOn: $simulcast, label: {
86 | Text("Simulcast")
87 | })
88 | }
89 |
90 | Button("Publish") {
91 | let captureOptions = CameraCaptureOptions(
92 | device: device,
93 | dimensions: .h1080_169
94 | )
95 |
96 | let publishOptions = VideoPublishOptions(
97 | name: providedPublishOptions.name,
98 | encoding: VideoEncoding(maxBitrate: VideoParameters.presetH1080_169.encoding.maxBitrate, maxFps: maxFPS),
99 | screenShareEncoding: providedPublishOptions.screenShareEncoding,
100 | simulcast: simulcast,
101 | simulcastLayers: providedPublishOptions.simulcastLayers,
102 | screenShareSimulcastLayers: providedPublishOptions.screenShareSimulcastLayers,
103 | preferredCodec: preferredVideoCodec,
104 | preferredBackupCodec: preferredBackupVideoCodec
105 | )
106 |
107 | onPublish(captureOptions, publishOptions)
108 | }
109 | .keyboardShortcut(.defaultAction)
110 |
111 | Spacer()
112 | }
113 | .onAppear(perform: {
114 | Task { @MainActor in
115 | devices = try await CameraCapturer.captureDevices().singleDeviceforEachPosition()
116 | }
117 | })
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Shared/Views/ScreenShareSourcePickerView.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import CoreGraphics
18 | import Foundation
19 | import LiveKit
20 | import SwiftUI
21 |
22 | #if os(macOS)
23 |
24 | @available(macOS 12.3, *)
25 | class ScreenShareSourcePickerCtrl: ObservableObject {
26 | @Published var tracks = [LocalVideoTrack]()
27 | @Published var mode: ScreenShareSourcePickerView.Mode = .display {
28 | didSet {
29 | guard oldValue != mode else { return }
30 | Task.detached { [weak self] in
31 | guard let self else { return }
32 | try await self.restartTracks()
33 | }
34 | }
35 | }
36 |
37 | private func restartTracks() async throws {
38 | // stop in parallel
39 | await withThrowingTaskGroup(of: Void.self) { group in
40 | for track in tracks {
41 | group.addTask {
42 | try await track.stop()
43 | }
44 | }
45 | }
46 |
47 | let sources = try await MacOSScreenCapturer.sources(for: mode == .display ? .display : .window)
48 | let options = ScreenShareCaptureOptions(dimensions: .h360_43, fps: 5)
49 | let _newTracks = sources.map { LocalVideoTrack.createMacOSScreenShareTrack(source: $0, options: options) }
50 |
51 | Task { @MainActor in
52 | self.tracks = _newTracks
53 | }
54 |
55 | // start in parallel
56 | await withThrowingTaskGroup(of: Void.self) { group in
57 | for track in _newTracks {
58 | group.addTask {
59 | try await track.start()
60 | }
61 | }
62 | }
63 | }
64 |
65 | init() {
66 | Task {
67 | try await restartTracks()
68 | }
69 | }
70 |
71 | deinit {
72 | print("\(type(of: self)) deinit")
73 |
74 | // copy
75 | let _tracks = tracks
76 |
77 | Task {
78 | // stop in parallel
79 | await withThrowingTaskGroup(of: Void.self) { group in
80 | for track in _tracks {
81 | group.addTask {
82 | try await track.stop()
83 | }
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | typealias OnPickScreenShareSource = (MacOSScreenCaptureSource) -> Void
91 |
92 | @available(macOS 12.3, *)
93 | struct ScreenShareSourcePickerView: View {
94 | public enum Mode {
95 | case display
96 | case window
97 | }
98 |
99 | @ObservedObject var ctrl = ScreenShareSourcePickerCtrl()
100 |
101 | let onPickScreenShareSource: OnPickScreenShareSource?
102 |
103 | private var columns = [
104 | GridItem(.fixed(250)),
105 | GridItem(.fixed(250)),
106 | ]
107 |
108 | init(onPickScreenShareSource: OnPickScreenShareSource? = nil) {
109 | self.onPickScreenShareSource = onPickScreenShareSource
110 | }
111 |
112 | var body: some View {
113 | VStack {
114 | Picker("", selection: $ctrl.mode) {
115 | Text("Entire Screen").tag(ScreenShareSourcePickerView.Mode.display)
116 | Text("Application Window").tag(ScreenShareSourcePickerView.Mode.window)
117 | }
118 | .pickerStyle(SegmentedPickerStyle())
119 |
120 | ScrollView(.vertical, showsIndicators: true) {
121 | LazyVGrid(columns: columns,
122 | alignment: .center,
123 | spacing: 10)
124 | {
125 | ForEach(ctrl.tracks) { track in
126 | ZStack {
127 | SwiftUIVideoView(track, layoutMode: .fit)
128 | .aspectRatio(1, contentMode: .fit)
129 | .onTapGesture {
130 | guard let capturer = track.capturer as? MacOSScreenCapturer,
131 | let source = capturer.captureSource else { return }
132 | onPickScreenShareSource?(source)
133 | }
134 |
135 | if let capturer = track.capturer as? MacOSScreenCapturer,
136 | let source = capturer.captureSource as? MacOSWindow,
137 | let appName = source.owningApplication?.applicationName
138 | {
139 | Text(appName)
140 | .shadow(color: .black, radius: 1)
141 | }
142 | }
143 | }
144 | }
145 | }
146 | .frame(minHeight: 350)
147 | }
148 | }
149 | }
150 |
151 | #endif
152 |
--------------------------------------------------------------------------------
/Shared/LiveKitExample.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import KeychainAccess
18 | import LiveKit
19 | import Logging
20 | import SwiftUI
21 |
22 | let sync = ValueStore(store: Keychain(service: "io.livekit.example.SwiftSDK.1"),
23 | key: "preferences",
24 | default: Preferences())
25 |
26 | struct RoomSwitchView: View {
27 | @EnvironmentObject var appCtx: AppContext
28 | @EnvironmentObject var roomCtx: RoomContext
29 | @EnvironmentObject var room: Room
30 |
31 | var shouldShowRoomView: Bool {
32 | room.connectionState == .connected || room.connectionState == .reconnecting
33 | }
34 |
35 | func computeTitle() -> String {
36 | if shouldShowRoomView {
37 | var elements: [String] = []
38 | if let roomName = room.name {
39 | elements.append(roomName)
40 | }
41 | if let localParticipantName = room.localParticipant.name {
42 | elements.append(localParticipantName)
43 | }
44 | if let localParticipantIdentity = room.localParticipant.identity {
45 | elements.append(String(describing: localParticipantIdentity))
46 | }
47 | return elements.joined(separator: " ")
48 | }
49 |
50 | return "LiveKit"
51 | }
52 |
53 | var body: some View {
54 | ZStack {
55 | Color.black
56 | .ignoresSafeArea()
57 |
58 | if shouldShowRoomView {
59 | RoomView()
60 | } else {
61 | ConnectView()
62 | }
63 | }
64 | .navigationTitle(computeTitle())
65 | }
66 | }
67 |
68 | // Attaches RoomContext and Room to the environment
69 | struct RoomContextView: View {
70 | @EnvironmentObject var appCtx: AppContext
71 | @StateObject var roomCtx = RoomContext(store: sync)
72 |
73 | var body: some View {
74 | RoomSwitchView()
75 | .environmentObject(roomCtx)
76 | .environmentObject(roomCtx.room)
77 | .environment(\.colorScheme, .dark)
78 | .foregroundColor(Color.white)
79 | .onDisappear {
80 | print("\(String(describing: type(of: self))) onDisappear")
81 | Task {
82 | await roomCtx.disconnect()
83 | }
84 | }
85 | .onOpenURL(perform: { url in
86 |
87 | guard let urlComponent = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return }
88 | guard let host = url.host else { return }
89 |
90 | let secureValue = urlComponent.queryItems?.first(where: { $0.name == "secure" })?.value?.lowercased()
91 | let secure = ["true", "1"].contains { $0 == secureValue }
92 |
93 | let tokenValue = urlComponent.queryItems?.first(where: { $0.name == "token" })?.value ?? ""
94 |
95 | let e2eeValue = urlComponent.queryItems?.first(where: { $0.name == "e2ee" })?.value?.lowercased()
96 | let e2ee = ["true", "1"].contains { $0 == secureValue }
97 | let e2eeKey = urlComponent.queryItems?.first(where: { $0.name == "e2eeKey" })?.value ?? ""
98 |
99 | var builder = URLComponents()
100 | builder.scheme = secure ? "wss" : "ws"
101 | builder.host = host
102 | builder.port = url.port
103 |
104 | guard let builtUrl = builder.url?.absoluteString else { return }
105 |
106 | print("built URL: \(builtUrl), token: \(tokenValue)")
107 |
108 | Task { @MainActor in
109 | roomCtx.url = builtUrl
110 | roomCtx.token = tokenValue
111 | roomCtx.isE2eeEnabled = e2ee
112 | roomCtx.e2eeKey = e2eeKey
113 | if !roomCtx.token.isEmpty {
114 | let room = try await roomCtx.connect()
115 | appCtx.connectionHistory.update(room: room, e2ee: e2ee, e2eeKey: e2eeKey)
116 | }
117 | }
118 | })
119 | }
120 | }
121 |
122 | extension Decimal {
123 | mutating func round(_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode) {
124 | var localCopy = self
125 | NSDecimalRound(&self, &localCopy, scale, roundingMode)
126 | }
127 |
128 | func rounded(_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode) -> Decimal {
129 | var result = Decimal()
130 | var localCopy = self
131 | NSDecimalRound(&result, &localCopy, scale, roundingMode)
132 | return result
133 | }
134 |
135 | func remainder(of divisor: Decimal) -> Decimal {
136 | let s = self as NSDecimalNumber
137 | let d = divisor as NSDecimalNumber
138 | let b = NSDecimalNumberHandler(roundingMode: .down,
139 | scale: 0,
140 | raiseOnExactness: false,
141 | raiseOnOverflow: false,
142 | raiseOnUnderflow: false,
143 | raiseOnDivideByZero: false)
144 | let quotient = s.dividing(by: d, withBehavior: b)
145 |
146 | let subtractAmount = quotient.multiplying(by: d)
147 | return s.subtracting(subtractAmount) as Decimal
148 | }
149 | }
150 |
151 | @main
152 | struct LiveKitExample: App {
153 | @StateObject var appCtx = AppContext(store: sync)
154 |
155 | func nearestSafeScale(for target: Int, scale: Double) -> Decimal {
156 | let p = Decimal(sign: .plus, exponent: -3, significand: 1)
157 | let t = Decimal(target)
158 | var s = Decimal(scale).rounded(3, .down)
159 |
160 | while (t * s / 2).remainder(of: 2) != 0 {
161 | s = s + p
162 | }
163 |
164 | return s
165 | }
166 |
167 | init() {
168 | LoggingSystem.bootstrap {
169 | var logHandler = StreamLogHandler.standardOutput(label: $0)
170 | logHandler.logLevel = .debug
171 | return logHandler
172 | }
173 | }
174 |
175 | var body: some Scene {
176 | WindowGroup {
177 | RoomContextView()
178 | .environmentObject(appCtx)
179 | }
180 | .handlesExternalEvents(matching: Set(arrayLiteral: "*"))
181 | #if os(macOS)
182 | .windowStyle(.hiddenTitleBar)
183 | .windowToolbarStyle(.unifiedCompact)
184 | #endif
185 | }
186 | }
187 |
188 | #if os(macOS)
189 |
190 | extension View {
191 | func withHostingWindow(_ callback: @escaping (NSWindow) -> Void) -> some View {
192 | background(HostingWindowFinder(callback: callback))
193 | }
194 | }
195 |
196 | struct HostingWindowFinder: NSViewRepresentable {
197 | var callback: (NSWindow) -> Void
198 |
199 | func makeNSView(context _: Context) -> NSView {
200 | let view = NSView()
201 | DispatchQueue.main.async { [weak view] in
202 | if let window = view?.window {
203 | callback(window)
204 | }
205 | }
206 | return view
207 | }
208 |
209 | func updateNSView(_: NSView, context _: Context) {}
210 | }
211 | #endif
212 |
--------------------------------------------------------------------------------
/Shared/ConnectView.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 | import LiveKit
19 | import SFSafeSymbols
20 | import SwiftUI
21 |
22 | struct ConnectView: View {
23 | @EnvironmentObject var appCtx: AppContext
24 | @EnvironmentObject var roomCtx: RoomContext
25 | @EnvironmentObject var room: Room
26 |
27 | var body: some View {
28 | GeometryReader { geometry in
29 | ScrollView {
30 | VStack(alignment: .center, spacing: 40.0) {
31 | VStack(spacing: 10) {
32 | Image("logo")
33 | .resizable()
34 | .aspectRatio(contentMode: .fit)
35 | .frame(height: 30)
36 | .padding(.bottom, 10)
37 | Text("SDK Version \(LiveKitSDK.version)")
38 | .opacity(0.5)
39 | Text("Example App Version \(Bundle.main.appVersionLong) (\(Bundle.main.appBuild))")
40 | .opacity(0.5)
41 | }
42 |
43 | VStack(spacing: 15) {
44 | LKTextField(title: "Server URL", text: $roomCtx.url, type: .URL)
45 | LKTextField(title: "Token", text: $roomCtx.token, type: .ascii)
46 | LKTextField(title: "E2EE Key", text: $roomCtx.e2eeKey, type: .ascii)
47 |
48 | HStack {
49 | Menu {
50 | Toggle(isOn: $roomCtx.autoSubscribe) {
51 | Text("Auto-Subscribe")
52 | }
53 | Toggle(isOn: $roomCtx.isE2eeEnabled) {
54 | Text("Enable E2EE")
55 | }
56 | } label: {
57 | Image(systemSymbol: .boltFill)
58 | .renderingMode(.original)
59 | Text("Connect Options")
60 | }
61 | #if os(macOS)
62 | .menuStyle(BorderlessButtonMenuStyle(showsMenuIndicator: true))
63 | #elseif os(iOS)
64 | .menuStyle(BorderlessButtonMenuStyle())
65 | #endif
66 | .fixedSize()
67 |
68 | Menu {
69 | Toggle(isOn: $roomCtx.simulcast) {
70 | Text("Simulcast")
71 | }
72 | Toggle(isOn: $roomCtx.adaptiveStream) {
73 | Text("AdaptiveStream")
74 | }
75 | Toggle(isOn: $roomCtx.dynacast) {
76 | Text("Dynacast")
77 | }
78 | Toggle(isOn: $roomCtx.reportStats) {
79 | Text("Report stats")
80 | }
81 | } label: {
82 | Image(systemSymbol: .gear)
83 | .renderingMode(.original)
84 | Text("Room Options")
85 | }
86 | #if os(macOS)
87 | .menuStyle(BorderlessButtonMenuStyle(showsMenuIndicator: true))
88 | #elseif os(iOS)
89 | .menuStyle(BorderlessButtonMenuStyle())
90 | #endif
91 | .fixedSize()
92 | }
93 | }.frame(maxWidth: 350)
94 |
95 | if case .connecting = room.connectionState {
96 | HStack(alignment: .center) {
97 | ProgressView()
98 |
99 | LKButton(title: "Cancel") {
100 | roomCtx.cancelConnect()
101 | }
102 | }
103 | } else {
104 | HStack(alignment: .center) {
105 | Spacer()
106 |
107 | LKButton(title: "Connect") {
108 | Task.detached { @MainActor in
109 | let room = try await roomCtx.connect()
110 | appCtx.connectionHistory.update(room: room, e2ee: roomCtx.isE2eeEnabled, e2eeKey: roomCtx.e2eeKey)
111 | }
112 | }
113 |
114 | if !appCtx.connectionHistory.isEmpty {
115 | Menu {
116 | ForEach(appCtx.connectionHistory.sortedByUpdated) { entry in
117 | Button {
118 | Task.detached { @MainActor in
119 | let room = try await roomCtx.connect(entry: entry)
120 | appCtx.connectionHistory.update(room: room, e2ee: roomCtx.isE2eeEnabled, e2eeKey: roomCtx.e2eeKey)
121 | }
122 | } label: {
123 | Image(systemSymbol: .boltFill)
124 | .renderingMode(.original)
125 | Text(String(describing: entry))
126 | }
127 | }
128 |
129 | Divider()
130 |
131 | Button {
132 | appCtx.connectionHistory.removeAll()
133 | } label: {
134 | Image(systemSymbol: .xmarkCircleFill)
135 | .renderingMode(.original)
136 | Text("Clear history")
137 | }
138 |
139 | } label: {
140 | Image(systemSymbol: .clockFill)
141 | .renderingMode(.original)
142 | Text("Recent")
143 | }
144 | #if os(macOS)
145 | .menuStyle(BorderlessButtonMenuStyle(showsMenuIndicator: true))
146 | #elseif os(iOS)
147 | .menuStyle(BorderlessButtonMenuStyle())
148 | #endif
149 | .fixedSize()
150 | }
151 |
152 | Spacer()
153 | }
154 | }
155 | }
156 | .padding()
157 | .frame(width: geometry.size.width) // Make the scroll view full-width
158 | .frame(minHeight: geometry.size.height) // Set the content’s min height to the parent
159 | }
160 | }
161 | #if os(macOS)
162 | .frame(minWidth: 500, minHeight: 500)
163 | #endif
164 | .alert(isPresented: $roomCtx.shouldShowDisconnectReason) {
165 | Alert(title: Text("Disconnected"),
166 | message: Text("Reason: " + String(describing: roomCtx.latestError)))
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/Shared/Controllers/RoomContext.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import LiveKit
18 | import SwiftUI
19 |
20 | // This class contains the logic to control behavior of the whole app.
21 | final class RoomContext: ObservableObject {
22 | let jsonEncoder = JSONEncoder()
23 | let jsonDecoder = JSONDecoder()
24 |
25 | private let store: ValueStore
26 |
27 | // Used to show connection error dialog
28 | // private var didClose: Bool = false
29 | @Published var shouldShowDisconnectReason: Bool = false
30 | public var latestError: LiveKitError?
31 |
32 | public let room = Room()
33 |
34 | @Published var url: String = "" {
35 | didSet { store.value.url = url }
36 | }
37 |
38 | @Published var token: String = "" {
39 | didSet { store.value.token = token }
40 | }
41 |
42 | @Published var e2eeKey: String = "" {
43 | didSet { store.value.e2eeKey = e2eeKey }
44 | }
45 |
46 | @Published var isE2eeEnabled: Bool = false {
47 | didSet {
48 | store.value.isE2eeEnabled = isE2eeEnabled
49 | // room.set(isE2eeEnabled: isE2eeEnabled)
50 | }
51 | }
52 |
53 | // RoomOptions
54 | @Published var simulcast: Bool = true {
55 | didSet { store.value.simulcast = simulcast }
56 | }
57 |
58 | @Published var adaptiveStream: Bool = false {
59 | didSet { store.value.adaptiveStream = adaptiveStream }
60 | }
61 |
62 | @Published var dynacast: Bool = false {
63 | didSet { store.value.dynacast = dynacast }
64 | }
65 |
66 | @Published var reportStats: Bool = false {
67 | didSet { store.value.reportStats = reportStats }
68 | }
69 |
70 | // ConnectOptions
71 | @Published var autoSubscribe: Bool = true {
72 | didSet { store.value.autoSubscribe = autoSubscribe }
73 | }
74 |
75 | @Published var focusParticipant: Participant?
76 |
77 | @Published var showMessagesView: Bool = false
78 | @Published var messages: [ExampleRoomMessage] = []
79 |
80 | @Published var textFieldString: String = ""
81 |
82 | var _connectTask: Task?
83 |
84 | public init(store: ValueStore) {
85 | self.store = store
86 | room.add(delegate: self)
87 |
88 | url = store.value.url
89 | token = store.value.token
90 | isE2eeEnabled = store.value.isE2eeEnabled
91 | e2eeKey = store.value.e2eeKey
92 | simulcast = store.value.simulcast
93 | adaptiveStream = store.value.adaptiveStream
94 | dynacast = store.value.dynacast
95 | reportStats = store.value.reportStats
96 | autoSubscribe = store.value.autoSubscribe
97 |
98 | #if os(iOS)
99 | UIApplication.shared.isIdleTimerDisabled = true
100 | #endif
101 | }
102 |
103 | deinit {
104 | #if os(iOS)
105 | UIApplication.shared.isIdleTimerDisabled = false
106 | #endif
107 | print("RoomContext.deinit")
108 | }
109 |
110 | func cancelConnect() {
111 | _connectTask?.cancel()
112 | }
113 |
114 | @MainActor
115 | func connect(entry: ConnectionHistory? = nil) async throws -> Room {
116 | if let entry {
117 | url = entry.url
118 | token = entry.token
119 | isE2eeEnabled = entry.e2ee
120 | e2eeKey = entry.e2eeKey
121 | }
122 |
123 | let connectOptions = ConnectOptions(
124 | autoSubscribe: autoSubscribe
125 | )
126 |
127 | var e2eeOptions: E2EEOptions? = nil
128 | if isE2eeEnabled {
129 | let keyProvider = BaseKeyProvider(isSharedKey: true)
130 | keyProvider.setKey(key: e2eeKey)
131 | e2eeOptions = E2EEOptions(keyProvider: keyProvider)
132 | }
133 |
134 | let roomOptions = RoomOptions(
135 | defaultCameraCaptureOptions: CameraCaptureOptions(
136 | dimensions: .h1080_169
137 | ),
138 | defaultScreenShareCaptureOptions: ScreenShareCaptureOptions(
139 | dimensions: .h1080_169,
140 | useBroadcastExtension: true
141 | ),
142 | defaultVideoPublishOptions: VideoPublishOptions(
143 | simulcast: simulcast
144 | ),
145 | adaptiveStream: true,
146 | dynacast: true,
147 | // isE2eeEnabled: isE2eeEnabled,
148 | e2eeOptions: e2eeOptions,
149 | reportRemoteTrackStatistics: true
150 | )
151 |
152 | let connectTask = Task.detached { [weak self] in
153 | guard let self else { return }
154 | try await self.room.connect(url: self.url,
155 | token: self.token,
156 | connectOptions: connectOptions,
157 | roomOptions: roomOptions)
158 | }
159 |
160 | _connectTask = connectTask
161 | try await connectTask.value
162 |
163 | return room
164 | }
165 |
166 | func disconnect() async {
167 | await room.disconnect()
168 | }
169 |
170 | func sendMessage() {
171 | // Make sure the message is not empty
172 | guard !textFieldString.isEmpty else { return }
173 |
174 | let roomMessage = ExampleRoomMessage(messageId: UUID().uuidString,
175 | senderSid: room.localParticipant.sid,
176 | senderIdentity: room.localParticipant.identity,
177 | text: textFieldString)
178 | textFieldString = ""
179 | messages.append(roomMessage)
180 |
181 | Task.detached { [weak self] in
182 | guard let self else { return }
183 | do {
184 | let json = try self.jsonEncoder.encode(roomMessage)
185 | try await self.room.localParticipant.publish(data: json)
186 | } catch {
187 | print("Failed to encode data \(error)")
188 | }
189 | }
190 | }
191 |
192 | #if os(macOS)
193 | weak var screenShareTrack: LocalTrackPublication?
194 |
195 | @available(macOS 12.3, *)
196 | func setScreenShareMacOS(isEnabled: Bool, screenShareSource: MacOSScreenCaptureSource? = nil) async throws {
197 | if isEnabled, let screenShareSource {
198 | let track = LocalVideoTrack.createMacOSScreenShareTrack(source: screenShareSource)
199 | let options = VideoPublishOptions(preferredCodec: VideoCodec.h264)
200 | screenShareTrack = try await room.localParticipant.publish(videoTrack: track, options: options)
201 | }
202 |
203 | if !isEnabled, let screenShareTrack {
204 | try await room.localParticipant.unpublish(publication: screenShareTrack)
205 | }
206 | }
207 | #endif
208 | }
209 |
210 | extension RoomContext: RoomDelegate {
211 | func room(_: Room, track publication: TrackPublication, didUpdateE2EEState e2eeState: E2EEState) {
212 | print("Did update e2eeState = [\(String(describing: e2eeState))] for publication \(publication.sid)")
213 | }
214 |
215 | func room(_ room: Room, didUpdateConnectionState connectionState: ConnectionState, from oldValue: ConnectionState) {
216 | print("Did update connectionState \(oldValue) -> \(connectionState)")
217 |
218 | if case .disconnected = connectionState,
219 | let error = room.disconnectError,
220 | error.type != .cancelled
221 | {
222 | latestError = room.disconnectError
223 |
224 | Task.detached { @MainActor [weak self] in
225 | guard let self else { return }
226 | self.shouldShowDisconnectReason = true
227 | // Reset state
228 | self.focusParticipant = nil
229 | self.showMessagesView = false
230 | self.textFieldString = ""
231 | self.messages.removeAll()
232 | // self.objectWillChange.send()
233 | }
234 | }
235 | }
236 |
237 | func room(_: Room, participantDidDisconnect participant: RemoteParticipant) {
238 | Task.detached { @MainActor [weak self] in
239 | guard let self else { return }
240 | if let focusParticipant = self.focusParticipant, focusParticipant.identity == participant.identity {
241 | self.focusParticipant = nil
242 | }
243 | }
244 | }
245 |
246 | func room(_: Room, participant _: RemoteParticipant?, didReceiveData data: Data, forTopic _: String) {
247 | do {
248 | let roomMessage = try jsonDecoder.decode(ExampleRoomMessage.self, from: data)
249 | // Update UI from main queue
250 | Task.detached { @MainActor [weak self] in
251 | guard let self else { return }
252 |
253 | withAnimation {
254 | // Add messages to the @Published messages property
255 | // which will trigger the UI to update
256 | self.messages.append(roomMessage)
257 | // Show the messages view when new messages arrive
258 | self.showMessagesView = true
259 | }
260 | }
261 |
262 | } catch {
263 | print("Failed to decode data \(error)")
264 | }
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/Shared/ParticipantView.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import LiveKit
18 | import SFSafeSymbols
19 | import SwiftUI
20 |
21 | struct ParticipantView: View {
22 | @ObservedObject var participant: Participant
23 | @EnvironmentObject var appCtx: AppContext
24 |
25 | var videoViewMode: VideoView.LayoutMode = .fill
26 | var onTap: ((_ participant: Participant) -> Void)?
27 |
28 | @State private var isRendering: Bool = false
29 |
30 | func bgView(systemSymbol: SFSymbol, geometry: GeometryProxy) -> some View {
31 | Image(systemSymbol: systemSymbol)
32 | .resizable()
33 | .aspectRatio(contentMode: .fit)
34 | .foregroundColor(Color.lkGray2)
35 | .frame(width: min(geometry.size.width, geometry.size.height) * 0.3)
36 | .frame(
37 | maxWidth: .infinity,
38 | maxHeight: .infinity
39 | )
40 | }
41 |
42 | var body: some View {
43 | GeometryReader { geometry in
44 |
45 | ZStack(alignment: .bottom) {
46 | // Background color
47 | Color.lkGray1
48 | .ignoresSafeArea()
49 |
50 | // VideoView for the Participant
51 | if let publication = participant.mainVideoPublication,
52 | !publication.isMuted,
53 | let track = publication.track as? VideoTrack,
54 | appCtx.videoViewVisible
55 | {
56 | ZStack(alignment: .topLeading) {
57 | SwiftUIVideoView(track,
58 | layoutMode: videoViewMode,
59 | mirrorMode: appCtx.videoViewMirrored ? .mirror : .auto,
60 | renderMode: appCtx.preferSampleBufferRendering ? .sampleBuffer : .auto,
61 | pinchToZoomOptions: appCtx.videoViewPinchToZoomOptions,
62 | isDebugMode: appCtx.showInformationOverlay,
63 | isRendering: $isRendering)
64 |
65 | if !isRendering {
66 | ProgressView().progressViewStyle(CircularProgressViewStyle())
67 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
68 | }
69 | }
70 | } else if let publication = participant.mainVideoPublication as? RemoteTrackPublication,
71 | case .notAllowed = publication.subscriptionState
72 | {
73 | // Show no permission icon
74 | bgView(systemSymbol: .exclamationmarkCircle, geometry: geometry)
75 | } else {
76 | // Show no camera icon
77 | bgView(systemSymbol: .videoSlashFill, geometry: geometry)
78 | }
79 |
80 | if appCtx.showInformationOverlay {
81 | VStack(alignment: .leading, spacing: 5) {
82 | // Video stats
83 | if let publication = participant.mainVideoPublication,
84 | !publication.isMuted,
85 | let track = publication.track as? VideoTrack
86 | {
87 | StatsView(track: track)
88 | }
89 | // Audio stats
90 | if let publication = participant.firstAudioPublication,
91 | !publication.isMuted,
92 | let track = publication.track as? AudioTrack
93 | {
94 | StatsView(track: track)
95 | }
96 | }
97 | .padding(8)
98 | .frame(
99 | minWidth: 0,
100 | maxWidth: .infinity,
101 | minHeight: 0,
102 | maxHeight: .infinity,
103 | alignment: .topLeading
104 | )
105 | }
106 |
107 | VStack(alignment: .trailing, spacing: 0) {
108 | // Show the sub-video view
109 | if let subVideoTrack = participant.subVideoTrack {
110 | SwiftUIVideoView(subVideoTrack,
111 | layoutMode: .fill,
112 | mirrorMode: appCtx.videoViewMirrored ? .mirror : .auto)
113 | .background(Color.black)
114 | .aspectRatio(contentMode: .fit)
115 | .frame(width: min(geometry.size.width, geometry.size.height) * 0.3)
116 | .cornerRadius(8)
117 | .padding()
118 | }
119 |
120 | // Bottom user info bar
121 | HStack {
122 | if let identity = participant.identity {
123 | Text(String(describing: identity))
124 | .lineLimit(1)
125 | .truncationMode(.tail)
126 | }
127 |
128 | if let publication = participant.mainVideoPublication,
129 | !publication.isMuted
130 | {
131 | // is remote
132 | if let remotePub = publication as? RemoteTrackPublication {
133 | Menu {
134 | if case .subscribed = remotePub.subscriptionState {
135 | Button {
136 | Task {
137 | try await remotePub.set(subscribed: false)
138 | }
139 | } label: {
140 | Text("Unsubscribe")
141 | }
142 | } else if case .unsubscribed = remotePub.subscriptionState {
143 | Button {
144 | Task {
145 | try await remotePub.set(subscribed: true)
146 | }
147 | } label: {
148 | Text("Subscribe")
149 | }
150 | }
151 | } label: {
152 | if case .subscribed = remotePub.subscriptionState {
153 | Image(systemSymbol: .videoFill)
154 | .foregroundColor(Color.green)
155 | } else if case .notAllowed = remotePub.subscriptionState {
156 | Image(systemSymbol: .exclamationmarkCircle)
157 | .foregroundColor(Color.red)
158 | } else {
159 | Image(systemSymbol: .videoSlashFill)
160 | }
161 | }
162 | #if os(macOS)
163 | .menuStyle(BorderlessButtonMenuStyle(showsMenuIndicator: true))
164 | #elseif os(iOS)
165 | .menuStyle(BorderlessButtonMenuStyle())
166 | #endif
167 | .fixedSize()
168 | } else {
169 | // local
170 | Image(systemSymbol: .videoFill)
171 | .foregroundColor(Color.green)
172 | }
173 |
174 | } else {
175 | Image(systemSymbol: .videoSlashFill)
176 | .foregroundColor(Color.white)
177 | }
178 |
179 | if let publication = participant.firstAudioPublication,
180 | !publication.isMuted
181 | {
182 | // is remote
183 | if let remotePub = publication as? RemoteTrackPublication {
184 | Menu {
185 | if case .subscribed = remotePub.subscriptionState {
186 | Button {
187 | Task {
188 | try await remotePub.set(subscribed: false)
189 | }
190 | } label: {
191 | Text("Unsubscribe")
192 | }
193 | } else if case .unsubscribed = remotePub.subscriptionState {
194 | Button {
195 | Task {
196 | try await remotePub.set(subscribed: true)
197 | }
198 | } label: {
199 | Text("Subscribe")
200 | }
201 | }
202 | } label: {
203 | if case .subscribed = remotePub.subscriptionState {
204 | Image(systemSymbol: .micFill)
205 | .foregroundColor(Color.orange)
206 | } else if case .notAllowed = remotePub.subscriptionState {
207 | Image(systemSymbol: .exclamationmarkCircle)
208 | .foregroundColor(Color.red)
209 | } else {
210 | Image(systemSymbol: .micSlashFill)
211 | }
212 | }
213 | #if os(macOS)
214 | .menuStyle(BorderlessButtonMenuStyle(showsMenuIndicator: true))
215 | #elseif os(iOS)
216 | .menuStyle(BorderlessButtonMenuStyle())
217 | #endif
218 | .fixedSize()
219 | } else {
220 | // local
221 | Image(systemSymbol: .micFill)
222 | .foregroundColor(Color.orange)
223 | }
224 |
225 | } else {
226 | Image(systemSymbol: .micSlashFill)
227 | .foregroundColor(Color.white)
228 | }
229 |
230 | if participant.connectionQuality == .excellent {
231 | Image(systemSymbol: .wifi)
232 | .foregroundColor(.green)
233 | } else if participant.connectionQuality == .good {
234 | Image(systemSymbol: .wifi)
235 | .foregroundColor(Color.orange)
236 | } else if participant.connectionQuality == .poor {
237 | Image(systemSymbol: .wifiExclamationmark)
238 | .foregroundColor(Color.red)
239 | }
240 |
241 | if participant.firstTrackEncryptionType == .none {
242 | Image(systemSymbol: .lockOpenFill)
243 | .foregroundColor(.red)
244 | } else {
245 | Image(systemSymbol: .lockFill)
246 | .foregroundColor(.green)
247 | }
248 |
249 | }.padding(5)
250 | .frame(minWidth: 0, maxWidth: .infinity)
251 | .background(Color.black.opacity(0.5))
252 | }
253 | }
254 | .cornerRadius(8)
255 | // Glow the border when the participant is speaking
256 | .overlay(
257 | participant.isSpeaking ?
258 | RoundedRectangle(cornerRadius: 5)
259 | .stroke(Color.blue, lineWidth: 5.0)
260 | : nil
261 | )
262 | }.gesture(TapGesture()
263 | .onEnded { _ in
264 | // Pass the tap event
265 | onTap?(participant)
266 | })
267 | }
268 | }
269 |
270 | struct StatsView: View {
271 | private let track: Track
272 | @ObservedObject private var observer: TrackDelegateObserver
273 |
274 | init(track: Track) {
275 | self.track = track
276 | observer = TrackDelegateObserver(track: track)
277 | }
278 |
279 | var body: some View {
280 | HStack(alignment: .top, spacing: 5) {
281 | VStack(alignment: .leading, spacing: 5) {
282 | if track is VideoTrack {
283 | HStack(spacing: 3) {
284 | Image(systemSymbol: .videoFill)
285 | Text("Video").fontWeight(.bold)
286 | if let dimensions = observer.dimensions {
287 | Text("\(dimensions.width)×\(dimensions.height)")
288 | }
289 | }
290 | } else if track is AudioTrack {
291 | HStack(spacing: 3) {
292 | Image(systemSymbol: .micFill)
293 | Text("Audio").fontWeight(.bold)
294 | }
295 | } else {
296 | Text("Unknown").fontWeight(.bold)
297 | }
298 |
299 | // if let trackStats = viewModel.statistics {
300 | ForEach(observer.allStatisticts, id: \.self) { trackStats in
301 | ForEach(trackStats.outboundRtpStream.sortedByRidIndex()) { stream in
302 |
303 | HStack(spacing: 3) {
304 | Image(systemSymbol: .arrowUp)
305 |
306 | if let codec = trackStats.codec.first(where: { $0.id == stream.codecId }) {
307 | Text(codec.mimeType ?? "?")
308 | }
309 |
310 | if let rid = stream.rid, !rid.isEmpty {
311 | Text(rid.uppercased())
312 | }
313 |
314 | Text(stream.formattedBps())
315 |
316 | if let reason = stream.qualityLimitationReason, reason != QualityLimitationReason.none {
317 | Image(systemSymbol: .exclamationmarkTriangleFill)
318 | Text(reason.rawValue.capitalized)
319 | }
320 | }
321 | }
322 | ForEach(trackStats.inboundRtpStream) { stream in
323 |
324 | HStack(spacing: 3) {
325 | Image(systemSymbol: .arrowDown)
326 |
327 | if let codec = trackStats.codec.first(where: { $0.id == stream.codecId }) {
328 | Text(codec.mimeType ?? "?")
329 | }
330 |
331 | Text(stream.formattedBps())
332 | }
333 | }
334 | }
335 | }
336 | .font(.system(size: 10))
337 | .foregroundColor(Color.white)
338 | .padding(5)
339 | .background(Color.black.opacity(0.5))
340 | .cornerRadius(8)
341 | }
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/Shared/RoomView.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 LiveKit
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import LiveKit
18 | import SFSafeSymbols
19 | import SwiftUI
20 |
21 | #if !os(macOS)
22 | let adaptiveMin = 170.0
23 | let toolbarPlacement: ToolbarItemPlacement = .bottomBar
24 | #else
25 | let adaptiveMin = 300.0
26 | let toolbarPlacement: ToolbarItemPlacement = .primaryAction
27 | #endif
28 |
29 | extension CIImage {
30 | // helper to create a `CIImage` for both platforms
31 | convenience init(named name: String) {
32 | #if !os(macOS)
33 | self.init(cgImage: UIImage(named: name)!.cgImage!)
34 | #else
35 | self.init(data: NSImage(named: name)!.tiffRepresentation!)!
36 | #endif
37 | }
38 | }
39 |
40 | #if os(macOS)
41 | // keeps weak reference to NSWindow
42 | class WindowAccess: ObservableObject {
43 | private weak var window: NSWindow?
44 |
45 | deinit {
46 | // reset changed properties
47 | DispatchQueue.main.async { [weak window] in
48 | window?.level = .normal
49 | }
50 | }
51 |
52 | @Published public var pinned: Bool = false {
53 | didSet {
54 | guard oldValue != pinned else { return }
55 | level = pinned ? .floating : .normal
56 | }
57 | }
58 |
59 | private var level: NSWindow.Level {
60 | get { window?.level ?? .normal }
61 | set {
62 | Task { @MainActor in
63 | window?.level = newValue
64 | objectWillChange.send()
65 | }
66 | }
67 | }
68 |
69 | public func set(window: NSWindow?) {
70 | self.window = window
71 | Task { @MainActor in
72 | objectWillChange.send()
73 | }
74 | }
75 | }
76 | #endif
77 |
78 | struct RoomView: View {
79 | @EnvironmentObject var appCtx: AppContext
80 | @EnvironmentObject var roomCtx: RoomContext
81 | @EnvironmentObject var room: Room
82 |
83 | @State var isCameraPublishingBusy = false
84 | @State var isMicrophonePublishingBusy = false
85 | @State var isScreenSharePublishingBusy = false
86 |
87 | @State private var screenPickerPresented = false
88 | @State private var publishOptionsPickerPresented = false
89 |
90 | @State private var cameraPublishOptions = VideoPublishOptions()
91 |
92 | #if os(macOS)
93 | @ObservedObject private var windowAccess = WindowAccess()
94 | #endif
95 |
96 | @State private var showConnectionTime = true
97 | @State private var canSwitchCameraPosition = false
98 |
99 | func messageView(_ message: ExampleRoomMessage) -> some View {
100 | let isMe = message.senderSid == room.localParticipant.sid
101 |
102 | return HStack {
103 | if isMe {
104 | Spacer()
105 | }
106 |
107 | // VStack(alignment: isMe ? .trailing : .leading) {
108 | // Text(message.identity)
109 | Text(message.text)
110 | .padding(8)
111 | .background(isMe ? Color.lkRed : Color.lkGray3)
112 | .foregroundColor(Color.white)
113 | .cornerRadius(18)
114 | // }
115 | if !isMe {
116 | Spacer()
117 | }
118 | }.padding(.vertical, 5)
119 | .padding(.horizontal, 10)
120 | }
121 |
122 | func scrollToBottom(_ scrollView: ScrollViewProxy) {
123 | guard let last = roomCtx.messages.last else { return }
124 | withAnimation {
125 | scrollView.scrollTo(last.id)
126 | }
127 | }
128 |
129 | func messagesView(geometry: GeometryProxy) -> some View {
130 | VStack(spacing: 0) {
131 | ScrollViewReader { scrollView in
132 | ScrollView(.vertical, showsIndicators: true) {
133 | LazyVStack(alignment: .center, spacing: 0) {
134 | ForEach(roomCtx.messages) {
135 | messageView($0)
136 | }
137 | }
138 | .padding(.vertical, 12)
139 | .padding(.horizontal, 7)
140 | }
141 | .onAppear(perform: {
142 | // Scroll to bottom when first showing the messages list
143 | scrollToBottom(scrollView)
144 | })
145 | .onChange(of: roomCtx.messages, perform: { _ in
146 | // Scroll to bottom when there is a new message
147 | scrollToBottom(scrollView)
148 | })
149 | .frame(
150 | minWidth: 0,
151 | maxWidth: .infinity,
152 | minHeight: 0,
153 | maxHeight: .infinity,
154 | alignment: .topLeading
155 | )
156 | }
157 | HStack(spacing: 0) {
158 | TextField("Enter message", text: $roomCtx.textFieldString)
159 | .textFieldStyle(PlainTextFieldStyle())
160 | .disableAutocorrection(true)
161 | // TODO: add iOS unique view modifiers
162 | // #if os(iOS)
163 | // .autocapitalization(.none)
164 | // .keyboardType(type.toiOSType())
165 | // #endif
166 |
167 | // .overlay(RoundedRectangle(cornerRadius: 10.0)
168 | // .strokeBorder(Color.white.opacity(0.3),
169 | // style: StrokeStyle(lineWidth: 1.0)))
170 |
171 | Button {
172 | roomCtx.sendMessage()
173 | } label: {
174 | Image(systemSymbol: .paperplaneFill)
175 | .foregroundColor(roomCtx.textFieldString.isEmpty ? nil : Color.lkRed)
176 | }
177 | .buttonStyle(.borderless)
178 | }
179 | .padding()
180 | .background(Color.lkGray2)
181 | }
182 | .background(Color.lkGray1)
183 | .cornerRadius(8)
184 | .frame(
185 | minWidth: 0,
186 | maxWidth: geometry.isTall ? .infinity : 320
187 | )
188 | }
189 |
190 | func sortedParticipants() -> [Participant] {
191 | room.allParticipants.values.sorted { p1, p2 in
192 | if p1 is LocalParticipant { return true }
193 | if p2 is LocalParticipant { return false }
194 | return (p1.joinedAt ?? Date()) < (p2.joinedAt ?? Date())
195 | }
196 | }
197 |
198 | func content(geometry: GeometryProxy) -> some View {
199 | VStack {
200 | if showConnectionTime {
201 | Text("Connected (\([room.serverRegion, room.serverNodeId, "\(String(describing: room.connectStopwatch.total().rounded(to: 2)))s"].compactMap { $0 }.joined(separator: ", ")))")
202 | .multilineTextAlignment(.center)
203 | .foregroundColor(.white)
204 | .padding()
205 | }
206 |
207 | if case .connecting = room.connectionState {
208 | Text("Re-connecting...")
209 | .multilineTextAlignment(.center)
210 | .foregroundColor(.white)
211 | .padding()
212 | }
213 |
214 | HorVStack(axis: geometry.isTall ? .vertical : .horizontal, spacing: 5) {
215 | Group {
216 | if let focusParticipant = roomCtx.focusParticipant {
217 | ZStack(alignment: .bottomTrailing) {
218 | ParticipantView(participant: focusParticipant,
219 | videoViewMode: appCtx.videoViewMode)
220 | { _ in
221 | roomCtx.focusParticipant = nil
222 | }
223 | .overlay(RoundedRectangle(cornerRadius: 5)
224 | .stroke(Color.lkRed.opacity(0.7), lineWidth: 5.0))
225 | Text("SELECTED")
226 | .font(.system(size: 10))
227 | .fontWeight(.bold)
228 | .foregroundColor(Color.white)
229 | .padding(.horizontal, 5)
230 | .padding(.vertical, 2)
231 | .background(Color.lkRed.opacity(0.7))
232 | .cornerRadius(8)
233 | .padding(.vertical, 35)
234 | .padding(.horizontal, 10)
235 | }
236 |
237 | } else {
238 | // Array([room.allParticipants.values, room.allParticipants.values].joined())
239 | ParticipantLayout(sortedParticipants(), spacing: 5) { participant in
240 | ParticipantView(participant: participant,
241 | videoViewMode: appCtx.videoViewMode)
242 | { participant in
243 | roomCtx.focusParticipant = participant
244 | }
245 | }
246 | }
247 | }
248 | .frame(
249 | minWidth: 0,
250 | maxWidth: .infinity,
251 | minHeight: 0,
252 | maxHeight: .infinity
253 | )
254 | // Show messages view if enabled
255 | if roomCtx.showMessagesView {
256 | messagesView(geometry: geometry)
257 | }
258 | }
259 | }
260 | .padding(5)
261 | }
262 |
263 | var body: some View {
264 | GeometryReader { geometry in
265 | content(geometry: geometry)
266 | }
267 | .toolbar {
268 | ToolbarItemGroup(placement: toolbarPlacement) {
269 | // Insufficient space on iOS bar
270 | #if os(macOS)
271 | Group {
272 | if let name = room.name {
273 | Text(name)
274 | .fontWeight(.bold)
275 | }
276 |
277 | if let identity = room.localParticipant.identity {
278 | Text(String(describing: identity))
279 | }
280 |
281 | Spacer()
282 |
283 | Picker("Mode", selection: $appCtx.videoViewMode) {
284 | Text("Fit").tag(VideoView.LayoutMode.fit)
285 | Text("Fill").tag(VideoView.LayoutMode.fill)
286 | }
287 | .pickerStyle(SegmentedPickerStyle())
288 | }
289 | #else
290 | Spacer()
291 | #endif
292 |
293 | Group {
294 | let isCameraEnabled = room.localParticipant.isCameraEnabled()
295 | let isMicrophoneEnabled = room.localParticipant.isMicrophoneEnabled()
296 | let isScreenShareEnabled = room.localParticipant.isScreenShareEnabled()
297 |
298 | Group {
299 | if isCameraEnabled, canSwitchCameraPosition {
300 | Menu {
301 | Button("Switch position") {
302 | Task {
303 | isCameraPublishingBusy = true
304 | defer { Task { @MainActor in isCameraPublishingBusy = false } }
305 | if let track = room.localParticipant.firstCameraVideoTrack as? LocalVideoTrack,
306 | let cameraCapturer = track.capturer as? CameraCapturer
307 | {
308 | try await cameraCapturer.switchCameraPosition()
309 | }
310 | }
311 | }
312 |
313 | Button("Disable") {
314 | Task {
315 | isCameraPublishingBusy = true
316 | defer { Task { @MainActor in isCameraPublishingBusy = false } }
317 | try await room.localParticipant.setCamera(enabled: !isCameraEnabled)
318 | }
319 | }
320 | } label: {
321 | Image(systemSymbol: .videoFill)
322 | .renderingMode(.original)
323 | }
324 | // disable while publishing/un-publishing
325 | .disabled(isCameraPublishingBusy)
326 | } else {
327 | // Toggle camera enabled
328 | Button(action: {
329 | if isCameraEnabled {
330 | Task {
331 | isCameraPublishingBusy = true
332 | defer { Task { @MainActor in isCameraPublishingBusy = false } }
333 | try await room.localParticipant.setCamera(enabled: false)
334 | }
335 | } else {
336 | publishOptionsPickerPresented = true
337 | }
338 | },
339 | label: {
340 | Image(systemSymbol: .videoFill)
341 | .renderingMode(isCameraEnabled ? .original : .template)
342 | })
343 | // disable while publishing/un-publishing
344 | .disabled(isCameraPublishingBusy)
345 | }
346 | }.popover(isPresented: $publishOptionsPickerPresented) {
347 | PublishOptionsView(publishOptions: cameraPublishOptions) { captureOptions, publishOptions in
348 | publishOptionsPickerPresented = false
349 | isCameraPublishingBusy = true
350 | cameraPublishOptions = publishOptions
351 | Task {
352 | defer { Task { @MainActor in isCameraPublishingBusy = false } }
353 | try await room.localParticipant.setCamera(enabled: true,
354 | captureOptions: captureOptions,
355 | publishOptions: publishOptions)
356 | }
357 | }
358 | .padding()
359 | }
360 |
361 | // Toggle microphone enabled
362 | Button(action: {
363 | Task {
364 | isMicrophonePublishingBusy = true
365 | defer { Task { @MainActor in isMicrophonePublishingBusy = false } }
366 | try await room.localParticipant.setMicrophone(enabled: !isMicrophoneEnabled)
367 | }
368 | },
369 | label: {
370 | Image(systemSymbol: .micFill)
371 | .renderingMode(isMicrophoneEnabled ? .original : .template)
372 | })
373 | // disable while publishing/un-publishing
374 | .disabled(isMicrophonePublishingBusy)
375 |
376 | #if os(iOS)
377 | Button(action: {
378 | Task {
379 | isScreenSharePublishingBusy = true
380 | defer { Task { @MainActor in isScreenSharePublishingBusy = false } }
381 | try await room.localParticipant.setScreenShare(enabled: !isScreenShareEnabled)
382 | }
383 | },
384 | label: {
385 | Image(systemSymbol: .rectangleFillOnRectangleFill)
386 | .renderingMode(isScreenShareEnabled ? .original : .template)
387 | })
388 | // disable while publishing/un-publishing
389 | .disabled(isScreenSharePublishingBusy)
390 | #elseif os(macOS)
391 | Button(action: {
392 | if #available(macOS 12.3, *) {
393 | if isScreenShareEnabled {
394 | // Turn off screen share
395 | Task {
396 | isScreenSharePublishingBusy = true
397 | defer { Task { @MainActor in isScreenSharePublishingBusy = false } }
398 | try await roomCtx.setScreenShareMacOS(isEnabled: false)
399 | }
400 | } else {
401 | screenPickerPresented = true
402 | }
403 | }
404 | },
405 | label: {
406 | Image(systemSymbol: .rectangleFillOnRectangleFill)
407 | .renderingMode(isScreenShareEnabled ? .original : .template)
408 | .foregroundColor(isScreenShareEnabled ? Color.green : Color.white)
409 | }).popover(isPresented: $screenPickerPresented) {
410 | if #available(macOS 12.3, *) {
411 | ScreenShareSourcePickerView { source in
412 | Task {
413 | isScreenSharePublishingBusy = true
414 | defer { Task { @MainActor in isScreenSharePublishingBusy = false } }
415 | try await roomCtx.setScreenShareMacOS(isEnabled: true, screenShareSource: source)
416 | }
417 | screenPickerPresented = false
418 | }.padding()
419 | }
420 | }
421 | .disabled(isScreenSharePublishingBusy)
422 | #endif
423 |
424 | // Toggle messages view (chat example)
425 | Button(action: {
426 | withAnimation {
427 | roomCtx.showMessagesView.toggle()
428 | }
429 | },
430 | label: {
431 | Image(systemSymbol: .messageFill)
432 | .renderingMode(roomCtx.showMessagesView ? .original : .template)
433 | })
434 | }
435 |
436 | // Spacer()
437 |
438 | #if os(iOS)
439 | SwiftUIAudioRoutePickerButton()
440 | #endif
441 |
442 | Menu {
443 | #if os(macOS)
444 | Button {
445 | if let url = URL(string: "livekit://") {
446 | NSWorkspace.shared.open(url)
447 | }
448 | } label: {
449 | Text("New window")
450 | }
451 |
452 | Divider()
453 |
454 | #endif
455 |
456 | Toggle("Show info overlay", isOn: $appCtx.showInformationOverlay)
457 |
458 | Group {
459 | Toggle("VideoView visible", isOn: $appCtx.videoViewVisible)
460 | Toggle("VideoView flip", isOn: $appCtx.videoViewMirrored)
461 | Toggle("VideoView renderMode: .sampleBuffer", isOn: $appCtx.preferSampleBufferRendering)
462 | #if os(iOS)
463 | Menu("Pinch to zoom") {
464 | Toggle("Zoom In", isOn: $appCtx.videoViewPinchToZoomOptions.bind(.zoomIn))
465 | Toggle("Zoom Out", isOn: $appCtx.videoViewPinchToZoomOptions.bind(.zoomOut))
466 | Toggle("Auto Reset", isOn: $appCtx.videoViewPinchToZoomOptions.bind(.resetOnRelease))
467 | }
468 | #endif
469 | Divider()
470 | }
471 |
472 | #if os(macOS)
473 |
474 | Group {
475 | Picker("Output device", selection: $appCtx.outputDevice) {
476 | ForEach(AudioManager.shared.outputDevices) { device in
477 | Text(device.isDefault ? "Default" : "\(device.name)").tag(device)
478 | }
479 | }
480 | Picker("Input device", selection: $appCtx.inputDevice) {
481 | ForEach(AudioManager.shared.inputDevices) { device in
482 | Text(device.isDefault ? "Default" : "\(device.name)").tag(device)
483 | }
484 | }
485 | }
486 | #endif
487 |
488 | Group {
489 | Divider()
490 |
491 | Button {
492 | Task {
493 | await room.localParticipant.unpublishAll()
494 | }
495 | } label: {
496 | Text("Unpublish all")
497 | }
498 |
499 | Divider()
500 |
501 | Menu {
502 | Button {
503 | Task {
504 | try await room.debug_simulate(scenario: .quickReconnect)
505 | }
506 | } label: {
507 | Text("Quick reconnect")
508 | }
509 |
510 | Button {
511 | Task {
512 | try await room.debug_simulate(scenario: .fullReconnect)
513 | }
514 | } label: {
515 | Text("Full reconnect")
516 | }
517 |
518 | Button {
519 | Task {
520 | try await room.debug_simulate(scenario: .nodeFailure)
521 | }
522 | } label: {
523 | Text("Node failure")
524 | }
525 |
526 | Button {
527 | Task {
528 | try await room.debug_simulate(scenario: .serverLeave)
529 | }
530 | } label: {
531 | Text("Server leave")
532 | }
533 |
534 | Button {
535 | Task {
536 | try await room.debug_simulate(scenario: .migration)
537 | }
538 | } label: {
539 | Text("Migration")
540 | }
541 |
542 | Button {
543 | Task {
544 | try await room.debug_simulate(scenario: .speakerUpdate(seconds: 3))
545 | }
546 | } label: {
547 | Text("Speaker update")
548 | }
549 | Button {
550 | Task {
551 | try await room.debug_simulate(scenario: .forceTCP)
552 | }
553 | } label: {
554 | Text("Force TCP")
555 | }
556 | Button {
557 | Task {
558 | try await room.debug_simulate(scenario: .forceTLS)
559 | }
560 | } label: {
561 | Text("Force TLS")
562 | }
563 | } label: {
564 | Text("Simulate scenario")
565 | }
566 | }
567 |
568 | Group {
569 | Menu {
570 | Button {
571 | Task {
572 | try await room.localParticipant.setTrackSubscriptionPermissions(allParticipantsAllowed: true)
573 | }
574 | } label: {
575 | Text("Allow all")
576 | }
577 |
578 | Button {
579 | Task {
580 | try await room.localParticipant.setTrackSubscriptionPermissions(allParticipantsAllowed: false)
581 | }
582 | } label: {
583 | Text("Disallow all")
584 | }
585 | } label: {
586 | Text("Track permissions")
587 | }
588 |
589 | Toggle("Prefer speaker output", isOn: $appCtx.preferSpeakerOutput)
590 |
591 | Toggle("E2EE enabled", isOn: $roomCtx.isE2eeEnabled)
592 | }
593 |
594 | } label: {
595 | Image(systemSymbol: .gear)
596 | .renderingMode(.original)
597 | }
598 |
599 | // Disconnect
600 | Button(action: {
601 | Task {
602 | await roomCtx.disconnect()
603 | }
604 | },
605 | label: {
606 | Image(systemSymbol: .xmarkCircleFill)
607 | .renderingMode(.original)
608 | })
609 | }
610 | }
611 | // #if os(macOS)
612 | // .withHostingWindow { self.windowAccess.set(window: $0) }
613 | // #endif
614 | .onAppear {
615 | Task { @MainActor in
616 | canSwitchCameraPosition = try await CameraCapturer.canSwitchPosition()
617 | }
618 | Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in
619 | Task { @MainActor in
620 | withAnimation {
621 | showConnectionTime = false
622 | }
623 | }
624 | }
625 | }
626 | }
627 | }
628 |
629 | struct ParticipantLayout: View {
630 | let views: [AnyView]
631 | let spacing: CGFloat
632 |
633 | init(
634 | _ data: Data,
635 | id: KeyPath = \.self,
636 | spacing: CGFloat,
637 | @ViewBuilder content: @escaping (Data.Element) -> Content
638 | ) {
639 | self.spacing = spacing
640 | views = data.map { AnyView(content($0[keyPath: id])) }
641 | }
642 |
643 | func computeColumn(with geometry: GeometryProxy) -> (x: Int, y: Int) {
644 | let sqr = Double(views.count).squareRoot()
645 | let r: [Int] = [Int(sqr.rounded()), Int(sqr.rounded(.up))]
646 | let c = geometry.isTall ? r : r.reversed()
647 | return (x: c[0], y: c[1])
648 | }
649 |
650 | func grid(axis: Axis, geometry: GeometryProxy) -> some View {
651 | ScrollView([axis == .vertical ? .vertical : .horizontal]) {
652 | HorVGrid(axis: axis, columns: [GridItem(.flexible())], spacing: spacing) {
653 | ForEach(0 ..< views.count, id: \.self) { i in
654 | views[i]
655 | .aspectRatio(1, contentMode: .fill)
656 | }
657 | }
658 | .padding(axis == .horizontal ? [.leading, .trailing] : [.top, .bottom],
659 | max(0, ((axis == .horizontal ? geometry.size.width : geometry.size.height)
660 | - ((axis == .horizontal ? geometry.size.height : geometry.size.width) * CGFloat(views.count)) - (spacing * CGFloat(views.count - 1))) / 2))
661 | }
662 | }
663 |
664 | var body: some View {
665 | GeometryReader { geometry in
666 | if views.isEmpty {
667 | EmptyView()
668 | } else if geometry.size.width <= 300 {
669 | grid(axis: .vertical, geometry: geometry)
670 | } else if geometry.size.height <= 300 {
671 | grid(axis: .horizontal, geometry: geometry)
672 | } else {
673 | let verticalWhenTall: Axis = geometry.isTall ? .vertical : .horizontal
674 | let horizontalWhenTall: Axis = geometry.isTall ? .horizontal : .vertical
675 |
676 | switch views.count {
677 | // simply return first view
678 | case 1: views[0]
679 | case 3: HorVStack(axis: verticalWhenTall, spacing: spacing) {
680 | views[0]
681 | HorVStack(axis: horizontalWhenTall, spacing: spacing) {
682 | views[1]
683 | views[2]
684 | }
685 | }
686 | case 5: HorVStack(axis: verticalWhenTall, spacing: spacing) {
687 | views[0]
688 | if geometry.isTall {
689 | HStack(spacing: spacing) {
690 | views[1]
691 | views[2]
692 | }
693 | HStack(spacing: spacing) {
694 | views[3]
695 | views[4]
696 | }
697 | } else {
698 | VStack(spacing: spacing) {
699 | views[1]
700 | views[3]
701 | }
702 | VStack(spacing: spacing) {
703 | views[2]
704 | views[4]
705 | }
706 | }
707 | }
708 | // case 6:
709 | // if geometry.isTall {
710 | // VStack {
711 | // HStack {
712 | // views[0]
713 | // views[1]
714 | // }
715 | // HStack {
716 | // views[2]
717 | // views[3]
718 | // }
719 | // HStack {
720 | // views[4]
721 | // views[5]
722 | // }
723 | // }
724 | // } else {
725 | // VStack {
726 | // HStack {
727 | // views[0]
728 | // views[1]
729 | // views[2]
730 | // }
731 | // HStack {
732 | // views[3]
733 | // views[4]
734 | // views[5]
735 | // }
736 | // }
737 | // }
738 | default:
739 | let c = computeColumn(with: geometry)
740 | VStack(spacing: spacing) {
741 | ForEach(0 ... (c.y - 1), id: \.self) { y in
742 | HStack(spacing: spacing) {
743 | ForEach(0 ... (c.x - 1), id: \.self) { x in
744 | let index = (y * c.x) + x
745 | if index < views.count {
746 | views[index]
747 | }
748 | }
749 | }
750 | }
751 | }
752 | }
753 | }
754 | }
755 | }
756 | }
757 |
758 | struct HorVStack: View {
759 | let axis: Axis
760 | let horizontalAlignment: HorizontalAlignment
761 | let verticalAlignment: VerticalAlignment
762 | let spacing: CGFloat?
763 | let content: () -> Content
764 |
765 | init(axis: Axis = .horizontal,
766 | horizontalAlignment: HorizontalAlignment = .center,
767 | verticalAlignment: VerticalAlignment = .center,
768 | spacing: CGFloat? = nil,
769 | @ViewBuilder content: @escaping () -> Content)
770 | {
771 | self.axis = axis
772 | self.horizontalAlignment = horizontalAlignment
773 | self.verticalAlignment = verticalAlignment
774 | self.spacing = spacing
775 | self.content = content
776 | }
777 |
778 | var body: some View {
779 | Group {
780 | if axis == .vertical {
781 | VStack(alignment: horizontalAlignment, spacing: spacing, content: content)
782 | } else {
783 | HStack(alignment: verticalAlignment, spacing: spacing, content: content)
784 | }
785 | }
786 | }
787 | }
788 |
789 | struct HorVGrid: View {
790 | let axis: Axis
791 | let spacing: CGFloat?
792 | let content: () -> Content
793 | let columns: [GridItem]
794 |
795 | init(axis: Axis = .horizontal,
796 | columns: [GridItem],
797 | spacing: CGFloat? = nil,
798 | @ViewBuilder content: @escaping () -> Content)
799 | {
800 | self.axis = axis
801 | self.spacing = spacing
802 | self.columns = columns
803 | self.content = content
804 | }
805 |
806 | var body: some View {
807 | Group {
808 | if axis == .vertical {
809 | LazyVGrid(columns: columns, spacing: spacing, content: content)
810 | } else {
811 | LazyHGrid(rows: columns, spacing: spacing, content: content)
812 | }
813 | }
814 | }
815 | }
816 |
817 | extension GeometryProxy {
818 | public var isTall: Bool {
819 | size.height > size.width
820 | }
821 |
822 | var isWide: Bool {
823 | size.width > size.height
824 | }
825 | }
826 |
--------------------------------------------------------------------------------
/LiveKitExample-main.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 680FE2F227A8EF7700B6F6DB /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = 680FE2F127A8EF7700B6F6DB /* SFSafeSymbols */; };
11 | 680FE2F427A8EFF700B6F6DB /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = 680FE2F327A8EFF700B6F6DB /* SFSafeSymbols */; };
12 | 6816968E2AF96240008ED486 /* Participant+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6816968D2AF96240008ED486 /* Participant+Helpers.swift */; };
13 | 6816968F2AF96240008ED486 /* Participant+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6816968D2AF96240008ED486 /* Participant+Helpers.swift */; };
14 | 6816B1A8272D45DF005ADB85 /* ExampleRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6816B1A7272D45DF005ADB85 /* ExampleRoomMessage.swift */; };
15 | 6816B1A9272D45DF005ADB85 /* ExampleRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6816B1A7272D45DF005ADB85 /* ExampleRoomMessage.swift */; };
16 | 6816B1B0272D9198005ADB85 /* ParticipantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6816B1AF272D9198005ADB85 /* ParticipantView.swift */; };
17 | 6816B1B1272D9198005ADB85 /* ParticipantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6816B1AF272D9198005ADB85 /* ParticipantView.swift */; };
18 | 681A0AB727D888D80097E3F4 /* LiveKit in Frameworks */ = {isa = PBXBuildFile; productRef = 681A0AB627D888D80097E3F4 /* LiveKit */; };
19 | 681A0AB827D88B190097E3F4 /* ReplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 683F05F4273F96B20080C7AC /* ReplayKit.framework */; platformFilter = maccatalyst; };
20 | 681E3F39271FC772007BB547 /* RoomContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F38271FC772007BB547 /* RoomContext.swift */; };
21 | 681E3F3A271FC772007BB547 /* RoomContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F38271FC772007BB547 /* RoomContext.swift */; };
22 | 681E3F3F271FC795007BB547 /* Custom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F3E271FC795007BB547 /* Custom.swift */; };
23 | 681E3F40271FC795007BB547 /* Custom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F3E271FC795007BB547 /* Custom.swift */; };
24 | 681E3F43271FC7AD007BB547 /* RoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F41271FC7AC007BB547 /* RoomView.swift */; };
25 | 681E3F44271FC7AD007BB547 /* RoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F41271FC7AC007BB547 /* RoomView.swift */; };
26 | 681E3F45271FC7AD007BB547 /* ConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F42271FC7AD007BB547 /* ConnectView.swift */; };
27 | 681E3F46271FC7AD007BB547 /* ConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F42271FC7AD007BB547 /* ConnectView.swift */; };
28 | 683332C227D9DC3800378C41 /* (null) in Frameworks */ = {isa = PBXBuildFile; };
29 | 683720D227A06404007DA986 /* ConnectionHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 683720D127A06404007DA986 /* ConnectionHistory.swift */; };
30 | 683720D327A06404007DA986 /* ConnectionHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 683720D127A06404007DA986 /* ConnectionHistory.swift */; };
31 | 6845DD5627DEBDD90009B5CD /* LiveKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6845DD5527DEBDD90009B5CD /* LiveKit */; };
32 | 6847616427B44A1A001611BE /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6847616327B44A1A001611BE /* Bundle.swift */; };
33 | 6847616527B44A1A001611BE /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6847616327B44A1A001611BE /* Bundle.swift */; };
34 | 6867533B27A65652003707B9 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6867533A27A65652003707B9 /* AppContext.swift */; };
35 | 6867533C27A65652003707B9 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6867533A27A65652003707B9 /* AppContext.swift */; };
36 | 687230F82B14AE0A0098CCE6 /* PublishOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 687230F72B14AE0A0098CCE6 /* PublishOptionsView.swift */; };
37 | 687230F92B14AE0A0098CCE6 /* PublishOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 687230F72B14AE0A0098CCE6 /* PublishOptionsView.swift */; };
38 | 68816CC127B4D6BC00E24622 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 68816CC027B4D6BC00E24622 /* KeychainAccess */; };
39 | 68816CC327B4D94200E24622 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 68816CC227B4D94200E24622 /* KeychainAccess */; };
40 | 68816CC527B4DCD500E24622 /* SecureStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68816CC427B4DCD500E24622 /* SecureStore.swift */; };
41 | 68816CC627B4DCD500E24622 /* SecureStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68816CC427B4DCD500E24622 /* SecureStore.swift */; };
42 | 6884B77C2750507400732D47 /* ScreenShareSourcePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6884B77B2750507400732D47 /* ScreenShareSourcePickerView.swift */; };
43 | 6884B77D2750507400732D47 /* ScreenShareSourcePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6884B77B2750507400732D47 /* ScreenShareSourcePickerView.swift */; };
44 | 688D9319283FE23F003CA647 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 68B3853E271E780700711D5F /* Assets.xcassets */; };
45 | 688D931A283FE244003CA647 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 68B3853E271E780700711D5F /* Assets.xcassets */; };
46 | 68B3854C271E780700711D5F /* LiveKitExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B3853C271E780600711D5F /* LiveKitExample.swift */; };
47 | 68B3854D271E780700711D5F /* LiveKitExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B3853C271E780600711D5F /* LiveKitExample.swift */; };
48 | 68EA18E827F2E91000F9AE48 /* BroadcastExt.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 683F05F3273F96B20080C7AC /* BroadcastExt.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
49 | 68FBA43F2A38B49C0015853E /* LiveKit in Frameworks */ = {isa = PBXBuildFile; productRef = 68FBA43E2A38B49C0015853E /* LiveKit */; };
50 | D7AA477B285A0FFC00EB41AE /* SampleHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AA477A285A0FFC00EB41AE /* SampleHandler.swift */; };
51 | D7FECD462860B42700D04D1C /* LoggingOSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FECD452860B42700D04D1C /* LoggingOSLog.swift */; };
52 | /* End PBXBuildFile section */
53 |
54 | /* Begin PBXContainerItemProxy section */
55 | 68EA18E927F2E91000F9AE48 /* PBXContainerItemProxy */ = {
56 | isa = PBXContainerItemProxy;
57 | containerPortal = 68B38537271E780600711D5F /* Project object */;
58 | proxyType = 1;
59 | remoteGlobalIDString = 683F05F2273F96B20080C7AC;
60 | remoteInfo = BroadcastExt;
61 | };
62 | /* End PBXContainerItemProxy section */
63 |
64 | /* Begin PBXCopyFilesBuildPhase section */
65 | 68EA18EB27F2E91100F9AE48 /* Embed Foundation Extensions */ = {
66 | isa = PBXCopyFilesBuildPhase;
67 | buildActionMask = 2147483647;
68 | dstPath = "";
69 | dstSubfolderSpec = 13;
70 | files = (
71 | 68EA18E827F2E91000F9AE48 /* BroadcastExt.appex in Embed Foundation Extensions */,
72 | );
73 | name = "Embed Foundation Extensions";
74 | runOnlyForDeploymentPostprocessing = 0;
75 | };
76 | /* End PBXCopyFilesBuildPhase section */
77 |
78 | /* Begin PBXFileReference section */
79 | 6816968D2AF96240008ED486 /* Participant+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Participant+Helpers.swift"; sourceTree = ""; };
80 | 6816B1A7272D45DF005ADB85 /* ExampleRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleRoomMessage.swift; sourceTree = ""; };
81 | 6816B1AF272D9198005ADB85 /* ParticipantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantView.swift; sourceTree = ""; };
82 | 681E3F38271FC772007BB547 /* RoomContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomContext.swift; sourceTree = ""; };
83 | 681E3F3E271FC795007BB547 /* Custom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Custom.swift; sourceTree = ""; };
84 | 681E3F41271FC7AC007BB547 /* RoomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomView.swift; sourceTree = ""; };
85 | 681E3F42271FC7AD007BB547 /* ConnectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectView.swift; sourceTree = ""; };
86 | 683720D127A06404007DA986 /* ConnectionHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionHistory.swift; sourceTree = ""; };
87 | 683F05F3273F96B20080C7AC /* BroadcastExt.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = BroadcastExt.appex; sourceTree = BUILT_PRODUCTS_DIR; };
88 | 683F05F4273F96B20080C7AC /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; };
89 | 683F05F9273F96B20080C7AC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
90 | 683F0603273FAD690080C7AC /* iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iOS.entitlements; sourceTree = ""; };
91 | 683F0604273FADC20080C7AC /* BroadcastExt.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BroadcastExt.entitlements; sourceTree = ""; };
92 | 6847616327B44A1A001611BE /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; };
93 | 6865EA2527513B4500FFAFC3 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
94 | 6865EA2D27513B6D00FFAFC3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
95 | 6867533A27A65652003707B9 /* AppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = ""; };
96 | 687230F72B14AE0A0098CCE6 /* PublishOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishOptionsView.swift; sourceTree = ""; };
97 | 68816CC427B4DCD500E24622 /* SecureStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStore.swift; sourceTree = ""; };
98 | 6884B77B2750507400732D47 /* ScreenShareSourcePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenShareSourcePickerView.swift; sourceTree = ""; };
99 | 68B3853C271E780600711D5F /* LiveKitExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveKitExample.swift; sourceTree = ""; };
100 | 68B3853E271E780700711D5F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
101 | 68B38543271E780700711D5F /* LiveKitExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LiveKitExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
102 | 68B38549271E780700711D5F /* LiveKitExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LiveKitExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
103 | 68B3854B271E780700711D5F /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; };
104 | 68DEF27E2919EEFA00258494 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/ReplayKit.framework; sourceTree = DEVELOPER_DIR; };
105 | 9E7835E62751A71500559DEC /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.0.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
106 | D7AA477A285A0FFC00EB41AE /* SampleHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleHandler.swift; sourceTree = ""; };
107 | D7FECD452860B42700D04D1C /* LoggingOSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingOSLog.swift; sourceTree = ""; };
108 | /* End PBXFileReference section */
109 |
110 | /* Begin PBXFrameworksBuildPhase section */
111 | 683F05F0273F96B20080C7AC /* Frameworks */ = {
112 | isa = PBXFrameworksBuildPhase;
113 | buildActionMask = 2147483647;
114 | files = (
115 | 681A0AB827D88B190097E3F4 /* ReplayKit.framework in Frameworks */,
116 | 681A0AB727D888D80097E3F4 /* LiveKit in Frameworks */,
117 | );
118 | runOnlyForDeploymentPostprocessing = 0;
119 | };
120 | 68B38540271E780700711D5F /* Frameworks */ = {
121 | isa = PBXFrameworksBuildPhase;
122 | buildActionMask = 2147483647;
123 | files = (
124 | 68FBA43F2A38B49C0015853E /* LiveKit in Frameworks */,
125 | 680FE2F227A8EF7700B6F6DB /* SFSafeSymbols in Frameworks */,
126 | 68816CC127B4D6BC00E24622 /* KeychainAccess in Frameworks */,
127 | );
128 | runOnlyForDeploymentPostprocessing = 0;
129 | };
130 | 68B38546271E780700711D5F /* Frameworks */ = {
131 | isa = PBXFrameworksBuildPhase;
132 | buildActionMask = 2147483647;
133 | files = (
134 | 683332C227D9DC3800378C41 /* (null) in Frameworks */,
135 | 680FE2F427A8EFF700B6F6DB /* SFSafeSymbols in Frameworks */,
136 | 68816CC327B4D94200E24622 /* KeychainAccess in Frameworks */,
137 | 6845DD5627DEBDD90009B5CD /* LiveKit in Frameworks */,
138 | );
139 | runOnlyForDeploymentPostprocessing = 0;
140 | };
141 | /* End PBXFrameworksBuildPhase section */
142 |
143 | /* Begin PBXGroup section */
144 | 681E3F47271FCB40007BB547 /* Frameworks */ = {
145 | isa = PBXGroup;
146 | children = (
147 | 68DEF27E2919EEFA00258494 /* ReplayKit.framework */,
148 | 9E7835E62751A71500559DEC /* CoreGraphics.framework */,
149 | 683F05F4273F96B20080C7AC /* ReplayKit.framework */,
150 | );
151 | name = Frameworks;
152 | sourceTree = "";
153 | };
154 | 683720D427A0640D007DA986 /* Support */ = {
155 | isa = PBXGroup;
156 | children = (
157 | 6816B1A7272D45DF005ADB85 /* ExampleRoomMessage.swift */,
158 | 683720D127A06404007DA986 /* ConnectionHistory.swift */,
159 | 6847616327B44A1A001611BE /* Bundle.swift */,
160 | 68816CC427B4DCD500E24622 /* SecureStore.swift */,
161 | 6816968D2AF96240008ED486 /* Participant+Helpers.swift */,
162 | );
163 | path = Support;
164 | sourceTree = "";
165 | };
166 | 683F05F6273F96B20080C7AC /* BroadcastExt */ = {
167 | isa = PBXGroup;
168 | children = (
169 | 683F0604273FADC20080C7AC /* BroadcastExt.entitlements */,
170 | 683F05F9273F96B20080C7AC /* Info.plist */,
171 | D7AA477A285A0FFC00EB41AE /* SampleHandler.swift */,
172 | D7FECD452860B42700D04D1C /* LoggingOSLog.swift */,
173 | );
174 | path = BroadcastExt;
175 | sourceTree = "";
176 | };
177 | 685271EA27443907006B4D6A /* Controllers */ = {
178 | isa = PBXGroup;
179 | children = (
180 | 681E3F38271FC772007BB547 /* RoomContext.swift */,
181 | 6867533A27A65652003707B9 /* AppContext.swift */,
182 | );
183 | path = Controllers;
184 | sourceTree = "";
185 | };
186 | 6865EA2427513B4500FFAFC3 /* iOS */ = {
187 | isa = PBXGroup;
188 | children = (
189 | 6865EA2527513B4500FFAFC3 /* Info.plist */,
190 | 683F05F6273F96B20080C7AC /* BroadcastExt */,
191 | 683F0603273FAD690080C7AC /* iOS.entitlements */,
192 | );
193 | path = iOS;
194 | sourceTree = "";
195 | };
196 | 6884B77A2750505B00732D47 /* Views */ = {
197 | isa = PBXGroup;
198 | children = (
199 | 6884B77B2750507400732D47 /* ScreenShareSourcePickerView.swift */,
200 | 687230F72B14AE0A0098CCE6 /* PublishOptionsView.swift */,
201 | );
202 | path = Views;
203 | sourceTree = "";
204 | };
205 | 68B38536271E780600711D5F = {
206 | isa = PBXGroup;
207 | children = (
208 | 68B3853B271E780600711D5F /* Shared */,
209 | 6865EA2427513B4500FFAFC3 /* iOS */,
210 | 68B3854A271E780700711D5F /* macOS */,
211 | 68B38544271E780700711D5F /* Products */,
212 | 681E3F47271FCB40007BB547 /* Frameworks */,
213 | );
214 | sourceTree = "";
215 | };
216 | 68B3853B271E780600711D5F /* Shared */ = {
217 | isa = PBXGroup;
218 | children = (
219 | 683720D427A0640D007DA986 /* Support */,
220 | 6884B77A2750505B00732D47 /* Views */,
221 | 685271EA27443907006B4D6A /* Controllers */,
222 | 681E3F3E271FC795007BB547 /* Custom.swift */,
223 | 681E3F42271FC7AD007BB547 /* ConnectView.swift */,
224 | 681E3F41271FC7AC007BB547 /* RoomView.swift */,
225 | 68B3853C271E780600711D5F /* LiveKitExample.swift */,
226 | 6816B1AF272D9198005ADB85 /* ParticipantView.swift */,
227 | 68B3853E271E780700711D5F /* Assets.xcassets */,
228 | );
229 | path = Shared;
230 | sourceTree = "";
231 | };
232 | 68B38544271E780700711D5F /* Products */ = {
233 | isa = PBXGroup;
234 | children = (
235 | 68B38543271E780700711D5F /* LiveKitExample.app */,
236 | 68B38549271E780700711D5F /* LiveKitExample.app */,
237 | 683F05F3273F96B20080C7AC /* BroadcastExt.appex */,
238 | );
239 | name = Products;
240 | sourceTree = "";
241 | };
242 | 68B3854A271E780700711D5F /* macOS */ = {
243 | isa = PBXGroup;
244 | children = (
245 | 6865EA2D27513B6D00FFAFC3 /* Info.plist */,
246 | 68B3854B271E780700711D5F /* macOS.entitlements */,
247 | );
248 | path = macOS;
249 | sourceTree = "";
250 | };
251 | /* End PBXGroup section */
252 |
253 | /* Begin PBXNativeTarget section */
254 | 683F05F2273F96B20080C7AC /* BroadcastExt */ = {
255 | isa = PBXNativeTarget;
256 | buildConfigurationList = 683F05FD273F96B20080C7AC /* Build configuration list for PBXNativeTarget "BroadcastExt" */;
257 | buildPhases = (
258 | 683F05EF273F96B20080C7AC /* Sources */,
259 | 683F05F0273F96B20080C7AC /* Frameworks */,
260 | 683F05F1273F96B20080C7AC /* Resources */,
261 | );
262 | buildRules = (
263 | );
264 | dependencies = (
265 | );
266 | name = BroadcastExt;
267 | packageProductDependencies = (
268 | 681A0AB627D888D80097E3F4 /* LiveKit */,
269 | );
270 | productName = BroadcastExt;
271 | productReference = 683F05F3273F96B20080C7AC /* BroadcastExt.appex */;
272 | productType = "com.apple.product-type.app-extension";
273 | };
274 | 68B38542271E780700711D5F /* LiveKitExample (iOS) */ = {
275 | isa = PBXNativeTarget;
276 | buildConfigurationList = 68B38554271E780700711D5F /* Build configuration list for PBXNativeTarget "LiveKitExample (iOS)" */;
277 | buildPhases = (
278 | 68B3853F271E780700711D5F /* Sources */,
279 | 68B38540271E780700711D5F /* Frameworks */,
280 | 68B38541271E780700711D5F /* Resources */,
281 | 68EA18EB27F2E91100F9AE48 /* Embed Foundation Extensions */,
282 | );
283 | buildRules = (
284 | );
285 | dependencies = (
286 | 68EA18EA27F2E91000F9AE48 /* PBXTargetDependency */,
287 | );
288 | name = "LiveKitExample (iOS)";
289 | packageProductDependencies = (
290 | 680FE2F127A8EF7700B6F6DB /* SFSafeSymbols */,
291 | 68816CC027B4D6BC00E24622 /* KeychainAccess */,
292 | 68FBA43E2A38B49C0015853E /* LiveKit */,
293 | );
294 | productName = "Multiplatform-SwiftUI (iOS)";
295 | productReference = 68B38543271E780700711D5F /* LiveKitExample.app */;
296 | productType = "com.apple.product-type.application";
297 | };
298 | 68B38548271E780700711D5F /* LiveKitExample (macOS) */ = {
299 | isa = PBXNativeTarget;
300 | buildConfigurationList = 68B38557271E780700711D5F /* Build configuration list for PBXNativeTarget "LiveKitExample (macOS)" */;
301 | buildPhases = (
302 | 68B38545271E780700711D5F /* Sources */,
303 | 68B38546271E780700711D5F /* Frameworks */,
304 | 68B38547271E780700711D5F /* Resources */,
305 | );
306 | buildRules = (
307 | );
308 | dependencies = (
309 | );
310 | name = "LiveKitExample (macOS)";
311 | packageProductDependencies = (
312 | 680FE2F327A8EFF700B6F6DB /* SFSafeSymbols */,
313 | 68816CC227B4D94200E24622 /* KeychainAccess */,
314 | 6845DD5527DEBDD90009B5CD /* LiveKit */,
315 | );
316 | productName = "Multiplatform-SwiftUI (macOS)";
317 | productReference = 68B38549271E780700711D5F /* LiveKitExample.app */;
318 | productType = "com.apple.product-type.application";
319 | };
320 | /* End PBXNativeTarget section */
321 |
322 | /* Begin PBXProject section */
323 | 68B38537271E780600711D5F /* Project object */ = {
324 | isa = PBXProject;
325 | attributes = {
326 | BuildIndependentTargetsInParallel = 1;
327 | LastSwiftUpdateCheck = 1330;
328 | LastUpgradeCheck = 1520;
329 | TargetAttributes = {
330 | 683F05F2273F96B20080C7AC = {
331 | CreatedOnToolsVersion = 13.1;
332 | };
333 | 68B38542271E780700711D5F = {
334 | CreatedOnToolsVersion = 13.0;
335 | };
336 | 68B38548271E780700711D5F = {
337 | CreatedOnToolsVersion = 13.0;
338 | };
339 | };
340 | };
341 | buildConfigurationList = 68B3853A271E780600711D5F /* Build configuration list for PBXProject "LiveKitExample-main" */;
342 | compatibilityVersion = "Xcode 12.0";
343 | developmentRegion = en;
344 | hasScannedForEncodings = 0;
345 | knownRegions = (
346 | en,
347 | Base,
348 | );
349 | mainGroup = 68B38536271E780600711D5F;
350 | packageReferences = (
351 | 685271E727407BBC006B4D6A /* XCRemoteSwiftPackageReference "swift-protobuf" */,
352 | 680FE2F027A8EF7700B6F6DB /* XCRemoteSwiftPackageReference "SFSafeSymbols" */,
353 | 68816CBF27B4D6BC00E24622 /* XCRemoteSwiftPackageReference "KeychainAccess" */,
354 | 68FBA43D2A38B49C0015853E /* XCRemoteSwiftPackageReference "client-sdk-swift" */,
355 | );
356 | productRefGroup = 68B38544271E780700711D5F /* Products */;
357 | projectDirPath = "";
358 | projectRoot = "";
359 | targets = (
360 | 68B38542271E780700711D5F /* LiveKitExample (iOS) */,
361 | 68B38548271E780700711D5F /* LiveKitExample (macOS) */,
362 | 683F05F2273F96B20080C7AC /* BroadcastExt */,
363 | );
364 | };
365 | /* End PBXProject section */
366 |
367 | /* Begin PBXResourcesBuildPhase section */
368 | 683F05F1273F96B20080C7AC /* Resources */ = {
369 | isa = PBXResourcesBuildPhase;
370 | buildActionMask = 2147483647;
371 | files = (
372 | );
373 | runOnlyForDeploymentPostprocessing = 0;
374 | };
375 | 68B38541271E780700711D5F /* Resources */ = {
376 | isa = PBXResourcesBuildPhase;
377 | buildActionMask = 2147483647;
378 | files = (
379 | 688D931A283FE244003CA647 /* Assets.xcassets in Resources */,
380 | );
381 | runOnlyForDeploymentPostprocessing = 0;
382 | };
383 | 68B38547271E780700711D5F /* Resources */ = {
384 | isa = PBXResourcesBuildPhase;
385 | buildActionMask = 2147483647;
386 | files = (
387 | 688D9319283FE23F003CA647 /* Assets.xcassets in Resources */,
388 | );
389 | runOnlyForDeploymentPostprocessing = 0;
390 | };
391 | /* End PBXResourcesBuildPhase section */
392 |
393 | /* Begin PBXSourcesBuildPhase section */
394 | 683F05EF273F96B20080C7AC /* Sources */ = {
395 | isa = PBXSourcesBuildPhase;
396 | buildActionMask = 2147483647;
397 | files = (
398 | D7FECD462860B42700D04D1C /* LoggingOSLog.swift in Sources */,
399 | D7AA477B285A0FFC00EB41AE /* SampleHandler.swift in Sources */,
400 | );
401 | runOnlyForDeploymentPostprocessing = 0;
402 | };
403 | 68B3853F271E780700711D5F /* Sources */ = {
404 | isa = PBXSourcesBuildPhase;
405 | buildActionMask = 2147483647;
406 | files = (
407 | 681E3F45271FC7AD007BB547 /* ConnectView.swift in Sources */,
408 | 6867533B27A65652003707B9 /* AppContext.swift in Sources */,
409 | 681E3F39271FC772007BB547 /* RoomContext.swift in Sources */,
410 | 683720D227A06404007DA986 /* ConnectionHistory.swift in Sources */,
411 | 681E3F43271FC7AD007BB547 /* RoomView.swift in Sources */,
412 | 6816B1B0272D9198005ADB85 /* ParticipantView.swift in Sources */,
413 | 68816CC527B4DCD500E24622 /* SecureStore.swift in Sources */,
414 | 6816968E2AF96240008ED486 /* Participant+Helpers.swift in Sources */,
415 | 68B3854C271E780700711D5F /* LiveKitExample.swift in Sources */,
416 | 681E3F3F271FC795007BB547 /* Custom.swift in Sources */,
417 | 6847616427B44A1A001611BE /* Bundle.swift in Sources */,
418 | 687230F82B14AE0A0098CCE6 /* PublishOptionsView.swift in Sources */,
419 | 6816B1A8272D45DF005ADB85 /* ExampleRoomMessage.swift in Sources */,
420 | 6884B77C2750507400732D47 /* ScreenShareSourcePickerView.swift in Sources */,
421 | );
422 | runOnlyForDeploymentPostprocessing = 0;
423 | };
424 | 68B38545271E780700711D5F /* Sources */ = {
425 | isa = PBXSourcesBuildPhase;
426 | buildActionMask = 2147483647;
427 | files = (
428 | 681E3F46271FC7AD007BB547 /* ConnectView.swift in Sources */,
429 | 6867533C27A65652003707B9 /* AppContext.swift in Sources */,
430 | 681E3F3A271FC772007BB547 /* RoomContext.swift in Sources */,
431 | 683720D327A06404007DA986 /* ConnectionHistory.swift in Sources */,
432 | 681E3F44271FC7AD007BB547 /* RoomView.swift in Sources */,
433 | 6816B1B1272D9198005ADB85 /* ParticipantView.swift in Sources */,
434 | 68816CC627B4DCD500E24622 /* SecureStore.swift in Sources */,
435 | 6816968F2AF96240008ED486 /* Participant+Helpers.swift in Sources */,
436 | 68B3854D271E780700711D5F /* LiveKitExample.swift in Sources */,
437 | 681E3F40271FC795007BB547 /* Custom.swift in Sources */,
438 | 6847616527B44A1A001611BE /* Bundle.swift in Sources */,
439 | 687230F92B14AE0A0098CCE6 /* PublishOptionsView.swift in Sources */,
440 | 6816B1A9272D45DF005ADB85 /* ExampleRoomMessage.swift in Sources */,
441 | 6884B77D2750507400732D47 /* ScreenShareSourcePickerView.swift in Sources */,
442 | );
443 | runOnlyForDeploymentPostprocessing = 0;
444 | };
445 | /* End PBXSourcesBuildPhase section */
446 |
447 | /* Begin PBXTargetDependency section */
448 | 68EA18EA27F2E91000F9AE48 /* PBXTargetDependency */ = {
449 | isa = PBXTargetDependency;
450 | target = 683F05F2273F96B20080C7AC /* BroadcastExt */;
451 | targetProxy = 68EA18E927F2E91000F9AE48 /* PBXContainerItemProxy */;
452 | };
453 | /* End PBXTargetDependency section */
454 |
455 | /* Begin XCBuildConfiguration section */
456 | 683F05FE273F96B20080C7AC /* Debug */ = {
457 | isa = XCBuildConfiguration;
458 | buildSettings = {
459 | CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/iOS/BroadcastExt/BroadcastExt.entitlements";
460 | CODE_SIGN_STYLE = Automatic;
461 | CURRENT_PROJECT_VERSION = 24;
462 | DEVELOPMENT_TEAM = 76TVFCUKK7;
463 | INFOPLIST_FILE = "$(SRCROOT)/iOS/BroadcastExt/Info.plist";
464 | INFOPLIST_KEY_CFBundleDisplayName = BroadcastExt;
465 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
466 | LD_RUNPATH_SEARCH_PATHS = (
467 | "$(inherited)",
468 | "@executable_path/Frameworks",
469 | "@executable_path/../../Frameworks",
470 | );
471 | MARKETING_VERSION = 1.0.24;
472 | PRODUCT_BUNDLE_IDENTIFIER = io.livekit.example.SwiftSDK.1.BroadcastExt;
473 | PRODUCT_NAME = "$(TARGET_NAME)";
474 | SDKROOT = iphoneos;
475 | SKIP_INSTALL = YES;
476 | SWIFT_EMIT_LOC_STRINGS = YES;
477 | TARGETED_DEVICE_FAMILY = "1,2";
478 | };
479 | name = Debug;
480 | };
481 | 683F05FF273F96B20080C7AC /* Release */ = {
482 | isa = XCBuildConfiguration;
483 | buildSettings = {
484 | CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/iOS/BroadcastExt/BroadcastExt.entitlements";
485 | CODE_SIGN_STYLE = Automatic;
486 | CURRENT_PROJECT_VERSION = 24;
487 | DEVELOPMENT_TEAM = 76TVFCUKK7;
488 | INFOPLIST_FILE = "$(SRCROOT)/iOS/BroadcastExt/Info.plist";
489 | INFOPLIST_KEY_CFBundleDisplayName = BroadcastExt;
490 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
491 | LD_RUNPATH_SEARCH_PATHS = (
492 | "$(inherited)",
493 | "@executable_path/Frameworks",
494 | "@executable_path/../../Frameworks",
495 | );
496 | MARKETING_VERSION = 1.0.24;
497 | PRODUCT_BUNDLE_IDENTIFIER = io.livekit.example.SwiftSDK.1.BroadcastExt;
498 | PRODUCT_NAME = "$(TARGET_NAME)";
499 | SDKROOT = iphoneos;
500 | SKIP_INSTALL = YES;
501 | SWIFT_EMIT_LOC_STRINGS = YES;
502 | TARGETED_DEVICE_FAMILY = "1,2";
503 | VALIDATE_PRODUCT = YES;
504 | };
505 | name = Release;
506 | };
507 | 68B38552271E780700711D5F /* Debug */ = {
508 | isa = XCBuildConfiguration;
509 | buildSettings = {
510 | ALWAYS_SEARCH_USER_PATHS = NO;
511 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO;
512 | CLANG_ANALYZER_NONNULL = YES;
513 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
514 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
515 | CLANG_CXX_LIBRARY = "libc++";
516 | CLANG_ENABLE_MODULES = YES;
517 | CLANG_ENABLE_OBJC_ARC = YES;
518 | CLANG_ENABLE_OBJC_WEAK = YES;
519 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
520 | CLANG_WARN_BOOL_CONVERSION = YES;
521 | CLANG_WARN_COMMA = YES;
522 | CLANG_WARN_CONSTANT_CONVERSION = YES;
523 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
524 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
525 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
526 | CLANG_WARN_EMPTY_BODY = YES;
527 | CLANG_WARN_ENUM_CONVERSION = YES;
528 | CLANG_WARN_INFINITE_RECURSION = YES;
529 | CLANG_WARN_INT_CONVERSION = YES;
530 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
531 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
532 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
533 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
534 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
535 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
536 | CLANG_WARN_STRICT_PROTOTYPES = YES;
537 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
538 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
539 | CLANG_WARN_UNREACHABLE_CODE = YES;
540 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
541 | COPY_PHASE_STRIP = NO;
542 | DEAD_CODE_STRIPPING = YES;
543 | DEBUG_INFORMATION_FORMAT = dwarf;
544 | ENABLE_BITCODE = NO;
545 | ENABLE_STRICT_OBJC_MSGSEND = YES;
546 | ENABLE_TESTABILITY = YES;
547 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
548 | GCC_C_LANGUAGE_STANDARD = gnu11;
549 | GCC_DYNAMIC_NO_PIC = NO;
550 | GCC_NO_COMMON_BLOCKS = YES;
551 | GCC_OPTIMIZATION_LEVEL = 0;
552 | GCC_PREPROCESSOR_DEFINITIONS = (
553 | "DEBUG=1",
554 | "GL_SILENCE_DEPRECATION=1",
555 | );
556 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
557 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
558 | GCC_WARN_UNDECLARED_SELECTOR = YES;
559 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
560 | GCC_WARN_UNUSED_FUNCTION = YES;
561 | GCC_WARN_UNUSED_VARIABLE = YES;
562 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
563 | MTL_FAST_MATH = YES;
564 | ONLY_ACTIVE_ARCH = YES;
565 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
566 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
567 | SWIFT_VERSION = 5.0;
568 | };
569 | name = Debug;
570 | };
571 | 68B38553271E780700711D5F /* Release */ = {
572 | isa = XCBuildConfiguration;
573 | buildSettings = {
574 | ALWAYS_SEARCH_USER_PATHS = NO;
575 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO;
576 | CLANG_ANALYZER_NONNULL = YES;
577 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
578 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
579 | CLANG_CXX_LIBRARY = "libc++";
580 | CLANG_ENABLE_MODULES = YES;
581 | CLANG_ENABLE_OBJC_ARC = YES;
582 | CLANG_ENABLE_OBJC_WEAK = YES;
583 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
584 | CLANG_WARN_BOOL_CONVERSION = YES;
585 | CLANG_WARN_COMMA = YES;
586 | CLANG_WARN_CONSTANT_CONVERSION = YES;
587 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
588 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
589 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
590 | CLANG_WARN_EMPTY_BODY = YES;
591 | CLANG_WARN_ENUM_CONVERSION = YES;
592 | CLANG_WARN_INFINITE_RECURSION = YES;
593 | CLANG_WARN_INT_CONVERSION = YES;
594 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
595 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
596 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
597 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
598 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
599 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
600 | CLANG_WARN_STRICT_PROTOTYPES = YES;
601 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
602 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
603 | CLANG_WARN_UNREACHABLE_CODE = YES;
604 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
605 | COPY_PHASE_STRIP = NO;
606 | DEAD_CODE_STRIPPING = YES;
607 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
608 | ENABLE_BITCODE = NO;
609 | ENABLE_NS_ASSERTIONS = NO;
610 | ENABLE_STRICT_OBJC_MSGSEND = YES;
611 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
612 | GCC_C_LANGUAGE_STANDARD = gnu11;
613 | GCC_NO_COMMON_BLOCKS = YES;
614 | GCC_PREPROCESSOR_DEFINITIONS = "GL_SILENCE_DEPRECATION=1";
615 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
616 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
617 | GCC_WARN_UNDECLARED_SELECTOR = YES;
618 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
619 | GCC_WARN_UNUSED_FUNCTION = YES;
620 | GCC_WARN_UNUSED_VARIABLE = YES;
621 | MTL_ENABLE_DEBUG_INFO = NO;
622 | MTL_FAST_MATH = YES;
623 | SWIFT_COMPILATION_MODE = wholemodule;
624 | SWIFT_OPTIMIZATION_LEVEL = "-O";
625 | SWIFT_VERSION = 5.0;
626 | };
627 | name = Release;
628 | };
629 | 68B38555271E780700711D5F /* Debug */ = {
630 | isa = XCBuildConfiguration;
631 | buildSettings = {
632 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
633 | ASSETCATALOG_COMPILER_APPICON_NAME = iOS;
634 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
635 | CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/iOS/iOS.entitlements";
636 | CODE_SIGN_IDENTITY = "Apple Development";
637 | CODE_SIGN_STYLE = Automatic;
638 | CURRENT_PROJECT_VERSION = 2024031101;
639 | DEVELOPMENT_TEAM = 76TVFCUKK7;
640 | ENABLE_PREVIEWS = YES;
641 | INFOPLIST_FILE = iOS/Info.plist;
642 | INFOPLIST_KEY_NSCameraUsageDescription = "Please allow Camera access";
643 | INFOPLIST_KEY_NSMicrophoneUsageDescription = "Please allow Microphone access";
644 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
645 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
646 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
647 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
648 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
649 | LD_RUNPATH_SEARCH_PATHS = (
650 | "$(inherited)",
651 | "@executable_path/Frameworks",
652 | );
653 | MARKETING_VERSION = 2.0.5;
654 | PRODUCT_BUNDLE_IDENTIFIER = io.livekit.example.SwiftSDK.1;
655 | PRODUCT_NAME = LiveKitExample;
656 | PROVISIONING_PROFILE_SPECIFIER = "";
657 | SDKROOT = iphoneos;
658 | SWIFT_EMIT_LOC_STRINGS = YES;
659 | TARGETED_DEVICE_FAMILY = "1,2";
660 | };
661 | name = Debug;
662 | };
663 | 68B38556271E780700711D5F /* Release */ = {
664 | isa = XCBuildConfiguration;
665 | buildSettings = {
666 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
667 | ASSETCATALOG_COMPILER_APPICON_NAME = iOS;
668 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
669 | CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/iOS/iOS.entitlements";
670 | CODE_SIGN_IDENTITY = "Apple Development";
671 | CODE_SIGN_STYLE = Automatic;
672 | CURRENT_PROJECT_VERSION = 2024031101;
673 | DEVELOPMENT_TEAM = 76TVFCUKK7;
674 | ENABLE_PREVIEWS = YES;
675 | INFOPLIST_FILE = iOS/Info.plist;
676 | INFOPLIST_KEY_NSCameraUsageDescription = "Please allow Camera access";
677 | INFOPLIST_KEY_NSMicrophoneUsageDescription = "Please allow Microphone access";
678 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
679 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
680 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
681 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
682 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
683 | LD_RUNPATH_SEARCH_PATHS = (
684 | "$(inherited)",
685 | "@executable_path/Frameworks",
686 | );
687 | MARKETING_VERSION = 2.0.5;
688 | PRODUCT_BUNDLE_IDENTIFIER = io.livekit.example.SwiftSDK.1;
689 | PRODUCT_NAME = LiveKitExample;
690 | PROVISIONING_PROFILE_SPECIFIER = "";
691 | SDKROOT = iphoneos;
692 | SWIFT_EMIT_LOC_STRINGS = YES;
693 | TARGETED_DEVICE_FAMILY = "1,2";
694 | VALIDATE_PRODUCT = YES;
695 | };
696 | name = Release;
697 | };
698 | 68B38558271E780700711D5F /* Debug */ = {
699 | isa = XCBuildConfiguration;
700 | buildSettings = {
701 | ASSETCATALOG_COMPILER_APPICON_NAME = MacOS;
702 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
703 | CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/macOS/macOS.entitlements";
704 | CODE_SIGN_IDENTITY = "Apple Development";
705 | CODE_SIGN_STYLE = Automatic;
706 | COMBINE_HIDPI_IMAGES = YES;
707 | CURRENT_PROJECT_VERSION = 2024031101;
708 | DEAD_CODE_STRIPPING = YES;
709 | DEVELOPMENT_TEAM = 76TVFCUKK7;
710 | ENABLE_HARDENED_RUNTIME = YES;
711 | ENABLE_PREVIEWS = YES;
712 | INFOPLIST_FILE = "$(SRCROOT)/macOS/Info.plist";
713 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
714 | INFOPLIST_KEY_NSCameraUsageDescription = "Please allow Camera access";
715 | INFOPLIST_KEY_NSMicrophoneUsageDescription = "Please allow Microphone access";
716 | LD_RUNPATH_SEARCH_PATHS = (
717 | "$(inherited)",
718 | "@executable_path/../Frameworks",
719 | );
720 | MACOSX_DEPLOYMENT_TARGET = 11.0;
721 | MARKETING_VERSION = 2.0.5;
722 | PRODUCT_BUNDLE_IDENTIFIER = io.livekit.example.SwiftSDK.1;
723 | PRODUCT_NAME = LiveKitExample;
724 | PROVISIONING_PROFILE_SPECIFIER = "";
725 | SDKROOT = macosx;
726 | SWIFT_EMIT_LOC_STRINGS = YES;
727 | };
728 | name = Debug;
729 | };
730 | 68B38559271E780700711D5F /* Release */ = {
731 | isa = XCBuildConfiguration;
732 | buildSettings = {
733 | ASSETCATALOG_COMPILER_APPICON_NAME = MacOS;
734 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
735 | CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/macOS/macOS.entitlements";
736 | CODE_SIGN_IDENTITY = "Apple Development";
737 | CODE_SIGN_STYLE = Automatic;
738 | COMBINE_HIDPI_IMAGES = YES;
739 | CURRENT_PROJECT_VERSION = 2024031101;
740 | DEAD_CODE_STRIPPING = YES;
741 | DEVELOPMENT_TEAM = 76TVFCUKK7;
742 | ENABLE_HARDENED_RUNTIME = YES;
743 | ENABLE_PREVIEWS = YES;
744 | INFOPLIST_FILE = "$(SRCROOT)/macOS/Info.plist";
745 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
746 | INFOPLIST_KEY_NSCameraUsageDescription = "Please allow Camera access";
747 | INFOPLIST_KEY_NSMicrophoneUsageDescription = "Please allow Microphone access";
748 | LD_RUNPATH_SEARCH_PATHS = (
749 | "$(inherited)",
750 | "@executable_path/../Frameworks",
751 | );
752 | MACOSX_DEPLOYMENT_TARGET = 11.0;
753 | MARKETING_VERSION = 2.0.5;
754 | PRODUCT_BUNDLE_IDENTIFIER = io.livekit.example.SwiftSDK.1;
755 | PRODUCT_NAME = LiveKitExample;
756 | PROVISIONING_PROFILE_SPECIFIER = "";
757 | SDKROOT = macosx;
758 | SWIFT_EMIT_LOC_STRINGS = YES;
759 | };
760 | name = Release;
761 | };
762 | /* End XCBuildConfiguration section */
763 |
764 | /* Begin XCConfigurationList section */
765 | 683F05FD273F96B20080C7AC /* Build configuration list for PBXNativeTarget "BroadcastExt" */ = {
766 | isa = XCConfigurationList;
767 | buildConfigurations = (
768 | 683F05FE273F96B20080C7AC /* Debug */,
769 | 683F05FF273F96B20080C7AC /* Release */,
770 | );
771 | defaultConfigurationIsVisible = 0;
772 | defaultConfigurationName = Release;
773 | };
774 | 68B3853A271E780600711D5F /* Build configuration list for PBXProject "LiveKitExample-main" */ = {
775 | isa = XCConfigurationList;
776 | buildConfigurations = (
777 | 68B38552271E780700711D5F /* Debug */,
778 | 68B38553271E780700711D5F /* Release */,
779 | );
780 | defaultConfigurationIsVisible = 0;
781 | defaultConfigurationName = Release;
782 | };
783 | 68B38554271E780700711D5F /* Build configuration list for PBXNativeTarget "LiveKitExample (iOS)" */ = {
784 | isa = XCConfigurationList;
785 | buildConfigurations = (
786 | 68B38555271E780700711D5F /* Debug */,
787 | 68B38556271E780700711D5F /* Release */,
788 | );
789 | defaultConfigurationIsVisible = 0;
790 | defaultConfigurationName = Release;
791 | };
792 | 68B38557271E780700711D5F /* Build configuration list for PBXNativeTarget "LiveKitExample (macOS)" */ = {
793 | isa = XCConfigurationList;
794 | buildConfigurations = (
795 | 68B38558271E780700711D5F /* Debug */,
796 | 68B38559271E780700711D5F /* Release */,
797 | );
798 | defaultConfigurationIsVisible = 0;
799 | defaultConfigurationName = Release;
800 | };
801 | /* End XCConfigurationList section */
802 |
803 | /* Begin XCRemoteSwiftPackageReference section */
804 | 680FE2F027A8EF7700B6F6DB /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = {
805 | isa = XCRemoteSwiftPackageReference;
806 | repositoryURL = "https://github.com/SFSafeSymbols/SFSafeSymbols";
807 | requirement = {
808 | kind = upToNextMajorVersion;
809 | minimumVersion = 4.1.1;
810 | };
811 | };
812 | 685271E727407BBC006B4D6A /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
813 | isa = XCRemoteSwiftPackageReference;
814 | repositoryURL = "https://github.com/apple/swift-protobuf.git";
815 | requirement = {
816 | kind = upToNextMajorVersion;
817 | minimumVersion = 1.18.0;
818 | };
819 | };
820 | 68816CBF27B4D6BC00E24622 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = {
821 | isa = XCRemoteSwiftPackageReference;
822 | repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git";
823 | requirement = {
824 | kind = upToNextMajorVersion;
825 | minimumVersion = 4.2.2;
826 | };
827 | };
828 | 68FBA43D2A38B49C0015853E /* XCRemoteSwiftPackageReference "client-sdk-swift" */ = {
829 | isa = XCRemoteSwiftPackageReference;
830 | repositoryURL = "https://github.com/livekit/client-sdk-swift";
831 | requirement = {
832 | branch = main;
833 | kind = branch;
834 | };
835 | };
836 | /* End XCRemoteSwiftPackageReference section */
837 |
838 | /* Begin XCSwiftPackageProductDependency section */
839 | 680FE2F127A8EF7700B6F6DB /* SFSafeSymbols */ = {
840 | isa = XCSwiftPackageProductDependency;
841 | package = 680FE2F027A8EF7700B6F6DB /* XCRemoteSwiftPackageReference "SFSafeSymbols" */;
842 | productName = SFSafeSymbols;
843 | };
844 | 680FE2F327A8EFF700B6F6DB /* SFSafeSymbols */ = {
845 | isa = XCSwiftPackageProductDependency;
846 | package = 680FE2F027A8EF7700B6F6DB /* XCRemoteSwiftPackageReference "SFSafeSymbols" */;
847 | productName = SFSafeSymbols;
848 | };
849 | 681A0AB627D888D80097E3F4 /* LiveKit */ = {
850 | isa = XCSwiftPackageProductDependency;
851 | productName = LiveKit;
852 | };
853 | 6845DD5527DEBDD90009B5CD /* LiveKit */ = {
854 | isa = XCSwiftPackageProductDependency;
855 | productName = LiveKit;
856 | };
857 | 68816CC027B4D6BC00E24622 /* KeychainAccess */ = {
858 | isa = XCSwiftPackageProductDependency;
859 | package = 68816CBF27B4D6BC00E24622 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
860 | productName = KeychainAccess;
861 | };
862 | 68816CC227B4D94200E24622 /* KeychainAccess */ = {
863 | isa = XCSwiftPackageProductDependency;
864 | package = 68816CBF27B4D6BC00E24622 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
865 | productName = KeychainAccess;
866 | };
867 | 68FBA43E2A38B49C0015853E /* LiveKit */ = {
868 | isa = XCSwiftPackageProductDependency;
869 | package = 68FBA43D2A38B49C0015853E /* XCRemoteSwiftPackageReference "client-sdk-swift" */;
870 | productName = LiveKit;
871 | };
872 | /* End XCSwiftPackageProductDependency section */
873 | };
874 | rootObject = 68B38537271E780600711D5F /* Project object */;
875 | }
876 |
--------------------------------------------------------------------------------