From a1578bd67a4a5777dc92fe25e9efac0e80883507 Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Fri, 4 Apr 2025 16:40:52 -0300 Subject: [PATCH 1/3] iOS demo for the p2p-webrtc video-transform example --- .../video-transform/client/ios/.gitignore | 2 + .../video-transform/client/ios/README.md | 18 + .../SimpleChatbot.xcodeproj/project.pbxproj | 715 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/swiftpm/Package.resolved | 24 + .../xcschemes/SimpleChatbot.xcscheme | 78 ++ .../AppIcon.appiconset/Contents.json | 14 + .../AppIcon.appiconset/appstore.png | Bin 0 -> 29465 bytes .../Assets.xcassets/Contents.json | 6 + .../pipecat.imageset/Contents.json | 21 + .../pipecat.imageset/Square Black.svg | 8 + .../vision.imageset/Contents.json | 21 + .../vision.imageset/vision.svg | 9 + .../client/ios/SimpleChatbot/Info.plist | 19 + .../Preview Assets.xcassets/Contents.json | 6 + .../ios/SimpleChatbot/SimpleChatbotApp.swift | 18 + .../model/CallContainerModel.swift | 284 +++++++ .../model/MockCallContainerModel.swift | 43 ++ .../ios/SimpleChatbot/types/LiveMessage.swift | 21 + .../ios/SimpleChatbot/views/MeetingView.swift | 81 ++ .../ios/SimpleChatbot/views/PreJoinView.swift | 44 ++ .../views/components/CameraButtonView.swift | 42 + .../views/components/ChatView.swift | 96 +++ .../views/components/MicrophoneView.swift | 44 ++ .../views/components/ToastModifier.swift | 31 + .../views/extensions/CustomColors.swift | 26 + .../views/settings/SettingsManager.swift | 21 + .../views/settings/SettingsPreference.swift | 9 + .../views/settings/SettingsView.swift | 82 ++ .../SimpleChatbotTests.swift | 29 + .../SimpleChatbotUITests.swift | 34 + .../SimpleChatbotUITestsLaunchTests.swift | 25 + 33 files changed, 1887 insertions(+) create mode 100644 examples/p2p-webrtc/video-transform/client/ios/.gitignore create mode 100644 examples/p2p-webrtc/video-transform/client/ios/README.md create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.pbxproj create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/xcshareddata/xcschemes/SimpleChatbot.xcscheme create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/AppIcon.appiconset/appstore.png create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/Contents.json create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/pipecat.imageset/Contents.json create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/pipecat.imageset/Square Black.svg create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/vision.imageset/Contents.json create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/vision.imageset/vision.svg create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Info.plist create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/SimpleChatbotApp.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/model/CallContainerModel.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/model/MockCallContainerModel.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/types/LiveMessage.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/MeetingView.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/PreJoinView.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/CameraButtonView.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/ChatView.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/MicrophoneView.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/ToastModifier.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/extensions/CustomColors.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsManager.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsPreference.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsView.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotTests/SimpleChatbotTests.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotUITests/SimpleChatbotUITests.swift create mode 100644 examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotUITests/SimpleChatbotUITestsLaunchTests.swift diff --git a/examples/p2p-webrtc/video-transform/client/ios/.gitignore b/examples/p2p-webrtc/video-transform/client/ios/.gitignore new file mode 100644 index 000000000..f89982e48 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/.gitignore @@ -0,0 +1,2 @@ +/SimpleChatbot.xcodeproj/xcuserdata/ +/SimpleChatbot.xcodeproj/project.xcworkspace/xcuserdata/ diff --git a/examples/p2p-webrtc/video-transform/client/ios/README.md b/examples/p2p-webrtc/video-transform/client/ios/README.md new file mode 100644 index 000000000..02c27582d --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/README.md @@ -0,0 +1,18 @@ +# iOS implementation + +Basic implementation using the [Pipecat iOS SDK](https://docs.pipecat.ai/client/ios/introduction). + +## Prerequisites + +1. Run the bot server. See the [server README](../../server). +2. Install [Xcode 15](https://developer.apple.com/xcode/), and set up your device [to run your own applications](https://developer.apple.com/documentation/xcode/distributing-your-app-to-registered-devices). + +## Running locally + +1. Clone this repository locally. +2. Open the SimpleChatbot.xcodeproj in Xcode. +3. Tell Xcode to update its Package Dependencies by clicking File -> Packages -> Update to Latest Package Versions. +4. Build the project. +5. Run the project on your device. +6. Connect to the URL you are testing. + diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.pbxproj b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.pbxproj new file mode 100644 index 000000000..169a2cba8 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.pbxproj @@ -0,0 +1,715 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 60; + objects = { + +/* Begin PBXBuildFile section */ + 90031FA72C616EE700408370 /* SimpleChatbotApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FA62C616EE700408370 /* SimpleChatbotApp.swift */; }; + 90031FA92C616EE700408370 /* PreJoinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FA82C616EE700408370 /* PreJoinView.swift */; }; + 90031FAB2C616EE800408370 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 90031FAA2C616EE800408370 /* Assets.xcassets */; }; + 90031FAE2C616EE800408370 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 90031FAD2C616EE800408370 /* Preview Assets.xcassets */; }; + 90031FB82C616EE900408370 /* SimpleChatbotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FB72C616EE900408370 /* SimpleChatbotTests.swift */; }; + 90031FC22C616EE900408370 /* SimpleChatbotUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FC12C616EE900408370 /* SimpleChatbotUITests.swift */; }; + 90031FC42C616EE900408370 /* SimpleChatbotUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FC32C616EE900408370 /* SimpleChatbotUITestsLaunchTests.swift */; }; + 90031FDC2C6D5DD700408370 /* ToastModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FDB2C6D5DD700408370 /* ToastModifier.swift */; }; + 90383A912D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc in Frameworks */ = {isa = PBXBuildFile; productRef = 90383A902D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc */; }; + 90383A932D9C35B300D0DDA3 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90383A922D9C35B300D0DDA3 /* ChatView.swift */; }; + 90383A962D9C35BD00D0DDA3 /* LiveMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90383A942D9C35BD00D0DDA3 /* LiveMessage.swift */; }; + 90383A982D9D85E700D0DDA3 /* CameraButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90383A972D9D85E700D0DDA3 /* CameraButtonView.swift */; }; + 90ABB98E2C735ED6000D9CC7 /* MeetingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB98D2C735ED6000D9CC7 /* MeetingView.swift */; }; + 90ABB9932C73820D000D9CC7 /* MicrophoneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9922C73820D000D9CC7 /* MicrophoneView.swift */; }; + 90ABB9982C738356000D9CC7 /* CustomColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9972C738356000D9CC7 /* CustomColors.swift */; }; + 90ABB99A2C73A6A9000D9CC7 /* MockCallContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9992C73A6A9000D9CC7 /* MockCallContainerModel.swift */; }; + 90ABB99D2C73C2D1000D9CC7 /* CallContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB99C2C73C2D1000D9CC7 /* CallContainerModel.swift */; }; + 90ABB9A32C74E1CE000D9CC7 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9A22C74E1CE000D9CC7 /* SettingsView.swift */; }; + 90ABB9A62C74EA8A000D9CC7 /* SettingsPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9A52C74EA8A000D9CC7 /* SettingsPreference.swift */; }; + 90ABB9A82C74EAB1000D9CC7 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9A72C74EAB1000D9CC7 /* SettingsManager.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 90031FB42C616EE900408370 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 90031F9B2C616EE700408370 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 90031FA22C616EE700408370; + remoteInfo = SimpleChatbot; + }; + 90031FBE2C616EE900408370 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 90031F9B2C616EE700408370 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 90031FA22C616EE700408370; + remoteInfo = SimpleChatbot; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 90031FA32C616EE700408370 /* SimpleChatbot.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleChatbot.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 90031FA62C616EE700408370 /* SimpleChatbotApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleChatbotApp.swift; sourceTree = ""; }; + 90031FA82C616EE700408370 /* PreJoinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreJoinView.swift; sourceTree = ""; }; + 90031FAA2C616EE800408370 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 90031FAD2C616EE800408370 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 90031FB32C616EE900408370 /* SimpleChatbotTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleChatbotTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 90031FB72C616EE900408370 /* SimpleChatbotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleChatbotTests.swift; sourceTree = ""; }; + 90031FBD2C616EE900408370 /* SimpleChatbotUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleChatbotUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 90031FC12C616EE900408370 /* SimpleChatbotUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleChatbotUITests.swift; sourceTree = ""; }; + 90031FC32C616EE900408370 /* SimpleChatbotUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleChatbotUITestsLaunchTests.swift; sourceTree = ""; }; + 90031FD62C63FD6A00408370 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 90031FDB2C6D5DD700408370 /* ToastModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastModifier.swift; sourceTree = ""; }; + 90383A922D9C35B300D0DDA3 /* ChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; + 90383A942D9C35BD00D0DDA3 /* LiveMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveMessage.swift; sourceTree = ""; }; + 90383A972D9D85E700D0DDA3 /* CameraButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraButtonView.swift; sourceTree = ""; }; + 90ABB98D2C735ED6000D9CC7 /* MeetingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingView.swift; sourceTree = ""; }; + 90ABB9922C73820D000D9CC7 /* MicrophoneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrophoneView.swift; sourceTree = ""; }; + 90ABB9972C738356000D9CC7 /* CustomColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomColors.swift; sourceTree = ""; }; + 90ABB9992C73A6A9000D9CC7 /* MockCallContainerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallContainerModel.swift; sourceTree = ""; }; + 90ABB99C2C73C2D1000D9CC7 /* CallContainerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallContainerModel.swift; sourceTree = ""; }; + 90ABB9A22C74E1CE000D9CC7 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 90ABB9A52C74EA8A000D9CC7 /* SettingsPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPreference.swift; sourceTree = ""; }; + 90ABB9A72C74EAB1000D9CC7 /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 90031FA02C616EE700408370 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 90383A912D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 90031FB02C616EE900408370 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 90031FBA2C616EE900408370 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 90031F9A2C616EE700408370 = { + isa = PBXGroup; + children = ( + 90031FA52C616EE700408370 /* SimpleChatbot */, + 90031FB62C616EE900408370 /* SimpleChatbotTests */, + 90031FC02C616EE900408370 /* SimpleChatbotUITests */, + 90031FA42C616EE700408370 /* Products */, + ); + sourceTree = ""; + }; + 90031FA42C616EE700408370 /* Products */ = { + isa = PBXGroup; + children = ( + 90031FA32C616EE700408370 /* SimpleChatbot.app */, + 90031FB32C616EE900408370 /* SimpleChatbotTests.xctest */, + 90031FBD2C616EE900408370 /* SimpleChatbotUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 90031FA52C616EE700408370 /* SimpleChatbot */ = { + isa = PBXGroup; + children = ( + 90383A952D9C35BD00D0DDA3 /* types */, + 90ABB99B2C73C2C5000D9CC7 /* model */, + 90031FDD2C6D61E000408370 /* views */, + 90031FD62C63FD6A00408370 /* Info.plist */, + 90031FA62C616EE700408370 /* SimpleChatbotApp.swift */, + 90031FAA2C616EE800408370 /* Assets.xcassets */, + 90031FAC2C616EE800408370 /* Preview Content */, + ); + path = SimpleChatbot; + sourceTree = ""; + }; + 90031FAC2C616EE800408370 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 90031FAD2C616EE800408370 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 90031FB62C616EE900408370 /* SimpleChatbotTests */ = { + isa = PBXGroup; + children = ( + 90031FB72C616EE900408370 /* SimpleChatbotTests.swift */, + ); + path = SimpleChatbotTests; + sourceTree = ""; + }; + 90031FC02C616EE900408370 /* SimpleChatbotUITests */ = { + isa = PBXGroup; + children = ( + 90031FC12C616EE900408370 /* SimpleChatbotUITests.swift */, + 90031FC32C616EE900408370 /* SimpleChatbotUITestsLaunchTests.swift */, + ); + path = SimpleChatbotUITests; + sourceTree = ""; + }; + 90031FDD2C6D61E000408370 /* views */ = { + isa = PBXGroup; + children = ( + 90ABB99E2C73C3A9000D9CC7 /* components */, + 90ABB9962C738346000D9CC7 /* extensions */, + 90ABB9A42C74EA52000D9CC7 /* settings */, + 90031FA82C616EE700408370 /* PreJoinView.swift */, + 90ABB98D2C735ED6000D9CC7 /* MeetingView.swift */, + ); + path = views; + sourceTree = ""; + }; + 90383A952D9C35BD00D0DDA3 /* types */ = { + isa = PBXGroup; + children = ( + 90383A942D9C35BD00D0DDA3 /* LiveMessage.swift */, + ); + path = types; + sourceTree = ""; + }; + 90ABB9962C738346000D9CC7 /* extensions */ = { + isa = PBXGroup; + children = ( + 90ABB9972C738356000D9CC7 /* CustomColors.swift */, + ); + path = extensions; + sourceTree = ""; + }; + 90ABB99B2C73C2C5000D9CC7 /* model */ = { + isa = PBXGroup; + children = ( + 90ABB9992C73A6A9000D9CC7 /* MockCallContainerModel.swift */, + 90ABB99C2C73C2D1000D9CC7 /* CallContainerModel.swift */, + ); + path = model; + sourceTree = ""; + }; + 90ABB99E2C73C3A9000D9CC7 /* components */ = { + isa = PBXGroup; + children = ( + 90383A972D9D85E700D0DDA3 /* CameraButtonView.swift */, + 90383A922D9C35B300D0DDA3 /* ChatView.swift */, + 90ABB9922C73820D000D9CC7 /* MicrophoneView.swift */, + 90031FDB2C6D5DD700408370 /* ToastModifier.swift */, + ); + path = components; + sourceTree = ""; + }; + 90ABB9A42C74EA52000D9CC7 /* settings */ = { + isa = PBXGroup; + children = ( + 90ABB9A52C74EA8A000D9CC7 /* SettingsPreference.swift */, + 90ABB9A72C74EAB1000D9CC7 /* SettingsManager.swift */, + 90ABB9A22C74E1CE000D9CC7 /* SettingsView.swift */, + ); + path = settings; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 90031FA22C616EE700408370 /* SimpleChatbot */ = { + isa = PBXNativeTarget; + buildConfigurationList = 90031FC72C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbot" */; + buildPhases = ( + 90031F9F2C616EE700408370 /* Sources */, + 90031FA02C616EE700408370 /* Frameworks */, + 90031FA12C616EE700408370 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SimpleChatbot; + packageProductDependencies = ( + 90383A902D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc */, + ); + productName = SimpleChatbot; + productReference = 90031FA32C616EE700408370 /* SimpleChatbot.app */; + productType = "com.apple.product-type.application"; + }; + 90031FB22C616EE900408370 /* SimpleChatbotTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 90031FCA2C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbotTests" */; + buildPhases = ( + 90031FAF2C616EE900408370 /* Sources */, + 90031FB02C616EE900408370 /* Frameworks */, + 90031FB12C616EE900408370 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 90031FB52C616EE900408370 /* PBXTargetDependency */, + ); + name = SimpleChatbotTests; + productName = SimpleChatbotTests; + productReference = 90031FB32C616EE900408370 /* SimpleChatbotTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 90031FBC2C616EE900408370 /* SimpleChatbotUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 90031FCD2C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbotUITests" */; + buildPhases = ( + 90031FB92C616EE900408370 /* Sources */, + 90031FBA2C616EE900408370 /* Frameworks */, + 90031FBB2C616EE900408370 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 90031FBF2C616EE900408370 /* PBXTargetDependency */, + ); + name = SimpleChatbotUITests; + productName = SimpleChatbotUITests; + productReference = 90031FBD2C616EE900408370 /* SimpleChatbotUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 90031F9B2C616EE700408370 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; + TargetAttributes = { + 90031FA22C616EE700408370 = { + CreatedOnToolsVersion = 15.4; + }; + 90031FB22C616EE900408370 = { + CreatedOnToolsVersion = 15.4; + TestTargetID = 90031FA22C616EE700408370; + }; + 90031FBC2C616EE900408370 = { + CreatedOnToolsVersion = 15.4; + TestTargetID = 90031FA22C616EE700408370; + }; + }; + }; + buildConfigurationList = 90031F9E2C616EE700408370 /* Build configuration list for PBXProject "SimpleChatbot" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 90031F9A2C616EE700408370; + packageReferences = ( + 90383A8F2D9C357F00D0DDA3 /* XCLocalSwiftPackageReference "../../../../../../ios-public-repos/pipecat-client-ios-small-webrtc" */, + ); + productRefGroup = 90031FA42C616EE700408370 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 90031FA22C616EE700408370 /* SimpleChatbot */, + 90031FB22C616EE900408370 /* SimpleChatbotTests */, + 90031FBC2C616EE900408370 /* SimpleChatbotUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 90031FA12C616EE700408370 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 90031FAE2C616EE800408370 /* Preview Assets.xcassets in Resources */, + 90031FAB2C616EE800408370 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 90031FB12C616EE900408370 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 90031FBB2C616EE900408370 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 90031F9F2C616EE700408370 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 90ABB99D2C73C2D1000D9CC7 /* CallContainerModel.swift in Sources */, + 90ABB9A62C74EA8A000D9CC7 /* SettingsPreference.swift in Sources */, + 90383A962D9C35BD00D0DDA3 /* LiveMessage.swift in Sources */, + 90ABB99A2C73A6A9000D9CC7 /* MockCallContainerModel.swift in Sources */, + 90031FA92C616EE700408370 /* PreJoinView.swift in Sources */, + 90383A982D9D85E700D0DDA3 /* CameraButtonView.swift in Sources */, + 90383A932D9C35B300D0DDA3 /* ChatView.swift in Sources */, + 90ABB9982C738356000D9CC7 /* CustomColors.swift in Sources */, + 90ABB98E2C735ED6000D9CC7 /* MeetingView.swift in Sources */, + 90ABB9A32C74E1CE000D9CC7 /* SettingsView.swift in Sources */, + 90ABB9932C73820D000D9CC7 /* MicrophoneView.swift in Sources */, + 90ABB9A82C74EAB1000D9CC7 /* SettingsManager.swift in Sources */, + 90031FDC2C6D5DD700408370 /* ToastModifier.swift in Sources */, + 90031FA72C616EE700408370 /* SimpleChatbotApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 90031FAF2C616EE900408370 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 90031FB82C616EE900408370 /* SimpleChatbotTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 90031FB92C616EE900408370 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 90031FC42C616EE900408370 /* SimpleChatbotUITestsLaunchTests.swift in Sources */, + 90031FC22C616EE900408370 /* SimpleChatbotUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 90031FB52C616EE900408370 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 90031FA22C616EE700408370 /* SimpleChatbot */; + targetProxy = 90031FB42C616EE900408370 /* PBXContainerItemProxy */; + }; + 90031FBF2C616EE900408370 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 90031FA22C616EE700408370 /* SimpleChatbot */; + targetProxy = 90031FBE2C616EE900408370 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 90031FC52C616EE900408370 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 90031FC62C616EE900408370 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 90031FC82C616EE900408370 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"SimpleChatbot/Preview Content\""; + DEVELOPMENT_TEAM = EEBGKV9N3N; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SimpleChatbot/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbot; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 90031FC92C616EE900408370 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"SimpleChatbot/Preview Content\""; + DEVELOPMENT_TEAM = EEBGKV9N3N; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SimpleChatbot/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbot; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 90031FCB2C616EE900408370 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = EEBGKV9N3N; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbotTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SimpleChatbot.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SimpleChatbot"; + }; + name = Debug; + }; + 90031FCC2C616EE900408370 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = EEBGKV9N3N; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbotTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SimpleChatbot.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SimpleChatbot"; + }; + name = Release; + }; + 90031FCE2C616EE900408370 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = EEBGKV9N3N; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbotUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = SimpleChatbot; + }; + name = Debug; + }; + 90031FCF2C616EE900408370 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = EEBGKV9N3N; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbotUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = SimpleChatbot; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 90031F9E2C616EE700408370 /* Build configuration list for PBXProject "SimpleChatbot" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 90031FC52C616EE900408370 /* Debug */, + 90031FC62C616EE900408370 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 90031FC72C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbot" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 90031FC82C616EE900408370 /* Debug */, + 90031FC92C616EE900408370 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 90031FCA2C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbotTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 90031FCB2C616EE900408370 /* Debug */, + 90031FCC2C616EE900408370 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 90031FCD2C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbotUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 90031FCE2C616EE900408370 /* Debug */, + 90031FCF2C616EE900408370 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 90383A8F2D9C357F00D0DDA3 /* XCLocalSwiftPackageReference "../../../../../../ios-public-repos/pipecat-client-ios-small-webrtc" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../../../../../ios-public-repos/pipecat-client-ios-small-webrtc"; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 90383A902D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc */ = { + isa = XCSwiftPackageProductDependency; + productName = PipecatClientIOSSmallWebrtc; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 90031F9B2C616EE700408370 /* Project object */; +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..08de0be8d --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..07802b253 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,24 @@ +{ + "originHash" : "64a760bff8651c5464a11324100b2fb46be172491d43cf2e87e2e5a2d88303c7", + "pins" : [ + { + "identity" : "pipecat-client-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pipecat-ai/pipecat-client-ios.git", + "state" : { + "revision" : "992641fb5f7d1a794ecfc33babb5fe36e2a8ffdd", + "version" : "0.3.5" + } + }, + { + "identity" : "webrtc", + "kind" : "remoteSourceControl", + "location" : "https://github.com/stasel/WebRTC", + "state" : { + "revision" : "5b2eb61cace7d62726b29a38b768b07d6bc55c45", + "version" : "134.0.0" + } + } + ], + "version" : 3 +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/xcshareddata/xcschemes/SimpleChatbot.xcscheme b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/xcshareddata/xcschemes/SimpleChatbot.xcscheme new file mode 100644 index 000000000..632ae199d --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/xcshareddata/xcschemes/SimpleChatbot.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..44ac9cc28 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "appstore.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/AppIcon.appiconset/appstore.png b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/AppIcon.appiconset/appstore.png new file mode 100644 index 0000000000000000000000000000000000000000..e6e615ad298a8ebfefb9de795944823cd1410f69 GIT binary patch literal 29465 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 + + + + + + + diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/vision.imageset/Contents.json b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/vision.imageset/Contents.json new file mode 100644 index 000000000..0c0cddcdc --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/vision.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "vision.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/vision.imageset/vision.svg b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/vision.imageset/vision.svg new file mode 100644 index 000000000..8225a460f --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Assets.xcassets/vision.imageset/vision.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Info.plist b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Info.plist new file mode 100644 index 000000000..c5a7b5685 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Info.plist @@ -0,0 +1,19 @@ + + + + + UIBackgroundModes + + voip + + NSCameraUsageDescription + Camera is necessary for transmitting video in a call + NSMicrophoneUsageDescription + Microphone is necessary for transmitting audio in a call + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Preview Content/Preview Assets.xcassets/Contents.json b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/SimpleChatbotApp.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/SimpleChatbotApp.swift new file mode 100644 index 000000000..7b1e78703 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/SimpleChatbotApp.swift @@ -0,0 +1,18 @@ +import SwiftUI + +@main +struct SimpleChatbotApp: App { + + @StateObject var callContainerModel = CallContainerModel() + + var body: some Scene { + WindowGroup { + if (!callContainerModel.isInCall) { + PreJoinView().environmentObject(callContainerModel) + } else { + MeetingView().environmentObject(callContainerModel) + } + } + } + +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/model/CallContainerModel.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/model/CallContainerModel.swift new file mode 100644 index 000000000..0f35f1326 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/model/CallContainerModel.swift @@ -0,0 +1,284 @@ +import SwiftUI + +import PipecatClientIOSSmallWebrtc +import PipecatClientIOS + +class CallContainerModel: ObservableObject { + + @Published var voiceClientStatus: String = TransportState.disconnected.description + @Published var isInCall: Bool = false + @Published var isBotReady: Bool = false + + @Published var isMicEnabled: Bool = false + @Published var isCamEnabled: Bool = false + @Published var localCamId: MediaTrackId? = nil + @Published var botCamId: MediaTrackId? = nil + + @Published var toastMessage: String? = nil + @Published var showToast: Bool = false + + @Published var messages: [LiveMessage] = [] + @Published var liveBotMessage: LiveMessage? + @Published var liveUserMessage: LiveMessage? + + var rtviClientIOS: RTVIClient? + + @Published var selectedMic: MediaDeviceId? = nil { + didSet { + guard let selectedMic else { return } // don't store nil + var settings = SettingsManager.getSettings() + settings.selectedMic = selectedMic.id + SettingsManager.updateSettings(settings: settings) + } + } + @Published var availableMics: [MediaDeviceInfo] = [] + + init() { + // Changing the log level + PipecatClientIOS.setLogLevel(.warn) + PipecatClientIOSSmallWebrtc.setLogLevel(.info) + } + + @MainActor + func connect(backendURL: String) { + self.resetLiveMessages() + + let baseUrl = backendURL.trimmingCharacters(in: .whitespacesAndNewlines) + if(baseUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty){ + self.showError(message: "Need to fill the backendURL") + return + } + + let currentSettings = SettingsManager.getSettings() + let rtviClientOptions = RTVIClientOptions.init( + enableMic: currentSettings.enableMic, + enableCam: currentSettings.enableCam, + params: RTVIClientParams( + config: [ + .init( + service: SmallWebRTCTransport.SERVICE_NAME, + options: [ + .init(name: "server_url", value: .string(baseUrl)) + ] + ) + ] + ) + ) + self.rtviClientIOS = RTVIClient.init( + transport: SmallWebRTCTransport.init(options: rtviClientOptions), + options: rtviClientOptions + ) + self.rtviClientIOS?.delegate = self + + // Registering the llm helper, we will need this to handle the function calling + let llmHelper = try? self.rtviClientIOS?.registerHelper(service: "llm", helper: LLMHelper.self) + llmHelper?.delegate = self + + self.rtviClientIOS?.start() { result in + switch result { + case .failure(let error): + self.showError(message: error.localizedDescription) + self.rtviClientIOS = nil + case .success(): + // Apply initial mic preference + if let selectedMic = SettingsManager.getSettings().selectedMic { + self.selectMic(MediaDeviceId(id: selectedMic)) + } + // Populate available devices list + self.availableMics = self.rtviClientIOS?.getAllMics() ?? [] + } + } + self.saveCredentials(backendURL: backendURL) + } + + @MainActor + func disconnect() { + self.rtviClientIOS?.disconnect(completion: nil) + self.rtviClientIOS?.release() + self.rtviClientIOS = nil + } + + func showError(message: String) { + self.toastMessage = message + self.showToast = true + // Hide the toast after 5 seconds + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + self.showToast = false + self.toastMessage = nil + } + } + + @MainActor + func toggleMicInput() { + self.rtviClientIOS?.enableMic(enable: !self.isMicEnabled) { result in + switch result { + case .success(): + self.isMicEnabled = self.rtviClientIOS?.isMicEnabled ?? false + case .failure(let error): + self.showError(message: error.localizedDescription) + } + } + } + + @MainActor + func toggleCamInput() { + self.rtviClientIOS?.enableCam(enable: !self.isCamEnabled) { result in + switch result { + case .success(): + self.isCamEnabled = self.rtviClientIOS?.isCamEnabled ?? false + case .failure(let error): + self.showError(message: error.localizedDescription) + } + } + } + + func saveCredentials(backendURL: String) { + var currentSettings = SettingsManager.getSettings() + currentSettings.backendURL = backendURL + // Saving the settings + SettingsManager.updateSettings(settings: currentSettings) + } + + @MainActor + func selectMic(_ mic: MediaDeviceId) { + self.selectedMic = mic + self.rtviClientIOS?.updateMic(micId: mic, completion: nil) + } + + private func createLiveMessage(content:String = "", type:MessageType) { + // Creating a new one + DispatchQueue.main.async { + let liveMessage = LiveMessage(content: content, type: type, updatedAt: Date()) + self.messages.append(liveMessage) + if type == .bot { + self.liveBotMessage = liveMessage + } else if type == .user { + self.liveUserMessage = liveMessage + } + } + } + + private func appendTextToLiveMessage(fromBot: Bool, content:String) { + DispatchQueue.main.async { + // Updating the last message with the new content + if fromBot { + self.liveBotMessage?.content += content + } else { + self.liveUserMessage?.content += content + } + } + } + + private func resetLiveMessages() { + DispatchQueue.main.async { + self.messages = [] + } + } +} + +extension CallContainerModel:RTVIClientDelegate, LLMHelperDelegate { + + private func handleEvent(eventName: String, eventValue: Any? = nil) { + if let value = eventValue { + print("Pipecat Demo, received event: \(eventName), value:\(value)") + } else { + print("Pipecat Demo, received event: \(eventName)") + } + } + + func onTransportStateChanged(state: TransportState) { + Task { @MainActor in + self.handleEvent(eventName: "onTransportStateChanged", eventValue: state) + self.voiceClientStatus = state.description + self.isInCall = ( state == .connecting || state == .connected || state == .ready || state == .authenticating ) + self.createLiveMessage(content: state.description, type: .system) + } + } + + func onBotReady(botReadyData: BotReadyData) { + Task { @MainActor in + self.handleEvent(eventName: "onBotReady") + self.isBotReady = true + } + } + + func onConnected() { + Task { @MainActor in + self.handleEvent(eventName: "onConnected") + self.isMicEnabled = self.rtviClientIOS?.isMicEnabled ?? false + self.isCamEnabled = self.rtviClientIOS?.isCamEnabled ?? false + } + } + + func onDisconnected() { + Task { @MainActor in + self.handleEvent(eventName: "onDisconnected") + self.isBotReady = false + } + } + + func onError(message: String) { + Task { @MainActor in + self.handleEvent(eventName: "onError", eventValue: message) + self.showError(message: message) + } + } + + func onAvailableMicsUpdated(mics: [MediaDeviceInfo]) { + Task { @MainActor in + self.availableMics = mics + } + } + + func onMicUpdated(mic: MediaDeviceInfo?) { + Task { @MainActor in + self.selectedMic = mic?.id + } + } + + func onBotTranscript(data: String) { + self.handleEvent(eventName: "onBotTranscript", eventValue: data) + } + + func onTracksUpdated(tracks: Tracks) { + self.handleEvent(eventName: "onTracksUpdated", eventValue: tracks) + Task { @MainActor in + self.localCamId = tracks.local.video + self.botCamId = tracks.bot?.video ?? nil + } + } + + func onUserStartedSpeaking() { + self.createLiveMessage(content: "User started speaking", type: .system) + self.handleEvent(eventName: "onUserStartedSpeaking") + self.createLiveMessage(type: .user) + } + + func onUserStoppedSpeaking() { + self.createLiveMessage(content: "User stopped speaking", type: .system) + self.handleEvent(eventName: "onUserStoppedSpeaking") + } + + func onBotStartedSpeaking() { + self.createLiveMessage(content: "Bot started speaking", type: .system) + self.handleEvent(eventName: "onBotStartedSpeaking") + self.createLiveMessage(type: .bot) + } + + func onBotStoppedSpeaking() { + self.createLiveMessage(content: "Bot stopped speaking", type: .system) + self.handleEvent(eventName: "onBotStoppedSpeaking") + } + + func onUserTranscript(data: Transcript) { + if data.final ?? false { + self.handleEvent(eventName: "onUserTranscript", eventValue: data.text) + self.appendTextToLiveMessage(fromBot: false, content: data.text) + } + } + + func onBotTTSText(data: BotTTSText) { + self.appendTextToLiveMessage(fromBot: true, content: data.text) + } + +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/model/MockCallContainerModel.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/model/MockCallContainerModel.swift new file mode 100644 index 000000000..aabc6e2a2 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/model/MockCallContainerModel.swift @@ -0,0 +1,43 @@ +import SwiftUI +import PipecatClientIOS + +class MockCallContainerModel: CallContainerModel { + + override init() { + super.init() + let liveMessageFromSystem = LiveMessage( + content: "System message", + type: .system, + updatedAt: Date() + ) + let liveMessageFromUser = LiveMessage( + content: "Message from User", + type: .user, + updatedAt: Date() + ) + let liveMessageFromBot = LiveMessage( + content: "Message from bot", + type: .bot, + updatedAt: Date() + ) + self.messages = [ liveMessageFromSystem, liveMessageFromUser, liveMessageFromBot ] + } + + override func connect(backendURL: String) { + print("connect") + } + + override func disconnect() { + print("disconnect") + } + + override func showError(message: String) { + self.toastMessage = message + self.showToast = true + // Hide the toast after 5 seconds + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + self.showToast = false + self.toastMessage = nil + } + } +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/types/LiveMessage.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/types/LiveMessage.swift new file mode 100644 index 000000000..6e85214c0 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/types/LiveMessage.swift @@ -0,0 +1,21 @@ +import Foundation + +enum MessageType { + case bot, user, system +} + +class LiveMessage: ObservableObject, Identifiable, Equatable { + @Published var content: String + let type: MessageType + let updatedAt: Date + + init(content: String, type: MessageType, updatedAt: Date) { + self.content = content + self.type = type + self.updatedAt = updatedAt + } + + static func == (lhs: LiveMessage, rhs: LiveMessage) -> Bool { + lhs.updatedAt == rhs.updatedAt + } +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/MeetingView.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/MeetingView.swift new file mode 100644 index 000000000..8a1a5c361 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/MeetingView.swift @@ -0,0 +1,81 @@ +import SwiftUI +import PipecatClientIOSSmallWebrtc + +struct MeetingView: View { + + @State private var showingSettings = false + @EnvironmentObject private var model: CallContainerModel + + var body: some View { + VStack { + ZStack { + SmallWebRTCVideoViewSwiftUI(videoTrack: self.model.botCamId, videoScaleMode: .fill) + .edgesIgnoringSafeArea(.all) + + VStack { + ChatView() + .frame(maxHeight: .infinity) + + HStack { + MicrophoneView(audioLevel: 0, isMuted: !self.model.isMicEnabled) + .frame(width: 100, height: 100) + .onTapGesture { + self.model.toggleMicInput() + } + CameraButtonView(trackId: self.model.localCamId, isMuted: !self.model.isCamEnabled) + .frame(width: 120, height: 120) + .onTapGesture { + self.model.toggleCamInput() + } + } + .padding() + } + } + Button(action: { + self.showingSettings = true + }) { + HStack { + Image(systemName: "gearshape") + .resizable() + .frame(width: 24, height: 24) + Text("Settings") + } + .frame(maxWidth: .infinity) + .padding() + .sheet(isPresented: $showingSettings) { + SettingsView(showingSettings: $showingSettings).environmentObject(self.model) + } + } + .foregroundColor(.black) + .background(Color.white) + .border(Color.buttonsBorder, width: 1) + .cornerRadius(12) + .padding([.horizontal]) + + Button(action: { + self.model.disconnect() + }) { + HStack { + Image(systemName: "rectangle.portrait.and.arrow.right") + .resizable() + .frame(width: 24, height: 24) + Text("End") + } + .frame(maxWidth: .infinity) + .padding() + } + .foregroundColor(.white) + .background(Color.black) + .cornerRadius(12) + .padding([.bottom, .horizontal]) + } + .background(Color.backgroundApp) + .toast(message: model.toastMessage, isShowing: model.showToast) + } +} + +#Preview { + let mockModel = MockCallContainerModel() + let result = MeetingView().environmentObject(mockModel as CallContainerModel) + return result +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/PreJoinView.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/PreJoinView.swift new file mode 100644 index 000000000..64f9952fc --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/PreJoinView.swift @@ -0,0 +1,44 @@ +import SwiftUI + +struct PreJoinView: View { + + @State var backendURL: String + + @EnvironmentObject private var model: CallContainerModel + + init() { + let currentSettings = SettingsManager.getSettings() + self.backendURL = currentSettings.backendURL + } + + var body: some View { + VStack(spacing: 20) { + Image("pipecat") + .resizable() + .frame(width: 80, height: 80) + Text("Pipecat Client iOS.") + .font(.headline) + TextField("Server URL", text: $backendURL) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .frame(maxWidth: .infinity) + .padding([.bottom, .horizontal]) + Button("Connect") { + Task { + await self.model.connect(backendURL: self.backendURL) + } + } + .padding() + .background(Color.black) + .foregroundColor(.white) + .cornerRadius(8) + } + .padding() + .frame(maxHeight: .infinity) + .background(Color.backgroundApp) + .toast(message: model.toastMessage, isShowing: model.showToast) + } +} + +#Preview { + PreJoinView().environmentObject(MockCallContainerModel() as CallContainerModel) +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/CameraButtonView.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/CameraButtonView.swift new file mode 100644 index 000000000..c5c50e6cc --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/CameraButtonView.swift @@ -0,0 +1,42 @@ +import SwiftUI +import PipecatClientIOS +import PipecatClientIOSSmallWebrtc + +struct CameraButtonView: View { + var trackId: MediaTrackId? + var isMuted: Bool + + var body: some View { + GeometryReader { geometry in + let width = geometry.size.width + let circleSize = width * 0.9 + let innerCircleSize = width * 0.82 + + ZStack { + Circle() + .stroke(Color.gray, lineWidth: 1) + .frame(width: circleSize) + + if (!isMuted){ + SmallWebRTCVideoViewSwiftUI(videoTrack: trackId, videoScaleMode: .fill) + .aspectRatio(1, contentMode: .fit) + .clipShape(Circle()) + } else { + Circle() + .fill(Color.disabledVision) + .frame(width: innerCircleSize) + Image("vision") + .resizable() + .scaledToFit() + .frame(width: width * 0.3) + .foregroundColor(.green) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) // Ensures the ZStack is centered + } + } +} + +#Preview { + CameraButtonView(trackId: nil, isMuted: true) +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/ChatView.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/ChatView.swift new file mode 100644 index 000000000..751b4cbc3 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/ChatView.swift @@ -0,0 +1,96 @@ +import SwiftUI + +struct ChatView: View { + @EnvironmentObject private var model: CallContainerModel + @State private var timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect() + + var body: some View { + VStack { + ScrollViewReader { scrollViewProxy in + ScrollView { + VStack(spacing: 10) { + ForEach(self.model.messages) { message in + MessageView(message: message) + .frame(maxWidth: .infinity, alignment: messageAlignment(for: message.type)) + .padding(.horizontal) + .id(message.id) + } + } + .onChange(of: self.model.messages) { _, _ in + scrollToLastMessage(scrollViewProxy) + } + } + .onReceive(timer) { _ in + scrollToLastMessage(scrollViewProxy) + } + .onAppear { + scrollToLastMessage(scrollViewProxy) + } + } + } + .edgesIgnoringSafeArea(.bottom) + } + + private func messageAlignment(for type: MessageType) -> Alignment { + switch type { + case .bot: return .leading + case .user: return .trailing + case .system: return .center + } + } + + private func scrollToLastMessage(_ scrollViewProxy: ScrollViewProxy) { + if let lastMessageId = self.model.messages.last?.id { + withAnimation { + scrollViewProxy.scrollTo(lastMessageId, anchor: .bottom) + } + } + } +} + +struct MessageView: View { + @ObservedObject var message: LiveMessage + + var body: some View { + HStack { + if message.type == .bot { + Image(systemName: "gearshape") + .resizable() + .frame(width: 24, height: 24) + } + + Text(message.content) + .padding(message.type == .system ? 5 : 10) + .foregroundColor(.white) + .background(messageBackgroundColor(for: message.type)) + .cornerRadius(15) + .overlay( + RoundedRectangle(cornerRadius: 15) + .stroke(Color.gray.opacity(0.5), lineWidth: 1) + ) + } + .padding(messagePadding(for: message.type)) + } + + private func messageBackgroundColor(for type: MessageType) -> Color { + switch type { + case .bot: return .black + case .user: return .gray + case .system: return .blue.opacity(0.6) + } + } + + private func messagePadding(for type: MessageType) -> EdgeInsets { + switch type { + case .bot: return EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 40) + case .user: return EdgeInsets(top: 0, leading: 40, bottom: 0, trailing: 0) + case .system: return EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) + } + } +} + +#Preview { + let mockModel = MockCallContainerModel() + let result = ChatView().environmentObject(mockModel as CallContainerModel) + return result +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/MicrophoneView.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/MicrophoneView.swift new file mode 100644 index 000000000..7cdaf1e80 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/MicrophoneView.swift @@ -0,0 +1,44 @@ +import SwiftUI + +struct MicrophoneView: View { + var audioLevel: Float // Current audio level + var isMuted: Bool // Muted state + + var body: some View { + GeometryReader { geometry in + let width = geometry.size.width + let circleSize = width * 0.9 + let innerCircleSize = width * 0.82 + let audioCircleSize = CGFloat(audioLevel) * (width * 0.95) + + ZStack { + Circle() + .stroke(Color.gray, lineWidth: 1) + .frame(width: circleSize) + + Circle() + .fill(isMuted ? Color.disabledMic : Color.backgroundCircle) + .frame(width: innerCircleSize) + + if !isMuted { + Circle() + .fill(Color.micVolume) + .opacity(0.5) + .frame(width: audioCircleSize) + .animation(.easeInOut(duration: 0.2), value: audioLevel) + } + + Image(systemName: isMuted ? "mic.slash.fill" : "mic.fill") + .resizable() + .scaledToFit() + .frame(width: width * 0.2) + .foregroundColor(.white) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) // Ensures the ZStack is centered + } + } +} + +#Preview { + MicrophoneView(audioLevel: 1, isMuted: false) +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/ToastModifier.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/ToastModifier.swift new file mode 100644 index 000000000..9e36125eb --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/components/ToastModifier.swift @@ -0,0 +1,31 @@ +import SwiftUI + +struct ToastModifier: ViewModifier { + var message: String? + var isShowing: Bool + + func body(content: Content) -> some View { + ZStack { + content + if isShowing, let message = message { + VStack { + Text(message) + .padding() + .background(Color.black.opacity(0.7)) + .foregroundColor(.white) + .cornerRadius(8) + .transition(.slide) + .padding(.top, 50) + Spacer() + } + .animation(.easeInOut(duration: 0.5), value: isShowing) + } + } + } +} + +extension View { + func toast(message: String?, isShowing: Bool) -> some View { + self.modifier(ToastModifier(message: message, isShowing: isShowing)) + } +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/extensions/CustomColors.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/extensions/CustomColors.swift new file mode 100644 index 000000000..44faeee50 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/extensions/CustomColors.swift @@ -0,0 +1,26 @@ +import SwiftUI + +public extension Color { + + static let backgroundCircle = Color(hex: "#374151") + static let backgroundCircleNotConnected = Color(hex: "#D1D5DB") + static let backgroundApp = Color(hex: "#F9FAFB") + static let buttonsBorder = Color(hex: "#E5E7EB") + static let micVolume = Color(hex: "#86EFAC") + static let disabledMic = Color(hex: "#ee6b6e") + static let disabledVision = Color(hex: "#BBF7D0") + + init(hex: String) { + let scanner = Scanner(string: hex) + _ = scanner.scanString("#") + + var rgb: UInt64 = 0 + scanner.scanHexInt64(&rgb) + + let red = Double((rgb >> 16) & 0xFF) / 255.0 + let green = Double((rgb >> 8) & 0xFF) / 255.0 + let blue = Double(rgb & 0xFF) / 255.0 + + self.init(red: red, green: green, blue: blue) + } +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsManager.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsManager.swift new file mode 100644 index 000000000..495780dfd --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsManager.swift @@ -0,0 +1,21 @@ +import Foundation + +class SettingsManager { + private static let preferencesKey = "settingsPreference" + + static func getSettings() -> SettingsPreference { + if let data = UserDefaults.standard.data(forKey: preferencesKey), + let settings = try? JSONDecoder().decode(SettingsPreference.self, from: data) { + return settings + } else { + // default values in case we don't have any settings + return SettingsPreference(enableMic: true, enableCam: true, backendURL: "http://YOUR_IP:7860") + } + } + + static func updateSettings(settings: SettingsPreference) { + if let data = try? JSONEncoder().encode(settings) { + UserDefaults.standard.set(data, forKey: preferencesKey) + } + } +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsPreference.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsPreference.swift new file mode 100644 index 000000000..5314340b2 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsPreference.swift @@ -0,0 +1,9 @@ +import Foundation + +struct SettingsPreference: Codable { + var selectedMic: String? + var enableMic: Bool + var enableCam: Bool + var backendURL: String +} + diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsView.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsView.swift new file mode 100644 index 000000000..22ac55b0c --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot/views/settings/SettingsView.swift @@ -0,0 +1,82 @@ +import SwiftUI + +struct SettingsView: View { + + @EnvironmentObject private var model: CallContainerModel + + @Binding var showingSettings: Bool + + @State private var isMicEnabled: Bool = true + @State private var isCamEnabled: Bool = true + @State private var backendURL: String = "" + + var body: some View { + NavigationView { + Form { + Section { + List(model.availableMics, id: \.self.id.id) { mic in + Button(action: { + model.selectMic(mic.id) + }) { + HStack { + Text(mic.name) + Spacer() + if mic.id == model.selectedMic { + Image(systemName: "checkmark") + } + } + } + } + } header: { + VStack(alignment: .leading) { + Text("Audio Settings") + Text("(No selection = system default)") + } + } + Section(header: Text("Start options")) { + Toggle("Enable Microphone", isOn: $isMicEnabled) + Toggle("Enable Cam", isOn: $isCamEnabled) + } + Section(header: Text("Server")) { + TextField("Backend URL", text: $backendURL) + .keyboardType(.URL) + } + } + .navigationTitle("Settings") + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Close") { + self.saveSettings() + self.showingSettings = false + } + } + } + .onAppear { + self.loadSettings() + } + } + } + + private func saveSettings() { + let newSettings = SettingsPreference( + selectedMic: model.selectedMic?.id, + enableMic: isMicEnabled, + enableCam: isCamEnabled, + backendURL: backendURL + ) + SettingsManager.updateSettings(settings: newSettings) + } + + private func loadSettings() { + let savedSettings = SettingsManager.getSettings() + self.isMicEnabled = savedSettings.enableMic + self.isCamEnabled = savedSettings.enableCam + self.backendURL = savedSettings.backendURL + } +} + +#Preview { + let mockModel = MockCallContainerModel() + let result = SettingsView(showingSettings: .constant(true)).environmentObject(mockModel as CallContainerModel) + return result +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotTests/SimpleChatbotTests.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotTests/SimpleChatbotTests.swift new file mode 100644 index 000000000..8fcb737d7 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotTests/SimpleChatbotTests.swift @@ -0,0 +1,29 @@ +import XCTest +@testable import SimpleChatbot + +final class SimpleChatbotTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotUITests/SimpleChatbotUITests.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotUITests/SimpleChatbotUITests.swift new file mode 100644 index 000000000..3a4d61916 --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotUITests/SimpleChatbotUITests.swift @@ -0,0 +1,34 @@ +import XCTest + +final class SimpleChatbotUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotUITests/SimpleChatbotUITestsLaunchTests.swift b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotUITests/SimpleChatbotUITestsLaunchTests.swift new file mode 100644 index 000000000..87d03195d --- /dev/null +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbotUITests/SimpleChatbotUITestsLaunchTests.swift @@ -0,0 +1,25 @@ +import XCTest + +final class SimpleChatbotUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} From 58714865dffa95d000d11b8ecc1eb3230230747e Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Mon, 7 Apr 2025 16:48:18 -0300 Subject: [PATCH 2/3] Using the public version of pipecat-client-ios-small-webrtc --- .../SimpleChatbot.xcodeproj/project.pbxproj | 26 ++++++++++++++----- .../xcshareddata/swiftpm/Package.resolved | 11 +++++++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.pbxproj b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.pbxproj index 169a2cba8..b6ea476e4 100644 --- a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.pbxproj +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -19,6 +19,7 @@ 90383A932D9C35B300D0DDA3 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90383A922D9C35B300D0DDA3 /* ChatView.swift */; }; 90383A962D9C35BD00D0DDA3 /* LiveMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90383A942D9C35BD00D0DDA3 /* LiveMessage.swift */; }; 90383A982D9D85E700D0DDA3 /* CameraButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90383A972D9D85E700D0DDA3 /* CameraButtonView.swift */; }; + 90383A9B2DA4620800D0DDA3 /* PipecatClientIOSSmallWebrtc in Frameworks */ = {isa = PBXBuildFile; productRef = 90383A9A2DA4620800D0DDA3 /* PipecatClientIOSSmallWebrtc */; }; 90ABB98E2C735ED6000D9CC7 /* MeetingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB98D2C735ED6000D9CC7 /* MeetingView.swift */; }; 90ABB9932C73820D000D9CC7 /* MicrophoneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9922C73820D000D9CC7 /* MicrophoneView.swift */; }; 90ABB9982C738356000D9CC7 /* CustomColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9972C738356000D9CC7 /* CustomColors.swift */; }; @@ -78,6 +79,7 @@ buildActionMask = 2147483647; files = ( 90383A912D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc in Frameworks */, + 90383A9B2DA4620800D0DDA3 /* PipecatClientIOSSmallWebrtc in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -233,6 +235,7 @@ name = SimpleChatbot; packageProductDependencies = ( 90383A902D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc */, + 90383A9A2DA4620800D0DDA3 /* PipecatClientIOSSmallWebrtc */, ); productName = SimpleChatbot; productReference = 90031FA32C616EE700408370 /* SimpleChatbot.app */; @@ -307,7 +310,7 @@ ); mainGroup = 90031F9A2C616EE700408370; packageReferences = ( - 90383A8F2D9C357F00D0DDA3 /* XCLocalSwiftPackageReference "../../../../../../ios-public-repos/pipecat-client-ios-small-webrtc" */, + 90383A992DA4620800D0DDA3 /* XCRemoteSwiftPackageReference "pipecat-client-ios-small-webrtc" */, ); productRefGroup = 90031FA42C616EE700408370 /* Products */; projectDirPath = ""; @@ -697,18 +700,27 @@ }; /* End XCConfigurationList section */ -/* Begin XCLocalSwiftPackageReference section */ - 90383A8F2D9C357F00D0DDA3 /* XCLocalSwiftPackageReference "../../../../../../ios-public-repos/pipecat-client-ios-small-webrtc" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = "../../../../../../ios-public-repos/pipecat-client-ios-small-webrtc"; +/* Begin XCRemoteSwiftPackageReference section */ + 90383A992DA4620800D0DDA3 /* XCRemoteSwiftPackageReference "pipecat-client-ios-small-webrtc" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pipecat-ai/pipecat-client-ios-small-webrtc"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.0.1; + }; }; -/* End XCLocalSwiftPackageReference section */ +/* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 90383A902D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc */ = { isa = XCSwiftPackageProductDependency; productName = PipecatClientIOSSmallWebrtc; }; + 90383A9A2DA4620800D0DDA3 /* PipecatClientIOSSmallWebrtc */ = { + isa = XCSwiftPackageProductDependency; + package = 90383A992DA4620800D0DDA3 /* XCRemoteSwiftPackageReference "pipecat-client-ios-small-webrtc" */; + productName = PipecatClientIOSSmallWebrtc; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 90031F9B2C616EE700408370 /* Project object */; diff --git a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 07802b253..a36695ef7 100644 --- a/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/examples/p2p-webrtc/video-transform/client/ios/SimpleChatbot.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "64a760bff8651c5464a11324100b2fb46be172491d43cf2e87e2e5a2d88303c7", + "originHash" : "77cb3fee4071811f880e69dbcd5a8ba01711a73372960391d6366c4c3a0d36eb", "pins" : [ { "identity" : "pipecat-client-ios", @@ -10,6 +10,15 @@ "version" : "0.3.5" } }, + { + "identity" : "pipecat-client-ios-small-webrtc", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pipecat-ai/pipecat-client-ios-small-webrtc", + "state" : { + "revision" : "a6e4516b1fcbed772ca97a9616dddc9329097958", + "version" : "0.0.1" + } + }, { "identity" : "webrtc", "kind" : "remoteSourceControl", From 3bbc75110a4074e71d9cf852e1e1c26627dc8404 Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Mon, 7 Apr 2025 16:54:26 -0300 Subject: [PATCH 3/3] Mentioning the iOS client inside the changelog and fixing the readme. --- CHANGELOG.md | 2 ++ examples/p2p-webrtc/video-transform/client/ios/README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af21e0bca..19da1d16b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added a new iOS client option to the `SmallWebRTCTransport` **video-transform** example. + - Added new processors `ProducerProcessor` and `ConsumerProcessor`. The producer processor processes frames from the pipeline and decides whether the consumers should consume it or not. If so, the same frame that is received by diff --git a/examples/p2p-webrtc/video-transform/client/ios/README.md b/examples/p2p-webrtc/video-transform/client/ios/README.md index 02c27582d..d9cd6539c 100644 --- a/examples/p2p-webrtc/video-transform/client/ios/README.md +++ b/examples/p2p-webrtc/video-transform/client/ios/README.md @@ -5,7 +5,7 @@ Basic implementation using the [Pipecat iOS SDK](https://docs.pipecat.ai/client/ ## Prerequisites 1. Run the bot server. See the [server README](../../server). -2. Install [Xcode 15](https://developer.apple.com/xcode/), and set up your device [to run your own applications](https://developer.apple.com/documentation/xcode/distributing-your-app-to-registered-devices). +2. Install [Xcode](https://developer.apple.com/xcode/), and set up your device [to run your own applications](https://developer.apple.com/documentation/xcode/distributing-your-app-to-registered-devices). ## Running locally