diff --git a/application-client/openvidu-ios/LICENSE b/application-client/openvidu-ios/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/application-client/openvidu-ios/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.pbxproj b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.pbxproj new file mode 100644 index 00000000..994b611e --- /dev/null +++ b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.pbxproj @@ -0,0 +1,552 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 5807F0662C610D09000F07E5 /* ConfigureUrlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807F0652C610D09000F07E5 /* ConfigureUrlsView.swift */; }; + 58FE57B12C4E637800759796 /* HttpClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE57B02C4E637800759796 /* HttpClient.swift */; }; + 58FE57B32C4E699200759796 /* TokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE57B22C4E699200759796 /* TokenModel.swift */; }; + 680FE2F227A8EF7700B6F6DB /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = 680FE2F127A8EF7700B6F6DB /* SFSafeSymbols */; }; + 6813CEFD2C09D2E30025091A /* Binding+OptionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6813CEFC2C09D2E30025091A /* Binding+OptionSet.swift */; }; + 6816968E2AF96240008ED486 /* Participant+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6816968D2AF96240008ED486 /* Participant+Helpers.swift */; }; + 6816B1B0272D9198005ADB85 /* ParticipantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6816B1AF272D9198005ADB85 /* ParticipantView.swift */; }; + 681E3F39271FC772007BB547 /* RoomContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F38271FC772007BB547 /* RoomContext.swift */; }; + 681E3F3F271FC795007BB547 /* Custom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F3E271FC795007BB547 /* Custom.swift */; }; + 681E3F43271FC7AD007BB547 /* RoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F41271FC7AC007BB547 /* RoomView.swift */; }; + 681E3F45271FC7AD007BB547 /* ConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681E3F42271FC7AD007BB547 /* ConnectView.swift */; }; + 6847616427B44A1A001611BE /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6847616327B44A1A001611BE /* Bundle.swift */; }; + 6867533B27A65652003707B9 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6867533A27A65652003707B9 /* AppContext.swift */; }; + 68816CC127B4D6BC00E24622 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 68816CC027B4D6BC00E24622 /* KeychainAccess */; }; + 68816CC527B4DCD500E24622 /* SecureStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68816CC427B4DCD500E24622 /* SecureStore.swift */; }; + 688D931A283FE244003CA647 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 68B3853E271E780700711D5F /* Assets.xcassets */; }; + 68B3854C271E780700711D5F /* OpenViduApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B3853C271E780600711D5F /* OpenViduApp.swift */; }; + 68FBA43F2A38B49C0015853E /* LiveKit in Frameworks */ = {isa = PBXBuildFile; productRef = 68FBA43E2A38B49C0015853E /* LiveKit */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 68EA18EB27F2E91100F9AE48 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 5807F0652C610D09000F07E5 /* ConfigureUrlsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigureUrlsView.swift; sourceTree = ""; }; + 58FE57B02C4E637800759796 /* HttpClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpClient.swift; sourceTree = ""; }; + 58FE57B22C4E699200759796 /* TokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenModel.swift; sourceTree = ""; }; + 6813CEFC2C09D2E30025091A /* Binding+OptionSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+OptionSet.swift"; sourceTree = ""; }; + 6816968D2AF96240008ED486 /* Participant+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Participant+Helpers.swift"; sourceTree = ""; }; + 6816B1AF272D9198005ADB85 /* ParticipantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantView.swift; sourceTree = ""; }; + 681E3F38271FC772007BB547 /* RoomContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomContext.swift; sourceTree = ""; }; + 681E3F3E271FC795007BB547 /* Custom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Custom.swift; sourceTree = ""; }; + 681E3F41271FC7AC007BB547 /* RoomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomView.swift; sourceTree = ""; }; + 681E3F42271FC7AD007BB547 /* ConnectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectView.swift; sourceTree = ""; }; + 683F05F4273F96B20080C7AC /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; }; + 683F0603273FAD690080C7AC /* iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iOS.entitlements; sourceTree = ""; }; + 6847616327B44A1A001611BE /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; + 6865EA2527513B4500FFAFC3 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6867533A27A65652003707B9 /* AppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = ""; }; + 68816CC427B4DCD500E24622 /* SecureStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStore.swift; sourceTree = ""; }; + 68B3853C271E780600711D5F /* OpenViduApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenViduApp.swift; sourceTree = ""; }; + 68B3853E271E780700711D5F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 68B38543271E780700711D5F /* OpenViduIOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenViduIOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 68DEF27E2919EEFA00258494 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/ReplayKit.framework; sourceTree = DEVELOPER_DIR; }; + 9E7835E62751A71500559DEC /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.0.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 68B38540271E780700711D5F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 68FBA43F2A38B49C0015853E /* LiveKit in Frameworks */, + 680FE2F227A8EF7700B6F6DB /* SFSafeSymbols in Frameworks */, + 68816CC127B4D6BC00E24622 /* KeychainAccess in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 58FE57AF2C4E62FD00759796 /* Utils */ = { + isa = PBXGroup; + children = ( + 58FE57B02C4E637800759796 /* HttpClient.swift */, + ); + path = Utils; + sourceTree = ""; + }; + 58FE57B42C4FAFD600759796 /* Contexts */ = { + isa = PBXGroup; + children = ( + 6867533A27A65652003707B9 /* AppContext.swift */, + 681E3F38271FC772007BB547 /* RoomContext.swift */, + ); + path = Contexts; + sourceTree = ""; + }; + 681E3F47271FCB40007BB547 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 68DEF27E2919EEFA00258494 /* ReplayKit.framework */, + 9E7835E62751A71500559DEC /* CoreGraphics.framework */, + 683F05F4273F96B20080C7AC /* ReplayKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 683720D427A0640D007DA986 /* Support */ = { + isa = PBXGroup; + children = ( + 6847616327B44A1A001611BE /* Bundle.swift */, + 68816CC427B4DCD500E24622 /* SecureStore.swift */, + 6816968D2AF96240008ED486 /* Participant+Helpers.swift */, + 6813CEFC2C09D2E30025091A /* Binding+OptionSet.swift */, + 58FE57B22C4E699200759796 /* TokenModel.swift */, + ); + path = Support; + sourceTree = ""; + }; + 6865EA2427513B4500FFAFC3 /* iOS */ = { + isa = PBXGroup; + children = ( + 6865EA2527513B4500FFAFC3 /* Info.plist */, + 683F0603273FAD690080C7AC /* iOS.entitlements */, + ); + path = iOS; + sourceTree = ""; + }; + 6884B77A2750505B00732D47 /* Views */ = { + isa = PBXGroup; + children = ( + 6816B1AF272D9198005ADB85 /* ParticipantView.swift */, + 5807F0652C610D09000F07E5 /* ConfigureUrlsView.swift */, + 681E3F41271FC7AC007BB547 /* RoomView.swift */, + 681E3F42271FC7AD007BB547 /* ConnectView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 68B38536271E780600711D5F = { + isa = PBXGroup; + children = ( + 68B3853B271E780600711D5F /* Shared */, + 6865EA2427513B4500FFAFC3 /* iOS */, + 68B38544271E780700711D5F /* Products */, + 681E3F47271FCB40007BB547 /* Frameworks */, + ); + sourceTree = ""; + }; + 68B3853B271E780600711D5F /* Shared */ = { + isa = PBXGroup; + children = ( + 58FE57B42C4FAFD600759796 /* Contexts */, + 58FE57AF2C4E62FD00759796 /* Utils */, + 683720D427A0640D007DA986 /* Support */, + 6884B77A2750505B00732D47 /* Views */, + 681E3F3E271FC795007BB547 /* Custom.swift */, + 68B3853C271E780600711D5F /* OpenViduApp.swift */, + 68B3853E271E780700711D5F /* Assets.xcassets */, + ); + path = Shared; + sourceTree = ""; + }; + 68B38544271E780700711D5F /* Products */ = { + isa = PBXGroup; + children = ( + 68B38543271E780700711D5F /* OpenViduIOS.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 68B38542271E780700711D5F /* OpenViduIOS (iOS) */ = { + isa = PBXNativeTarget; + buildConfigurationList = 68B38554271E780700711D5F /* Build configuration list for PBXNativeTarget "OpenViduIOS (iOS)" */; + buildPhases = ( + 68B3853F271E780700711D5F /* Sources */, + 68B38540271E780700711D5F /* Frameworks */, + 68B38541271E780700711D5F /* Resources */, + 68EA18EB27F2E91100F9AE48 /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "OpenViduIOS (iOS)"; + packageProductDependencies = ( + 680FE2F127A8EF7700B6F6DB /* SFSafeSymbols */, + 68816CC027B4D6BC00E24622 /* KeychainAccess */, + 68FBA43E2A38B49C0015853E /* LiveKit */, + ); + productName = "Multiplatform-SwiftUI (iOS)"; + productReference = 68B38543271E780700711D5F /* OpenViduIOS.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 68B38537271E780600711D5F /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1330; + LastUpgradeCheck = 1520; + TargetAttributes = { + 68B38542271E780700711D5F = { + CreatedOnToolsVersion = 13.0; + }; + }; + }; + buildConfigurationList = 68B3853A271E780600711D5F /* Build configuration list for PBXProject "OpenViduIOS" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 68B38536271E780600711D5F; + packageReferences = ( + 685271E727407BBC006B4D6A /* XCRemoteSwiftPackageReference "swift-protobuf" */, + 680FE2F027A8EF7700B6F6DB /* XCRemoteSwiftPackageReference "SFSafeSymbols" */, + 68816CBF27B4D6BC00E24622 /* XCRemoteSwiftPackageReference "KeychainAccess" */, + 68FBA43D2A38B49C0015853E /* XCRemoteSwiftPackageReference "client-sdk-swift" */, + ); + productRefGroup = 68B38544271E780700711D5F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 68B38542271E780700711D5F /* OpenViduIOS (iOS) */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 68B38541271E780700711D5F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 688D931A283FE244003CA647 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 68B3853F271E780700711D5F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 681E3F45271FC7AD007BB547 /* ConnectView.swift in Sources */, + 6867533B27A65652003707B9 /* AppContext.swift in Sources */, + 5807F0662C610D09000F07E5 /* ConfigureUrlsView.swift in Sources */, + 681E3F39271FC772007BB547 /* RoomContext.swift in Sources */, + 681E3F43271FC7AD007BB547 /* RoomView.swift in Sources */, + 6816B1B0272D9198005ADB85 /* ParticipantView.swift in Sources */, + 68816CC527B4DCD500E24622 /* SecureStore.swift in Sources */, + 6813CEFD2C09D2E30025091A /* Binding+OptionSet.swift in Sources */, + 6816968E2AF96240008ED486 /* Participant+Helpers.swift in Sources */, + 68B3854C271E780700711D5F /* OpenViduApp.swift in Sources */, + 58FE57B12C4E637800759796 /* HttpClient.swift in Sources */, + 58FE57B32C4E699200759796 /* TokenModel.swift in Sources */, + 681E3F3F271FC795007BB547 /* Custom.swift in Sources */, + 6847616427B44A1A001611BE /* Bundle.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 68B38552271E780700711D5F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + 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; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_BITCODE = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "GL_SILENCE_DEPRECATION=1", + ); + 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; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 68B38553271E780700711D5F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + 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; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_BITCODE = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = "GL_SILENCE_DEPRECATION=1"; + 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; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 68B38555271E780700711D5F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = iOS; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/iOS/iOS.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.0; + DEVELOPMENT_TEAM = N5K98M7Q94; + ENABLE_HARDENED_RUNTIME = NO; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = iOS/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "OpenVidu iOS"; + INFOPLIST_KEY_NSCameraUsageDescription = "Please allow Camera access"; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "Please allow Microphone access"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = io.openvidu.ios; + PRODUCT_NAME = OpenViduIOS; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 68B38556271E780700711D5F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = iOS; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/iOS/iOS.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.0; + DEVELOPMENT_TEAM = N5K98M7Q94; + ENABLE_HARDENED_RUNTIME = NO; + "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = iOS/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "OpenVidu iOS"; + INFOPLIST_KEY_NSCameraUsageDescription = "Please allow Camera access"; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "Please allow Microphone access"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = io.openvidu.ios; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = io.test.prueba; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = io.test.prueba; + PRODUCT_NAME = OpenViduIOS; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 68B3853A271E780600711D5F /* Build configuration list for PBXProject "OpenViduIOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 68B38552271E780700711D5F /* Debug */, + 68B38553271E780700711D5F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 68B38554271E780700711D5F /* Build configuration list for PBXNativeTarget "OpenViduIOS (iOS)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 68B38555271E780700711D5F /* Debug */, + 68B38556271E780700711D5F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 680FE2F027A8EF7700B6F6DB /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SFSafeSymbols/SFSafeSymbols"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.3.0; + }; + }; + 685271E727407BBC006B4D6A /* XCRemoteSwiftPackageReference "swift-protobuf" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-protobuf.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.26.0; + }; + }; + 68816CBF27B4D6BC00E24622 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.2.2; + }; + }; + 68FBA43D2A38B49C0015853E /* XCRemoteSwiftPackageReference "client-sdk-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/livekit/client-sdk-swift"; + requirement = { + kind = exactVersion; + version = 2.0.11; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 680FE2F127A8EF7700B6F6DB /* SFSafeSymbols */ = { + isa = XCSwiftPackageProductDependency; + package = 680FE2F027A8EF7700B6F6DB /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; + productName = SFSafeSymbols; + }; + 68816CC027B4D6BC00E24622 /* KeychainAccess */ = { + isa = XCSwiftPackageProductDependency; + package = 68816CBF27B4D6BC00E24622 /* XCRemoteSwiftPackageReference "KeychainAccess" */; + productName = KeychainAccess; + }; + 68FBA43E2A38B49C0015853E /* LiveKit */ = { + isa = XCSwiftPackageProductDependency; + package = 68FBA43D2A38B49C0015853E /* XCRemoteSwiftPackageReference "client-sdk-swift" */; + productName = LiveKit; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 68B38537271E780600711D5F /* Project object */; +} diff --git a/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..2139e3fb --- /dev/null +++ b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,60 @@ +{ + "originHash" : "bf59b79f5d4ea0a54ef7e929b035c5821f5094ee990a7a369ad0cb3713f6c62d", + "pins" : [ + { + "identity" : "client-sdk-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/livekit/client-sdk-swift", + "state" : { + "revision" : "367d95fc9a16bab88c3ec73e019726f4608b2c18", + "version" : "2.0.11" + } + }, + { + "identity" : "keychainaccess", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git", + "state" : { + "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7", + "version" : "4.2.2" + } + }, + { + "identity" : "sfsafesymbols", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols", + "state" : { + "revision" : "e2e28f4e56e1769c2ec3c61c9355fc64eb7a535a", + "version" : "5.3.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", + "version" : "1.6.1" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "d57a5aecf24a25b32ec4a74be2f5d0a995a47c4b", + "version" : "1.27.0" + } + }, + { + "identity" : "webrtc-xcframework", + "kind" : "remoteSourceControl", + "location" : "https://github.com/livekit/webrtc-xcframework.git", + "state" : { + "revision" : "5640582dbac33b193608574d4aa2a8d5d825db74", + "version" : "125.6422.2" + } + } + ], + "version" : 3 +} diff --git a/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/xcuserdata/openvidu.xcuserdatad/UserInterfaceState.xcuserstate b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/xcuserdata/openvidu.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 00000000..8a448fa5 Binary files /dev/null and b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/project.xcworkspace/xcuserdata/openvidu.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/application-client/openvidu-ios/OpenViduIOS.xcodeproj/xcshareddata/xcschemes/BroadcastExt.xcscheme b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/xcshareddata/xcschemes/BroadcastExt.xcscheme new file mode 100644 index 00000000..d80a4317 --- /dev/null +++ b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/xcshareddata/xcschemes/BroadcastExt.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application-client/openvidu-ios/OpenViduIOS.xcodeproj/xcshareddata/xcschemes/OpenViduTutorial.xcscheme b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/xcshareddata/xcschemes/OpenViduTutorial.xcscheme new file mode 100644 index 00000000..4c04749e --- /dev/null +++ b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/xcshareddata/xcschemes/OpenViduTutorial.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application-client/openvidu-ios/OpenViduIOS.xcodeproj/xcuserdata/openvidu.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/xcuserdata/openvidu.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 00000000..01b2a19f --- /dev/null +++ b/application-client/openvidu-ios/OpenViduIOS.xcodeproj/xcuserdata/openvidu.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + diff --git a/application-client/openvidu-ios/README.md b/application-client/openvidu-ios/README.md new file mode 100644 index 00000000..d0a57e39 --- /dev/null +++ b/application-client/openvidu-ios/README.md @@ -0,0 +1,23 @@ +# Basic iOS + +Basic client application built for iOS using Swift. It internally uses [LiveKit Swift SDK (iOS/macOS)](https://github.com/livekit/client-sdk-swift). + +For further information, check the [tutorial documentation](https://livekit-tutorials.openvidu.io/tutorials/application-client/ios/). + +## Prerequisites + +- [Xcode](https://apps.apple.com/es/app/xcode/id497799835?mt=12) + +## Run + +1. Download repository + +```bash +git clone https://github.com/OpenVidu/openvidu-livekit-tutorials.git +``` + +2. Launch Xcode and open `OpenViduIOS.xcodeproj` + +3. **Run** the project from the menu **Product** → **Run** or by ⌘R. + +If you encounter code signing issues, make sure you change the **Team** and **bundle id** from the previous step. diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/AccentColor.colorset/Contents.json b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..22c4bb0a --- /dev/null +++ b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/AccentColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/Contents.json b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVBlue.colorset/Contents.json b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVBlue.colorset/Contents.json new file mode 100644 index 00000000..fbdef902 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVBlue.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0xAA", + "green" : "0x88", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVGray.colorset/Contents.json b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVGray.colorset/Contents.json new file mode 100644 index 00000000..f3b13148 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVGray.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x4D", + "green" : "0x4D", + "red" : "0x4D" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVGray2.colorset/Contents.json b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVGray2.colorset/Contents.json new file mode 100644 index 00000000..a35f84ea --- /dev/null +++ b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVGray2.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x29", + "green" : "0x27", + "red" : "0x1E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVYellow.colorset/Contents.json b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVYellow.colorset/Contents.json new file mode 100644 index 00000000..9fe8035b --- /dev/null +++ b/application-client/openvidu-ios/Shared/Assets.xcassets/Colors/OVYellow.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0xCB", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Contents.json b/application-client/openvidu-ios/Shared/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/Contents.json b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Contents.json b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Contents.json new file mode 100644 index 00000000..98c276d2 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "Icon-App-20x20@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-20x20@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-29x29@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-40x40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-40x40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-60x60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "Icon-App-60x60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "Icon-App-20x20@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-20x20@2x 1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-29x29@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@2x 1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-40x40@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-40x40@2x 1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-76x76@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "Icon-App-76x76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "Icon-App-83.5x83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "ios.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@1x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..4df0a955 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@1x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@2x 1.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@2x 1.png new file mode 100644 index 00000000..ab664347 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@2x 1.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@2x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..ab664347 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@2x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@3x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..79646b9d Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-20x20@3x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@1x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..188d6f06 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@1x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@2x 1.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@2x 1.png new file mode 100644 index 00000000..eeb3c553 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@2x 1.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@2x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..eeb3c553 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@2x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@3x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..922c6c34 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-29x29@3x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@1x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..ab664347 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@1x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@2x 1.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@2x 1.png new file mode 100644 index 00000000..d5a92d4d Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@2x 1.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@2x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..d5a92d4d Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@2x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@3x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..87cceab5 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-40x40@3x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-60x60@2x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..87cceab5 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-60x60@2x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-60x60@3x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..2a955124 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-60x60@3x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-76x76@1x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..36aa1a16 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-76x76@1x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-76x76@2x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..cb6f6a36 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-76x76@2x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-83.5x83.5@2x.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..ae3deec6 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/ios.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/ios.png new file mode 100644 index 00000000..53d9d34f Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Icons/iOS.appiconset/ios.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Images/Contents.json b/application-client/openvidu-ios/Shared/Assets.xcassets/Images/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Assets.xcassets/Images/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/Contents.json b/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/Contents.json new file mode 100644 index 00000000..4e12055a --- /dev/null +++ b/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "logo 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "logo 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/logo 1.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/logo 1.png new file mode 100644 index 00000000..a5ecc89b Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/logo 1.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/logo 2.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/logo 2.png new file mode 100644 index 00000000..bbe90369 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/logo 2.png differ diff --git a/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/logo.png b/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/logo.png new file mode 100644 index 00000000..7a73eee3 Binary files /dev/null and b/application-client/openvidu-ios/Shared/Assets.xcassets/Images/logo.imageset/logo.png differ diff --git a/application-client/openvidu-ios/Shared/Contexts/AppContext.swift b/application-client/openvidu-ios/Shared/Contexts/AppContext.swift new file mode 100644 index 00000000..8a2a025b --- /dev/null +++ b/application-client/openvidu-ios/Shared/Contexts/AppContext.swift @@ -0,0 +1,35 @@ +/* + * Copyright 2024 OpenVidu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Combine +import LiveKit +import SwiftUI + +// This class contains the logic to control behavior of the whole app. +final class AppContext: ObservableObject { + private let store: ValueStore + + @Published var applicationServerUrl: String = "" { + didSet { store.value.applicationServerUrl = applicationServerUrl } + } + + public init(store: ValueStore) { + self.store = store + + applicationServerUrl = store.value.applicationServerUrl + } + +} diff --git a/application-client/openvidu-ios/Shared/Contexts/RoomContext.swift b/application-client/openvidu-ios/Shared/Contexts/RoomContext.swift new file mode 100644 index 00000000..553e3d6c --- /dev/null +++ b/application-client/openvidu-ios/Shared/Contexts/RoomContext.swift @@ -0,0 +1,116 @@ +/* + * Copyright 2024 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import LiveKit +import SwiftUI + +// This class contains the logic to control behavior of the room. +final class RoomContext: ObservableObject { + let jsonEncoder = JSONEncoder() + let jsonDecoder = JSONDecoder() + + private let store: ValueStore + + // Used to show connection error dialog + @Published var shouldShowDisconnectReason: Bool = false + public var latestError: LiveKitError? + + public let room = Room() + + @Published var livekitUrl: String = "" { + didSet { store.value.livekitUrl = livekitUrl } + } + + @Published var name: String = "" { + didSet { store.value.name = name } + } + + @Published var localParticipantName: String = "" { + didSet { store.value.localParticipantName = localParticipantName } + } + + @Published var token: String = "" { + didSet { store.value.token = token } + } + + @Published var textFieldString: String = "" + + var _connectTask: Task? + + public init(store: ValueStore) { + self.store = store + room.add(delegate: self) + + livekitUrl = store.value.livekitUrl + name = store.value.name + token = store.value.token + localParticipantName = store.value.localParticipantName + + #if os(iOS) + UIApplication.shared.isIdleTimerDisabled = true + #endif + } + + deinit { + #if os(iOS) + UIApplication.shared.isIdleTimerDisabled = false + #endif + print("RoomContext.deinit") + } + + func cancelConnect() { + _connectTask?.cancel() + } + + @MainActor + func connect() async throws -> Room { + + let connectTask = Task.detached { [weak self] in + guard let self else { return } + try await self.room.connect(url: self.livekitUrl,token: self.token) + } + + _connectTask = connectTask + try await connectTask.value + + return room + } + + func disconnect() async { + await room.disconnect() + } + +} + +extension RoomContext: RoomDelegate { + + func room(_ room: Room, didUpdateConnectionState connectionState: ConnectionState, from oldValue: ConnectionState) { + print("Did update connectionState \(oldValue) -> \(connectionState)") + + if case .disconnected = connectionState, + let error = room.disconnectError, + error.type != .cancelled + { + latestError = room.disconnectError + + Task.detached { @MainActor [weak self] in + guard let self else { return } + self.shouldShowDisconnectReason = true + } + } + } + +} diff --git a/application-client/openvidu-ios/Shared/Custom.swift b/application-client/openvidu-ios/Shared/Custom.swift new file mode 100644 index 00000000..ddf30f49 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Custom.swift @@ -0,0 +1,114 @@ +/* + * Copyright 2024 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import SwiftUI + +extension Color { + static let ovYellow = Color("OVYellow") + static let ovBlue = Color("OVBlue") + static let ovGray = Color("OVGray") + static let ovGray2 = Color("OVGray2") +} + +struct LazyView: View { + let build: () -> Content + init(_ build: @autoclosure @escaping () -> Content) { + self.build = build + } + + var body: Content { + build() + } +} + +// Default button style for this example +struct LKButton: View { + let title: String + let action: () -> Void + + var body: some View { + Button(action: action, + label: { + Text(title.uppercased()) + .fontWeight(.bold) + .padding(.horizontal, 12) + .padding(.vertical, 10) + }) + .background(Color.ovBlue) + .cornerRadius(8) + } +} + +#if os(iOS) + extension LKTextField.`Type` { + func toiOSType() -> UIKeyboardType { + switch self { + case .URL: return .URL + case .ascii: return .asciiCapable + default: return .default + } + } + } +#endif + +#if os(macOS) + // Avoid showing focus border around textfield for macOS + extension NSTextField { + override open var focusRingType: NSFocusRingType { + get { .none } + set {} + } + } +#endif + +struct LKTextField: View { + enum `Type` { + case `default` + case URL + case ascii + case secret + } + + let title: String + @Binding var text: String + var type: Type = .default + + var body: some View { + VStack(alignment: .leading, spacing: 10.0) { + Text(title) + .fontWeight(.bold) + + Group { + if type == .secret { + SecureField("", text: $text) + } else { + TextField("", text: $text) + } + } + .textFieldStyle(.plain) + .disableAutocorrection(true) + .padding() + .overlay(RoundedRectangle(cornerRadius: 10.0) + .strokeBorder(Color.white.opacity(0.3), + style: StrokeStyle(lineWidth: 1.0))) + #if os(iOS) + .autocapitalization(.none) + .keyboardType(type.toiOSType()) + #endif + + }.frame(maxWidth: .infinity) + } +} diff --git a/application-client/openvidu-ios/Shared/OpenViduApp.swift b/application-client/openvidu-ios/Shared/OpenViduApp.swift new file mode 100644 index 00000000..eb4dbb36 --- /dev/null +++ b/application-client/openvidu-ios/Shared/OpenViduApp.swift @@ -0,0 +1,188 @@ +/* + * Copyright 2024 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import KeychainAccess +import LiveKit +import Logging +import SwiftUI + +let sync = ValueStore(store: Keychain(service: "io.openvidu.ios"), + key: "preferences", + default: Preferences()) + +struct RoomSwitchView: View { + @EnvironmentObject var appCtx: AppContext + @EnvironmentObject var roomCtx: RoomContext + @EnvironmentObject var room: Room + + var shouldShowRoomView: Bool { + room.connectionState == .connected || room.connectionState == .reconnecting + } + + var shouldShowConfigureUrlsView: Bool { + appCtx.applicationServerUrl.isEmpty || roomCtx.livekitUrl.isEmpty + + } + + func computeTitle() -> String { + if shouldShowRoomView { + var elements: [String] = [] + if let roomName = room.name { + elements.append(roomName) + } + if let localParticipantName = room.localParticipant.name { + elements.append(localParticipantName) + } + if let localParticipantIdentity = room.localParticipant.identity { + elements.append(String(describing: localParticipantIdentity)) + } + return elements.joined(separator: " ") + } + + return "OpenVidu" + } + + var body: some View { + ZStack { + Color.black + .ignoresSafeArea() + + if shouldShowRoomView { + RoomView() + } else { + if shouldShowConfigureUrlsView { + ConfigureUrlsView() + } else { + ConnectView() + } + } + } + .navigationTitle(computeTitle()) + } +} + +// Attaches RoomContext and Room to the environment +struct RoomContextView: View { + @EnvironmentObject var appCtx: AppContext + @StateObject var roomCtx = RoomContext(store: sync) + + var body: some View { + RoomSwitchView() + .environmentObject(roomCtx) + .environmentObject(roomCtx.room) + .environment(\.colorScheme, .dark) + .foregroundColor(Color.white) + .onDisappear { + print("\(String(describing: type(of: self))) onDisappear") + Task { + await roomCtx.disconnect() + } + } + .onOpenURL(perform: { url in + + guard let urlComponent = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return } + guard let host = url.host else { return } + + let secureValue = urlComponent.queryItems?.first(where: { $0.name == "secure" })?.value?.lowercased() + let secure = ["true", "1"].contains { $0 == secureValue } + + let tokenValue = urlComponent.queryItems?.first(where: { $0.name == "token" })?.value ?? "" + + var builder = URLComponents() + builder.scheme = secure ? "wss" : "ws" + builder.host = host + builder.port = url.port + + guard let builtUrl = builder.url?.absoluteString else { return } + + print("built URL: \(builtUrl), token: \(tokenValue)") + + Task { @MainActor in + roomCtx.livekitUrl = builtUrl + roomCtx.token = tokenValue + if !roomCtx.token.isEmpty { + try await roomCtx.connect() + } + } + }) + } +} + +extension Decimal { + mutating func round(_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode) { + var localCopy = self + NSDecimalRound(&self, &localCopy, scale, roundingMode) + } + + func rounded(_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode) -> Decimal { + var result = Decimal() + var localCopy = self + NSDecimalRound(&result, &localCopy, scale, roundingMode) + return result + } + + func remainder(of divisor: Decimal) -> Decimal { + let s = self as NSDecimalNumber + let d = divisor as NSDecimalNumber + let b = NSDecimalNumberHandler(roundingMode: .down, + scale: 0, + raiseOnExactness: false, + raiseOnOverflow: false, + raiseOnUnderflow: false, + raiseOnDivideByZero: false) + let quotient = s.dividing(by: d, withBehavior: b) + + let subtractAmount = quotient.multiplying(by: d) + return s.subtracting(subtractAmount) as Decimal + } +} + +@main +struct OpenViduApp: App { + @StateObject var appCtx = AppContext(store: sync) + + func nearestSafeScale(for target: Int, scale: Double) -> Decimal { + let p = Decimal(sign: .plus, exponent: -3, significand: 1) + let t = Decimal(target) + var s = Decimal(scale).rounded(3, .down) + + while (t * s / 2).remainder(of: 2) != 0 { + s = s + p + } + + return s + } + + init() { + LoggingSystem.bootstrap { + var logHandler = StreamLogHandler.standardOutput(label: $0) + logHandler.logLevel = .debug + return logHandler + } + } + + var body: some Scene { + WindowGroup { + RoomContextView() + .environmentObject(appCtx) + } + .handlesExternalEvents(matching: Set(arrayLiteral: "*")) + #if os(macOS) + .windowStyle(.hiddenTitleBar) + .windowToolbarStyle(.unifiedCompact) + #endif + } +} diff --git a/application-client/openvidu-ios/Shared/Support/Binding+OptionSet.swift b/application-client/openvidu-ios/Shared/Support/Binding+OptionSet.swift new file mode 100644 index 00000000..583aa156 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Support/Binding+OptionSet.swift @@ -0,0 +1,47 @@ +/* + * Copyright 2024 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import SwiftUI + +extension Binding where Value: OptionSet, Value == Value.Element { + func bindedValue(_ options: Value) -> Bool { + wrappedValue.contains(options) + } + + func bind( + _ options: Value, + animate: Bool = false + ) -> Binding { + .init { () -> Bool in + self.wrappedValue.contains(options) + } set: { newValue in + let body = { + if newValue { + self.wrappedValue.insert(options) + } else { + self.wrappedValue.remove(options) + } + } + guard animate else { + body() + return + } + withAnimation { + body() + } + } + } +} diff --git a/application-client/openvidu-ios/Shared/Support/Bundle.swift b/application-client/openvidu-ios/Shared/Support/Bundle.swift new file mode 100644 index 00000000..299f86c2 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Support/Bundle.swift @@ -0,0 +1,30 @@ +/* + * Copyright 2024 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +public extension Bundle { + var appName: String { getInfo("CFBundleName") } + var displayName: String { getInfo("CFBundleDisplayName") } + var language: String { getInfo("CFBundleDevelopmentRegion") } + var identifier: String { getInfo("CFBundleIdentifier") } + + var appBuild: String { getInfo("CFBundleVersion") } + var appVersionLong: String { getInfo("CFBundleShortVersionString") } + var appVersionShort: String { getInfo("CFBundleShortVersion") } + + private func getInfo(_ str: String) -> String { infoDictionary?[str] as? String ?? "⚠️" } +} diff --git a/application-client/openvidu-ios/Shared/Support/Participant+Helpers.swift b/application-client/openvidu-ios/Shared/Support/Participant+Helpers.swift new file mode 100644 index 00000000..44645433 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Support/Participant+Helpers.swift @@ -0,0 +1,28 @@ +/* + * Copyright 2024 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import LiveKit + +public extension Participant { + var mainVideoPublication: TrackPublication? { + firstCameraPublication + } + + var mainVideoTrack: VideoTrack? { + firstCameraVideoTrack + } + +} diff --git a/application-client/openvidu-ios/Shared/Support/SecureStore.swift b/application-client/openvidu-ios/Shared/Support/SecureStore.swift new file mode 100644 index 00000000..f076f1a2 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Support/SecureStore.swift @@ -0,0 +1,87 @@ +/* + * Copyright 2024 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Combine +import KeychainAccess +import LiveKit +import SwiftUI + +struct Preferences: Codable, Equatable { + // General + var applicationServerUrl = "" + + // Room + var livekitUrl = "" + var name = "" + var token = "" + var localParticipantName = "" + +} + +let encoder = JSONEncoder() +let decoder = JSONDecoder() + +class ValueStore: ObservableObject { + private let store: Keychain + private let key: String + private let message = "" + private weak var timer: Timer? + + public var value: T { + didSet { + guard oldValue != value else { return } + lazySync() + } + } + + private var storeWithOptions: Keychain { + store + .accessibility(.whenUnlocked) + .synchronizable(true) + } + + public init(store: Keychain, key: String, default: T) { + self.store = store + self.key = key + value = `default` + + if let data = try? storeWithOptions.getData(key), + let result = try? decoder.decode(T.self, from: data) + { + value = result + } + } + + deinit { + timer?.invalidate() + } + + public func lazySync() { + timer?.invalidate() + timer = Timer.scheduledTimer(withTimeInterval: 1, + repeats: false, + block: { _ in self.sync() }) + } + + public func sync() { + do { + let data = try encoder.encode(value) + try storeWithOptions.set(data, key: key) + } catch { + print("Failed to write in Keychain, error: \(error)") + } + } +} diff --git a/application-client/openvidu-ios/Shared/Support/TokenModel.swift b/application-client/openvidu-ios/Shared/Support/TokenModel.swift new file mode 100644 index 00000000..f21f30a6 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Support/TokenModel.swift @@ -0,0 +1,27 @@ +/* + * Copyright 2024 OpenVidu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import Foundation + +struct TokenRequest: Codable { + let roomName: String + let participantName: String +} + +struct TokenResponse: Codable { + let token: String +} diff --git a/application-client/openvidu-ios/Shared/Utils/HttpClient.swift b/application-client/openvidu-ios/Shared/Utils/HttpClient.swift new file mode 100644 index 00000000..5cf72ffa --- /dev/null +++ b/application-client/openvidu-ios/Shared/Utils/HttpClient.swift @@ -0,0 +1,55 @@ +/* + * Copyright 2024 OpenVidu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +class HTTPClient { + + func getToken(applicationServerUrl: String, roomName: String, participantName: String) async throws -> String { + guard let url = URL(string: "\(applicationServerUrl)/token") else { + throw URLError(.badURL) + } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let tokenRequest = TokenRequest(roomName: roomName, participantName: participantName) + let jsonData = try JSONEncoder().encode(tokenRequest) + request.httpBody = jsonData + + do { + let (data, response) = try await URLSession.shared.data(for: request) + + guard let httpResponse = response as? HTTPURLResponse else { + throw URLError(.badServerResponse) + } + + guard httpResponse.statusCode == 200 else { + let errorResponse = try? JSONDecoder().decode([String: String].self, from: data) + let errorMessage = errorResponse?["errorMessage"] ?? "Unknown error" + throw NSError(domain: "HTTPServiceError", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: "HTTP Error: \(errorMessage)"]) + } + + let tokenResponse = try JSONDecoder().decode(TokenResponse.self, from: data) + return tokenResponse.token + + } catch { + print("Error occurred: \(error.localizedDescription)") + throw error + } + } +} diff --git a/application-client/openvidu-ios/Shared/Views/ConfigureUrlsView.swift b/application-client/openvidu-ios/Shared/Views/ConfigureUrlsView.swift new file mode 100644 index 00000000..ad4aeead --- /dev/null +++ b/application-client/openvidu-ios/Shared/Views/ConfigureUrlsView.swift @@ -0,0 +1,95 @@ +/* + * Copyright 2024 OpenVidu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation +import LiveKit +import SFSafeSymbols +import SwiftUI + +struct ConfigureUrlsView: View { + + @EnvironmentObject var appCtx: AppContext + @EnvironmentObject var roomCtx: RoomContext + + @State private var applicationServerUrl: String = "" + @State private var livekitUrl: String = "" + @State private var errorMessage: String = "" + + var body: some View { + GeometryReader { geometry in + Color.ovGray + .ignoresSafeArea() + ScrollView { + VStack(alignment: .center, spacing: 40.0) { + + Text("Configure the LiveKit URL and the Application Server URL before connecting to the Room") + + VStack(spacing: 15) { + LKTextField(title: "Application Server URL", text: $applicationServerUrl, type: .URL) + LKTextField(title: "LiveKit URL", text: $livekitUrl, type: .URL) + } + .frame(maxWidth: 350) + + + HStack(alignment: .center) { + Spacer() + + LKButton(title: "Save") { + Task.detached { @MainActor in + let isApplicationServerValid = isValidURL(self.applicationServerUrl) + let isLivekitUrlValid = isValidURL(self.livekitUrl) + + if !isApplicationServerValid || !isLivekitUrlValid { + print("Invalid URLs") + errorMessage = "There was an error with the url values" + return + } + appCtx.applicationServerUrl = self.applicationServerUrl + roomCtx.livekitUrl = self.livekitUrl + errorMessage = "" + } + } + + Spacer() + } + + Text(errorMessage) + .foregroundColor(.red) + + + } + .padding() + .frame(width: geometry.size.width) // Make the scroll view full-width + .frame(minHeight: geometry.size.height) // Set the content’s min height to the parent + } + } +#if os(macOS) + .frame(minWidth: 500, minHeight: 500) +#endif + .alert(isPresented: $roomCtx.shouldShowDisconnectReason) { + Alert(title: Text("Disconnected"), + message: Text("Reason: " + String(describing: roomCtx.latestError))) + } + } + + func isValidURL(_ urlString: String) -> Bool { + guard let url = URL(string: urlString), + let scheme = url.scheme else { + return false + } + return ["http", "https", "ws", "wss"].contains(scheme) + } +} diff --git a/application-client/openvidu-ios/Shared/Views/ConnectView.swift b/application-client/openvidu-ios/Shared/Views/ConnectView.swift new file mode 100644 index 00000000..a2c98877 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Views/ConnectView.swift @@ -0,0 +1,125 @@ +/* + * Copyright 2024 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation +import LiveKit +import SFSafeSymbols +import SwiftUI + +struct ConnectView: View { + @EnvironmentObject var appCtx: AppContext + @EnvironmentObject var roomCtx: RoomContext + @EnvironmentObject var room: Room + + var httpService = HTTPClient() + + + var body: some View { + GeometryReader { geometry in + Color.ovGray + .ignoresSafeArea() + ScrollView { + VStack(alignment: .center, spacing: 40.0) { + VStack(spacing: 10) { + Image("logo") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 50) + .padding(.bottom, 10) + } + + VStack(spacing: 15) { + LKTextField(title: "Participant Name", text: $roomCtx.localParticipantName, type: .ascii) + LKTextField(title: "Room Name", text: $roomCtx.name, type: .ascii) + } + .frame(maxWidth: 350) + + if case .connecting = room.connectionState { + HStack(alignment: .center) { + ProgressView() + + LKButton(title: "Cancel") { + roomCtx.cancelConnect() + } + } + } else { + HStack(alignment: .center) { + + + Spacer() + + LKButton(title: "Reset urls") { + Task.detached { @MainActor in + roomCtx.livekitUrl = "" + appCtx.applicationServerUrl = "" + } + } + + + LKButton(title: "Connect") { + Task.detached { @MainActor in + await connectToRoom() + } + } + + Spacer() + } + } + } + .padding() + .frame(width: geometry.size.width) // Make the scroll view full-width + .frame(minHeight: geometry.size.height) // Set the content’s min height to the parent + } + } + #if os(macOS) + .frame(minWidth: 500, minHeight: 500) + #endif + .alert(isPresented: $roomCtx.shouldShowDisconnectReason) { + Alert(title: Text("Disconnected"), + message: Text("Reason: " + String(describing: roomCtx.latestError))) + } + } + + func connectToRoom() async { + let livekitUrl = roomCtx.livekitUrl + let roomName = roomCtx.name + let participantName = roomCtx.localParticipantName + let applicationServerUrl = appCtx.applicationServerUrl + + guard !livekitUrl.isEmpty, !roomName.isEmpty else { + print("LiveKit URL or room name is empty") + return + } + + do { + let token = try await httpService.getToken(applicationServerUrl: applicationServerUrl, roomName: roomName, participantName: participantName) + print("Token received: \(token)") + + if token.isEmpty { + print("Received empty token") + return + } + + roomCtx.token = token + print("Connecting to room...") + try await roomCtx.connect() + print("Room connected") + } catch { + print("Failed to get token: \(error.localizedDescription)") + } + } + +} diff --git a/application-client/openvidu-ios/Shared/Views/ParticipantView.swift b/application-client/openvidu-ios/Shared/Views/ParticipantView.swift new file mode 100644 index 00000000..af048bed --- /dev/null +++ b/application-client/openvidu-ios/Shared/Views/ParticipantView.swift @@ -0,0 +1,103 @@ +/* + * Copyright 2024 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import LiveKit +import SFSafeSymbols +import SwiftUI + +struct ParticipantView: View { + @ObservedObject var participant: Participant + @EnvironmentObject var appCtx: AppContext + + var videoViewMode: VideoView.LayoutMode = .fill + + @State private var isRendering: Bool = false + + func bgView(systemSymbol: SFSymbol, geometry: GeometryProxy) -> some View { + Image(systemSymbol: systemSymbol) + .resizable() + .aspectRatio(contentMode: .fit) + .foregroundColor(Color.ovGray2) + .frame(width: min(geometry.size.width, geometry.size.height) * 0.3) + .frame( + maxWidth: .infinity, + maxHeight: .infinity + ) + } + + var body: some View { + GeometryReader { geometry in + + ZStack(alignment: .bottom) { + // Background color + Color.ovGray + .ignoresSafeArea() + + // VideoView for the Participant + if let publication = participant.mainVideoPublication, + !publication.isMuted, + let track = publication.track as? VideoTrack + { + ZStack(alignment: .topLeading) { + SwiftUIVideoView(track, + layoutMode: videoViewMode, + isRendering: $isRendering) + + if !isRendering { + ProgressView().progressViewStyle(CircularProgressViewStyle()) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + } + + // Display participant name in the top leading corner + if let participantName = participant.identity?.description { + Text(participantName) // Assuming `participant` has a `name` property + .font(.headline) + .foregroundColor(.white) + .padding(8) + .background(Color.black.opacity(0.6)) + .cornerRadius(8) + .padding([.top, .leading], 16) // Adjust padding as needed + } + + } + } else if let publication = participant.mainVideoPublication as? RemoteTrackPublication, + case .notAllowed = publication.subscriptionState + { + // Show no permission icon + bgView(systemSymbol: .exclamationmarkCircle, geometry: geometry) + } else { + // Show no camera icon + ZStack(alignment: .topLeading) { + // Display participant name in the top leading corner + bgView(systemSymbol: .videoSlashFill, geometry: geometry) + + if let participantName = participant.identity?.description { + Text(participantName) // Assuming `participant` has a `name` property + .font(.headline) + .foregroundColor(.white) + .padding(8) + .background(Color.black.opacity(0.6)) + .cornerRadius(8) + .padding([.top, .leading], 16) // Adjust padding as needed + } + } + + } + } + .cornerRadius(8) + } + } +} diff --git a/application-client/openvidu-ios/Shared/Views/RoomView.swift b/application-client/openvidu-ios/Shared/Views/RoomView.swift new file mode 100644 index 00000000..fd87aa18 --- /dev/null +++ b/application-client/openvidu-ios/Shared/Views/RoomView.swift @@ -0,0 +1,385 @@ +/* + * Copyright 2024 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import LiveKit +import SFSafeSymbols +import SwiftUI + +#if !os(macOS) +let adaptiveMin = 170.0 +let toolbarPlacement: ToolbarItemPlacement = .bottomBar +#else +let adaptiveMin = 300.0 +let toolbarPlacement: ToolbarItemPlacement = .primaryAction +#endif + +extension CIImage { + // helper to create a `CIImage` for both platforms + convenience init(named name: String) { +#if !os(macOS) + self.init(cgImage: UIImage(named: name)!.cgImage!) +#else + self.init(data: NSImage(named: name)!.tiffRepresentation!)! +#endif + } +} + +#if os(macOS) +// keeps weak reference to NSWindow +class WindowAccess: ObservableObject { + private weak var window: NSWindow? + + deinit { + // reset changed properties + DispatchQueue.main.async { [weak window] in + window?.level = .normal + } + } + + @Published public var pinned: Bool = false { + didSet { + guard oldValue != pinned else { return } + level = pinned ? .floating : .normal + } + } + + private var level: NSWindow.Level { + get { window?.level ?? .normal } + set { + Task { @MainActor in + window?.level = newValue + objectWillChange.send() + } + } + } + + public func set(window: NSWindow?) { + self.window = window + Task { @MainActor in + objectWillChange.send() + } + } +} +#endif + +struct RoomView: View { + @EnvironmentObject var appCtx: AppContext + @EnvironmentObject var roomCtx: RoomContext + @EnvironmentObject var room: Room + + @State var isCameraPublishingBusy = false + @State var isMicrophonePublishingBusy = false + @State var isScreenSharePublishingBusy = false + + @State private var screenPickerPresented = false + @State private var publishOptionsPickerPresented = false + + @State private var cameraPublishOptions = VideoPublishOptions() + +#if os(macOS) + @ObservedObject private var windowAccess = WindowAccess() +#endif + + func sortedParticipants() -> [Participant] { + room.allParticipants.values.sorted { p1, p2 in + if p1 is LocalParticipant { return true } + if p2 is LocalParticipant { return false } + return (p1.joinedAt ?? Date()) < (p2.joinedAt ?? Date()) + } + } + + func content(geometry: GeometryProxy) -> some View { + VStack { + + HStack { + // Title Text + Text(roomCtx.name) + .font(.title2) + .fontWeight(.bold) + .multilineTextAlignment(.center) + .foregroundColor(.white) + .padding() + + Spacer() // Pushes the button to the right + Button(action: { + Task { + await roomCtx.disconnect() + } + }, label: { + HStack { + Image(systemSymbol: .xmarkCircleFill) + .renderingMode(.original) + Text("Leave Room") + .font(.headline) + .fontWeight(.semibold) + } + .padding(8) + .background(Color.red.opacity(0.8)) // Background color for the button + .foregroundColor(.white) // Text color + .cornerRadius(8) + }) + //.padding() + } + + // Re-connecting Status + if case .connecting = room.connectionState { + Text("Re-connecting...") + .font(.subheadline) + .foregroundColor(.white) + .padding() + .background(Color.black.opacity(0.6)) + .cornerRadius(8) + .padding(.bottom) + } + // Participant layout + HorVStack(axis: geometry.isTall ? .vertical : .horizontal, spacing: 5) { + Group { + ParticipantLayout(sortedParticipants(), spacing: 5) { participant in + ParticipantView(participant: participant, videoViewMode: .fill) + } + } + .frame( + minWidth: 0, + maxWidth: .infinity, + minHeight: 0, + maxHeight: .infinity + ) + } + .padding(5) + } + } + + var body: some View { + GeometryReader { geometry in + content(geometry: geometry) + } + .toolbar { + ToolbarItemGroup(placement: toolbarPlacement) { + HStack { + Spacer() + + let isMicrophoneEnabled = room.localParticipant.isMicrophoneEnabled() + let isCameraEnabled = room.localParticipant.isCameraEnabled() + + Button(action: { + Task { + isCameraPublishingBusy = true + defer { Task { @MainActor in isCameraPublishingBusy = false } } + do { + try await room.localParticipant.setCamera(enabled: !isCameraEnabled) + } catch { + print("Failed to toggle camera: \(error)") + } + } + }, label: { + Image(systemSymbol: isCameraEnabled ? .videoFill : .videoSlashFill) + .renderingMode(isCameraEnabled ? .original : .template) + }) + .disabled(isCameraPublishingBusy) + + + Button(action: { + Task { + isMicrophonePublishingBusy = true + defer { Task { @MainActor in isMicrophonePublishingBusy = false } } + do { + try await room.localParticipant.setMicrophone(enabled: !isMicrophoneEnabled) + } catch { + print("Failed to toggle microphone: \(error)") + } + } + }, + label: { + Image(systemSymbol: isMicrophoneEnabled ? .micFill : .micSlashFill) + .renderingMode(isMicrophoneEnabled ? .original : .template) + }) + .disabled(isMicrophonePublishingBusy) + + Spacer() + } + } + } + } + +} + +struct ParticipantLayout: View { + let views: [AnyView] + let spacing: CGFloat + + init( + _ data: Data, + id: KeyPath = \.self, + spacing: CGFloat, + @ViewBuilder content: @escaping (Data.Element) -> Content + ) { + self.spacing = spacing + views = data.map { AnyView(content($0[keyPath: id])) } + } + + func computeColumn(with geometry: GeometryProxy) -> (x: Int, y: Int) { + let sqr = Double(views.count).squareRoot() + let r: [Int] = [Int(sqr.rounded()), Int(sqr.rounded(.up))] + let c = geometry.isTall ? r : r.reversed() + return (x: c[0], y: c[1]) + } + + func grid(axis: Axis, geometry: GeometryProxy) -> some View { + ScrollView([axis == .vertical ? .vertical : .horizontal]) { + HorVGrid(axis: axis, columns: [GridItem(.flexible())], spacing: spacing) { + ForEach(0 ..< views.count, id: \.self) { i in + views[i] + .aspectRatio(1, contentMode: .fill) + } + } + .padding(axis == .horizontal ? [.leading, .trailing] : [.top, .bottom], + max(0, ((axis == .horizontal ? geometry.size.width : geometry.size.height) + - ((axis == .horizontal ? geometry.size.height : geometry.size.width) * CGFloat(views.count)) - (spacing * CGFloat(views.count - 1))) / 2)) + } + } + + var body: some View { + GeometryReader { geometry in + if views.isEmpty { + EmptyView() + } else if geometry.size.width <= 300 { + grid(axis: .vertical, geometry: geometry) + } else if geometry.size.height <= 300 { + grid(axis: .horizontal, geometry: geometry) + } else { + let verticalWhenTall: Axis = geometry.isTall ? .vertical : .horizontal + let horizontalWhenTall: Axis = geometry.isTall ? .horizontal : .vertical + + switch views.count { + // simply return first view + case 1: views[0] + case 3: HorVStack(axis: verticalWhenTall, spacing: spacing) { + views[0] + HorVStack(axis: horizontalWhenTall, spacing: spacing) { + views[1] + views[2] + } + } + case 5: HorVStack(axis: verticalWhenTall, spacing: spacing) { + views[0] + if geometry.isTall { + HStack(spacing: spacing) { + views[1] + views[2] + } + HStack(spacing: spacing) { + views[3] + views[4] + } + } else { + VStack(spacing: spacing) { + views[1] + views[3] + } + VStack(spacing: spacing) { + views[2] + views[4] + } + } + } + default: + let c = computeColumn(with: geometry) + VStack(spacing: spacing) { + ForEach(0 ... (c.y - 1), id: \.self) { y in + HStack(spacing: spacing) { + ForEach(0 ... (c.x - 1), id: \.self) { x in + let index = (y * c.x) + x + if index < views.count { + views[index] + } + } + } + } + } + } + } + } + } +} + +struct HorVStack: View { + let axis: Axis + let horizontalAlignment: HorizontalAlignment + let verticalAlignment: VerticalAlignment + let spacing: CGFloat? + let content: () -> Content + + init(axis: Axis = .horizontal, + horizontalAlignment: HorizontalAlignment = .center, + verticalAlignment: VerticalAlignment = .center, + spacing: CGFloat? = nil, + @ViewBuilder content: @escaping () -> Content) + { + self.axis = axis + self.horizontalAlignment = horizontalAlignment + self.verticalAlignment = verticalAlignment + self.spacing = spacing + self.content = content + } + + var body: some View { + Group { + if axis == .vertical { + VStack(alignment: horizontalAlignment, spacing: spacing, content: content) + } else { + HStack(alignment: verticalAlignment, spacing: spacing, content: content) + } + } + } +} + +struct HorVGrid: View { + let axis: Axis + let spacing: CGFloat? + let content: () -> Content + let columns: [GridItem] + + init(axis: Axis = .horizontal, + columns: [GridItem], + spacing: CGFloat? = nil, + @ViewBuilder content: @escaping () -> Content) + { + self.axis = axis + self.spacing = spacing + self.columns = columns + self.content = content + } + + var body: some View { + Group { + if axis == .vertical { + LazyVGrid(columns: columns, spacing: spacing, content: content) + } else { + LazyHGrid(rows: columns, spacing: spacing, content: content) + } + } + } +} + +extension GeometryProxy { + public var isTall: Bool { + size.height > size.width + } + + var isWide: Bool { + size.width > size.height + } +} diff --git a/application-client/openvidu-ios/iOS/Info.plist b/application-client/openvidu-ios/iOS/Info.plist new file mode 100644 index 00000000..5f756d0e --- /dev/null +++ b/application-client/openvidu-ios/iOS/Info.plist @@ -0,0 +1,77 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + LSApplicationCategoryType + public.app-category.video + CFBundleDisplayName + $(DISPLAY_NAME) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleURLTypes + + + CFBundleURLSchemes + + livekit + + + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption + + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSCameraUsageDescription + uses your camera for video chat + NSFaceIDUsageDescription + Keychain is used to store all preferences. + NSMicrophoneUsageDescription + uses your microphone for video chat + RTCAppGroupIdentifier + group.io.lopenvidu.ios + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + audio + voip + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + + diff --git a/application-client/openvidu-ios/iOS/iOS.entitlements b/application-client/openvidu-ios/iOS/iOS.entitlements new file mode 100644 index 00000000..07d22941 --- /dev/null +++ b/application-client/openvidu-ios/iOS/iOS.entitlements @@ -0,0 +1,20 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.network.client + + com.apple.security.network.server + + keychain-access-groups + + $(AppIdentifierPrefix)keychain-group.io.lopenvidu.ios + + +