Skip to content

Commit 4ac8674

Browse files
authored
feat: Swift Package Manager support for iOS and macOS (#1142)
## What Adds Swift Package Manager support for the iOS and macOS plugins, following the Flutter SPM plugin convention: - `ios/livekit_client/Package.swift` and `macos/livekit_client/Package.swift`, with `Sources/livekit_client/` symlink trees into `shared_swift/` (and the iOS broadcast files in `ios/Classes/`), so the existing sources stay the single source of truth. - The packages depend on flutter_webrtc's Swift package (`../flutter_webrtc`, resolved by the Flutter tool) and consume its `flutter-webrtc` and `WebRTC` products. Reusing flutter_webrtc's WebRTC binary target keeps a single copy of the xcframework in the package graph, mirroring how the podspecs share the WebRTC-SDK pod today. - The example app's Xcode projects get the SPM integration committed (package reference, product dependency, prepare pre-action in the Runner scheme), matching what `flutter` would generate. CocoaPods is unchanged: podspecs and `Classes/` are untouched, and apps with SPM disabled keep building through pods. ## Depends on - flutter-webrtc/flutter-webrtc#2062, including the `WebRTC` library product export. **This PR can only land after a flutter_webrtc release that ships SPM support**, at which point the pinned flutter_webrtc version needs a bump. ## Testing With Flutter 3.44.1 (SPM on by default) and a local `dependency_overrides` pointing flutter_webrtc at the PR 2062 branch: - `flutter build macos --debug` and `flutter build ios --debug --simulator` of the example both succeed. - Both plugins compile as Swift packages (verified `LiveKitPlugin` / `FlutterWebRTCPlugin` symbols in the app binaries) with exactly one embedded `WebRTC.framework`. - `permission_handler_apple` has no SPM support and keeps building through CocoaPods in hybrid mode. Note for local testing: the Flutter tool copies plugins that declare SwiftPM dependencies on other plugins into the app's build directory with rsync. With the example app living inside this repo that copy recurses into itself (flutter_tools bug, to be filed upstream). Workaround is to make `example/build` a symlink to a directory outside the repo before building. --- ## Update 2026-08-03 flutter_webrtc 1.6.0 shipped with Swift Package Manager support, so this PR is unblocked: - Bumped the pinned flutter_webrtc to 1.6.0 and removed the need for any local overrides. Verified the example builds via SPM against the released package from pub.dev, including the versioned pub cache path rewrite. - Added a CI step to the iOS and macOS jobs that symlinks example/build outside the repository, working around the Flutter tool's rsync recursion when a plugin with SwiftPM plugin dependencies hosts its own example app (upstream flutter_tools bug, to be filed). - Merged main (RPC tester, data stream fix, Dart 3.13 fixes).
1 parent 202ce36 commit 4ac8674

38 files changed

Lines changed: 217 additions & 3 deletions

.changes/swift-package-manager

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
minor type="added" "Swift Package Manager support for iOS and macOS. CocoaPods remains fully supported."

.github/workflows/build.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ jobs:
107107
else
108108
xcodebuild -downloadPlatform iOS
109109
fi
110+
# The Flutter tool copies plugins with SwiftPM plugin dependencies into the
111+
# app build directory with rsync. Since the example lives inside the plugin
112+
# repo that copy would recurse into its own output, so point the build
113+
# directory outside the repository.
114+
- name: Move example build dir outside the repository
115+
working-directory: ./example
116+
run: |
117+
mkdir -p "$RUNNER_TEMP/example-build"
118+
rm -rf build
119+
ln -s "$RUNNER_TEMP/example-build" build
110120
- name: Build for iOS
111121
working-directory: ./example
112122
run: flutter build ios --release --no-codesign
@@ -131,6 +141,13 @@ jobs:
131141
- name: Clone repository
132142
uses: actions/checkout@v6
133143
- uses: ./.github/actions/setup-flutter
144+
# See the iOS job, same flutter tool rsync recursion workaround.
145+
- name: Move example build dir outside the repository
146+
working-directory: ./example
147+
run: |
148+
mkdir -p "$RUNNER_TEMP/example-build"
149+
rm -rf build
150+
ln -s "$RUNNER_TEMP/example-build" build
134151
- name: Build for macOS
135152
working-directory: ./example
136153
run: flutter build macos --release

example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
1111
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
1212
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
13+
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
1314
896215755592B078E91F47B7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09B43753FB1E220F505A0AA8 /* Pods_Runner.framework */; };
1415
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
1516
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
@@ -65,6 +66,7 @@
6566
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
6667
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
6768
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
69+
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
6870
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
6971
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
7072
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
@@ -92,6 +94,7 @@
9294
isa = PBXFrameworksBuildPhase;
9395
buildActionMask = 2147483647;
9496
files = (
97+
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
9598
896215755592B078E91F47B7 /* Pods_Runner.framework in Frameworks */,
9699
);
97100
runOnlyForDeploymentPostprocessing = 0;
@@ -119,6 +122,7 @@
119122
9740EEB11CF90186004384FC /* Flutter */ = {
120123
isa = PBXGroup;
121124
children = (
125+
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
122126
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
123127
9740EEB21CF90195004384FC /* Debug.xcconfig */,
124128
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@@ -212,6 +216,9 @@
212216
D7FECD2E2860997500D04D1C /* PBXTargetDependency */,
213217
);
214218
name = Runner;
219+
packageProductDependencies = (
220+
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
221+
);
215222
productName = Runner;
216223
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
217224
productType = "com.apple.product-type.application";
@@ -262,6 +269,9 @@
262269
Base,
263270
);
264271
mainGroup = 97C146E51CF9000F007C117D;
272+
packageReferences = (
273+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
274+
);
265275
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
266276
projectDirPath = "";
267277
projectRoot = "";
@@ -826,6 +836,20 @@
826836
defaultConfigurationName = Release;
827837
};
828838
/* End XCConfigurationList section */
839+
840+
/* Begin XCLocalSwiftPackageReference section */
841+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
842+
isa = XCLocalSwiftPackageReference;
843+
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
844+
};
845+
/* End XCLocalSwiftPackageReference section */
846+
847+
/* Begin XCSwiftPackageProductDependency section */
848+
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
849+
isa = XCSwiftPackageProductDependency;
850+
productName = FlutterGeneratedPluginSwiftPackage;
851+
};
852+
/* End XCSwiftPackageProductDependency section */
829853
};
830854
rootObject = 97C146E61CF9000F007C117D /* Project object */;
831855
}

example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55
<BuildAction
66
parallelizeBuildables = "YES"
77
buildImplicitDependencies = "YES">
8+
<PreActions>
9+
<ExecutionAction
10+
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
11+
<ActionContent
12+
title = "Run Prepare Flutter Framework Script"
13+
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
14+
<EnvironmentBuildable>
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
18+
BuildableName = "Runner.app"
19+
BlueprintName = "Runner"
20+
ReferencedContainer = "container:Runner.xcodeproj">
21+
</BuildableReference>
22+
</EnvironmentBuildable>
23+
</ActionContent>
24+
</ExecutionAction>
25+
</PreActions>
826
<BuildActionEntries>
927
<BuildActionEntry
1028
buildForTesting = "YES"

example/macos/Runner.xcodeproj/project.pbxproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
2929
68BB07F6271CCD00006A51C2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B68429BE2B91C13835FDA3EA /* Pods_Runner.framework */; };
3030
68BB07F7271CCD00006A51C2 /* Pods_Runner.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = B68429BE2B91C13835FDA3EA /* Pods_Runner.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
31+
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
3132
/* End PBXBuildFile section */
3233

3334
/* Begin PBXContainerItemProxy section */
@@ -70,6 +71,7 @@
7071
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
7172
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
7273
3E50A2648E87CCDA0D2CD408 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
74+
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
7375
79954C9DA9D4E0174C7AAEBA /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
7476
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
7577
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
@@ -82,6 +84,7 @@
8284
isa = PBXFrameworksBuildPhase;
8385
buildActionMask = 2147483647;
8486
files = (
87+
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
8588
68BB07F6271CCD00006A51C2 /* Pods_Runner.framework in Frameworks */,
8689
);
8790
runOnlyForDeploymentPostprocessing = 0;
@@ -133,6 +136,7 @@
133136
33CEB47122A05771004F2AC0 /* Flutter */ = {
134137
isa = PBXGroup;
135138
children = (
139+
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
136140
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
137141
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
138142
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
@@ -193,6 +197,9 @@
193197
33CC11202044C79F0003C045 /* PBXTargetDependency */,
194198
);
195199
name = Runner;
200+
packageProductDependencies = (
201+
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
202+
);
196203
productName = Runner;
197204
productReference = 33CC10ED2044A3C60003C045 /* example.app */;
198205
productType = "com.apple.product-type.application";
@@ -232,6 +239,9 @@
232239
Base,
233240
);
234241
mainGroup = 33CC10E42044A3C60003C045;
242+
packageReferences = (
243+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
244+
);
235245
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
236246
projectDirPath = "";
237247
projectRoot = "";
@@ -636,6 +646,20 @@
636646
defaultConfigurationName = Release;
637647
};
638648
/* End XCConfigurationList section */
649+
650+
/* Begin XCLocalSwiftPackageReference section */
651+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
652+
isa = XCLocalSwiftPackageReference;
653+
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
654+
};
655+
/* End XCLocalSwiftPackageReference section */
656+
657+
/* Begin XCSwiftPackageProductDependency section */
658+
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
659+
isa = XCSwiftPackageProductDependency;
660+
productName = FlutterGeneratedPluginSwiftPackage;
661+
};
662+
/* End XCSwiftPackageProductDependency section */
639663
};
640664
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
641665
}

example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55
<BuildAction
66
parallelizeBuildables = "YES"
77
buildImplicitDependencies = "YES">
8+
<PreActions>
9+
<ExecutionAction
10+
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
11+
<ActionContent
12+
title = "Run Prepare Flutter Framework Script"
13+
scriptText = "&quot;$FLUTTER_ROOT&quot;/packages/flutter_tools/bin/macos_assemble.sh prepare&#10;">
14+
<EnvironmentBuildable>
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
18+
BuildableName = "example.app"
19+
BlueprintName = "Runner"
20+
ReferencedContainer = "container:Runner.xcodeproj">
21+
</BuildableReference>
22+
</EnvironmentBuildable>
23+
</ActionContent>
24+
</ExecutionAction>
25+
</PreActions>
826
<BuildActionEntries>
927
<BuildActionEntry
1028
buildForTesting = "YES"

ios/livekit_client/Package.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// swift-tools-version: 5.9
2+
/*
3+
* Copyright 2026 LiveKit
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import PackageDescription
18+
19+
let package = Package(
20+
name: "livekit_client",
21+
platforms: [
22+
.iOS("13.0")
23+
],
24+
products: [
25+
.library(name: "livekit-client", targets: ["livekit_client"])
26+
],
27+
dependencies: [
28+
.package(name: "FlutterFramework", path: "../FlutterFramework"),
29+
// Resolved by the Flutter tool to the flutter_webrtc plugin package.
30+
.package(name: "flutter_webrtc", path: "../flutter_webrtc")
31+
],
32+
targets: [
33+
.target(
34+
name: "livekit_client",
35+
dependencies: [
36+
.product(name: "FlutterFramework", package: "FlutterFramework"),
37+
.product(name: "flutter-webrtc", package: "flutter_webrtc"),
38+
.product(name: "WebRTC", package: "flutter_webrtc")
39+
]
40+
)
41+
]
42+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../shared_swift/AVAudioPCMBuffer.swift
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../shared_swift/AudioConverter.swift
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../shared_swift/AudioProcessing.swift

0 commit comments

Comments
 (0)