diff --git a/.githooks/README.md b/.githooks/README.md new file mode 100644 index 00000000..78504c21 --- /dev/null +++ b/.githooks/README.md @@ -0,0 +1,7 @@ +# iOS UIKit Git Hooks + +To apply these hooks: + +```sh +git config --local core.hooksPath .githooks/ +``` \ No newline at end of file diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 00000000..1ec76ee6 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,40 @@ +#!/bin/zsh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin '../' pod 'AgoraUIKit_macOS', :path => '../' end diff --git a/Agora-SwiftUI-Example/Agora-SwiftUI-Example.xcodeproj/project.pbxproj b/Agora-SwiftUI-Example/Agora-SwiftUI-Example.xcodeproj/project.pbxproj index 256f2214..3b03571f 100644 --- a/Agora-SwiftUI-Example/Agora-SwiftUI-Example.xcodeproj/project.pbxproj +++ b/Agora-SwiftUI-Example/Agora-SwiftUI-Example.xcodeproj/project.pbxproj @@ -7,11 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + F327EEC928057D2A00C86D3D /* AgoraUIKit in Frameworks */ = {isa = PBXBuildFile; productRef = F327EEC828057D2A00C86D3D /* AgoraUIKit */; }; + F327EECB28057D3200C86D3D /* AgoraRtmControl in Frameworks */ = {isa = PBXBuildFile; productRef = F327EECA28057D3200C86D3D /* AgoraRtmControl */; }; F327FCFB259B97ED00922764 /* Agora_SwiftUI_ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F327FCFA259B97ED00922764 /* Agora_SwiftUI_ExampleApp.swift */; }; F327FCFD259B97ED00922764 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F327FCFC259B97ED00922764 /* ContentView.swift */; }; F327FCFF259B97EF00922764 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F327FCFE259B97EF00922764 /* Assets.xcassets */; }; F327FD02259B97EF00922764 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F327FD01259B97EF00922764 /* Preview Assets.xcassets */; }; - F3DDD460278D8C38003D4A16 /* AgoraUIKit_iOS in Frameworks */ = {isa = PBXBuildFile; productRef = F3DDD45F278D8C38003D4A16 /* AgoraUIKit_iOS */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -29,7 +30,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F3DDD460278D8C38003D4A16 /* AgoraUIKit_iOS in Frameworks */, + F327EEC928057D2A00C86D3D /* AgoraUIKit in Frameworks */, + F327EECB28057D3200C86D3D /* AgoraRtmControl in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -106,7 +108,8 @@ ); name = "Agora-SwiftUI-Example"; packageProductDependencies = ( - F3DDD45F278D8C38003D4A16 /* AgoraUIKit_iOS */, + F327EEC828057D2A00C86D3D /* AgoraUIKit */, + F327EECA28057D3200C86D3D /* AgoraRtmControl */, ); productName = "Agora-SwiftUI-Example"; productReference = F327FCF7259B97ED00922764 /* Agora-SwiftUI-Example.app */; @@ -353,9 +356,13 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ - F3DDD45F278D8C38003D4A16 /* AgoraUIKit_iOS */ = { + F327EEC828057D2A00C86D3D /* AgoraUIKit */ = { isa = XCSwiftPackageProductDependency; - productName = AgoraUIKit_iOS; + productName = AgoraUIKit; + }; + F327EECA28057D3200C86D3D /* AgoraRtmControl */ = { + isa = XCSwiftPackageProductDependency; + productName = AgoraRtmControl; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/Agora-SwiftUI-Example/Agora-SwiftUI-Example/ContentView.swift b/Agora-SwiftUI-Example/Agora-SwiftUI-Example/ContentView.swift index e8c5652a..bc5ce278 100644 --- a/Agora-SwiftUI-Example/Agora-SwiftUI-Example/ContentView.swift +++ b/Agora-SwiftUI-Example/Agora-SwiftUI-Example/ContentView.swift @@ -6,7 +6,7 @@ // import SwiftUI -import AgoraUIKit_iOS +import AgoraUIKit struct ContentView: View { @State private var connectedToChannel = false diff --git a/Agora-UIKit-Example/Agora-UIKit-Example.xcodeproj/project.pbxproj b/Agora-UIKit-Example/Agora-UIKit-Example.xcodeproj/project.pbxproj index b94c48d3..571134fc 100644 --- a/Agora-UIKit-Example/Agora-UIKit-Example.xcodeproj/project.pbxproj +++ b/Agora-UIKit-Example/Agora-UIKit-Example.xcodeproj/project.pbxproj @@ -7,7 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - F39B87D327200A8900C644C7 /* AgoraUIKit_iOS in Frameworks */ = {isa = PBXBuildFile; productRef = F39B87D227200A8900C644C7 /* AgoraUIKit_iOS */; }; + F366512627FDF05700DDD521 /* AgoraUIKit in Frameworks */ = {isa = PBXBuildFile; productRef = F366512527FDF05700DDD521 /* AgoraUIKit */; }; + F366512827FDF07100DDD521 /* AgoraRtmControl in Frameworks */ = {isa = PBXBuildFile; productRef = F366512727FDF07100DDD521 /* AgoraRtmControl */; }; F3C4328E256FB56C00E2AD43 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C4328D256FB56C00E2AD43 /* AppDelegate.swift */; }; F3C43290256FB56C00E2AD43 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C4328F256FB56C00E2AD43 /* SceneDelegate.swift */; }; F3C43292256FB56C00E2AD43 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C43291256FB56C00E2AD43 /* ViewController.swift */; }; @@ -16,8 +17,21 @@ F3C4329A256FB56F00E2AD43 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F3C43298256FB56F00E2AD43 /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + F33CA68E27FB3C7500541A7A /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - F39B87D0272009FA00C644C7 /* iOS-UIKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "iOS-UIKit"; path = ..; sourceTree = ""; }; + F366512327FDF03F00DDD521 /* iOS-UIKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "iOS-UIKit"; path = ..; sourceTree = ""; }; F3C4328A256FB56C00E2AD43 /* Agora-UIKit-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Agora-UIKit-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; F3C4328D256FB56C00E2AD43 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; F3C4328F256FB56C00E2AD43 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -33,35 +47,44 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F39B87D327200A8900C644C7 /* AgoraUIKit_iOS in Frameworks */, + F366512627FDF05700DDD521 /* AgoraUIKit in Frameworks */, + F366512827FDF07100DDD521 /* AgoraRtmControl in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - F39B87CF272009FA00C644C7 /* Packages */ = { + E4B1C9811C27629FB0E02419 /* Pods */ = { isa = PBXGroup; children = ( - F39B87D0272009FA00C644C7 /* iOS-UIKit */, ); - name = Packages; + path = Pods; sourceTree = ""; }; - F39B87D127200A8900C644C7 /* Frameworks */ = { + F366512427FDF05700DDD521 /* Frameworks */ = { isa = PBXGroup; children = ( ); name = Frameworks; sourceTree = ""; }; + F39B87CF272009FA00C644C7 /* Packages */ = { + isa = PBXGroup; + children = ( + F366512327FDF03F00DDD521 /* iOS-UIKit */, + ); + name = Packages; + sourceTree = ""; + }; F3C43281256FB56C00E2AD43 = { isa = PBXGroup; children = ( F39B87CF272009FA00C644C7 /* Packages */, F3C4328C256FB56C00E2AD43 /* Agora-UIKit-Example */, F3C4328B256FB56C00E2AD43 /* Products */, - F39B87D127200A8900C644C7 /* Frameworks */, + E4B1C9811C27629FB0E02419 /* Pods */, + F366512427FDF05700DDD521 /* Frameworks */, ); sourceTree = ""; }; @@ -97,6 +120,7 @@ F3C43286256FB56C00E2AD43 /* Sources */, F3C43287256FB56C00E2AD43 /* Frameworks */, F3C43288256FB56C00E2AD43 /* Resources */, + F33CA68E27FB3C7500541A7A /* Embed Frameworks */, ); buildRules = ( ); @@ -104,7 +128,8 @@ ); name = "Agora-UIKit-Example"; packageProductDependencies = ( - F39B87D227200A8900C644C7 /* AgoraUIKit_iOS */, + F366512527FDF05700DDD521 /* AgoraUIKit */, + F366512727FDF07100DDD521 /* AgoraRtmControl */, ); productName = "Agora-UIKit-Example"; productReference = F3C4328A256FB56C00E2AD43 /* Agora-UIKit-Example.app */; @@ -366,9 +391,13 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ - F39B87D227200A8900C644C7 /* AgoraUIKit_iOS */ = { + F366512527FDF05700DDD521 /* AgoraUIKit */ = { + isa = XCSwiftPackageProductDependency; + productName = AgoraUIKit; + }; + F366512727FDF07100DDD521 /* AgoraRtmControl */ = { isa = XCSwiftPackageProductDependency; - productName = AgoraUIKit_iOS; + productName = AgoraRtmControl; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/Agora-UIKit-Example/Agora-UIKit-Example/ViewController.swift b/Agora-UIKit-Example/Agora-UIKit-Example/ViewController.swift index 51dfcd5c..8997ddc8 100644 --- a/Agora-UIKit-Example/Agora-UIKit-Example/ViewController.swift +++ b/Agora-UIKit-Example/Agora-UIKit-Example/ViewController.swift @@ -6,9 +6,8 @@ // import UIKit - import AgoraRtcKit -import AgoraUIKit_iOS +import AgoraUIKit class ViewController: UIViewController { @@ -18,6 +17,9 @@ class ViewController: UIViewController { var agSettings = AgoraSettings() agSettings.enabledButtons = [.cameraButton, .micButton, .flipButton] + agSettings.buttonPosition = .right + AgoraVideoViewer.printLevel = .verbose + let agoraView = AgoraVideoViewer( connectionData: AgoraConnectionData( appId: <#Agora App ID#>, diff --git a/Agora-UIKit-Example/Podfile b/Agora-UIKit-Example/Podfile new file mode 100644 index 00000000..c09ee1ee --- /dev/null +++ b/Agora-UIKit-Example/Podfile @@ -0,0 +1,10 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'Agora-UIKit-Example' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for Agora-UIKit-Example + pod 'AgoraUIKit_iOS', :path => '../' +end diff --git a/AgoraRtmControl_iOS.podspec b/AgoraRtmControl_iOS.podspec new file mode 100644 index 00000000..0709a71a --- /dev/null +++ b/AgoraRtmControl_iOS.podspec @@ -0,0 +1,31 @@ +# +# Be sure to run `pod lib lint AgoraRtmControl_iOS.podspec' to ensure this is a +# valid spec before submitting. +# +# Any lines starting with a # are optional, but their use is encouraged +# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'AgoraRtmControl_iOS' + s.module_name = 'AgoraRtmControl' + s.version = ENV['LIB_VERSION'] || '1.8.0' + s.summary = 'Agora Real-time Messaging Wrapper.' + + s.description = <<-DESC +Use this Pod to interact with Agora Real-time messaging SDK with additional properties and commands, +to make the usage simpler with the AgoraRtmController class. + DESC + + s.homepage = 'https://github.com/AgoraIO-Community/iOS-UIKit' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'Max Cobb' => 'max@agora.io' } + s.source = { :git => 'https://github.com/AgoraIO-Community/iOS-UIKit.git', :tag => s.version.to_s } + + s.ios.deployment_target = '11.0' + s.swift_versions = ['5.0'] + + s.static_framework = true + s.source_files = 'Sources/AgoraRtmControl/*' + s.dependency 'AgoraRtm_iOS', '~> 1.4.10' +end diff --git a/AgoraRtmControl_macOS.podspec b/AgoraRtmControl_macOS.podspec new file mode 100644 index 00000000..db8416d6 --- /dev/null +++ b/AgoraRtmControl_macOS.podspec @@ -0,0 +1,31 @@ +# +# Be sure to run `pod lib lint AgoraRtmControl_macOS.podspec' to ensure this is a +# valid spec before submitting. +# +# Any lines starting with a # are optional, but their use is encouraged +# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'AgoraRtmControl_macOS' + s.module_name = 'AgoraRtmControl' + s.version = ENV['LIB_VERSION'] || '1.8.0' + s.summary = 'Agora Real-time Messaging Wrapper.' + + s.description = <<-DESC +Use this Pod to interact with Agora Real-time messaging SDK with additional properties and commands, +to make the usage simpler with the AgoraRtmController class. + DESC + + s.homepage = 'https://github.com/AgoraIO-Community/iOS-UIKit' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'Max Cobb' => 'max@agora.io' } + s.source = { :git => 'https://github.com/AgoraIO-Community/iOS-UIKit.git', :tag => s.version.to_s } + + s.macos.deployment_target = '10.14' + s.swift_versions = ['5.0'] + + s.static_framework = true + s.source_files = 'Sources/AgoraRtmControl/*' + s.dependency 'AgoraRtm_macOS', '~> 1.4.10' +end diff --git a/AgoraUIKit_iOS.podspec b/AgoraUIKit_iOS.podspec index 07911d58..7bc6e129 100644 --- a/AgoraUIKit_iOS.podspec +++ b/AgoraUIKit_iOS.podspec @@ -8,7 +8,8 @@ Pod::Spec.new do |s| s.name = 'AgoraUIKit_iOS' - s.version = '1.7.6' + s.module_name = 'AgoraUIKit' + s.version = ENV['LIB_VERSION'] || '1.8.0' s.summary = 'Agora video session UIKit template.' s.description = <<-DESC @@ -23,9 +24,14 @@ Use this Pod to create a video UIKit view that can be easily added to your iOS a s.ios.deployment_target = '13.0' s.swift_versions = ['5.0'] + s.static_framework = true s.source_files = 'Sources/Agora-UIKit/*' - s.dependency 'AgoraRtcEngine_iOS/RtcBasic' - s.dependency 'AgoraRtm_iOS', '~> 1.4.10' + s.dependency 'AgoraRtcEngine_iOS/RtcBasic', '~> 3.7.0' + s.default_subspec = 'UIKitFull' - s.static_framework = true + s.subspec 'UIKitBasic' do |cs| + end + s.subspec 'UIKitFull' do |cs| + cs.dependency 'AgoraRtmControl_iOS', "#{s.version.to_s}" + end end diff --git a/AgoraUIKit_macOS.podspec b/AgoraUIKit_macOS.podspec index 8ba30392..a61d1d9d 100644 --- a/AgoraUIKit_macOS.podspec +++ b/AgoraUIKit_macOS.podspec @@ -1,5 +1,5 @@ # -# Be sure to run `pod lib lint Agora-UIKit.podspec' to ensure this is a +# Be sure to run `pod lib lint AgoraUIKit_macOS.podspec' to ensure this is a # valid spec before submitting. # # Any lines starting with a # are optional, but their use is encouraged @@ -8,7 +8,8 @@ Pod::Spec.new do |s| s.name = 'AgoraUIKit_macOS' - s.version = '1.7.6' + s.module_name = 'AgoraUIKit' + s.version = ENV['LIB_VERSION'] || '1.8.0' s.summary = 'Agora video session AppKit template.' s.description = <<-DESC @@ -20,12 +21,18 @@ Use this Pod to create a video AppKit view that can be easily added to your macO s.author = { 'Max Cobb' => 'max@agora.io' } s.source = { :git => 'https://github.com/AgoraIO-Community/iOS-UIKit.git', :tag => s.version.to_s } - s.macos.deployment_target = '10.14' + s.macos.deployment_target = '10.15' s.swift_versions = ['5.0'] + s.static_framework = true s.source_files = 'Sources/Agora-UIKit/*' s.pod_target_xcconfig = { 'ONLY_ACTIVE_ARCH' => 'YES' } - s.dependency 'AgoraRtcEngine_macOS/RtcBasic' - s.dependency 'AgoraRtm_macOS', '~> 1.4.10' - s.static_framework = false + s.dependency 'AgoraRtcEngine_macOS/RtcBasic', '~> 3.7.0' + s.default_subspec = 'UIKitFull' + + s.subspec 'UIKitBasic' do |cs| + end + s.subspec 'UIKitFull' do |cs| + cs.dependency 'AgoraRtmControl_macOS', "#{s.version.to_s}" + end end diff --git a/Package.swift b/Package.swift index 775dbe92..a132672e 100644 --- a/Package.swift +++ b/Package.swift @@ -7,27 +7,35 @@ let package = Package( name: "AgoraUIKit_iOS", platforms: [.iOS(.v13)], products: [ - .library(name: "AgoraUIKit_iOS", targets: ["AgoraUIKit_iOS"]) + .library(name: "AgoraUIKit", targets: ["AgoraUIKit"]), + .library(name: "AgoraRtmControl", targets: ["AgoraRtmControl"]) ], dependencies: [ .package( name: "AgoraRtcKit", url: "https://github.com/AgoraIO/AgoraRtcEngine_iOS", - "3.4.5"..."3.6.2" + .upToNextMinor(from: Version(3, 7, 0)) ), .package( name: "AgoraRtmKit", url: "https://github.com/AgoraIO/AgoraRtm_iOS", - from: "1.4.10" + .upToNextMinor(from: Version(1, 4, 10)) ) ], targets: [ .target( - name: "AgoraUIKit_iOS", - dependencies: ["AgoraRtcKit", "AgoraRtmKit"], -// dependencies: [.product(name: "RtcBasic", package: "AgoraRtcKit"), "AgoraRtmKit"], + name: "AgoraUIKit", + dependencies: [.product(name: "RtcBasic", package: "AgoraRtcKit")], path: "Sources/Agora-UIKit" ), - .testTarget(name: "AgoraUIKit-Tests", dependencies: ["AgoraUIKit_iOS"], path: "Tests/Agora-UIKit-Tests") + .target( + name: "AgoraRtmControl", + dependencies: ["AgoraRtmKit"], + path: "Sources/AgoraRtmControl" + ), + .testTarget( + name: "AgoraUIKit-Tests", dependencies: ["AgoraUIKit", "AgoraRtmControl"], + path: "Tests/Agora-UIKit-Tests" + ) ] ) diff --git a/README.md b/README.md index 4bca581b..061ddc4a 100644 --- a/README.md +++ b/README.md @@ -10,117 +10,91 @@

- - - - Instantly integrate Agora in your own application or prototype using iOS or macOS.

+[More information available on this repo\'s Wiki](https://github.com/AgoraIO-Community/iOS-UIKit/wiki) + +[Click here for full documentation](https://agoraio-community.github.io/iOS-UIKit/) ## Requirements -- Device - - Either an iOS device with 12.0 or later - - Or a macOS computer with 10.14 or later -- Xcode 11 or later -- *CocoaPods (if installing with CocoaPods) +- iOS 13.0+ or a macOS 10.15 or later +- Xcode 12.3 or later +- CocoaPods (if installing with CocoaPods) - [An Agora developer account](https://www.agora.io/en/blog/how-to-get-started-with-agora?utm_source=github&utm_repo=agora-ios-uikit) -Once you have an Agora developer account and an App ID, you're ready to use this pod. - -[Click here for full documentation](https://agoraio-community.github.io/iOS-UIKit/) +Once you have an Agora developer account and an App ID, you're ready to use this package. ## Installation -### Swift Package Manager (Recommended, iOS Only) - -Add the URL of this repository to your Xcode 11+ Project. - -Go to File > Swift Packages > Add Package Dependency, and paste in this link: +Swift Package Manager and CocoaPods are both available offered for installation methods. -`https://github.com/AgoraIO-Community/iOS-UIKit` +The Pod for this package is called `AgoraUIKit_iOS` and `AgoraUIKit_macOS` for the two available platforms. -If you are using the developer preview, add `4.0.0-preview` in the version box there, otherwise use a version from `1.0.0` up to `2.0.0`. - ---- - -> If you have issues installing the Swift Package: -> In Xcode's File menu, select 'Swift Packages' and then 'Reset Package Caches'. - -### CocoaPods - -In your iOS or macOS project, add this pod to your repository by adding a file named `Podfile`, with contents similar to this: - -```ruby -# Uncomment the next line to define a global platform for your project -# platform :ios, '12.0' - -target 'Agora-UIKit-Example' do - # Comment the next line if you don't want to use dynamic frameworks - use_frameworks! - - # Uncomment the next line if you want to install for iOS - # pod 'AgoraUIKit_iOS', '~> 1.0' - - # Uncomment the next line if you want to install for macOS - # pod 'AgoraUIKit_macOS', '~> 1.0' -end -``` - -And then install the pods using `pod install --repo-update` - -If any of these steps are unclear, look at ["Using Cocoapods" on cocoapods.org](https://guides.cocoapods.org/using/using-cocoapods.html). -The installation will change slightly once this pod is out of pre-release. +See the [Installation wiki](https://github.com/AgoraIO-Community/iOS-UIKit/wiki/Installation) page for more information on installing the package. ## Usage Once installed, open your application `.xcodeproj` file. Or `.xcworkspace` if using CocoaPods. -Decide where you want to add your `AgoraVideoViewer`, and in the same file import `Agora_UIKit` or `Agora_AppKit` for iOS and macOS respectively. -Next, create an `AgoraVideoViewer` object and frame it in your scene like you would any other `UIView` or `NSView`. The `AgoraVideoViewer` object must be provided `AgoraConnectionData` and a UIViewController/NSViewController on creation. - -AgoraConnectionData has two values for initialising. These are appId and rtcToken, as well as an optional rtmToken. - -An `AgoraVideoViewer` can be created like this: +The main view for Agora UIKit is `AgoraVideoViewer`. This is an example of a minimal creation that gives you a view similar to the one at the top of this README: ```swift import AgoraRtcKit -import AgoraUIKit_iOS +import AgoraUIKit let agoraView = AgoraVideoViewer( connectionData: AgoraConnectionData( - appId: "my-app-id", - rtcToken: "my-channel-token", - rtmToken: "my-channel-rtm-token" - ), - style: .grid, - delegate: self + appId: "<#my-app-id#>", + rtcToken: "<#my-channel-token#>", + rtmToken: "<#my-channel-rtm-token#>" + ), delegate: self ) ``` -An alternative style is `.floating`, as seen in the image above. - -To join a channel, simply call: +Frame your newly created AgoraVideoViewer in the app scene, then join a channel by calling: ```swift agoraView.join(channel: "test", as: .broadcaster) ``` +[More examples available on the wiki](https://github.com/AgoraIO-Community/iOS-UIKit/wiki/Examples) + ## Documentation For full documentation, see our [AgoraUIKit documentation page](https://agoraio-community.github.io/iOS-UIKit/). -### Roadmap +## Error Handling and Troubleshooting + +For tips on how to overcome some common errors, [see the wiki page](https://github.com/AgoraIO-Community/iOS-UIKit/wiki/Error-Handling-and-Troubleshooting). + +## Roadmap - [x] Muting/Unmuting a remote member -- [ ] Usernames +- [x] Usernames ([Settable value, not currently rendered](https://agoraio-community.github.io/iOS-UIKit/documentation/agorauikit_ios/agoraconnectiondata/username)) - [ ] Promoting an audience member to a broadcaster role. - [ ] Layout for Voice Calls - [ ] Cloud recording ## UIKits -The plan is to grow this library and have similar offerings across all supported platforms. There are already similar libraries for [Android](https://github.com/AgoraIO-Community/Android-UIKit/), [React Native](https://github.com/AgoraIO-Community/ReactNative-UIKit), and [Flutter](https://github.com/AgoraIO-Community/Flutter-UIKit/), so be sure to check them out. +The plan is to grow this library and have similar offerings across all supported platforms. There are already similar libraries for [Android](https://github.com/AgoraIO-Community/Android-UIKit/), [React Native](https://github.com/AgoraIO-Community/ReactNative-UIKit), [Flutter](https://github.com/AgoraIO-Community/Flutter-UIKit/) and [Web React](https://github.com/AgoraIO-Community/Web-React-UIKit) so be sure to check them out. + +## UML Diagrams + +- AgoraUIKit + +

+ UML of AgoraUIKit +

+ +- AgoraRtmControl + +

+ UML of AgoraRtmControl +

+ +> generated with `swiftplantuml classdiagram Sources` diff --git a/Sources/Agora-UIKit/AgoraCollectionViewer.swift b/Sources/Agora-UIKit/AgoraCollectionViewer.swift index ea7c679f..fe3146fd 100644 --- a/Sources/Agora-UIKit/AgoraCollectionViewer.swift +++ b/Sources/Agora-UIKit/AgoraCollectionViewer.swift @@ -222,7 +222,7 @@ extension AgoraVideoViewer: MPCollectionViewDelegate, MPCollectionViewDataSource internal func refreshCollectionData() { switch self.style { case .floating, .collection: - if self.agSettings.showSelf { + if self.agoraSettings.showSelf { self.collectionViewVideos = Array(self.userVideoLookup.values) } else { self.collectionViewVideos = Array(self.userVideoLookup.filter { $0.key != self.userID}.values) diff --git a/Sources/Agora-UIKit/AgoraConnectionData.swift b/Sources/Agora-UIKit/AgoraConnectionData.swift index 7a2bb486..349c062b 100644 --- a/Sources/Agora-UIKit/AgoraConnectionData.swift +++ b/Sources/Agora-UIKit/AgoraConnectionData.swift @@ -24,6 +24,7 @@ public struct AgoraConnectionData { set { self.rtcToken = newValue } } + /// Token to be used to connect to a RTM channel, can be nil. public var rtmToken: String? /// Channel the object is connected to. This cannot be set with the initialiser. public var channel: String? diff --git a/Sources/Agora-UIKit/AgoraRtmController+Helpers.swift b/Sources/Agora-UIKit/AgoraRtmController+Helpers.swift deleted file mode 100644 index c2bfaea0..00000000 --- a/Sources/Agora-UIKit/AgoraRtmController+Helpers.swift +++ /dev/null @@ -1,159 +0,0 @@ -// -// AgoraRtmController+Helpers.swift -// -// -// Created by Max Cobb on 30/09/2021. -// - -import AgoraRtmKit - -// MARK: Helper Methods -extension AgoraRtmController { - /// Type of decoded message coming from other users - public enum DecodedRtmAction { - /// Mute is when a user is requesting another user to mute or unmute a device - case mute(_: MuteRequest) - /// DecodedRtmAction type containing data about a user (local or remote) - case userData(_: UserData) - /// Message that contains a small action request, such as a ping or requesting a user's data - case dataRequest(_: RtmDataRequest) - } - - /// Decode message to a compatible DecodedRtmMessage type. - /// - Parameters: - /// - data: Raw data input, should be utf8 encoded JSON string of MuteRequest or UserData. - /// - rtmId: Sender Real-time Messaging ID. - /// - Returns: DecodedRtmMessage enum of the appropriate type. - internal static func decodeRawRtmData(data: Data, from rtmId: String) -> DecodedRtmAction? { - let decoder = JSONDecoder() - if let userData = try? decoder.decode(UserData.self, from: data) { - return .userData(userData) - } else if let muteReq = try? decoder.decode(MuteRequest.self, from: data) { - return .mute(muteReq) - } else if let requestVal = try? decoder.decode(RtmDataRequest.self, from: data) { - return .dataRequest(requestVal) - } - return nil - } - - /// Share local UserData to all connected channels. - /// Call this method when personal details are updated. - open func broadcastPersonalData() { - for channel in self.channels { self.sendPersonalData(to: channel.value) } - } - - /// Share local UserData to a specific channel - /// - Parameter channel: Channel to share UserData with. - open func sendPersonalData(to channel: AgoraRtmChannel) { - self.sendRaw(message: self.personalData, channel: channel) { sendMsgState in - switch sendMsgState { - case .errorOk: - AgoraVideoViewer.agoraPrint( - .verbose, message: "Personal data sent to channel successfully" - ) - case .errorFailure, .errorTimeout, .tooOften, - .invalidMessage, .errorNotInitialized, .notLoggedIn: - AgoraVideoViewer.agoraPrint( - .error, message: "Could not send message to channel \(sendMsgState.rawValue)" - ) - @unknown default: - AgoraVideoViewer.agoraPrint(.error, message: "Could not send message to channel (unknown)") - } - } - } - - /// Share local UserData to a specific RTM member - /// - Parameter member: Member to share UserData with. - open func sendPersonalData(to member: String) { - self.sendRaw(message: self.personalData, member: member) { sendMsgState in - switch sendMsgState { - case .ok: - AgoraVideoViewer.agoraPrint( - .verbose, message: "Personal data sent to member successfully" - ) - case .failure, .timeout, .tooOften, .invalidMessage, .notInitialized, .notLoggedIn, - .peerUnreachable, .cachedByServer, .invalidUserId, .imcompatibleMessage: - AgoraVideoViewer.agoraPrint( - .error, message: "Could not send message to channel \(sendMsgState.rawValue)" - ) - @unknown default: - AgoraVideoViewer.agoraPrint(.error, message: "Could not send message to channel (unknown)") - } - } - } - - /// Send a raw codable message over RTM to the channel. - /// - Parameters: - /// - message: Codable message to send over RTM. - /// - channel: String channel name to send the message to. - /// - callback: Callback, to see if the message was sent successfully. - public func sendRaw( - message: Value, channel: String, - callback: @escaping (AgoraRtmSendChannelMessageErrorCode) -> Void - ) where Value: Codable { - if let channel = self.channels[channel], let data = try? JSONEncoder().encode(message) { - channel.send( - AgoraRtmRawMessage(rawData: data, description: "AgoraUIKit"), completion: callback - ) - } - } - - /// Create raw message from codable object - /// - Parameter codableObj: Codable object to be sent over the Real-time Messaging network. - /// - Returns: AgoraRtmRawMessage that is ready to be sent across the Agora Real-time Messaging network. - public static func createRawRtm(from codableObj: Value) -> AgoraRtmRawMessage? where Value: Codable { - if let data = try? JSONEncoder().encode(codableObj) { - return AgoraRtmRawMessage(rawData: data, description: "AgoraUIKit") - } - AgoraVideoViewer.agoraPrint(.error, message: "Message could not be encoded to JSON") - return nil - } - - /// Send a raw codable message over RTM to the channel - /// - Parameters: - /// - message: Codable message to send over RTM - /// - channel: AgoraRtmChannel to send the message over - /// - callback: Callback, to see if the message was sent successfully. - public func sendRaw( - message: Value, channel: AgoraRtmChannel, - callback: @escaping (AgoraRtmSendChannelMessageErrorCode) -> Void - ) where Value: Codable { - if let rawMsg = AgoraRtmController.createRawRtm(from: message) { - channel.send(rawMsg, completion: callback) - return - } - callback(.invalidMessage) - } - - /// Send a raw codable message over RTM to a member - /// - Parameters: - /// - message: Codable message to send over RTM - /// - channel: member, or RTM ID to send the message to - /// - callback: Callback, to see if the message was sent successfully. - public func sendRaw( - message: Value, member: String, - callback: @escaping (AgoraRtmSendPeerMessageErrorCode) -> Void - ) where Value: Codable { - guard let rawMsg = AgoraRtmController.createRawRtm(from: message) else { - callback(.imcompatibleMessage) - return - } - self.rtmKit.send(rawMsg, toPeer: member, completion: callback) - } - - /// Send a raw codable message over RTM to a member - /// - Parameters: - /// - message: Codable message to send over RTM - /// - channel: member, or RTC User ID to send the message to - /// - callback: Callback, to see if the message was sent successfully. - public func sendRaw( - message: Value, user: UInt, - callback: @escaping (AgoraRtmSendPeerMessageErrorCode) -> Void - ) where Value: Codable { - if let rtmId = self.rtcLookup[user] { - self.sendRaw(message: message, member: rtmId, callback: callback) - } else { - callback(.peerUnreachable) - } - } -} diff --git a/Sources/Agora-UIKit/AgoraSettings.swift b/Sources/Agora-UIKit/AgoraSettings.swift index 4395aa1d..0390468a 100644 --- a/Sources/Agora-UIKit/AgoraSettings.swift +++ b/Sources/Agora-UIKit/AgoraSettings.swift @@ -6,7 +6,9 @@ // import AgoraRtcKit +#if canImport(AgoraRtmControl) import AgoraRtmKit +#endif /// Settings used for the display and behaviour of AgoraVideoViewer public struct AgoraSettings { @@ -14,6 +16,7 @@ public struct AgoraSettings { /// Delegate for Agora Rtc Engine callbacks public weak var rtcDelegate: AgoraRtcEngineDelegate? + #if canImport(AgoraRtmControl) /// Delegate for Agora RTM callbacks public weak var rtmDelegate: AgoraRtmDelegate? @@ -22,6 +25,7 @@ public struct AgoraSettings { /// Whether RTM should be initialised and used public var rtmEnabled: Bool = true + #endif /// URL to fetch tokens from. If supplied, this package will automatically fetch tokens /// when the Agora Engine indicates it will be needed. diff --git a/Sources/Agora-UIKit/AgoraSingleVideoView+RtmDelegate.swift b/Sources/Agora-UIKit/AgoraSingleVideoView+RtmDelegate.swift index feeb7c01..d373308c 100644 --- a/Sources/Agora-UIKit/AgoraSingleVideoView+RtmDelegate.swift +++ b/Sources/Agora-UIKit/AgoraSingleVideoView+RtmDelegate.swift @@ -10,11 +10,22 @@ import UIKit #elseif os(macOS) import AppKit #endif +#if canImport(AgoraRtmControl) +import AgoraRtmControl +#endif /// Protocol for being able to access the AgoraRtmController and presenting alerts public protocol SingleVideoViewDelegate: AnyObject { + #if canImport(AgoraRtmControl) /// RTM Controller class for managing RTM messages var rtmController: AgoraRtmController? { get set } + func createRequest( + to uid: UInt, + fromString str: String + ) -> Bool + func sendMuteRequest(to rtcId: UInt, mute: Bool, device: AgoraVideoViewer.MutingDevices, isForceful: Bool) + + #endif #if os(iOS) /// presentAlert is a way to show any alerts that want to display. /// These could be relating to video or audio unmuting requests. @@ -38,6 +49,8 @@ extension SingleVideoViewDelegate { viewCont.present(alert, animated: animated) } else if let vidViewer = self as? AgoraVideoViewer { vidViewer.delegate?.presentAlert(alert: alert, animated: animated) + } else { + AgoraVideoViewer.agoraPrint(.error, message: "Could not present popup") } } #endif diff --git a/Sources/Agora-UIKit/AgoraSingleVideoView+RtmOptions.swift b/Sources/Agora-UIKit/AgoraSingleVideoView+RtmOptions.swift index e0ce72da..c6dc53b7 100644 --- a/Sources/Agora-UIKit/AgoraSingleVideoView+RtmOptions.swift +++ b/Sources/Agora-UIKit/AgoraSingleVideoView+RtmOptions.swift @@ -10,8 +10,30 @@ import Foundation import UIKit #elseif os(macOS) import AppKit +#if canImport(AgoraRtmControl) +import AgoraRtmControl #endif +#endif + +extension AgoraSingleVideoView { + func updateUserOptions() { + #if os(macOS) && canImport(AgoraRtmControl) + if !Thread.isMainThread { + DispatchQueue.main.async { + self.updateUserOptions() + } + return + } + guard let userOptions = self.userOptions as? NSPopUpButton else { + return + } + userOptions.removeAllItems() + self.addItems(to: userOptions) + #endif + } +} +#if canImport(AgoraRtmControl) extension AgoraSingleVideoView { /// Find the string for the option ready to request the remote user to mute or unmute their mic or camera @@ -20,7 +42,7 @@ extension AgoraSingleVideoView { /// - isMuted: Boolean option to mute or unmute device /// - Returns: String to be displayed in the mute/unmute option open func userOptionsString( - for option: AgoraRtmController.MutingDevices, isMuted: Bool + for option: AgoraVideoViewer.MutingDevices, isMuted: Bool ) -> String { switch option { case .camera: @@ -30,21 +52,6 @@ extension AgoraSingleVideoView { } } - func updateUserOptions() { - #if os(macOS) - if !Thread.isMainThread { - DispatchQueue.main.async { - self.updateUserOptions() - } - return - } - guard let userOptions = self.userOptions as? NSPopUpButton else { - return - } - userOptions.removeAllItems() - self.addItems(to: userOptions) - #endif - } #if os(macOS) open func addItems(to userOptionsBtn: NSPopUpButton) { let actionItem = NSMenuItem() @@ -53,7 +60,7 @@ extension AgoraSingleVideoView { attributes: [ NSAttributedString.Key.foregroundColor: self.micFlagColor ] ) userOptionsBtn.menu?.insertItem(actionItem, at: 0) - AgoraRtmController.MutingDevices.allCases.forEach { enumCase in + AgoraVideoViewer.MutingDevices.allCases.forEach { enumCase in var isMuted: Bool switch enumCase { case .camera: @@ -71,7 +78,7 @@ extension AgoraSingleVideoView { /// - Parameter sender: Button that was selected @objc open func optionsBtnSelected(sender: UIButton) { let alert = UIAlertController(title: "Request Action", message: nil, preferredStyle: .actionSheet) - AgoraRtmController.MutingDevices.allCases.forEach { enumCase in + AgoraVideoViewer.MutingDevices.allCases.forEach { enumCase in var isMuted: Bool switch enumCase { case .camera: @@ -88,14 +95,18 @@ extension AgoraSingleVideoView { ) } alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - self.singleVideoViewDelegate?.presentAlert(alert: alert, animated: true) + if self.singleVideoViewDelegate != nil { + self.singleVideoViewDelegate?.presentAlert(alert: alert, animated: true) + } else { + AgoraVideoViewer.agoraPrint(.error, message: "Could not present popup") + } } /// Action selected such as mute/unmute microphone/camera. /// - Parameter sender: UIAlertAction that was selected. open func optionsActionSelected(sender: UIAlertAction) { if let actionTitle = sender.title, - let reqError = self.singleVideoViewDelegate?.rtmController?.createRequest( + let reqError = self.singleVideoViewDelegate?.createRequest( to: self.uid, fromString: actionTitle ), !reqError { AgoraVideoViewer.agoraPrint(.error, message: "invalid action title: \(actionTitle)") @@ -106,7 +117,7 @@ extension AgoraSingleVideoView { /// - Parameter sender: Button that was selected @objc public func optionsBtnSelected(sender: NSPopUpButton) { if let actionTitle = sender.selectedItem?.title, - let reqError = self.singleVideoViewDelegate?.rtmController?.createRequest( + let reqError = self.singleVideoViewDelegate?.createRequest( to: self.uid, fromString: actionTitle ), !reqError { AgoraVideoViewer.agoraPrint(.error, message: "invalid action title: \(actionTitle)") @@ -115,3 +126,4 @@ extension AgoraSingleVideoView { #endif } +#endif diff --git a/Sources/Agora-UIKit/AgoraSingleVideoView.swift b/Sources/Agora-UIKit/AgoraSingleVideoView.swift index 1e0904e6..b4151994 100644 --- a/Sources/Agora-UIKit/AgoraSingleVideoView.swift +++ b/Sources/Agora-UIKit/AgoraSingleVideoView.swift @@ -37,7 +37,9 @@ public class AgoraSingleVideoView: MPView { /// Whether the options label should be visible or not. public var showOptions: Bool = true { didSet { + #if canImport(AgoraRtmControl) self.userOptions?.isHidden = !self.showOptions + #endif } } /// Unique ID for this user, used by the video feed. @@ -59,6 +61,7 @@ public class AgoraSingleVideoView: MPView { case microphone } + #if canImport(AgoraRtmControl) lazy var userOptions: MPView? = { #if os(iOS) let userOptionsBtn = MPButton.newToggleButton( @@ -97,6 +100,7 @@ public class AgoraSingleVideoView: MPView { // userOptionsBtn.isHidden = true return userOptionsBtn }() + #endif /// Icon to show if this user is muting their microphone lazy var mutedFlag: MPView = { diff --git a/Sources/Agora-UIKit/AgoraUIKit+AgoraRtmController+Extensions.swift b/Sources/Agora-UIKit/AgoraUIKit+AgoraRtmController+Extensions.swift new file mode 100644 index 00000000..ffccf5e3 --- /dev/null +++ b/Sources/Agora-UIKit/AgoraUIKit+AgoraRtmController+Extensions.swift @@ -0,0 +1,171 @@ +// +// AgoraUIKit+AgoraRtmController+Extensions.swift +// +// +// Created by Max Cobb on 04/04/2022. +// + +import AgoraRtcKit +#if canImport(AgoraRtmKit) +import AgoraRtmKit +#endif + +extension AgoraVideoViewer { + /// Data about a user (local or remote) + public struct UserData: Codable { + /// Type of message being sent + var messageType: String? = "UserData" + /// ID used in the RTM connection + var rtmId: String + /// ID used in the RTC (Video/Audio) connection + var rtcId: UInt? + /// Username to be displayed for remote users + var username: String? + /// Role of the user (broadcaster or audience) + var role: AgoraClientRole.RawValue + /// Properties about the Agora SDK versions this user is using + var agora: AgoraVersions = .current + /// Agora UIKit platform (iOS, Android, Flutter, React Native) + var uikit: AgoraUIKit = .current + func prettyPrint() -> String { + """ + rtm: \(rtmId) + rtc: \(rtcId ?? 0) + username: \(username ?? "nil") + role: \(role) + agora: \n\(agora.prettyPrint()) + uikit: \n\(uikit.prettyPrint()) + """ + } + } + + /// Data about the Agora SDK versions a user is using (local or remote) + public struct AgoraVersions: Codable { + /// Versions of the local users current RTM and RTC SDKs + static var current: AgoraVersions { + var version = AgoraVersions() + #if canImport(AgoraRtmKit) + version.rtm = AgoraRtmKit.getSDKVersion() + #endif + version.rtc = AgoraRtcEngineKit.getSdkVersion() + return version + } + /// Version string of the RTM SDK + var rtm: String? + /// Version string of the RTC SDK + var rtc: String? + func prettyPrint() -> String { + """ + rtc: \(rtc ?? "none found") + rtm: \(rtm ?? "none found") + """ + } + } + + public var agConnection: AgoraConnectionData { + get { self.connectionData } + set { self.connectionData = newValue } + } + public var rtcEngine: AgoraRtcEngineKit { self.agkit } + public var videoLookup: [UInt: AgoraSingleVideoView] { self.userVideoLookup } + +} +#if canImport(AgoraRtmControl) +import AgoraRtmKit +import AgoraRtmControl + +extension AgoraVideoViewer: RtmControllerDelegate { + + public func rtmStateChanged( + from oldState: AgoraRtmController.RTMStatus, to newState: AgoraRtmController.RTMStatus + ) { self.delegate?.rtmStateChanged(from: oldState, to: newState) } + + /// Decode an incoming AgoraRtmMessage + /// - Parameters: + /// - message: Incoming RTM message. + /// - peerId: Id of the peer this message is coming from + public func decodeMessage(message: AgoraRtmMessage, from peerId: String) { + var messageData: Data! + if let message = message as? AgoraRtmRawMessage { + messageData = message.rawData + } else if let msgData = message.text.data(using: .utf8) { + messageData = msgData + } else { + return + } + if let decodedMsg = AgoraVideoViewer.decodeRtmData( + data: messageData, from: peerId + ) { self.handleDecodedMessage(decodedMsg, from: peerId) } + } + + func handleDecodedMessage(_ rtmAction: DecodedRtmAction, from peerId: String) { + switch rtmAction { + case .mute(let muteReq): + self.handleMuteRequest(muteReq: muteReq) + case .userData(let user): + AgoraVideoViewer.agoraPrint( + .verbose, message: "Received user data: \n\(user.prettyPrint())" + ) + self.rtmLookup[user.rtmId] = user + if let rtcId = user.rtcId { + self.rtcLookup[rtcId] = user.rtmId + self.videoLookup[rtcId]? + .showOptions = self.agoraSettings.showRemoteRequestOptions + } + case .dataRequest(let requestVal): + switch requestVal.type { + case .userData: + self.sendPersonalData(to: peerId) + case .ping: + self.rtmController?.sendCodable( + message: RtmDataRequest(type: .pong), member: peerId + ) {_ in } + case .pong: + AgoraVideoViewer.agoraPrint( + .verbose, message: "Received pong from \(peerId)" + ) + self.handlePongRequest(from: peerId) + } + } + } + + // MARK: RtmControllerDelegate Properties + + /// Agora Real-time Messaging Identifier (Agora RTM SDK). + public var rtmId: String { self.connectionData.rtmId } + /// Agora Real-time Communication Identifier (Agora Video/Audio SDK). + public var rtcId: UInt? { self.connectionData.rtcId } + /// Agora App ID from https://agora.io + public var appId: String { self.connectionData.appId } + /// Token to be used to connect to a RTM channel, can be nil. + public var rtmToken: String? { + get { self.connectionData.rtmToken } + set { self.connectionData.rtmToken = newValue } + } + + public func handlePongRequest(from peerId: String) { + self.delegate?.incomingPongRequest(from: peerId) + } + public func rtmChannelJoined( + name: String, channel: AgoraRtmChannel, code: AgoraRtmJoinChannelErrorCode + ) { + if code == .channelErrorOk { + self.sendPersonalData(to: channel) + } + self.delegate?.rtmChannelJoined(name: name, channel: channel, code: code) + } + public var rtmDelegate: AgoraRtmDelegate? { self.agoraSettings.rtmDelegate } + public var rtmChannelDelegate: AgoraRtmChannelDelegate? { self.agoraSettings.rtmChannelDelegate } + public func channel(_ channel: AgoraRtmChannel, memberJoined member: AgoraRtmMember) { + self.sendPersonalData(to: member.userId) + } + public func personalData() -> some Codable { + UserData( + rtmId: self.rtmId, + rtcId: self.rtcId == 0 ? nil : self.rtcId, + username: self.connectionData?.username, + role: self.userRole.rawValue + ) + } +} +#endif diff --git a/Sources/Agora-UIKit/AgoraRtmController+MuteRequests.swift b/Sources/Agora-UIKit/AgoraUIKit+AgoraRtmController+MuteRequests.swift similarity index 83% rename from Sources/Agora-UIKit/AgoraRtmController+MuteRequests.swift rename to Sources/Agora-UIKit/AgoraUIKit+AgoraRtmController+MuteRequests.swift index 02215ccd..66c60aee 100644 --- a/Sources/Agora-UIKit/AgoraRtmController+MuteRequests.swift +++ b/Sources/Agora-UIKit/AgoraUIKit+AgoraRtmController+MuteRequests.swift @@ -1,8 +1,8 @@ // -// AgoraRtmController+MuteRequests.swift +// AgoraUIKit+AgoraRtmController+MuteRequests.swift // // -// Created by Max Cobb on 29/07/2021. +// Created by Max Cobb on 04/04/2022. // #if os(iOS) @@ -10,14 +10,25 @@ import UIKit #elseif os(macOS) import AppKit #endif +#if canImport(AgoraRtmControl) +import AgoraRtmControl +#endif -extension AgoraRtmController { +extension AgoraVideoViewer { /// Devices that can be muted/unmuted - public enum MutingDevices: Int, CaseIterable { + @objc public enum MutingDevices: Int, CaseIterable { /// The device camera case camera = 0 /// The device microphone case microphone = 1 + var strVal: String { + switch self { + case .camera: + return "camera" + case .microphone: + return "micropohne" + } + } } /// Structure that contains information about a mute request @@ -29,7 +40,7 @@ extension AgoraRtmController { /// Whether the request is to mute or unmute a device public var mute: Bool /// Device to be muted or unmuted - public var device: AgoraRtmController.MutingDevices.RawValue + public var device: MutingDevices.RawValue /// Whether this is a request or a forceful change public var isForceful: Bool @@ -41,7 +52,7 @@ extension AgoraRtmController { /// - isForceful: Whether this is a request or a forceful change public init( rtcId: UInt, mute: Bool, - device: AgoraRtmController.MutingDevices, isForceful: Bool + device: MutingDevices, isForceful: Bool ) { self.rtcId = rtcId self.mute = mute @@ -68,30 +79,7 @@ extension AgoraRtmController { public var type: DataRequestType } - /// Create and send request to user to mute/unmute a device - /// - Parameters: - /// - uid: RTM User ID to send the request to - /// - str: String from the action label to - /// - Returns: Boolean stating if the request was valid or not - open func createRequest( - to uid: UInt, - fromString str: String - ) -> Bool { - switch str { - case MPButton.unmuteCameraString: - self.sendMuteRequest(to: uid, mute: false, device: .camera) - case MPButton.muteCameraString: - self.sendMuteRequest(to: uid, mute: true, device: .camera) - case MPButton.unmuteMicString: - self.sendMuteRequest(to: uid, mute: false, device: .microphone) - case MPButton.muteMicString: - self.sendMuteRequest(to: uid, mute: true, device: .microphone) - default: - return false - } - return true - } - + #if canImport(AgoraRtmControl) /// Create and send request to mute/unmute a device /// - Parameters: /// - rtcId: RTC User ID to send the request to @@ -104,7 +92,7 @@ extension AgoraRtmController { return } let muteReq = MuteRequest(rtcId: rtcId, mute: mute, device: device, isForceful: isForceful) - self.sendRaw(message: muteReq, user: rtcId) { sendStatus in + self.rtmController?.sendCodable(message: muteReq, user: rtcId) { sendStatus in if sendStatus == .ok { AgoraVideoViewer.agoraPrint(.verbose, message: "message was sent!") } else { @@ -112,14 +100,43 @@ extension AgoraRtmController { } } } + #endif } +#if canImport(AgoraRtmControl) +extension SingleVideoViewDelegate { + /// Create and send request to user to mute/unmute a device + /// - Parameters: + /// - uid: RTM User ID to send the request to + /// - str: String from the action label to + /// - Returns: Boolean stating if the request was valid or not + public func createRequest( + to uid: UInt, + fromString str: String + ) -> Bool { + switch str { + case MPButton.unmuteCameraString: + self.sendMuteRequest(to: uid, mute: false, device: .camera, isForceful: false) + case MPButton.muteCameraString: + self.sendMuteRequest(to: uid, mute: true, device: .camera, isForceful: false) + case MPButton.unmuteMicString: + self.sendMuteRequest(to: uid, mute: false, device: .microphone, isForceful: false) + case MPButton.muteMicString: + self.sendMuteRequest(to: uid, mute: true, device: .microphone, isForceful: false) + default: + return false + } + return true + } +} +#endif + extension AgoraVideoViewer { /// Handle mute request, by showing popup or directly changing the device state /// - Parameter muteReq: Incoming mute request data - open func handleMuteRequest(muteReq: AgoraRtmController.MuteRequest) { - guard let device = AgoraRtmController.MutingDevices(rawValue: muteReq.device) else { + open func handleMuteRequest(muteReq: MuteRequest) { + guard let device = MutingDevices(rawValue: muteReq.device) else { return } if device == .camera, self.agoraSettings.cameraEnabled == !muteReq.mute { return } @@ -128,7 +145,7 @@ extension AgoraVideoViewer { AgoraVideoViewer.agoraPrint( .error, message: "user \(muteReq.rtcId) (self) should \(muteReq.mute ? "" : "un")mute" + - " their \(device) by \(muteReq.isForceful ? "force" : "request")" + " their \(device.strVal) by \(muteReq.isForceful ? "force" : "request")" ) func setDevice(_ sender: Any? = nil) { switch device { @@ -142,7 +159,7 @@ extension AgoraVideoViewer { setDevice() return } - let alertTitle = "\(muteReq.mute ? "" : "un")mute \(device)?" + let alertTitle = "\(muteReq.mute ? "" : "un")mute \(device.strVal)?" #if os(iOS) let alert = UIAlertController( title: alertTitle, message: nil, diff --git a/Sources/Agora-UIKit/AgoraUIKit+AgoraRtmController+SendHelpers.swift b/Sources/Agora-UIKit/AgoraUIKit+AgoraRtmController+SendHelpers.swift new file mode 100644 index 00000000..35913386 --- /dev/null +++ b/Sources/Agora-UIKit/AgoraUIKit+AgoraRtmController+SendHelpers.swift @@ -0,0 +1,97 @@ +// +// AgoraUIKit+AgoraRtmController+Extensions.swift +// +// +// Created by Max Cobb on 04/04/2022. +// + +import Foundation +#if canImport(AgoraRtmControl) +import AgoraRtmKit +import AgoraRtmControl +#endif + +extension AgoraVideoViewer { + /// Type of decoded message coming from other users + public enum DecodedRtmAction { + /// Mute is when a user is requesting another user to mute or unmute a device + case mute(_: MuteRequest) + /// DecodedRtmAction type containing data about a user (local or remote) + case userData(_: AgoraVideoViewer.UserData) + /// Message that contains a small action request, such as a ping or requesting a user's data + case dataRequest(_: RtmDataRequest) + } + + /// Decode message to a compatible DecodedRtmMessage type. + /// - Parameters: + /// - data: Raw data input, should be utf8 encoded JSON string of MuteRequest or UserData. + /// - rtmId: Sender Real-time Messaging ID. + /// - Returns: DecodedRtmMessage enum of the appropriate type. + internal static func decodeRtmData(data: Data, from rtmId: String) -> DecodedRtmAction? { + let decoder = JSONDecoder() + if let userData = try? decoder.decode(AgoraVideoViewer.UserData.self, from: data) { + return .userData(userData) + } else if let muteReq = try? decoder.decode(MuteRequest.self, from: data) { + return .mute(muteReq) + } else if let requestVal = try? decoder.decode(RtmDataRequest.self, from: data) { + return .dataRequest(requestVal) + } + return nil + } + + #if canImport(AgoraRtmControl) + /// Share local UserData to all connected channels. + /// Call this method when personal details are updated. + open func broadcastPersonalData() { + for channel in (self.rtmController?.channels ?? [String: AgoraRtmChannel]()) { + self.sendPersonalData(to: channel.value) + } + } + + /// Share local UserData to a specific channel + /// - Parameter channel: Channel to share UserData with. + open func sendPersonalData(to channel: AgoraRtmChannel) { + if self.rtmController == nil { + AgoraVideoViewer.agoraPrint( + .warning, message: "AgoraRtmController not included, override this method to send personal data" + ) + return + } + self.rtmController?.sendCodable(message: self.personalData(), channel: channel) { sendMsgState in + switch sendMsgState { + case .errorOk: + AgoraVideoViewer.agoraPrint( + .verbose, message: "Personal data sent to channel successfully" + ) + case .errorFailure, .errorTimeout, .tooOften, + .invalidMessage, .errorNotInitialized, .notLoggedIn: + AgoraVideoViewer.agoraPrint( + .error, message: "Could not send message to channel \(sendMsgState.rawValue)" + ) + @unknown default: + AgoraVideoViewer.agoraPrint(.error, message: "Could not send message to channel (unknown)") + } + } + } + + /// Share local UserData to a specific RTM member + /// - Parameter member: Member to share UserData with. + open func sendPersonalData(to member: String) { + self.rtmController?.sendCodable(message: self.personalData(), member: member) { sendMsgState in + switch sendMsgState { + case .ok: + AgoraVideoViewer.agoraPrint( + .verbose, message: "Personal data sent to member successfully" + ) + case .failure, .timeout, .tooOften, .invalidMessage, .notInitialized, .notLoggedIn, + .peerUnreachable, .cachedByServer, .invalidUserId, .imcompatibleMessage: + AgoraVideoViewer.agoraPrint( + .error, message: "Could not send message to channel \(sendMsgState.rawValue)" + ) + @unknown default: + AgoraVideoViewer.agoraPrint(.error, message: "Could not send message to channel (unknown)") + } + } + } + #endif +} diff --git a/Sources/Agora-UIKit/AgoraUIKit.swift b/Sources/Agora-UIKit/AgoraUIKit.swift index 61267267..3e4f7367 100644 --- a/Sources/Agora-UIKit/AgoraUIKit.swift +++ b/Sources/Agora-UIKit/AgoraUIKit.swift @@ -22,7 +22,7 @@ public struct AgoraUIKit: Codable { /// Framework type of UIKit. "native", "flutter", "reactnative" fileprivate(set) var framework: String /// Version of UIKit being used - static let version = "1.7.6" + static let version = "1.8.0" /// Framework type of UIKit. "native", "flutter", "reactnative" static let framework = "native" #if os(iOS) diff --git a/Sources/Agora-UIKit/AgoraVideoViewer+AgoraRtcEngineDelegate.swift b/Sources/Agora-UIKit/AgoraVideoViewer+AgoraRtcEngineDelegate.swift index 61815cc3..62942f7c 100644 --- a/Sources/Agora-UIKit/AgoraVideoViewer+AgoraRtcEngineDelegate.swift +++ b/Sources/Agora-UIKit/AgoraVideoViewer+AgoraRtcEngineDelegate.swift @@ -28,8 +28,11 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate { // Only show the camera options when we are a broadcaster self.getControlContainer().isHidden = !isHost - self.rtmController?.broadcastPersonalData() - self.agSettings.rtcDelegate?.rtcEngine?(engine, didClientRoleChanged: oldRole, newRole: newRole) + + #if canImport(AgoraRtmControl) + self.broadcastPersonalData() + #endif + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didClientRoleChanged: oldRole, newRole: newRole) } /// New User joined the channel @@ -44,7 +47,7 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate { ) { // Keeping track of all people in the session self.remoteUserIDs.insert(uid) - self.agSettings.rtcDelegate?.rtcEngine?(engine, didJoinedOfUid: uid, elapsed: elapsed) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didJoinedOfUid: uid, elapsed: elapsed) } /// This callback indicates the state change of the local audio stream, including the state of the audio recording and encoding, and allows you to troubleshoot issues when exceptions occur. @@ -70,7 +73,7 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate { } } } - self.agSettings.rtcDelegate?.rtcEngine?( + self.agoraSettings.rtcDelegate?.rtcEngine?( engine, remoteAudioStateChangedOfUid: uid, state: state, reason: reason, elapsed: elapsed ) @@ -105,7 +108,7 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate { // and remove this view from the list self.removeUserVideo(with: uid) } - self.agSettings.rtcDelegate?.rtcEngine?(engine, didOfflineOfUid: uid, reason: reason) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didOfflineOfUid: uid, reason: reason) } /** @@ -126,7 +129,7 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate { */ open func rtcEngine(_ engine: AgoraRtcEngineKit, activeSpeaker speakerUid: UInt) { self.activeSpeaker = speakerUid - self.agSettings.rtcDelegate?.rtcEngine?(engine, activeSpeaker: speakerUid) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, activeSpeaker: speakerUid) } /** @@ -156,7 +159,7 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate { default: break } - self.agSettings.rtcDelegate?.rtcEngine?( + self.agoraSettings.rtcDelegate?.rtcEngine?( engine, remoteVideoStateChangedOfUid: uid, state: state, reason: reason, elapsed: elapsed ) @@ -182,7 +185,7 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate { default: break } - self.agSettings.rtcDelegate?.rtcEngine?(engine, localVideoStateChange: state, error: error) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, localVideoStateChange: state, error: error) } /** @@ -207,7 +210,7 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate { default: break } - self.agSettings.rtcDelegate?.rtcEngine?(engine, localAudioStateChange: state, error: error) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, localAudioStateChange: state, error: error) } /** @@ -225,7 +228,7 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate { */ open func rtcEngine(_ engine: AgoraRtcEngineKit, firstLocalAudioFramePublished elapsed: Int) { self.addLocalVideo()?.audioMuted = false - self.agSettings.rtcDelegate?.rtcEngine?(engine, firstLocalAudioFramePublished: elapsed) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, firstLocalAudioFramePublished: elapsed) } /** @@ -244,7 +247,7 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate { ) } self.delegate?.tokenDidExpire(engine) - self.agSettings.rtcDelegate?.rtcEngineRequestToken?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineRequestToken?(engine) } /** @@ -265,6 +268,6 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate { ) } self.delegate?.tokenWillExpire(engine, tokenPrivilegeWillExpire: token) - self.agSettings.rtcDelegate?.rtcEngine?(engine, tokenPrivilegeWillExpire: token) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, tokenPrivilegeWillExpire: token) } } diff --git a/Sources/Agora-UIKit/AgoraVideoViewer+Buttons.swift b/Sources/Agora-UIKit/AgoraVideoViewer+Buttons.swift index 3ab8be14..73264b62 100644 --- a/Sources/Agora-UIKit/AgoraVideoViewer+Buttons.swift +++ b/Sources/Agora-UIKit/AgoraVideoViewer+Buttons.swift @@ -22,14 +22,14 @@ extension AgoraVideoViewer { _ resizeMask: inout UIView.AutoresizingMask, _ containerSize: inout CGSize ) { resizeMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin] - switch self.agSettings.buttonPosition { + switch self.agoraSettings.buttonPosition { case .top: frameOriginY = 30 resizeMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleBottomMargin] case .left, .right: containerSize = CGSize(width: containerSize.height, height: containerSize.width) frameOriginY = (self.bounds.height - CGFloat(contWidth)) / 2 - if self.agSettings.buttonPosition == .left { + if self.agoraSettings.buttonPosition == .left { frameOriginX = 30 resizeMask = [.flexibleTopMargin, .flexibleRightMargin, .flexibleBottomMargin] } else { @@ -45,10 +45,10 @@ extension AgoraVideoViewer { _ frameOriginX: inout CGFloat, _ frameOriginY: inout CGFloat, _ contWidth: CGFloat, _ resizeMask: inout NSView.AutoresizingMask, _ containerSize: inout CGSize ) { - switch self.agSettings.buttonPosition { + switch self.agoraSettings.buttonPosition { case .top, .bottom: frameOriginX = (self.bounds.width - CGFloat(contWidth)) / 2 - if self.agSettings.buttonPosition == .top { + if self.agoraSettings.buttonPosition == .top { frameOriginY = self.bounds.height - self.agoraSettings.buttonSize - 20 - 10 resizeMask = [.minXMargin, .maxXMargin, .minYMargin] } else { @@ -58,7 +58,7 @@ extension AgoraVideoViewer { case .left, .right: containerSize = CGSize(width: containerSize.height, height: containerSize.width) frameOriginY = (self.bounds.height - CGFloat(contWidth)) / 2 - if self.agSettings.buttonPosition == .left { + if self.agoraSettings.buttonPosition == .left { frameOriginX = 20 resizeMask = [.minYMargin, .maxXMargin, .maxYMargin] } else { diff --git a/Sources/Agora-UIKit/AgoraVideoViewer+Ordering.swift b/Sources/Agora-UIKit/AgoraVideoViewer+Ordering.swift index c7620d93..a3a2ee44 100644 --- a/Sources/Agora-UIKit/AgoraVideoViewer+Ordering.swift +++ b/Sources/Agora-UIKit/AgoraVideoViewer+Ordering.swift @@ -37,7 +37,7 @@ extension AgoraVideoViewer { uid: userId, micColor: self.agoraSettings.colors.micFlag, delegate: self ) remoteVideoView.canvas.renderMode = self.agoraSettings.videoRenderMode - if self.rtmController?.rtcLookup.index(forKey: userId) != nil { + if self.rtcLookup.index(forKey: userId) != nil { remoteVideoView.showOptions = self.agoraSettings.showRemoteRequestOptions } self.agkit.setupRemoteVideo(remoteVideoView.canvas) @@ -63,7 +63,7 @@ extension AgoraVideoViewer { let canView = userSingleView.canvas.view else { return } - self.agkit.muteRemoteVideoStream(userId, mute: true) +// self.agkit.muteRemoteVideoStream(userId, mute: true) userSingleView.canvas.view = nil canView.removeFromSuperview() self.userVideoLookup.removeValue(forKey: userId) diff --git a/Sources/Agora-UIKit/AgoraVideoViewer+Permissions.swift b/Sources/Agora-UIKit/AgoraVideoViewer+Permissions.swift index ebf01497..43a2a255 100644 --- a/Sources/Agora-UIKit/AgoraVideoViewer+Permissions.swift +++ b/Sources/Agora-UIKit/AgoraVideoViewer+Permissions.swift @@ -101,7 +101,7 @@ extension AgoraVideoViewer { func cameraMicSettingsPopup(successHandler: @escaping () -> Void) { #if os(iOS) if self.delegate?.presentAlert == nil { - AgoraVideoViewer.agoraPrint(.error, message: "Could not present settings popup") + AgoraVideoViewer.agoraPrint(.error, message: "Could not present popup") // just assume the user accepted this popup and move on successHandler() return diff --git a/Sources/Agora-UIKit/AgoraVideoViewer+RtcEngineDelegateOverflow.swift b/Sources/Agora-UIKit/AgoraVideoViewer+RtcEngineDelegateOverflow.swift index ba0367db..2bab5924 100644 --- a/Sources/Agora-UIKit/AgoraVideoViewer+RtcEngineDelegateOverflow.swift +++ b/Sources/Agora-UIKit/AgoraVideoViewer+RtcEngineDelegateOverflow.swift @@ -9,342 +9,320 @@ import AgoraRtcKit extension AgoraVideoViewer { open func rtcEngineConnectionDidLost(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineConnectionDidLost?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineConnectionDidLost?(engine) } open func rtcEngineLocalAudioMixingDidFinish(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineLocalAudioMixingDidFinish?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineLocalAudioMixingDidFinish?(engine) } open func rtcEngineRemoteAudioMixingDidStart(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineRemoteAudioMixingDidStart?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineRemoteAudioMixingDidStart?(engine) } open func rtcEngineRemoteAudioMixingDidFinish(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineRemoteAudioMixingDidFinish?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineRemoteAudioMixingDidFinish?(engine) } open func rtcEngineVideoDidStop(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineVideoDidStop?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineVideoDidStop?(engine) } open func rtcEngineCameraDidReady(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineCameraDidReady?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineCameraDidReady?(engine) } open func rtcEngineTranscodingUpdated(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineTranscodingUpdated?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineTranscodingUpdated?(engine) } open func rtcEngineConnectionDidBanned(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineConnectionDidBanned?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineConnectionDidBanned?(engine) } open func rtcEngineAirPlayIsConnected(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineAirPlayIsConnected?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineAirPlayIsConnected?(engine) } open func rtcEngineMediaEngineDidLoaded(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineMediaEngineDidLoaded?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineMediaEngineDidLoaded?(engine) } open func rtcEngineConnectionDidInterrupted(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineConnectionDidInterrupted?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineConnectionDidInterrupted?(engine) } open func rtcEngineMediaEngineDidStartCall(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineMediaEngineDidStartCall?(engine) + self.agoraSettings.rtcDelegate?.rtcEngineMediaEngineDidStartCall?(engine) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurWarning warningCode: AgoraWarningCode) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didOccurWarning: warningCode) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didOccurWarning: warningCode) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didOccurError: errorCode) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didOccurError: errorCode) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didLeaveChannelWith stats: AgoraChannelStats) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didLeaveChannelWith: stats) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didLeaveChannelWith: stats) } - open func rtcEngine(_ engine: AgoraRtcEngineKit, networkTypeChangedTo type: AgoraNetworkType) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, networkTypeChangedTo: type) + open func rtcEngine(_ engine: AgoraRtcEngineKit, networkTypeChangedToType type: AgoraNetworkType) { + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, networkTypeChangedToType: type) } + open func rtcEngine(_ engine: AgoraRtcEngineKit, reportLocalVoicePitchFrequency pitchInHz: Int) { + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, reportLocalVoicePitchFrequency: pitchInHz) + } open func rtcEngine(_ engine: AgoraRtcEngineKit, firstLocalAudioFrame elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, firstLocalAudioFrame: elapsed) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, firstLocalAudioFrame: elapsed) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didLocalPublishFallbackToAudioOnly isFallbackOrRecover: Bool) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didLocalPublishFallbackToAudioOnly: isFallbackOrRecover) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didLocalPublishFallbackToAudioOnly: isFallbackOrRecover) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didAudioRouteChanged routing: AgoraAudioOutputRouting) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didAudioRouteChanged: routing) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didAudioRouteChanged: routing) } #if os(iOS) open func rtcEngine(_ engine: AgoraRtcEngineKit, cameraFocusDidChangedTo rect: CGRect) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, cameraFocusDidChangedTo: rect) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, cameraFocusDidChangedTo: rect) } open func rtcEngine(_ engine: AgoraRtcEngineKit, cameraExposureDidChangedTo rect: CGRect) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, cameraExposureDidChangedTo: rect) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, cameraExposureDidChangedTo: rect) } #elseif os(macOS) public func rtcEngine(_ engine: AgoraRtcEngineKit, device deviceId: String, type deviceType: AgoraMediaDeviceType, stateChanged state: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, device: deviceId, type: deviceType, stateChanged: state) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, device: deviceId, type: deviceType, stateChanged: state) } #endif open func rtcEngine(_ engine: AgoraRtcEngineKit, reportRtcStats stats: AgoraChannelStats) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, reportRtcStats: stats) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, reportRtcStats: stats) } open func rtcEngine(_ engine: AgoraRtcEngineKit, lastmileQuality quality: AgoraNetworkQuality) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, lastmileQuality: quality) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, lastmileQuality: quality) } open func rtcEngine(_ engine: AgoraRtcEngineKit, lastmileProbeTest result: AgoraLastmileProbeResult) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, lastmileProbeTest: result) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, lastmileProbeTest: result) } open func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, localVideoStats: stats) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, localVideoStats: stats) } open func rtcEngine(_ engine: AgoraRtcEngineKit, localAudioStats stats: AgoraRtcLocalAudioStats) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, localAudioStats: stats) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, localAudioStats: stats) } open func rtcEngine(_ engine: AgoraRtcEngineKit, remoteVideoStats stats: AgoraRtcRemoteVideoStats) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, remoteVideoStats: stats) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, remoteVideoStats: stats) } open func rtcEngine(_ engine: AgoraRtcEngineKit, remoteAudioStats stats: AgoraRtcRemoteAudioStats) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, remoteAudioStats: stats) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, remoteAudioStats: stats) } open func rtcEngineDidAudioEffectFinish(_ engine: AgoraRtcEngineKit, soundId: Int) { - self.agSettings.rtcDelegate?.rtcEngineDidAudioEffectFinish?(engine, soundId: soundId) + self.agoraSettings.rtcDelegate?.rtcEngineDidAudioEffectFinish?(engine, soundId: soundId) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didReceive event: AgoraChannelMediaRelayEvent) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didReceive: event) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didReceive: event) } open func rtcEngine(_ engine: AgoraRtcEngineKit, streamUnpublishedWithUrl url: String) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, streamUnpublishedWithUrl: url) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, streamUnpublishedWithUrl: url) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didMicrophoneEnabled enabled: Bool) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didMicrophoneEnabled: enabled) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didMicrophoneEnabled: enabled) } open func rtcEngine(_ engine: AgoraRtcEngineKit, firstLocalVideoFramePublished elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, firstLocalVideoFramePublished: elapsed) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, firstLocalVideoFramePublished: elapsed) } open func rtcEngine(_ engine: AgoraRtcEngineKit, reportAudioVolumeIndicationOfSpeakers speakers: [AgoraRtcAudioVolumeInfo], totalVolume: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, reportAudioVolumeIndicationOfSpeakers: speakers, totalVolume: totalVolume) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, reportAudioVolumeIndicationOfSpeakers: speakers, totalVolume: totalVolume) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didRegisteredLocalUser userAccount: String, withUid uid: UInt) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didRegisteredLocalUser: userAccount, withUid: uid) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didRegisteredLocalUser: userAccount, withUid: uid) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didUpdatedUserInfo userInfo: AgoraUserInfo, withUid uid: UInt) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didUpdatedUserInfo: userInfo, withUid: uid) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didUpdatedUserInfo: userInfo, withUid: uid) } open func rtcEngine(_ engine: AgoraRtcEngineKit, connectionChangedTo state: AgoraConnectionStateType, reason: AgoraConnectionChangedReason) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, connectionChangedTo: state, reason: reason) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, connectionChangedTo: state, reason: reason) } open func rtcEngine(_ engine: AgoraRtcEngineKit, firstLocalVideoFrameWith size: CGSize, elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, firstLocalVideoFrameWith: size, elapsed: elapsed) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, firstLocalVideoFrameWith: size, elapsed: elapsed) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didVideoMuted muted: Bool, byUid uid: UInt) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didVideoMuted: muted, byUid: uid) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didVideoMuted: muted, byUid: uid) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didRemoteSubscribeFallbackToAudioOnly isFallbackOrRecover: Bool, byUid uid: UInt) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didRemoteSubscribeFallbackToAudioOnly: isFallbackOrRecover, byUid: uid) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didRemoteSubscribeFallbackToAudioOnly: isFallbackOrRecover, byUid: uid) } open func rtcEngine(_ engine: AgoraRtcEngineKit, localAudioMixingStateDidChanged state: AgoraAudioMixingStateCode, reason: AgoraAudioMixingReasonCode) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, localAudioMixingStateDidChanged: state, reason: reason) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, localAudioMixingStateDidChanged: state, reason: reason) } open func rtcEngine(_ engine: AgoraRtcEngineKit, channelMediaRelayStateDidChange state: AgoraChannelMediaRelayState, error: AgoraChannelMediaRelayError) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, channelMediaRelayStateDidChange: state, error: error) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, channelMediaRelayStateDidChange: state, error: error) } open func rtcEngine(_ engine: AgoraRtcEngineKit, firstRemoteAudioFrameOfUid uid: UInt, elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, firstRemoteAudioFrameOfUid: uid, elapsed: elapsed) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, firstRemoteAudioFrameOfUid: uid, elapsed: elapsed) } open func rtcEngine(_ engine: AgoraRtcEngineKit, firstRemoteAudioFrameDecodedOfUid uid: UInt, elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, firstRemoteAudioFrameDecodedOfUid: uid, elapsed: elapsed) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, firstRemoteAudioFrameDecodedOfUid: uid, elapsed: elapsed) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didAudioMuted muted: Bool, byUid uid: UInt) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didAudioMuted: muted, byUid: uid) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didAudioMuted: muted, byUid: uid) } open func rtcEngine(_ engine: AgoraRtcEngineKit, streamPublishedWithUrl url: String, errorCode: AgoraErrorCode) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, streamPublishedWithUrl: url, errorCode: errorCode) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, streamPublishedWithUrl: url, errorCode: errorCode) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didVideoEnabled enabled: Bool, byUid uid: UInt) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didVideoEnabled: enabled, byUid: uid) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didVideoEnabled: enabled, byUid: uid) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didLocalVideoEnabled enabled: Bool, byUid uid: UInt) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didLocalVideoEnabled: enabled, byUid: uid) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didLocalVideoEnabled: enabled, byUid: uid) } open func rtcEngine(_ engine: AgoraRtcEngineKit, rtmpStreamingEventWithUrl url: String, eventCode: AgoraRtmpStreamingEvent) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, rtmpStreamingEventWithUrl: url, eventCode: eventCode) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, rtmpStreamingEventWithUrl: url, eventCode: eventCode) } open func rtcEngine(_ engine: AgoraRtcEngineKit, virtualBackgroundSourceEnabled enabled: Bool, reason: AgoraVirtualBackgroundSourceStateReason) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, virtualBackgroundSourceEnabled: enabled, reason: reason) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, virtualBackgroundSourceEnabled: enabled, reason: reason) } open func rtcEngine(_ engine: AgoraRtcEngineKit, facePositionDidChangeWidth width: Int32, previewHeight height: Int32, faces: [AgoraFacePositionInfo]?) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, facePositionDidChangeWidth: width, previewHeight: height, faces: faces) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, facePositionDidChangeWidth: width, previewHeight: height, faces: faces) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didApiCallExecute error: Int, api: String, result: String) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didApiCallExecute: error, api: api, result: result) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didApiCallExecute: error, api: api, result: result) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didJoinChannel: channel, withUid: uid, elapsed: elapsed) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didJoinChannel: channel, withUid: uid, elapsed: elapsed) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didRejoinChannel channel: String, withUid uid: UInt, elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didRejoinChannel: channel, withUid: uid, elapsed: elapsed) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didRejoinChannel: channel, withUid: uid, elapsed: elapsed) } open func rtcEngine(_ engine: AgoraRtcEngineKit, videoSizeChangedOfUid uid: UInt, size: CGSize, rotation: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, videoSizeChangedOfUid: uid, size: size, rotation: rotation) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, videoSizeChangedOfUid: uid, size: size, rotation: rotation) } open func rtcEngine(_ engine: AgoraRtcEngineKit, networkQuality uid: UInt, txQuality: AgoraNetworkQuality, rxQuality: AgoraNetworkQuality) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, networkQuality: uid, txQuality: txQuality, rxQuality: rxQuality) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, networkQuality: uid, txQuality: txQuality, rxQuality: rxQuality) } open func rtcEngine(_ engine: AgoraRtcEngineKit, rtmpStreamingChangedToState url: String, state: AgoraRtmpStreamingState, errorCode: AgoraRtmpStreamingErrorCode) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, rtmpStreamingChangedToState: url, state: state, errorCode: errorCode) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, rtmpStreamingChangedToState: url, state: state, errorCode: errorCode) } open func rtcEngine(_ engine: AgoraRtcEngineKit, streamInjectedStatusOfUrl url: String, uid: UInt, status: AgoraInjectStreamStatus) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, streamInjectedStatusOfUrl: url, uid: uid, status: status) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, streamInjectedStatusOfUrl: url, uid: uid, status: status) } open func rtcEngine(_ engine: AgoraRtcEngineKit, receiveStreamMessageFromUid uid: UInt, streamId: Int, data: Data) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, receiveStreamMessageFromUid: uid, streamId: streamId, data: data) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, receiveStreamMessageFromUid: uid, streamId: streamId, data: data) } open func rtcEngine(_ engine: AgoraRtcEngineKit, firstRemoteVideoFrameOfUid uid: UInt, size: CGSize, elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, firstRemoteVideoFrameOfUid: uid, size: size, elapsed: elapsed) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, firstRemoteVideoFrameOfUid: uid, size: size, elapsed: elapsed) } open func rtcEngine(_ engine: AgoraRtcEngineKit, firstRemoteVideoDecodedOfUid uid: UInt, size: CGSize, elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, firstRemoteVideoDecodedOfUid: uid, size: size, elapsed: elapsed) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, firstRemoteVideoDecodedOfUid: uid, size: size, elapsed: elapsed) } open func rtcEngine(_ engine: AgoraRtcEngineKit, uploadLogResultRequestId requestId: String, success: Bool, reason: AgoraUploadErrorReason) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, uploadLogResultRequestId: requestId, success: success, reason: reason) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, uploadLogResultRequestId: requestId, success: success, reason: reason) } open func rtcEngine(_ engine: AgoraRtcEngineKit, superResolutionEnabledOfUid uid: UInt, enabled: Bool, reason: AgoraSuperResolutionStateReason) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, superResolutionEnabledOfUid: uid, enabled: enabled, reason: reason) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, superResolutionEnabledOfUid: uid, enabled: enabled, reason: reason) } open func rtcEngine(_ engine: AgoraRtcEngineKit, audioTransportStatsOfUid uid: UInt, delay: UInt, lost: UInt, rxKBitRate: UInt) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, audioTransportStatsOfUid: uid, delay: delay, lost: lost, rxKBitRate: rxKBitRate) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, audioTransportStatsOfUid: uid, delay: delay, lost: lost, rxKBitRate: rxKBitRate) } open func rtcEngine(_ engine: AgoraRtcEngineKit, videoTransportStatsOfUid uid: UInt, delay: UInt, lost: UInt, rxKBitRate: UInt) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, videoTransportStatsOfUid: uid, delay: delay, lost: lost, rxKBitRate: rxKBitRate) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, videoTransportStatsOfUid: uid, delay: delay, lost: lost, rxKBitRate: rxKBitRate) } open func rtcEngine(_ engine: AgoraRtcEngineKit, audioQualityOfUid uid: UInt, quality: AgoraNetworkQuality, delay: UInt, lost: UInt) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, audioQualityOfUid: uid, quality: quality, delay: delay, lost: lost) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, audioQualityOfUid: uid, quality: quality, delay: delay, lost: lost) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didAudioPublishStateChange channel: String, oldState: AgoraStreamPublishState, newState: AgoraStreamPublishState, elapseSinceLastState: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didAudioPublishStateChange: channel, oldState: oldState, newState: newState, elapseSinceLastState: elapseSinceLastState) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didAudioPublishStateChange: channel, oldState: oldState, newState: newState, elapseSinceLastState: elapseSinceLastState) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didVideoPublishStateChange channel: String, oldState: AgoraStreamPublishState, newState: AgoraStreamPublishState, elapseSinceLastState: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didVideoPublishStateChange: channel, oldState: oldState, newState: newState, elapseSinceLastState: elapseSinceLastState) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didVideoPublishStateChange: channel, oldState: oldState, newState: newState, elapseSinceLastState: elapseSinceLastState) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didAudioSubscribeStateChange channel: String, withUid uid: UInt, oldState: AgoraStreamSubscribeState, newState: AgoraStreamSubscribeState, elapseSinceLastState: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didAudioSubscribeStateChange: channel, withUid: uid, oldState: oldState, newState: newState, elapseSinceLastState: elapseSinceLastState) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didAudioSubscribeStateChange: channel, withUid: uid, oldState: oldState, newState: newState, elapseSinceLastState: elapseSinceLastState) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didVideoSubscribeStateChange channel: String, withUid uid: UInt, oldState: AgoraStreamSubscribeState, newState: AgoraStreamSubscribeState, elapseSinceLastState: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didVideoSubscribeStateChange: channel, withUid: uid, oldState: oldState, newState: newState, elapseSinceLastState: elapseSinceLastState) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didVideoSubscribeStateChange: channel, withUid: uid, oldState: oldState, newState: newState, elapseSinceLastState: elapseSinceLastState) } open func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurStreamMessageErrorFromUid uid: UInt, streamId: Int, error: Int, missed: Int, cached: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didOccurStreamMessageErrorFromUid: uid, streamId: streamId, error: error, missed: missed, cached: cached) - } -} - -/* -extension AgoraVideoViewer { - open func rtcEngineRequestToken(_ engine: AgoraRtcEngineKit) { - self.agSettings.rtcDelegate?.rtcEngineRequestToken?(engine) - } - - open func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, tokenPrivilegeWillExpire: token) - } - - open func rtcEngine(_ engine: AgoraRtcEngineKit, activeSpeaker speakerUid: UInt) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, activeSpeaker: speakerUid) + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didOccurStreamMessageErrorFromUid: uid, streamId: streamId, error: error, missed: missed, cached: cached) } - - open func rtcEngine(_ engine: AgoraRtcEngineKit, firstLocalAudioFramePublished elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, firstLocalAudioFramePublished: elapsed) + open func rtcEngine(_ engine: AgoraRtcEngineKit, contentInspectResult result: AgoraContentInspectResult) { + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, contentInspectResult: result) } - - open func rtcEngine(_ engine: AgoraRtcEngineKit, didClientRoleChanged oldRole: AgoraClientRole, newRole: AgoraClientRole) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didClientRoleChanged: oldRole, newRole: newRole) + public func rtcEngine(_ engine: AgoraRtcEngineKit, didRequest info: AgoraRtcAudioFileInfo, error: AgoraAudioFileInfoError) { + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didRequest: info, error: error) } - - open func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didJoinedOfUid: uid, elapsed: elapsed) + public func rtcEngine(_ engine: AgoraRtcEngineKit, wlAccStats currentStats: AgoraWlAccStats, averageStats: AgoraWlAccStats) { + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, wlAccStats: currentStats, averageStats: averageStats) } - - open func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, didOfflineOfUid: uid, reason: reason) + public func rtcEngine(_ engine: AgoraRtcEngineKit, wlAccMessage reason: AgoraWlAccReason, action: AgoraWlAccAction, wlAccMsg: String) { + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, wlAccMessage: reason, action: action, wlAccMsg: wlAccMsg) } - - open func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStateChange state: AgoraLocalVideoStreamState, error: AgoraLocalVideoStreamError) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, localVideoStateChange: state, error: error) + public func rtcEngine(_ engine: AgoraRtcEngineKit, reportAudioDeviceTestVolume volumeType: AgoraAudioDeviceTestVolumeType, volume: Int) { + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, reportAudioDeviceTestVolume: volumeType, volume: volume) } - - open func rtcEngine(_ engine: AgoraRtcEngineKit, localAudioStateChange state: AgoraAudioLocalState, error: AgoraAudioLocalError) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, localAudioStateChange: state, error: error) + public func rtcEngine(_ engine: AgoraRtcEngineKit, didClientRoleChangeFailed reason: AgoraClientRoleChangeFailedReason, currentRole: AgoraClientRole) { + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didClientRoleChangeFailed: reason, currentRole: currentRole) } - - open func rtcEngine(_ engine: AgoraRtcEngineKit, remoteVideoStateChangedOfUid uid: UInt, state: AgoraVideoRemoteState, reason: AgoraVideoRemoteStateReason, elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, remoteVideoStateChangedOfUid: uid, state: state, reason: reason, elapsed: elapsed) + public func rtcEngine(_ engine: AgoraRtcEngineKit, snapshotTaken channel: String, uid: UInt, filePath: String, width: Int, height: Int, errCode: Int) { + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, snapshotTaken: channel, uid: uid, filePath: filePath, width: width, height: height, errCode: errCode) } - - open func rtcEngine(_ engine: AgoraRtcEngineKit, remoteAudioStateChangedOfUid uid: UInt, state: AgoraAudioRemoteState, reason: AgoraAudioRemoteStateReason, elapsed: Int) { - self.agSettings.rtcDelegate?.rtcEngine?(engine, remoteAudioStateChangedOfUid: uid, state: state, reason: reason, elapsed: elapsed) + public func rtcEngine(_ engine: AgoraRtcEngineKit, didProxyConnected channel: String, withUid uid: UInt, proxyType: AgoraProxyType, localProxyIp: String, elapsed: Int) { + self.agoraSettings.rtcDelegate?.rtcEngine?(engine, didProxyConnected: channel, withUid: uid, proxyType: proxyType, localProxyIp: localProxyIp, elapsed: elapsed) } - } -*/ diff --git a/Sources/Agora-UIKit/AgoraVideoViewer+VideoControl.swift b/Sources/Agora-UIKit/AgoraVideoViewer+VideoControl.swift index 9f73198c..16f57c3b 100644 --- a/Sources/Agora-UIKit/AgoraVideoViewer+VideoControl.swift +++ b/Sources/Agora-UIKit/AgoraVideoViewer+VideoControl.swift @@ -7,6 +7,9 @@ import AgoraRtcKit import AVKit +#if canImport(AgoraRtmControl) +import AgoraRtmControl +#endif extension AgoraVideoViewer { @@ -17,11 +20,11 @@ extension AgoraVideoViewer { return } self.getControlContainer() - if let videoSource = self.agSettings.videoSource { + if let videoSource = self.agoraSettings.videoSource { self.agkit.setVideoSource(videoSource) } - if self.agSettings.externalAudioSettings.enabled { - let audioSource = self.agSettings.externalAudioSettings + if self.agoraSettings.externalAudioSettings.enabled { + let audioSource = self.agoraSettings.externalAudioSettings self.agkit.enableExternalAudioSource( withSampleRate: .init(audioSource.sampleRate), channelsPerFrame: .init(audioSource.channels) @@ -146,22 +149,19 @@ extension AgoraVideoViewer { ssButton.isSelected.toggle() ssButton.backgroundColor = ssButton.isSelected ? .systemGreen : .systemGray #elseif os(macOS) - ssButton.layer?.backgroundColor = ( - ssButton.isOn ? NSColor.systemGreen : NSColor.systemGray - ).cgColor + ssButton.layer?.backgroundColor = (ssButton.isOn ? NSColor.systemGreen : NSColor.systemGray).cgColor - if ssButton.isOn { - self.startSharingScreen() - } else { - self.agkit.stopScreenCapture() - } + if ssButton.isOn { self.startSharingScreen() + } else { self.agkit.stopScreenCapture() } #endif } /// Start a new screen capture (macOS only for now) /// - Parameter displayId: The display ID of the screen to be shared. This parameter specifies which screen you want to share. - ///
For information on how to get the displayId, see [Share the Screen](https://docs.agora.io/en/Voice/screensharing_mac?platform=macOS) - open func startSharingScreen(displayId: UInt = 0) { + /// - Parameter contentHint: The content hint for screen sharing, see [AgoraVideoContentHint](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/oc/Constants/AgoraVideoContentHint.html?platform=macOS). + /// + ///
For information on how to get the displayId, see [Share the Screen](https://docs.agora.io/en/Video/screensharing_mac?platform=macOS) + open func startSharingScreen(displayId: UInt = 0, contentHint: AgoraVideoContentHint = .none) { #if os(macOS) let rectangle = CGRect.zero let parameters = AgoraScreenCaptureParameters() @@ -170,7 +170,7 @@ extension AgoraVideoViewer { parameters.bitrate = 1000 parameters.captureMouseCursor = true self.agkit.startScreenCapture(byDisplayId: displayId, rectangle: rectangle, parameters: parameters) - self.agkit.setScreenCapture(.none) + self.agkit.setScreenCaptureContentHint(contentHint) #endif } @@ -251,14 +251,12 @@ extension AgoraVideoViewer { channel: String, as role: AgoraClientRole = .broadcaster, fetchToken: Bool = false, uid: UInt? = nil ) { - if self.connectionData == nil { - fatalError("No app ID is provided") - } + if self.connectionData == nil { fatalError("No app ID is provided") } if fetchToken { if let tokenURL = self.agoraSettings.tokenURL { AgoraVideoViewer.fetchToken( - urlBase: tokenURL, channelName: channel, - userId: self.userID) { result in + urlBase: tokenURL, channelName: channel, userId: self.userID + ) { result in switch result { case .success(let token): self.join(channel: channel, with: token, as: role, uid: uid) @@ -285,14 +283,10 @@ extension AgoraVideoViewer { channel: String, with token: String?, as role: AgoraClientRole = .broadcaster, uid: UInt? = nil ) { - if self.connectionData == nil { - fatalError("No app ID is provided") - } + if self.connectionData == nil { fatalError("No app ID is provided") } if role == .broadcaster { if !self.checkForPermissions(self.activePermissions, callback: { error in - if error != nil { - return - } + if error != nil { return } DispatchQueue.main.async { self.join(channel: channel, with: token, as: role, uid: uid) } @@ -318,10 +312,13 @@ extension AgoraVideoViewer { self?.userID = uid if self?.userRole == .broadcaster { self?.addLocalVideo() } self?.delegate?.joinedChannel(channel: channel) + #if canImport(AgoraRtmControl) self?.setupRtmController(joining: channel) + #endif } } + #if canImport(AgoraRtmControl) /// Initialise RTM to send messages across the network. open func setupRtmController(joining channel: String) { self.setupRtmController { rtmController in @@ -330,7 +327,7 @@ extension AgoraVideoViewer { } open func setupRtmController(callback: ((AgoraRtmController?) -> Void)? = nil) { - if !self.agSettings.rtmEnabled { return } + if !self.agoraSettings.rtmEnabled { return } if self.rtmController == nil { DispatchQueue.global(qos: .utility).async { self.rtmController = AgoraRtmController(delegate: self) @@ -341,6 +338,7 @@ extension AgoraVideoViewer { } } } + #endif internal func handleAlreadyInChannel( channel: String, with token: String?, diff --git a/Sources/Agora-UIKit/AgoraVideoViewer.swift b/Sources/Agora-UIKit/AgoraVideoViewer.swift index 21c63dbe..96b8f304 100644 --- a/Sources/Agora-UIKit/AgoraVideoViewer.swift +++ b/Sources/Agora-UIKit/AgoraVideoViewer.swift @@ -14,8 +14,9 @@ import CoreFoundation import CommonCrypto #endif import AgoraRtcKit -#if canImport(AgoraRtmKit) +#if canImport(AgoraRtmControl) import AgoraRtmKit +import AgoraRtmControl #endif /// An interface for getting some common delegate callbacks without needing to subclass. @@ -50,7 +51,7 @@ public protocol AgoraVideoViewerDelegate: AnyObject { /// A pong request has just come back to the local user, indicating that someone is still present in RTM /// - Parameter peerId: RTM ID of the remote user that sent the pong request. func incomingPongRequest(from peerId: String) - #if canImport(AgoraRtmKit) + #if canImport(AgoraRtmControl) /// State of RTM has changed /// - Parameters: /// - oldState: Previous state of RTM @@ -85,7 +86,7 @@ public extension AgoraVideoViewerDelegate { func extraButtons() -> [NSButton] { [] } #endif func incomingPongRequest(from peerId: String) {} - #if canImport(AgoraRtmKit) + #if canImport(AgoraRtmControl) func rtmStateChanged( from oldState: AgoraRtmController.RTMStatus, to newState: AgoraRtmController.RTMStatus ) {} @@ -99,6 +100,10 @@ public extension AgoraVideoViewerDelegate { /// View to contain all the video session objects, including camera feeds and buttons for settings open class AgoraVideoViewer: MPView, SingleVideoViewDelegate { + public var rtcLookup: [UInt: String] = [:] + + public var rtmLookup: [String: Codable] = [:] + /// Delegate for the AgoraVideoViewer, used for some important callback methods. public weak var delegate: AgoraVideoViewerDelegate? @@ -106,8 +111,10 @@ open class AgoraVideoViewer: MPView, SingleVideoViewDelegate { /// as well as agora video configuration. public internal(set) var agoraSettings: AgoraSettings + #if canImport(AgoraRtmControl) /// Controller class for managing RTM messages public var rtmController: AgoraRtmController? + #endif /// The rendering mode of the video view for all active videos. var videoRenderMode: AgoraVideoRenderMode { @@ -167,16 +174,18 @@ open class AgoraVideoViewer: MPView, SingleVideoViewDelegate { set { self.connectionData.rtcToken = newValue } } + #if canImport(AgoraRtmControl) /// Status of the RTM Engine var rtmState: AgoraRtmController.RTMStatus { if let rtmc = self.rtmController { return rtmc.rtmStatus - } else if self.agSettings.rtmEnabled { + } else if self.agoraSettings.rtmEnabled { return .initFailed } else { return .offline } } + #endif lazy internal var floatingVideoHolder: MPCollectionView = { let collView = AgoraCollectionViewer() @@ -335,9 +344,10 @@ open class AgoraVideoViewer: MPView, SingleVideoViewDelegate { } /// Used by storyboard to set the AgoraSettings tokenURL - @IBInspectable var tokenURL: String = "" { - didSet { - self.agoraSettings.tokenURL = tokenURL + @IBInspectable public var tokenURL: String? { + get { self.agoraSettings.tokenURL } + set { + self.agoraSettings.tokenURL = newValue } } /// Create view from NSCoder, this initialiser requires an appID key with a String value. @@ -354,14 +364,14 @@ open class AgoraVideoViewer: MPView, SingleVideoViewDelegate { internal var userVideosForGrid: [UInt: AgoraSingleVideoView] { if self.style == .floating { - if self.overrideActiveSpeaker == nil, self.activeSpeaker == nil, !self.agSettings.showSelf { + if self.overrideActiveSpeaker == nil, self.activeSpeaker == nil, !self.agoraSettings.showSelf { return [:] } return self.userVideoLookup.filter { $0.key == (self.overrideActiveSpeaker ?? self.activeSpeaker ?? self.userID) } } else if self.style == .grid { - return self.userVideoLookup.filter { ($0.key != self.userID || self.agSettings.showSelf) } + return self.userVideoLookup.filter { ($0.key != self.userID || self.agoraSettings.showSelf) } } else { return [:] } diff --git a/Sources/Agora-UIKit/AgoraRtmController+AgoraRtmDelegate.swift b/Sources/AgoraRtmControl/AgoraRtmController+AgoraRtmDelegate.swift similarity index 53% rename from Sources/Agora-UIKit/AgoraRtmController+AgoraRtmDelegate.swift rename to Sources/AgoraRtmControl/AgoraRtmController+AgoraRtmDelegate.swift index 57c3b6e8..78060a49 100644 --- a/Sources/Agora-UIKit/AgoraRtmController+AgoraRtmDelegate.swift +++ b/Sources/AgoraRtmControl/AgoraRtmController+AgoraRtmDelegate.swift @@ -16,13 +16,13 @@ extension AgoraRtmController: AgoraRtmDelegate, AgoraRtmChannelDelegate { /// The token used to connect to the current active channel has expired. /// - Parameter kit: Agora RTM Engine open func rtmKitTokenDidExpire(_ kit: AgoraRtmKit) { - if let tokenURL = self.agoraSettings.tokenURL { + if let tokenURL = self.delegate?.rtmToken { AgoraRtmController.fetchRtmToken( - urlBase: tokenURL, userId: self.connectionData.rtmId, + urlBase: tokenURL, userId: self.delegate.rtmId, callback: self.newTokenFetched(result:) ) } - self.delegate.agSettings.rtmDelegate?.rtmKitTokenDidExpire?(kit) + self.rtmDelegate?.rtmKitTokenDidExpire?(kit) } /** @@ -33,10 +33,8 @@ extension AgoraRtmController: AgoraRtmDelegate, AgoraRtmChannelDelegate { @param peerId The user ID of the sender. */ open func rtmKit(_ kit: AgoraRtmKit, messageReceived message: AgoraRtmMessage, fromPeer peerId: String) { - if let rawMsg = message as? AgoraRtmRawMessage { - self.decodeRawMessage(rawMsg: rawMsg, from: peerId) - } - self.delegate.agSettings.rtmDelegate?.rtmKit?(kit, messageReceived: message, fromPeer: peerId) + self.decodeMessage(message: message, from: peerId) + self.rtmDelegate?.rtmKit?(kit, messageReceived: message, fromPeer: peerId) } /** @@ -52,8 +50,8 @@ extension AgoraRtmController: AgoraRtmDelegate, AgoraRtmChannelDelegate { @param member The user joining the channel. See AgoraRtmMember. */ open func channel(_ channel: AgoraRtmChannel, memberJoined member: AgoraRtmMember) { - self.sendPersonalData(to: member.userId) - self.delegate.agSettings.rtmChannelDelegate?.channel?(channel, memberJoined: member) + self.delegate?.channel(channel, memberJoined: member) + self.rtmChannelDelegate?.channel?(channel, memberJoined: member) } /** @@ -70,46 +68,15 @@ extension AgoraRtmController: AgoraRtmDelegate, AgoraRtmChannelDelegate { messageReceived message: AgoraRtmMessage, from member: AgoraRtmMember ) { - if let rawMsg = message as? AgoraRtmRawMessage { - self.decodeRawMessage(rawMsg: rawMsg, from: member.userId) - } - self.delegate.agSettings.rtmChannelDelegate?.channel?(channel, messageReceived: message, from: member) + self.decodeMessage(message: message, from: member.userId) + self.rtmChannelDelegate?.channel?(channel, messageReceived: message, from: member) } - /// Decode an incoming AgoraRtmRawMessage + /// Decode an incoming AgoraRtmMessage /// - Parameters: - /// - rawMsg: Incoming Raw message. + /// - message: Incoming RTM message. /// - peerId: Id of the peer this message is coming from - open func decodeRawMessage(rawMsg: AgoraRtmRawMessage, from peerId: String) { - if let decodedRaw = AgoraRtmController.decodeRawRtmData(data: rawMsg.rawData, from: peerId) { - switch decodedRaw { - case .mute(let muteReq): - self.delegate.handleMuteRequest(muteReq: muteReq) - case .userData(let user): - AgoraVideoViewer.agoraPrint( - .verbose, message: "Received user data: \n\(user.prettyPrint())" - ) - self.rtmLookup[user.rtmId] = user - if let rtcId = user.rtcId { - self.rtcLookup[rtcId] = user.rtmId - self.delegate.videoLookup[rtcId]? - .showOptions = self.agoraSettings.showRemoteRequestOptions - } - case .dataRequest(let requestVal): - switch requestVal.type { - case .userData: - self.sendPersonalData(to: peerId) - case .ping: - self.sendRaw( - message: RtmDataRequest(type: .pong), member: peerId - ) {_ in } - case .pong: - AgoraVideoViewer.agoraPrint( - .verbose, message: "Received pong from \(peerId)" - ) - self.delegate.handlePongRequest(from: peerId) - } - } - } + open func decodeMessage(message: AgoraRtmMessage, from peerId: String) { + self.delegate.decodeMessage(message: message, from: peerId) } } diff --git a/Sources/AgoraRtmControl/AgoraRtmController+MuteRequests.swift b/Sources/AgoraRtmControl/AgoraRtmController+MuteRequests.swift new file mode 100644 index 00000000..d846c378 --- /dev/null +++ b/Sources/AgoraRtmControl/AgoraRtmController+MuteRequests.swift @@ -0,0 +1,6 @@ +// +// AgoraRtmController+MuteRequests.swift +// +// +// Created by Max Cobb on 29/07/2021. +// diff --git a/Sources/Agora-UIKit/AgoraRtmController+RtmDelegateOverflows.swift b/Sources/AgoraRtmControl/AgoraRtmController+RtmDelegateOverflows.swift similarity index 58% rename from Sources/Agora-UIKit/AgoraRtmController+RtmDelegateOverflows.swift rename to Sources/AgoraRtmControl/AgoraRtmController+RtmDelegateOverflows.swift index 85cf60cf..23326375 100644 --- a/Sources/Agora-UIKit/AgoraRtmController+RtmDelegateOverflows.swift +++ b/Sources/AgoraRtmControl/AgoraRtmController+RtmDelegateOverflows.swift @@ -9,40 +9,39 @@ import AgoraRtmKit extension AgoraRtmController { open func rtmKit(_ kit: AgoraRtmKit, peersOnlineStatusChanged onlineStatus: [AgoraRtmPeerOnlineStatus]) { - self.agoraSettings.rtmDelegate?.rtmKit?(kit, peersOnlineStatusChanged: onlineStatus) + self.rtmDelegate?.rtmKit?(kit, peersOnlineStatusChanged: onlineStatus) } open func rtmKit(_ kit: AgoraRtmKit, fileMessageReceived message: AgoraRtmFileMessage, fromPeer peerId: String) { - self.agoraSettings.rtmDelegate?.rtmKit?(kit, fileMessageReceived: message, fromPeer: peerId) + self.rtmDelegate?.rtmKit?(kit, fileMessageReceived: message, fromPeer: peerId) } open func rtmKit(_ kit: AgoraRtmKit, imageMessageReceived message: AgoraRtmImageMessage, fromPeer peerId: String) { - self.agoraSettings.rtmDelegate?.rtmKit?(kit, imageMessageReceived: message, fromPeer: peerId) + self.rtmDelegate?.rtmKit?(kit, imageMessageReceived: message, fromPeer: peerId) } open func rtmKit(_ kit: AgoraRtmKit, media requestId: Int64, uploadingProgress progress: AgoraRtmMediaOperationProgress) { - self.agoraSettings.rtmDelegate?.rtmKit?(kit, media: requestId, uploadingProgress: progress) + self.rtmDelegate?.rtmKit?(kit, media: requestId, uploadingProgress: progress) } open func rtmKit(_ kit: AgoraRtmKit, media requestId: Int64, downloadingProgress progress: AgoraRtmMediaOperationProgress) { - self.agoraSettings.rtmDelegate?.rtmKit?(kit, media: requestId, downloadingProgress: progress) + self.rtmDelegate?.rtmKit?(kit, media: requestId, downloadingProgress: progress) } open func rtmKit(_ kit: AgoraRtmKit, connectionStateChanged state: AgoraRtmConnectionState, reason: AgoraRtmConnectionChangeReason) { - self.agoraSettings.rtmDelegate?.rtmKit?(kit, connectionStateChanged: state, reason: reason) + self.rtmDelegate?.rtmKit?(kit, connectionStateChanged: state, reason: reason) } } extension AgoraRtmController { open func channel(_ channel: AgoraRtmChannel, memberCount count: Int32) { - self.agoraSettings.rtmChannelDelegate?.channel?(channel, memberCount: count) + self.rtmChannelDelegate?.channel?(channel, memberCount: count) } open func channel(_ channel: AgoraRtmChannel, memberLeft member: AgoraRtmMember) { - self.agoraSettings.rtmChannelDelegate?.channel?(channel, memberLeft: member) + self.rtmChannelDelegate?.channel?(channel, memberLeft: member) } open func channel(_ channel: AgoraRtmChannel, attributeUpdate attributes: [AgoraRtmChannelAttribute]) { - self.agoraSettings.rtmChannelDelegate?.channel?(channel, attributeUpdate: attributes) + self.rtmChannelDelegate?.channel?(channel, attributeUpdate: attributes) } open func channel(_ channel: AgoraRtmChannel, fileMessageReceived message: AgoraRtmFileMessage, from member: AgoraRtmMember) { - self.agoraSettings.rtmChannelDelegate?.channel?(channel, fileMessageReceived: message, from: member) + self.rtmChannelDelegate?.channel?(channel, fileMessageReceived: message, from: member) } open func channel(_ channel: AgoraRtmChannel, imageMessageReceived message: AgoraRtmImageMessage, from member: AgoraRtmMember) { - self.agoraSettings.rtmChannelDelegate?.channel?(channel, imageMessageReceived: message, from: member) + self.rtmChannelDelegate?.channel?(channel, imageMessageReceived: message, from: member) } } - diff --git a/Sources/AgoraRtmControl/AgoraRtmController+SendHelpers.swift b/Sources/AgoraRtmControl/AgoraRtmController+SendHelpers.swift new file mode 100644 index 00000000..1e5d53f0 --- /dev/null +++ b/Sources/AgoraRtmControl/AgoraRtmController+SendHelpers.swift @@ -0,0 +1,90 @@ +// +// AgoraRtmController+SendHelpers.swift +// +// +// Created by Max Cobb on 30/09/2021. +// + +import Foundation +import AgoraRtmKit + +// MARK: Helper Methods +extension AgoraRtmController { + /// Send a codable message over RTM to the channel. + /// - Parameters: + /// - message: Codable message to send over RTM. + /// - channel: String channel name to send the message to. + /// - callback: Callback, to see if the message was sent successfully. + public func sendCodable( + message: Value, channel: String, + callback: @escaping (AgoraRtmSendChannelMessageErrorCode) -> Void + ) where Value: Codable { + if let channel = self.channels[channel], + let data = try? JSONEncoder().encode(message), + let jsonString = String(data: data, encoding: .utf8) { + channel.send( + AgoraRtmMessage(text: jsonString), completion: callback + ) + } + } + + /// Create message from codable object + /// - Parameter codableObj: Codable object to be sent over the Real-time Messaging network. + /// - Returns: AgoraRtmMessage that is ready to be sent across the Agora Real-time Messaging network. + public static func createRtmMessage(from codableObj: Value) -> AgoraRtmMessage? where Value: Codable { + if let data = try? JSONEncoder().encode(codableObj), + let jsonString = String(data: data, encoding: .utf8) { + return AgoraRtmMessage(text: jsonString) + } + AgoraRtmController.agoraPrint(.error, message: "Message could not be encoded to JSON String") + return nil + } + + /// Send a codable message over RTM to the channel + /// - Parameters: + /// - message: Codable message to send over RTM + /// - channel: AgoraRtmChannel to send the message over + /// - callback: Callback, to see if the message was sent successfully. + public func sendCodable( + message: Value, channel: AgoraRtmChannel, + callback: @escaping (AgoraRtmSendChannelMessageErrorCode) -> Void + ) where Value: Codable { + if let msg = AgoraRtmController.createRtmMessage(from: message) { + channel.send(msg, completion: callback) + return + } + callback(.invalidMessage) + } + + /// Send a codable message over RTM to a member + /// - Parameters: + /// - message: Codable message to send over RTM + /// - channel: member, or RTM ID to send the message to + /// - callback: Callback, to see if the message was sent successfully. + public func sendCodable( + message: Value, member: String, + callback: @escaping (AgoraRtmSendPeerMessageErrorCode) -> Void + ) where Value: Codable { + guard let msg = AgoraRtmController.createRtmMessage(from: message) else { + callback(.imcompatibleMessage) + return + } + self.rtmKit.send(msg, toPeer: member, completion: callback) + } + + /// Send a codable message over RTM to a member + /// - Parameters: + /// - message: Codable message to send over RTM + /// - channel: member, or RTC User ID to send the message to + /// - callback: Callback, to see if the message was sent successfully. + public func sendCodable( + message: Value, user: UInt, + callback: @escaping (AgoraRtmSendPeerMessageErrorCode) -> Void + ) where Value: Codable { + if let rtmId = self.delegate.rtcLookup[user] { + self.sendCodable(message: message, member: rtmId, callback: callback) + } else { + callback(.peerUnreachable) + } + } +} diff --git a/Sources/Agora-UIKit/AgoraRtmController+Tokens.swift b/Sources/AgoraRtmControl/AgoraRtmController+Tokens.swift similarity index 91% rename from Sources/Agora-UIKit/AgoraRtmController+Tokens.swift rename to Sources/AgoraRtmControl/AgoraRtmController+Tokens.swift index 98494e55..c986552c 100644 --- a/Sources/Agora-UIKit/AgoraRtmController+Tokens.swift +++ b/Sources/AgoraRtmControl/AgoraRtmController+Tokens.swift @@ -64,7 +64,7 @@ extension AgoraRtmController { case .success(let token): self.updateToken(token) case .failure(let err): - AgoraVideoViewer.agoraPrint(.error, message: "Could not fetch rtm token: \(err)") + AgoraRtmController.agoraPrint(.error, message: "Could not fetch rtm token: \(err)") } } @@ -72,16 +72,16 @@ extension AgoraRtmController { self.rtmKit.renewToken(token) { _, renewStatus in switch renewStatus { case .ok: - AgoraVideoViewer.agoraPrint(.verbose, message: "token renewal success") + AgoraRtmController.agoraPrint(.verbose, message: "token renewal success") case .failure, .invalidArgument, .rejected, .tooOften, .tokenExpired, .invalidToken, .notInitialized, .notLoggedIn: - AgoraVideoViewer.agoraPrint( + AgoraRtmController.agoraPrint( .error, message: "cannot renew token: \(renewStatus): \(renewStatus.rawValue)" ) @unknown default: - AgoraVideoViewer.agoraPrint( + AgoraRtmController.agoraPrint( .error, message: "cannot renew token (unknown): \(renewStatus): \(renewStatus.rawValue)" ) diff --git a/Sources/Agora-UIKit/AgoraRtmController.swift b/Sources/AgoraRtmControl/AgoraRtmController.swift similarity index 51% rename from Sources/Agora-UIKit/AgoraRtmController.swift rename to Sources/AgoraRtmControl/AgoraRtmController.swift index 8d2a0226..28f4d239 100644 --- a/Sources/Agora-UIKit/AgoraRtmController.swift +++ b/Sources/AgoraRtmControl/AgoraRtmController.swift @@ -5,29 +5,10 @@ // Created by Max Cobb on 14/07/2021. // -import AgoraRtcKit import AgoraRtmKit /// Delegate for fetching data for our RTM Controller public protocol RtmControllerDelegate: AnyObject { - /// Instance of the RTC Engine being used - var rtcEngine: AgoraRtcEngineKit { get } - /// Struct for holding data about the connection to Agora service - var agConnection: AgoraConnectionData { get set } - /// Settings used for the display and behaviour - var agSettings: AgoraSettings { get } - /// Handle mute request, by showing popup or directly changing the device state - /// - Parameter muteReq: Incoming mute request data - func handleMuteRequest(muteReq: AgoraRtmController.MuteRequest) - /// A pong request has just come back to the local user, indicating that someone is still present in RTM - /// - Parameter peerId: RTM ID of the remote user that sent the pong request. - func handlePongRequest(from peerId: String) - /// Property used to access all the RTC connections to other broadcasters in an RTC channel - var videoLookup: [UInt: AgoraSingleVideoView] { get } - /// The role for the user. Either `.audience` or `.broadcaster`. - var userRole: AgoraClientRole { get set } - /// Delegate for the AgoraVideoViewer, used for some important callback methods. - var agoraViewerDelegate: AgoraVideoViewerDelegate? { get } /// Called after AgoraRtmController joins a channel /// - Parameters: /// - name: name of the channel joined @@ -37,41 +18,71 @@ public protocol RtmControllerDelegate: AnyObject { name: String, channel: AgoraRtmChannel, code: AgoraRtmJoinChannelErrorCode ) -} -public extension AgoraVideoViewer { - var agoraViewerDelegate: AgoraVideoViewerDelegate? { - return self.delegate - } -} - -extension AgoraVideoViewer: RtmControllerDelegate { - public var agConnection: AgoraConnectionData { - get { self.connectionData } - set { self.connectionData = newValue } - } - public var rtcEngine: AgoraRtcEngineKit { self.agkit } - public var agSettings: AgoraSettings { self.agoraSettings } - public var videoLookup: [UInt: AgoraSingleVideoView] { self.userVideoLookup } - public func handlePongRequest(from peerId: String) { - self.delegate?.incomingPongRequest(from: peerId) - } - public func rtmChannelJoined( - name: String, channel: AgoraRtmChannel, code: AgoraRtmJoinChannelErrorCode - ) { - self.delegate?.rtmChannelJoined(name: name, channel: channel, code: code) - } + /// Lookup remote user RTM ID based on their RTC ID + var rtcLookup: [UInt: String] { get set } + /// Get remote user data from their RTM ID + var rtmLookup: [String: Codable] { get set } + /// Delegate used for RTM + var rtmDelegate: AgoraRtmDelegate? { get } + /// Delegate used for RTM channel messages + var rtmChannelDelegate: AgoraRtmChannelDelegate? { get } + /// ID used by RTM + var rtmId: String { get } + /// ID used by RTC + var rtcId: UInt? { get } + /// App ID used, found on console.agora.io + var appId: String { get } + /// Token to connect to Agora RTM + var rtmToken: String? { get set } + /// URL for fetching Agora RTM tokens + var tokenURL: String? { get set } + func channel(_ channel: AgoraRtmChannel, memberJoined member: AgoraRtmMember) + /// Method to catch messages incoming from RTM, used to decode them and run any relevant actions + /// - Parameters: + /// - message: Message received from RTM + /// - peerId: ID of the user who sent the message + func decodeMessage(message: AgoraRtmMessage, from peerId: String) + /// State of the RTM Controller has changed + /// - Parameters: + /// - oldState: Previous state of AgoraRtmController + /// - newState: New state of AgoraRtmController + func rtmStateChanged(from oldState: AgoraRtmController.RTMStatus, to newState: AgoraRtmController.RTMStatus) } /// Class for controlling the RTM messages open class AgoraRtmController: NSObject { - /// Instance of the RTC Engine being used - var engine: AgoraRtcEngineKit { self.delegate.rtcEngine } - /// Struct for holding data about the connection to Agora service - var connectionData: AgoraConnectionData { self.delegate.agConnection } - /// Settings used for the display and behaviour - var agoraSettings: AgoraSettings { self.delegate.agSettings } + + /// Print level that will be visible in the developer console, default `.error` + public static var printLevel: PrintType = .warning + /// Level for an internal print statement + public enum PrintType: Int { + /// To use when an internal error has occurred + case error = 0 + /// To use when something is not being used or running correctly + case warning = 1 + /// To use for debugging issues + case debug = 2 + /// To use when we want all the possible logs + case verbose = 3 + var printString: String { + switch self { + case .error: return "ERROR" + case .warning: return "WARNING" + case .debug: return "DEBUG" + case .verbose: return "INFO" + } + } + } + + internal static func agoraPrint(_ tag: PrintType, message: Any) { + if tag.rawValue <= AgoraRtmController.printLevel.rawValue { + print("[AgoraRtmController \(tag.printString)]: \(message)") + } + } + var rtmDelegate: AgoraRtmDelegate? { self.delegate.rtmDelegate } + var rtmChannelDelegate: AgoraRtmChannelDelegate? { self.delegate.rtmChannelDelegate } /// Delegate for fetching data for our RTM Controller weak var delegate: RtmControllerDelegate! @@ -95,77 +106,19 @@ open class AgoraRtmController: NSObject { /// Status of the RTM Engine public internal(set) var rtmStatus: RTMStatus = .initialising { didSet { - self.delegate.agoraViewerDelegate?.rtmStateChanged(from: oldValue, to: self.rtmStatus) + self.delegate.rtmStateChanged(from: oldValue, to: self.rtmStatus) } } -// var videoViewer: AgoraVideoViewer + public internal(set) var rtmKit: AgoraRtmKit - /// Lookup remote user RTM ID based on their RTC ID - public internal(set) var rtcLookup: [UInt: String] = [:] - /// Get remote user data from their RTM ID - public internal(set) var rtmLookup: [String: UserData] = [:] /// RTM Channels created and joined by this RTM Controller public internal(set) var channels: [String: AgoraRtmChannel] = [:] /// Methods to be completed after login has finished (such as joining a channel) public internal(set) var afterLoginSteps: [() -> Void] = [] - /// Data about a user (local or remote) - public struct UserData: Codable { - /// Type of message being sent - var messageType: String? = "UserData" - /// ID used in the RTM connection - var rtmId: String - /// ID used in the RTC (Video/Audio) connection - var rtcId: UInt? - /// Username to be displayed for remote users - var username: String? - /// Role of the user (broadcaster or audience) - var role: AgoraClientRole.RawValue - /// Properties about the Agora SDK versions this user is using - var agora: AgoraVersions = .current - /// Agora UIKit platform (iOS, Android, Flutter, React Native) - var uikit: AgoraUIKit = .current - func prettyPrint() -> String { - """ - rtm: \(rtmId) - rtc: \(rtcId ?? 0) - username: \(username ?? "nil") - role: \(role) - agora: \n\(agora.prettyPrint()) - uikit: \n\(uikit.prettyPrint()) - """ - } - } - - /// Data about the Agora SDK versions a user is using (local or remote) - public struct AgoraVersions: Codable { - /// Versions of the local users current RTM and RTC SDKs - static var current: AgoraVersions { - AgoraVersions(rtm: AgoraRtmKit.getSDKVersion(), rtc: AgoraRtcEngineKit.getSdkVersion()) - } - /// Version string of the RTM SDK - var rtm: String - /// Version string of the RTC SDK - var rtc: String - func prettyPrint() -> String { - """ - rtc: \(rtc) - rtm: \(rtm) - """ - } - } - - var personalData: UserData { - UserData( - rtmId: self.connectionData.rtmId, - rtcId: self.connectionData.rtcId == 0 ? nil : self.connectionData.rtcId, - username: self.connectionData.username, role: self.delegate.userRole.rawValue - ) - } - - init?(delegate: RtmControllerDelegate) { + public init?(delegate: RtmControllerDelegate) { self.delegate = delegate - if let rtmKit = AgoraRtmKit(appId: delegate.agConnection.appId, delegate: nil) { + if let rtmKit = AgoraRtmKit(appId: delegate.appId, delegate: nil) { self.rtmKit = rtmKit } else { return nil } super.init() @@ -176,23 +129,23 @@ open class AgoraRtmController: NSObject { func rtmLogin(completion: @escaping (AgoraRtmLoginErrorCode) -> Void) { self.rtmStatus = .loggingIn - if let tokenURL = self.agoraSettings.tokenURL { - AgoraRtmController.fetchRtmToken(urlBase: tokenURL, userId: self.connectionData.rtmId) { fetchResult in + if let tokenURL = self.delegate.tokenURL, let rtmId = self.delegate?.rtmId { + AgoraRtmController.fetchRtmToken(urlBase: tokenURL, userId: rtmId) { fetchResult in switch fetchResult { case .success(let token): - self.rtmKit.login(byToken: token, user: self.connectionData.rtmId + self.rtmKit.login(byToken: token, user: rtmId ) { errcode in self.rtmLoggedIn(code: errcode) completion(errcode) } case .failure(let failErr): completion(.invalidToken) - AgoraVideoViewer.agoraPrint(.error, message: "could not fetch token: \(failErr)") + AgoraRtmController.agoraPrint(.error, message: "could not fetch token: \(failErr)") } } } else { self.rtmKit.login( - byToken: self.connectionData.rtmToken, user: self.connectionData.rtmId + byToken: self.delegate.rtmToken, user: self.delegate.rtmId ) { errcode in self.rtmLoggedIn(code: errcode) completion(errcode) @@ -211,9 +164,9 @@ open class AgoraRtmController: NSObject { case .unknown, .rejected, .invalidArgument, .invalidAppId, .invalidToken, .tokenExpired, .notAuthorized, .timeout, .loginTooOften, .loginNotInitialized: - AgoraVideoViewer.agoraPrint(.error, message: "could not log into rtm: \(code.rawValue)") + AgoraRtmController.agoraPrint(.error, message: "could not log into rtm: \(code.rawValue)") @unknown default: - AgoraVideoViewer.agoraPrint(.error, message: "unknown login code") + AgoraRtmController.agoraPrint(.error, message: "unknown login code") } self.rtmStatus = .loginFailed(code) } @@ -234,13 +187,13 @@ open class AgoraRtmController: NSObject { if err == .ok || err == .alreadyLogin { self.joinChannel(named: channel, callback: callback) } else { - AgoraVideoViewer.agoraPrint(.error, message: "Could not login to rtm") + AgoraRtmController.agoraPrint(.error, message: "Could not login to rtm") } } case .loggingIn: self.afterLoginSteps.append { self.joinChannel(named: channel, callback: callback) } case .loginFailed(let loginErr): - AgoraVideoViewer.agoraPrint(.error, message: "login failed: \(loginErr.rawValue)") + AgoraRtmController.agoraPrint(.error, message: "login failed: \(loginErr.rawValue)") case .loggedIn, .connected: guard let newChannel = self.rtmKit.createChannel(withId: channel, delegate: self) else { return @@ -254,7 +207,7 @@ open class AgoraRtmController: NSObject { self.joinChannel(named: channel, callback: callback) } case .initFailed: - AgoraVideoViewer.agoraPrint(.error, message: "Cannot log into a channel if RTM failed") + AgoraRtmController.agoraPrint(.error, message: "Cannot log into a channel if RTM failed") } } @@ -264,11 +217,11 @@ open class AgoraRtmController: NSObject { if let rtmChannel = self.channels[channel] { rtmChannel.leave { leaveStatus in if leaveStatus == .ok { - AgoraVideoViewer.agoraPrint(.verbose, message: "Successfully left RTM channel") + AgoraRtmController.agoraPrint(.verbose, message: "Successfully left RTM channel") self.channels.removeValue(forKey: channel) return } - AgoraVideoViewer.agoraPrint( + AgoraRtmController.agoraPrint( .error, message: "Could not leave RTM channel \(channel): \(leaveStatus.rawValue)" ) } @@ -287,16 +240,15 @@ open class AgoraRtmController: NSObject { switch code { case .channelErrorOk: self.rtmStatus = .connected - self.sendPersonalData(to: channel) self.channels[name] = channel case .channelErrorFailure, .channelErrorRejected, .channelErrorInvalidArgument, .channelErrorTimeout, .channelErrorExceedLimit, .channelErrorAlreadyJoined, .channelErrorTooOften, .sameChannelErrorTooOften, .channelErrorNotInitialized, .channelErrorNotLoggedIn: - AgoraVideoViewer.agoraPrint(.error, message: "could not join channel: \(code.rawValue)") + AgoraRtmController.agoraPrint(.error, message: "could not join channel: \(code.rawValue)") @unknown default: - AgoraVideoViewer.agoraPrint(.error, message: "join channel unknown response: \(code.rawValue)") + AgoraRtmController.agoraPrint(.error, message: "join channel unknown response: \(code.rawValue)") } - self.delegate.rtmChannelJoined(name: name, channel: channel, code: code) + self.delegate?.rtmChannelJoined(name: name, channel: channel, code: code) } } diff --git a/Tests/Agora-UIKit-Tests/RtcEncodingTests.swift b/Tests/Agora-UIKit-Tests/RtcEncodingTests.swift index 84e99e22..a2739091 100644 --- a/Tests/Agora-UIKit-Tests/RtcEncodingTests.swift +++ b/Tests/Agora-UIKit-Tests/RtcEncodingTests.swift @@ -6,6 +6,8 @@ // import XCTest +#if canImport(AgoraRtmController) && canImport(AgoraUIKit_iOS) +@testable import AgoraRtmController @testable import AgoraUIKit_iOS final class RtcEncodingTests: XCTestCase { @@ -32,3 +34,4 @@ final class RtcEncodingTests: XCTestCase { XCTAssertEqual(encodedeffsTest, encodedeffsTestCorrect, "UDID did not encode correctly: \(encodedeffsTest)") } } +#endif diff --git a/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift b/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift index 3310bcdd..cfc641fa 100644 --- a/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift +++ b/Tests/Agora-UIKit-Tests/RtmMessagingTests.swift @@ -1,14 +1,16 @@ import XCTest import AgoraRtcKit +#if canImport(AgoraRtmController) && canImport(AgoraUIKit_iOS) import AgoraRtmKit +import AgoraRtmController @testable import AgoraUIKit_iOS final class RtmMessagesTests: XCTestCase { func testEncodeMuteReq() throws { - let muteReq = AgoraRtmController.MuteRequest( + let muteReq = AgoraVideoViewer.MuteRequest( rtcId: 999, mute: true, device: .camera, isForceful: true ) - guard let rawMsg = AgoraRtmController.createRawRtm(from: muteReq) else { + guard let rawMsg = AgoraRtmController.createRtmMessage(from: muteReq) else { return XCTFail("MuteRequest should be encodable") } XCTAssert(rawMsg.text == "AgoraUIKit", "Message text data should be AgoraUIKit") @@ -27,7 +29,7 @@ final class RtmMessagesTests: XCTestCase { + "\"MuteRequest\",\"device\":0,\"isForceful\":true}" XCTAssertEqual(msgText, msgTextValid, "Message text not matching mstTextValid") - guard let decodedMsg = AgoraRtmController.decodeRawRtmData( + guard let decodedMsg = AgoraVideoViewer.decodeRtmData( data: rawMsg.rawData, from: "" ) else { return XCTFail("Failed to decode message") @@ -47,11 +49,11 @@ final class RtmMessagesTests: XCTestCase { } func testEncodeUserData() throws { - let userData = AgoraRtmController.UserData( + let userData = AgoraVideoViewer.UserData( rtmId: "1234-5678", rtcId: 190, username: "username", role: AgoraClientRole.broadcaster.rawValue, agora: .current, uikit: .current ) - guard let rawMsg = AgoraRtmController.createRawRtm(from: userData) else { + guard let rawMsg = AgoraRtmController.createRtmMessage(from: userData) else { return XCTFail("UserData should be encodable") } XCTAssert(rawMsg.text == "AgoraUIKit", "Message text data should be AgoraUIKit") @@ -76,7 +78,7 @@ final class RtmMessagesTests: XCTestCase { + "\"messageType\":\"UserData\",\"rtcId\":190}" XCTAssertEqual(msgText, msgTextValid, "Message text not matching msgTextValid") - guard let decodedMsg = AgoraRtmController.decodeRawRtmData(data: rawMsg.rawData, from: "") else { + guard let decodedMsg = AgoraVideoViewer.decodeRtmData(data: rawMsg.rawData, from: "") else { return XCTFail("Failed to decode message") } @@ -91,3 +93,4 @@ final class RtmMessagesTests: XCTestCase { } } } +#endif diff --git a/doc_generation.md b/doc_generation.md new file mode 100644 index 00000000..b20bcc9b --- /dev/null +++ b/doc_generation.md @@ -0,0 +1,19 @@ +# Building Docc for Agora UIKit + +First generate the docs with Xcode via Terminal: + +```sh +xcodebuild docbuild -scheme AgoraUIKit_iOS-Package \ + -derivedDataPath 'docc' \ + -destination generic/platform=iOS +``` + +Then turn the doccarchive into static docs: + +```sh +$(xcrun --find docc) process-archive \ + transform-for-static-hosting AgoraUIKit_iOS.doccarchive \ + --output-path docs \ + --hosting-base-path iOS-UIKit +``` + diff --git a/media/uml_agorartmcontrol.svg b/media/uml_agorartmcontrol.svg new file mode 100644 index 00000000..b805ea83 --- /dev/null +++ b/media/uml_agorartmcontrol.svg @@ -0,0 +1,192 @@ +«extension»AgoraRtmControllerrtmKit(_:peersOnlineStatusChanged:)rtmKit(_:fileMessageReceived:fromPeer:)rtmKit(_:imageMessageReceived:fromPeer:)rtmKit(_:media:uploadingProgress:)rtmKit(_:media:downloadingProgress:)rtmKit(_:connectionStateChanged:reason:)«extension»AgoraRtmControllerchannel(_:memberCount:)channel(_:memberLeft:)channel(_:attributeUpdate:)channel(_:fileMessageReceived:from:)channel(_:imageMessageReceived:from:)«extension»AgoraRtmControllerrtmKitTokenDidExpire(_:)rtmKit(_:messageReceived:fromPeer:)channel(_:memberJoined:)channel(_:messageReceived:from:)decodeMessage(message:from:)«extension»AgoraRtmControllersendCodable(message:channel:callback:)createRtmMessage(from:)sendCodable(message:channel:callback:)sendCodable(message:member:callback:)sendCodable(message:user:callback:)«extension»AgoraRtmControllerfetchRtmToken(urlBase:userId:callback:)newTokenFetched(result:)updateToken(_:)«protocol»RtmControllerDelegatertcLookup : [UInt: String]rtmLookup : [String: Codable]rtmDelegate : AgoraRtmDelegate?rtmChannelDelegate : AgoraRtmChannelDelegate?rtmId : StringrtcId : UInt?appId : StringrtmToken : String?tokenURL : String?rtmChannelJoined(name:channel:code:)channel(_:memberJoined:)decodeMessage(message:from:)rtmStateChanged(from:to:)AgoraRtmControllerprintLevel : PrintTypertmDelegate : AgoraRtmDelegate?rtmChannelDelegate : AgoraRtmChannelDelegate?delegate : RtmControllerDelegate!rtmStatus : RTMStatusrtmKit : AgoraRtmKitchannels : [String: AgoraRtmChannel]agoraPrint(_:message:)afterLoginSteps : [() -> Void]init(delegate:)rtmLogin(completion:)rtmLoggedIn(code:)joinChannel(named:callback:)leaveChannel(named:)rtmChannelJoined(name:channel:code:)AgoraRtmDelegateAgoraRtmChannelDelegateAnyObjectNSObjectextextextextinheritsinheritsinheritsinherits \ No newline at end of file diff --git a/media/uml_agorauikit.svg b/media/uml_agorauikit.svg new file mode 100644 index 00000000..7a3b697c --- /dev/null +++ b/media/uml_agorauikit.svg @@ -0,0 +1,814 @@ +«struct»AgoraViewerviewer : UIViewTypestyle : AgoraVideoViewer.StyleagoraSettings : AgoraSettingsdelegate : AgoraVideoViewerDelegate?makeUIView(context:)updateUIView(_:context:)init(connectionData:style:agoraSettings:delegate:)join(channel:with:as:)AgoraSingleVideoViewvideoMuted : BoolaudioMuted : BoolsingleVideoViewDelegate : SingleVideoViewDelegate?showOptions : Booluid : UIntcanvas : AgoraRtcVideoCanvashostingView : MPView?micFlagColor : MPColoruserOptions : MPView?mutedFlag : MPViewinit(uid:micColor:delegate:)setupOptions(visible:)setupMutedFlag()setBackground()init(coder:)«extension»AgoraSingleVideoViewupdateUserOptions()«extension»AgoraSingleVideoViewuserOptionsString(for:isMuted:)addItems(to:)optionsBtnSelected(sender:)optionsActionSelected(sender:)optionsBtnSelected(sender:)«extension»MPButtonvideoSymbolmuteVideoSelectedSymbol : String?micSymbolmuteMicSelectedSymbol : String?micSlashSymbolellipsisSymbolcameraRotateSymbolwandSymbolpersonSymbolscreenShareSymbolpinSymbolisOn : BoolvideoSymbolmuteVideoSelectedSymbol : String?micSymbolmuteMicSelectedSymbol : String?micSlashSymbolcameraRotateSymbolwandSymbolpersonSymbolscreenShareSymbolpinSymbolisOn : BoolmuteCameraStringmuteMicStringunmuteCameraStringunmuteMicStringnewToggleButton(unselected:selected:)«protocol»AgoraVideoViewerDelegatejoinedChannel(channel:)leftChannel(_:)tokenWillExpire(_:tokenPrivilegeWillExpire:)tokenDidExpire(_:)presentAlert(alert:animated:)extraButtons()extraButtons()incomingPongRequest(from:)rtmStateChanged(from:to:)rtmChannelJoined(name:channel:code:)«extension»AgoraVideoViewerDelegatejoinedChannel(channel:)leftChannel(_:)tokenWillExpire(_:tokenPrivilegeWillExpire:)tokenDidExpire(_:)presentAlert(alert:animated:)extraButtons()extraButtons()incomingPongRequest(from:)rtmStateChanged(from:to:)rtmChannelJoined(name:channel:code:)AgoraVideoViewerrtcLookup : [UInt: String]rtmLookup : [String: Codable]delegate : AgoraVideoViewerDelegate?agoraSettings : AgoraSettingsrtmController : AgoraRtmController?videoRenderMode : AgoraVideoRenderModeactiveSpeaker : UInt?overrideActiveSpeaker : UInt?userID : UIntconnectionData : AgoraConnectionData!userRole : AgoraClientRolecurrentRtcToken : String?rtmState : AgoraRtmController.RTMStatusfloatingVideoHolder : MPCollectionViewbackgroundVideoHolder : MPViewagkit : AgoraRtcEngineKitstyle : AgoraVideoViewer.StyleappID : StringstyleString : StringtokenURL : String?userVideoLookup : [UInt: AgoraSingleVideoView]userVideosForGrid : [UInt: AgoraSingleVideoView]collectionViewVideos : [AgoraSingleVideoView]controlContainer : MPBlurView?camButton : MPButton?micButton : MPButton?flipButton : MPButton?beautyButton : MPButton?screenShareButton : MPButton?beautyOptions : AgoraBeautyOptionsremoteUserIDs : Set<UInt>init(connectionData:style:agoraSettings:delegate:)init(coder:)«extension»AgoraVideoViewercheckForPermissions(_:alsoRequest:callback:)checkPermissions(for:alsoRequest:callback:)goToSettingsPage()errorVibe()cameraMicSettingsPopup(successHandler:)«struct»AgoraUIKitcurrent : AgoraUIKitplatform : Stringversion : Stringframework : StringversionframeworkplatformplatformplatformprettyPrint()«extension»AgoraVideoVieweractivePermissions : [AVMediaType]setupAgoraVideo()setCam(to:completion:)toggleCam(_:)setMic(to:completion:)toggleMic(_:)toggleScreenShare()startSharingScreen(displayId:)toggleBeautify()flipCamera()toggleBroadcast()setRole(to:)join(channel:as:fetchToken:uid:)join(channel:with:as:uid:)setupRtmController(joining:)setupRtmController(callback:)handleAlreadyInChannel(channel:with:as:uid:)leaveChannel(stopPreview:_:)updateToken(_:)exit()«protocol»SingleVideoViewDelegatertmController : AgoraRtmController?createRequest(to:fromString:)sendMuteRequest(to:mute:device:isForceful:)presentAlert(alert:animated:)«extension»SingleVideoViewDelegatepresentAlert(alert:animated:)«struct»AgoraConnectionDataappId : StringrtcToken : String?appToken : String?rtmToken : String?channel : String?rtcId : UIntrtmId : Stringusername : String?uidFrom(vendor:charSet:)fetchSerialMd5()init(appId:appToken:idLogic:)init(appId:rtcToken:rtmToken:idLogic:)«extension»AgoraVideoVieweraddLocalVideo()addUserVideo(with:)setRandomSpeaker()removeUserVideo(with:)«extension»AgoraVideoViewerreorganiseVideos()gridForTwo()formulateGrid(_:_:_:)setVideoHolderPosition()organiseGrid()«extension»AgoraVideoVieweragConnection : AgoraConnectionDatartcEngine : AgoraRtcEngineKitvideoLookup : [UInt: AgoraSingleVideoView]«extension»AgoraVideoViewerrtmId : StringrtcId : UInt?appId : StringrtmToken : String?rtmDelegate : AgoraRtmDelegate?rtmChannelDelegate : AgoraRtmChannelDelegate?rtmStateChanged(from:to:)decodeMessage(message:from:)handleDecodedMessage(_:from:)handlePongRequest(from:)rtmChannelJoined(name:channel:code:)channel(_:memberJoined:)personalData()«extension»AgoraVideoViewerdecodeRtmData(data:from:)broadcastPersonalData()sendPersonalData(to:)sendPersonalData(to:)«extension»AgoraVideoViewerfetchToken(urlBase:channelName:userId:callback:)newTokenFetched(result:)AgoraCollectionViewercellSpacing : CGFloatflowLayout : MPCollectionViewFlowLayoutinit(frame:collectionViewLayout:)init(frame:collectionViewLayout:)init()init(coder:)AgoraCollectionItembackgroundIconbackgroundIcon : MPButtonagoraVideoView : AgoraSingleVideoView?init(frame:)init(coder:)loadView()viewDidLoad()«extension»AgoraVideoViewercollectionView(_:cellForItemAt:)collectionView(_:numberOfItemsInSection:)collectionView(_:willDisplay:forItemAt:)collectionView(_:didEndDisplaying:forItemAt:)collectionView(_:itemForRepresentedObjectAt:)numberOfSections(in:)collectionView(_:numberOfItemsInSection:)collectionView(_:willDisplay:forRepresentedObjectAt:)collectionView(_:didSelectItemsAt:)refreshCollectionData()displayItem(_:at:)collectionView(_:didSelectItemAt:)«extension»AgoraVideoViewerplatformContainerSizing(_:_:_:_:_:)platformContainerSizing(_:_:_:_:_:)positionButtonContainer(_:_:_:)addVideoButtons(to:)setCamAndMicButtons()getControlContainer()getCameraButton()getMicButton()getScreenShareButton()getFlipButton()getBeautifyButton()«extension»AgoraVideoViewerprintLevel : PrintTypeagoraPrint(_:message:)fills(view:)«extension»AgoraVideoViewersendMuteRequest(to:mute:device:isForceful:)«extension»SingleVideoViewDelegatecreateRequest(to:fromString:)«extension»AgoraVideoViewerhandleMuteRequest(muteReq:)«struct»AgoraSettingsrtcDelegate : AgoraRtcEngineDelegate?rtmDelegate : AgoraRtmDelegate?rtmChannelDelegate : AgoraRtmChannelDelegate?rtmEnabled : BooltokenURL : String?videoRenderMode : AgoraVideoRenderModeenabledButtons : BuiltinButtonsbuttonPosition : PositionfloatPosition : PositionvideoConfiguration : AgoraVideoEncoderConfigurationvideoSource : AgoraVideoSourceProtocol?showSelf : BoolexternalAudioSettings : ExternalAudioSettingscolors : AgoraViewerColorslowBitRateStream : String?usingDualStream : BoolcameraEnabled : BoolshowRemoteRequestOptions : BoolmicEnabled : BoolgridThresholdHighBitrate : IntdefaultLowBitrateParambuttonSize : CGFloatbuttonMargin : CGFloatbuttonIconScale : UIImage.SymbolScalebuttonIconSize : CGFloatinit()«struct»AgoraViewerColorsmicFlag : MPColormicButtonNormal : MPColorcamButtonNormal : MPColormicButtonSelected : MPColorcamButtonSelected : MPColorbuttonDefaultNormal : MPColorbuttonDefaultSelected : MPColorbuttonTintColor : MPColorUIViewRepresentableMPViewAnyObjectCodableRtmControllerDelegateMPCollectionViewMPCollectionViewCellMPCollectionViewDelegateMPCollectionViewDataSourceAgoraRtcEngineDelegateinheritsinheritsinheritsinheritsinheritsinheritsinheritsconfirms toinheritsinheritsinheritsinheritsinheritsextextextextextextextextextextextextextextextextextext \ No newline at end of file