diff --git a/AgoraWidgets.podspec b/AgoraWidgets.podspec index 0729d1c..76ba031 100644 --- a/AgoraWidgets.podspec +++ b/AgoraWidgets.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "AgoraWidgets" - spec.version = "2.8.100" + spec.version = "2.8.103" spec.summary = "Agora widgets" spec.description = "Agora native widgets" spec.homepage = "https://docs.agora.io/en/agora-class/landing-page?platform=iOS" @@ -18,13 +18,14 @@ Pod::Spec.new do |spec| spec.dependency "AgoraUIBaseViews", ">=2.8.0" spec.dependency "AgoraWidget", ">=2.8.0" spec.dependency "AgoraLog", "1.0.2" - spec.dependency "Armin", ">=1.1.0" + spec.dependency "Armin", "~> 1.0" spec.dependency "SwifterSwift" spec.dependency "Masonry" + spec.dependency "AgoraRtcEngine_Special_iOS", '~> 3.7.2.133' # Netless - spec.dependency "Whiteboard" + spec.dependency "Whiteboard", ">=2.16.0" # Hyphenate spec.dependency "Agora_Chat_iOS", "1.0.6" diff --git a/AgoraWidgets_Local.podspec b/AgoraWidgets_Local.podspec index 4ac4b02..c92569e 100644 --- a/AgoraWidgets_Local.podspec +++ b/AgoraWidgets_Local.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "AgoraWidgets" - spec.version = "2.8.100" + spec.version = "2.8.103" spec.summary = "SDKs/AgoraWidgets." spec.description = "Agora native widgets" spec.homepage = "https://docs.agora.io/en/agora-class/landing-page?platform=iOS" @@ -24,9 +24,10 @@ Pod::Spec.new do |spec| spec.dependency "SwifterSwift" spec.dependency "Masonry" + spec.dependency "AgoraRtcEngine_Special_iOS", '>=3.7.2.133' # Netless - spec.dependency "Whiteboard" + spec.dependency "Whiteboard", ">=2.16.0" # Hyphenate spec.dependency "Agora_Chat_iOS", "1.0.6" diff --git a/Products/Builder/AgoraBuilder/AgoraBuilder.xcodeproj/project.pbxproj b/Products/Builder/AgoraBuilder/AgoraBuilder.xcodeproj/project.pbxproj index 376dfef..1c52873 100644 --- a/Products/Builder/AgoraBuilder/AgoraBuilder.xcodeproj/project.pbxproj +++ b/Products/Builder/AgoraBuilder/AgoraBuilder.xcodeproj/project.pbxproj @@ -116,7 +116,6 @@ 863FED262886A2F70014AFB5 /* Frameworks */, 863FED272886A2F70014AFB5 /* Resources */, 68FE6F8E28F3F4DC0090F438 /* Embed Frameworks */, - 01118E9E76330CAA6FCA5660 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -173,23 +172,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 01118E9E76330CAA6FCA5660 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-AgoraBuilder/Pods-AgoraBuilder-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-AgoraBuilder/Pods-AgoraBuilder-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AgoraBuilder/Pods-AgoraBuilder-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 2CEA6F2F031D78E03E304C02 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/Products/Builder/AgoraBuilder/Podfile b/Products/Builder/AgoraBuilder/Podfile index 1d9ace8..2b81bcd 100644 --- a/Products/Builder/AgoraBuilder/Podfile +++ b/Products/Builder/AgoraBuilder/Podfile @@ -15,6 +15,8 @@ target 'AgoraBuilder' do post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle" config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' end diff --git a/Products/Builder/PodFiles/AgoraWidgets_Local_Pod b/Products/Builder/PodFiles/AgoraWidgets_Local_Pod index 2a82cf9..f8a8819 100644 --- a/Products/Builder/PodFiles/AgoraWidgets_Local_Pod +++ b/Products/Builder/PodFiles/AgoraWidgets_Local_Pod @@ -1,13 +1,14 @@ # third libs - pod 'Armin', '1.1.0' + pod 'Armin', '1.1.0' pod 'SwifterSwift', '5.2.0' - pod 'Masonry', '1.1.0' - pod 'SDWebImage', '<=5.12.0' - pod 'AgoraLog', '1.0.2' + pod 'Masonry', '1.1.0' + pod 'SDWebImage', '<=5.12.0' + pod 'AgoraLog', '1.0.2' # agora libs - pod 'Whiteboard', '2.16.45' - pod 'Agora_Chat_iOS', '1.0.6' + pod 'AgoraRtcEngine_Special_iOS', '3.7.2.133' + pod 'Whiteboard', '2.16.90' + pod 'Agora_Chat_iOS', '1.0.6' # close source libs pod 'AgoraUIBaseViews/Source', :path => '../../../../apaas-common-libs-ios/AgoraUIBaseViews_Local.podspec' diff --git a/Products/Builder/PodFiles/AgoraWidgets_Pod b/Products/Builder/PodFiles/AgoraWidgets_Pod index 8f6aa7a..b89161c 100644 --- a/Products/Builder/PodFiles/AgoraWidgets_Pod +++ b/Products/Builder/PodFiles/AgoraWidgets_Pod @@ -1,13 +1,14 @@ # third libs - pod 'Armin', '1.1.0' + pod 'Armin', '1.1.0' pod 'SwifterSwift', '5.2.0' - pod 'Masonry', '1.1.0' - pod 'SDWebImage', '<=5.12.0' - pod 'AgoraLog', '1.0.2' + pod 'Masonry', '1.1.0' + pod 'SDWebImage', '<=5.12.0' + pod 'AgoraLog', '1.0.2' # agora libs - pod 'Whiteboard', '2.16.45' - pod 'Agora_Chat_iOS', '1.0.6' + pod 'AgoraRtcEngine_Special_iOS', '3.7.2.133' + pod 'Agora_Chat_iOS', '1.0.6' + pod 'Whiteboard', '2.16.90' # close source libs pod 'AgoraUIBaseViews/Binary', :path => '../../../AgoraUIBaseViews_Local.podspec' diff --git a/Products/Scripts/Build/build_local.sh b/Products/Scripts/Build/build_local.sh new file mode 100755 index 0000000..d95e636 --- /dev/null +++ b/Products/Scripts/Build/build_local.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# cd this file path +cd $(dirname $0) +echo pwd: `pwd` + +# difference +Repo_Name="open-apaas-extapp-ios" +SDK_Array=(AgoraWidgets) + +# import +. ../../../../apaas-cicd-ios/Products/Scripts/Other/v1/operation_print.sh + +# path +CICD_Scripts_Path="../../../../apaas-cicd-ios/Products/Scripts" +CICD_Build_Path="${CICD_Scripts_Path}/SDK/Build" +CICD_Pack_Path="${CICD_Scripts_Path}/SDK/Pack" +CICD_Upload_Path="${CICD_Scripts_Path}/SDK/Upload" + +# build +for SDK in ${SDK_Array[*]} +do + ${CICD_Build_Path}/v1/build.sh ${SDK} ${Repo_Name} + + errorPrint $? "${SDK} build" +done diff --git a/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemob.swift b/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemob.swift index 76ac3d5..a07d085 100644 --- a/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemob.swift +++ b/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemob.swift @@ -8,25 +8,6 @@ import Foundation import AgoraChat -typealias EasemobSuccessCompletion = () -> () -typealias EasemobStringCompletion = (String?) -> () -typealias EasemobMuteStateCompletion = (_ muted: Bool) -> () -typealias EasemobMessageListCompletion = ([AgoraChatMessage]?) -> () -typealias EasemobFailureCompletion = (AgoraChatErrorType) -> () - -protocol AgoraChatEasemobDelegate: NSObjectProtocol { - func didReceiveMessages(list: [AgoraChatMessage]) - func didSendMessages(list: [AgoraChatMessage]) - func didLocalMuteStateChanged(_ muted: Bool) - func didAllMuteStateChanged(_ muted: Bool) - func didReceiveAnnouncement(_ announcement: String?) - func didConnectionStateChaned(_ state: AgoraChatConnectionState) - func onEasemobLog(content: String, - extra: String?, - type: FcrEasemobLogType) - func didOccurError(type: AgoraChatErrorType) -} - class AgoraChatEasemob: NSObject { private weak var delegate: AgoraChatEasemobDelegate? private(set) var userConfig: AgoraChatEasemobUserConfig diff --git a/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobGroup.swift b/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobGroup.swift new file mode 100644 index 0000000..d05ca71 --- /dev/null +++ b/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobGroup.swift @@ -0,0 +1,819 @@ +// +// AgoraChatEasemobGroup.swift +// AgoraWidgets +// +// Created by DoubleCircle on 2022/7/20. +// + +import Foundation +import AgoraChat + +class AgoraChatEasemobGroup: NSObject { + private weak var delegate: AgoraChatEasemobDelegate? + private(set) var userConfig: AgoraChatEasemobUserConfig + private var chatRoomId: String + + private var latestMessageId = "" + private var loginRetryCount = 0 + private var joinRetryCountMap: [String: Int] = [:] + private let maxRetryCount = 10 + private let muteMemberKey = "muteMember" + + var recvRoomIds:Array { + get{ + return self.userConfig.recvRoomIds + } + } + var sendRoomIds:Array { + get{ + return self.userConfig.sendRoomIds + } + } + + var joinRoomIds:Array { + get { + var roomIds:Array = [] + roomIds.append(self.chatRoomId) + self.sendRoomIds.forEach { roomId in + if(!roomIds.contains(roomId)){ + roomIds.append(roomId) + } + } + self.recvRoomIds.forEach { roomId in + if(!roomIds.contains(roomId)){ + roomIds.append(roomId) + } + } + return roomIds + } + } + + init(appKey: String, + chatRoomId: String, + userConfig: AgoraChatEasemobUserConfig, + enableConsoleLog: Bool, + delegate: AgoraChatEasemobDelegate?) { + self.chatRoomId = chatRoomId + self.userConfig = userConfig + self.delegate = delegate + super.init() + + let option = AgoraChatOptions(appkey: appKey) + option.enableConsoleLog = false + option.isAutoLogin = false + + AgoraChatClient.shared().initializeSDK(with: option) + AgoraChatClient.shared().add(self, + delegateQueue: nil) + AgoraChatClient.shared().chatManager.add(self, + delegateQueue: nil) + AgoraChatClient.shared().roomManager.add(self, + delegateQueue: nil) + let extra = ["appKey": appKey, + "chatRoomId": chatRoomId, + "userId": userConfig.userName, + "role": "\(userConfig.role)", + "fcrRoomId": userConfig.fcrRoomId] + delegate?.onEasemobLog(content: "init", + extra: extra.agDescription, + type: .info) + } + + func login(token: String, + success: EasemobSuccessCompletion?, + failure: EasemobFailureCompletion?) { + let lowercaseName = userConfig.userName.lowercased() + let extra = ["token": token, + "lowercaseName": lowercaseName] + delegate?.onEasemobLog(content: "start login", + extra: extra.agDescription, + type: .info) + _login(token: token, + lowercaseName: lowercaseName, + success: success, + failure: failure) + } + + func join(success: EasemobSuccessCompletion?, + failure: EasemobFailureCompletion?) { + + var joinRoomCount = 0 + + let joinSuccessBlock: EasemobJoinSuccessCompletion = { [weak self] room in + guard let `self` = self else { + return + } + joinRoomCount += 1 + if(joinRoomCount == self.joinRoomIds.count){ + success?() + } + } + + let joinFailureBlock: EasemobJoinFailureCompletion = { [weak self] (roomId, errType) in + guard let `self` = self else { + return + } + failure?(errType) + } + self.joinRoomIds.forEach { roomId in + let extra = ["chatRoomId": roomId] + delegate?.onEasemobLog(content: "start join", + extra: extra.agDescription, + type: .info) + _join(roomId: roomId, success: joinSuccessBlock, + failure: joinFailureBlock) + } + } + + func logout() { + delegate?.onEasemobLog(content: "logout", + extra: nil, + type: .info) + + AgoraChatClient.shared().roomManager.leaveChatroom(chatRoomId, + completion: nil) + AgoraChatClient.shared().removeDelegate(self) + AgoraChatClient.shared().chatManager.remove(self) + AgoraChatClient.shared().roomManager.remove(self) + AgoraChatClient.shared().logout(false) + } + + func sendImageMessageData(_ data: Data?) { + let imageBody = AgoraChatImageMessageBody(data: data, + displayName: "image") + let ext = messageExt() + let message = AgoraChatMessage(conversationID: chatRoomId, + from: userConfig.userName, + to: chatRoomId, + body: imageBody, + ext: ext) + + let success: EasemobSendSuccessCompletion = { msgs in + // 只需要取第一个发送成功的 + self.delegate?.didSendMessages(list: [msgs[0]]) + } + let failure: EasemobSendFailureCompletion = { type in + self.delegate?.didOccurError(type: type) + } + batchSendMsg(message: message, success: success, failure: failure) + } + + + func batchSendMsg(message: AgoraChatMessage, success: EasemobSendSuccessCompletion?, failure: EasemobSendFailureCompletion?) { + + var sendResult: [AgoraChatMessage] = [] + + self.sendRoomIds.forEach { roomId in + let msg = AgoraChatMessage(conversationID: roomId, from: userConfig.userName, to: roomId, body: message.body, ext: message.ext) + msg.chatType = .chatRoom + + let extra = ["chatRoomId": roomId, + "from": userConfig.userName, + "to": roomId, + "text":message.agDescription] + delegate?.onEasemobLog(content: "send message", + extra: extra.agDescription, + type: .info) + + AgoraChatClient.shared().chatManager.send(msg, + progress: nil){ [weak self] (chatMessage, chatError) in + guard let `self` = self else { + return + } + + guard let message = chatMessage, + chatError == nil else { + failure?(.sendFailed(chatError!.code.rawValue)) + return + } + sendResult.append(chatMessage!) + if(self.sendRoomIds.count == sendResult.count){ + success?(sendResult) + } + } + } + } + + + func sendTextMessage(_ text: String) { + guard text.count > 0 else { + return + } + let textBody = AgoraChatTextMessageBody(text: text) + let ext = messageExt() + let message = AgoraChatMessage(conversationID: chatRoomId, + from: userConfig.userName, + to: chatRoomId, + body: textBody, + ext: ext) + + let success: EasemobSendSuccessCompletion = { msgs in + // 只需要取第一个发送成功的 + self.delegate?.didSendMessages(list: [msgs[0]]) + } + let failure: EasemobSendFailureCompletion = { type in + self.delegate?.didOccurError(type: type) + } + batchSendMsg(message: message, success: success, failure: failure) + } + + func sendCmdMessage(action: String) { + guard action.count > 0 else { + return + } + let textBody = AgoraChatCmdMessageBody(action: action) + let ext = messageExt() + let message = AgoraChatMessage(conversationID: chatRoomId, + from: userConfig.userName, + to: chatRoomId, + body: textBody, + ext: ext) + + let success: EasemobSendSuccessCompletion = { msgs in + // 只需要取第一个发送成功的 + self.delegate?.didReceiveMessages(list: [msgs[0]]) + } + let failure: EasemobSendFailureCompletion = { type in + self.delegate?.didOccurError(type: type) + } + batchSendMsg(message: message, success: success, failure: failure) + } + + func muteAll(mute: Bool) { + let extra = ["mute": "\(mute)"] + delegate?.onEasemobLog(content: "mute all", + extra: extra.agDescription, + type: .info) + + if mute { + AgoraChatClient.shared().roomManager.muteAllMembers(fromChatroom: chatRoomId) { [weak self] (chatRoom, chatError) in + guard let `self` = self else { + return + } + guard chatError == nil else { + var extra = ["aError": "\(chatError!.code.rawValue)"] + + self.delegate?.onEasemobLog(content: "mute all fail", + extra: extra.agDescription, + type: .error) + self.delegate?.didOccurError(type: .muteFailed(chatError!.code.rawValue)) + return + } + self.sendCmdMessage(action: "setAllMute") + } + } else { + AgoraChatClient.shared().roomManager.unmuteAllMembers(fromChatroom: chatRoomId) { [weak self] (chatRoom, chatError) in + guard let `self` = self else { + return + } + guard chatError == nil else { + var extra = ["aError": "\(chatError!.code.rawValue)"] + + self.delegate?.onEasemobLog(content: "unmute all fail", + extra: extra.agDescription, + type: .error) + self.delegate?.didOccurError(type: .muteFailed(chatError!.code.rawValue)) + return + } + self.sendCmdMessage(action: "removeAllMute") + } + } + } + + func muteUsers(userList: [String], + mute: Bool) { + let extra = ["mute": "\(mute)", + "userList": userList.description] + delegate?.onEasemobLog(content: "mute users", + extra: extra.agDescription, + type: .info) + // TODO: 移动端暂时没有该功能 + } + + func setAnnouncement(_ announcement: String?) { + let extra = ["announcement": announcement ?? ""] + delegate?.onEasemobLog(content: "set announcement", + extra: extra.agDescription, + type: .info) + AgoraChatClient.shared().roomManager.updateChatroomAnnouncement(withId: chatRoomId, + announcement: announcement) { [weak self] (chatRoom,chatError) in + guard let error = chatError else { + return + } + let extra = ["announcement": announcement ?? "", + "errorCode": "\(error.code)"] + self?.delegate?.onEasemobLog(content: "set announcement error", + extra: extra.agDescription, + type: .error) + } + } + + func getAllMutedState(success: EasemobMuteStateCompletion?, + failure: EasemobFailureCompletion?) { + let extra = ["chatRoomId": chatRoomId] + delegate?.onEasemobLog(content: "get chatroom specification", + extra: extra.agDescription, + type: .info) + AgoraChatClient.shared().roomManager.getChatroomSpecificationFromServer(withId: chatRoomId) { [weak self] (chatRoom, chatError) in + guard let `self` = self else { + return + } + guard chatError == nil else { + var extra = ["aError": "\(chatError!.code.rawValue)"] + + self.delegate?.onEasemobLog(content: "get chatroom specification fail", + extra: extra.agDescription, + type: .error) + self.delegate?.didOccurError(type: .fetchError(chatError!.code.rawValue)) + return + } + guard let room = chatRoom else { + return + } + + let isMuteAllMembers = room.isMuteAllMembers + let extra = ["isMuteAllMembers": isMuteAllMembers ? 1 : 0] + self.delegate?.onEasemobLog(content: "chatroom specification", + extra: extra.agDescription, + type: .info) + + + success?(room.isMuteAllMembers) + } + } + + func getLocalMutedState(success: EasemobMuteStateCompletion?, + failure: EasemobFailureCompletion?) { + let extra = ["chatRoomId": chatRoomId] + + delegate?.onEasemobLog(content: "get local mute state", + extra: extra.agDescription, + type: .info) + AgoraChatClient.shared().roomManager.getChatroomMuteListFromServer(withId: chatRoomId, + pageNumber: 0, + pageSize: 100) { [weak self] (muteList, chatError) in + guard let `self` = self else { + return + } + guard chatError == nil else { + var extra = ["aError": "\(chatError!.code.rawValue)"] + + self.delegate?.onEasemobLog(content: "get local mute state fail", + extra: extra.agDescription, + type: .error) + failure?(.fetchError(chatError!.code.rawValue)) + return + } + + var localMuted: Bool = false + + if let list = muteList { + localMuted = list.contains([self.userConfig.userName]) + } + + let extra = ["localMuted": localMuted ? 1 : 0] + self.delegate?.onEasemobLog(content: "local mute state", + extra: extra.agDescription, + type: .info) + success?(localMuted) + } + } + + func getAnnouncement(success: EasemobStringCompletion?, + failure: EasemobFailureCompletion?) { + var extra = ["chatRoomId": chatRoomId] + + self.delegate?.onEasemobLog(content: "get announcement", + extra: extra.agDescription, + type: .info) + AgoraChatClient.shared().roomManager.getChatroomAnnouncement(withId: chatRoomId) { [weak self] (announcement,chatError) in + guard let `self` = self else { + return + } + guard chatError == nil else { + var extra = ["aError": "\(chatError!.code.rawValue)"] + + self.delegate?.onEasemobLog(content: "get announcement fail", + extra: extra.agDescription, + type: .error) + failure?(.fetchError(chatError!.code.rawValue)) + return + } + let extra = ["announcement": announcement ?? ""] + self.delegate?.onEasemobLog(content: "announcement", + extra: extra.agDescription, + type: .info) + success?(announcement) + } + } + + + + func getHistoryMessages(success: EasemobMessageListCompletion?, + failure: EasemobFailureCompletion?) { + var historyMsg:Array = [] + var taskResult: [[AgoraChatMessage]] = [] + self.recvRoomIds.forEach { roomId in + var extra = ["chatRoomId": roomId] + + self.delegate?.onEasemobLog(content: "get history messages", + extra: extra.agDescription, + type: .info) + AgoraChatClient.shared().chatManager.asyncFetchHistoryMessages(fromServer: roomId, + conversationType: .chatRoom, + startMessageId: "", + pageSize: 50) { [weak self] (result,chatError) in + guard let `self` = self else { + return + } + guard chatError == nil else { + var extra = ["aError": "\(chatError!.code.rawValue)"] + + self.delegate?.onEasemobLog(content: "get history messages fail", + extra: extra.agDescription, + type: .error) + failure?(.fetchError(chatError!.code.rawValue)) + return + } + + let list = result?.list as? [AgoraChatMessage] ?? [] + let messageList = list.filter({return ($0.body.type == .text || $0.body.type == .image || $0.body.type == .cmd)}) + + taskResult.append(messageList) + // 所有消息获取到再回掉回去 + if(self.recvRoomIds.count == taskResult.count){ + taskResult.forEach { msgs in + msgs.forEach { msg in + historyMsg.append(msg) + } + } + + guard let last = historyMsg.last else { + success?(nil) + return + } + + historyMsg.sort{(a,b) -> Bool in + return a.timestamp < b.timestamp + } + self.latestMessageId = last.messageId + success?(historyMsg) + + // 从消息中回溯出当前是否在禁言, 有bug + let cmdMessageList:[AgoraChatMessage] = historyMsg.filter({ + if($0.body.type != .cmd){ + return false + } + + return true + + let body = $0.body as? AgoraChatCmdMessageBody + + if(body?.action == "setAllMute" || body?.action == "removeAllMute" || body?.action == "mute" || body?.action == "unmute"){ + return true + } + return false + }) + + guard let cmdMessage = cmdMessageList.last else { + return + } + + + let body = cmdMessage.body as? AgoraChatCmdMessageBody + let type = AgoraChatEasemobCmdMessageBodyActionType(rawValue: body!.action) + // 最后一条cmd消息是否是禁言 + switch type { + case .delete: + // recall消息暂不处理 + break + case .setAllMute: + self.delegate?.didAllMuteStateChanged(true) + case .removeAllMute: + self.delegate?.didAllMuteStateChanged(false) + case .mute: + guard let muteUserId = cmdMessage.ext?[muteMemberKey] as? String, + muteUserId == userConfig.userName else { + break + } + self.delegate?.didLocalMuteStateChanged(true) + case .unmute: + guard let muteUserId = cmdMessage.ext?[muteMemberKey] as? String, + muteUserId == userConfig.userName else { + break + } + self.delegate?.didLocalMuteStateChanged(false) + case .none: + break + } + } + } + } + } +} + +// MARK: - private +private extension AgoraChatEasemobGroup { + func _login(token: String, + lowercaseName: String, + success: EasemobSuccessCompletion?, + failure: EasemobFailureCompletion?) { + AgoraChatClient.shared().login(withUsername: lowercaseName, + token: token) { [weak self] (userName, aLoginError) in + guard let `self` = self else { + return + } + guard let loginError = aLoginError, + loginError.code != .userAlreadyLoginSame else { + self.loginRetryCount = 0 + self.joinRetryCountMap.removeAll() + self.delegate?.onEasemobLog(content: "login success", + extra: nil, + type: .info) + self.updateLocalUserInfo() + success?() + return + } + switch loginError.code { + case .userNotFound: + AgoraChatClient.shared().register(withUsername: self.userConfig.userName, + password: self.userConfig.password) { (userName, chatError) in + if let chatError = chatError { + self.loginRetryCount = 0 + self.joinRetryCountMap.removeAll() + let extra = ["userId":self.userConfig.userName, + "password":self.userConfig.password, + "code": "\(chatError.code.rawValue)"] + self.delegate?.onEasemobLog(content: "register fail", + extra: extra.agDescription, + type: .error) + failure?(.loginFailed) + return + } + self.delegate?.onEasemobLog(content: "register success", + extra: nil, + type: .info) + self._login(token: token, + lowercaseName: lowercaseName, + success: success, + failure: failure) + } + default: + guard self.loginRetryCount < self.maxRetryCount else { + self.loginRetryCount = 0 + failure?(.loginFailed) + return + } + self.loginRetryCount += 1 + self._login(token: token, + lowercaseName: lowercaseName, + success: success, + failure: failure) + } + } + } + + + + + func _join(roomId:String, success: EasemobJoinSuccessCompletion?, + failure: EasemobJoinFailureCompletion?) { + AgoraChatClient.shared().roomManager.joinChatroom(roomId) { [weak self] (chatRoom, chatError) in + guard let `self` = self else { + return + } + guard let `chatError` = chatError else { +// self.chatRoom = chatRoom + self.joinRetryCountMap[roomId] = 0 + let extra = ["chatRoomId":roomId] + self.delegate?.onEasemobLog(content: "join chat success", + extra: extra.agDescription, + type: .info) + success?(chatRoom) + return + } + + let retryCount = self.joinRetryCountMap[roomId] ?? 0 + guard retryCount < self.maxRetryCount else { + self.joinRetryCountMap[roomId] = 0 + let extra = ["chatRoomId":roomId] + self.delegate?.onEasemobLog(content: "join chat fail", + extra: extra.agDescription, + type: .error) + failure?(roomId, .joinFailed) + return + } + let extra = ["chatRoomId":roomId] + self.delegate?.onEasemobLog(content: "retry join chat", + extra: extra.agDescription, + type: .error) + self.joinRetryCountMap[roomId] = retryCount + 1 + self._join(roomId: roomId, success: success, + failure: failure) + } + } + + func updateLocalUserInfo() { + let userInfo = AgoraChatUserInfo() + userInfo.nickname = userConfig.nickName + let extDic = ["role": userConfig.role, "chatGroupUuids": self.userConfig.chatGroupUuids] as [String : Any] + + if let data = extDic.jsonData() { + let extString = String(data: data, + encoding: .utf8) + userInfo.ext = extString + } + + if let avatarUrl = userConfig.avatarurl { + userInfo.avatarUrl = avatarUrl + } + AgoraChatClient.shared().userInfoManager.updateOwn(userInfo) { [weak self] (chatUserInfo, chatError) in + guard let `self` = self else { + return + } + guard let `chatError` = chatError else { + let extra = ["ext": userInfo.ext ?? ""] + self.delegate?.onEasemobLog(content: "update user info success", + extra: extra.agDescription, + type: .info) + return + } + + let extra = ["ext": userInfo.ext ?? "", + "code": "\(chatError.code.rawValue)"] + self.delegate?.onEasemobLog(content: "update user info fail", + extra: extra.agDescription, + type: .error) + self.delegate?.didOccurError(type: .updateUserInfo) + } + } + + func messageExt() -> [String: Any] { + var ext: [String : Any] = ["msgtype": 0, + "role": userConfig.role, + "nickName": userConfig.nickName, + "roomUuid": userConfig.fcrRoomId] + ext["avatarUrl"] = userConfig.avatarurl + return ext + } +} + +// MARK: - Easemob Delegate +extension AgoraChatEasemobGroup: AgoraChatClientDelegate, + AgoraChatManagerDelegate, + AgoraChatroomManagerDelegate { + // MARK: AgoraChatClientDelegate + func connectionStateDidChange(_ aConnectionState: AgoraChatConnectionState) { + delegate?.didConnectionStateChaned(aConnectionState) + + let extra = ["state": aConnectionState] + delegate?.onEasemobLog(content: "connection state chaned", + extra: extra.agDescription, + type: .info) + } + + func userAccountDidLoginFromOtherDevice() { + delegate?.onEasemobLog(content: "user account did login from other device", + extra: nil, + type: .error) + + delegate?.didOccurError(type: .loginedFromRemote) + } + + func userAccountDidForced(toLogout aError: AgoraChatError?) { + delegate?.didOccurError(type: .forcedLogOut) + + var extra: [String: String]? + if let error = aError { + extra = ["aError": "\(error.code.rawValue)"] + } + + delegate?.onEasemobLog(content: "user account was forced to logout", + extra: extra?.agDescription, + type: .error) + } + + // MARK: AgoraChatManagerDelegate + func messagesDidReceive(_ aMessages: [AgoraChatMessage]) { + let extra = ["messages": aMessages.agDescription] + delegate?.onEasemobLog(content: "receive messages", + extra: extra.agDescription, + type: .info) + // 只接受接受组消息 + let list = aMessages.filter { message in + guard message.chatType == .chatRoom, + recvRoomIds.contains(message.to), + (message.body.type == .text || message.body.type == .image) else { + return false + } + return true + } + guard list.count > 0, + let last = list.last else { + return + } + latestMessageId = last.messageId + delegate?.didReceiveMessages(list: list) + } + + func cmdMessagesDidReceive(_ aCmdMessages: [AgoraChatMessage]) { + let extra = ["messages": aCmdMessages.agDescription] + delegate?.onEasemobLog(content: "easemob receive cmd messages", + extra: extra.agDescription, + type: .info) + + var messageList = [AgoraChatMessage]() + for cmdMessage in aCmdMessages { + guard recvRoomIds.contains(cmdMessage.to), + let body = cmdMessage.body as? AgoraChatCmdMessageBody, + let type = AgoraChatEasemobCmdMessageBodyActionType(rawValue: body.action) else { + continue + } + switch type { + case .delete: + // recall消息暂不处理 + break + case .setAllMute: + messageList.append(cmdMessage) + delegate?.didAllMuteStateChanged(true) + case .removeAllMute: + messageList.append(cmdMessage) + delegate?.didAllMuteStateChanged(false) + case .mute: + guard let muteUserId = cmdMessage.ext?[muteMemberKey] as? String, + muteUserId == userConfig.userName else { + continue + } + messageList.append(cmdMessage) + delegate?.didLocalMuteStateChanged(true) + case .unmute: + guard let muteUserId = cmdMessage.ext?[muteMemberKey] as? String, + muteUserId == userConfig.userName else { + continue + } + messageList.append(cmdMessage) + delegate?.didLocalMuteStateChanged(false) + } + } + guard messageList.count > 0 else { + return + } + delegate?.didReceiveMessages(list: messageList) + } + + // MARK: AgoraChatroomManagerDelegate + func chatroomMuteListDidUpdate(_ aChatroom: AgoraChatroom, + addedMutedMembers aMutes: [String], + muteExpire aMuteExpire: Int) { + let extra = ["aMutes": aMutes.agDescription] + delegate?.onEasemobLog(content: "users muted", + extra: extra.agDescription, + type: .info) + + guard aChatroom.chatroomId == chatRoomId, + aMutes.count > 0, + aMutes.contains(userConfig.userName) else { + return + } + delegate?.didLocalMuteStateChanged(true) + } + + func chatroomMuteListDidUpdate(_ aChatroom: AgoraChatroom, + removedMutedMembers aMutes: [String]) { + let extra = ["aMutes": aMutes.agDescription] + delegate?.onEasemobLog(content: "users unmuted", + extra: extra.agDescription, + type: .info) + + guard aChatroom.chatroomId == chatRoomId, + aMutes.count > 0, + aMutes.contains(userConfig.userName) else { + return + } + delegate?.didLocalMuteStateChanged(false) + } + + func chatroomAllMemberMuteChanged(_ aChatroom: AgoraChatroom, + isAllMemberMuted aMuted: Bool) { + let extra = ["muted": "\(aMuted)"] + delegate?.onEasemobLog(content: "users all muted", + extra: extra.agDescription, + type: .info) + guard aChatroom.chatroomId == chatRoomId else { + return + } + delegate?.didAllMuteStateChanged(aMuted) + } + + func chatroomAnnouncementDidUpdate(_ aChatroom: AgoraChatroom, + announcement aAnnouncement: String?) { + let extra = ["announcement": aAnnouncement ?? ""] + delegate?.onEasemobLog(content: "announcement updated", + extra: extra.agDescription, + type: .info) + guard aChatroom.chatroomId == chatRoomId else { + return + } + delegate?.didReceiveAnnouncement(aAnnouncement) + } +} diff --git a/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobModels.swift b/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobModels.swift index 947463c..adebf4a 100644 --- a/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobModels.swift +++ b/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobModels.swift @@ -9,6 +9,30 @@ import Foundation import AgoraChat import AgoraLog +typealias EasemobSuccessCompletion = () -> () +typealias EasemobJoinSuccessCompletion = (_ room:AgoraChatroom?) -> () +typealias EasemobSendSuccessCompletion = (_ msg:[AgoraChatMessage]) -> () +typealias EasemobStringCompletion = (String?) -> () +typealias EasemobMuteStateCompletion = (_ muted: Bool) -> () +typealias EasemobMessageListCompletion = ([AgoraChatMessage]?) -> () +typealias EasemobFailureCompletion = (AgoraChatErrorType) -> () +typealias EasemobSendFailureCompletion = (AgoraChatErrorType) -> () +typealias EasemobJoinFailureCompletion = (_ roomId:String, _ errType:AgoraChatErrorType) -> () + +protocol AgoraChatEasemobDelegate: NSObjectProtocol { + func didReceiveMessages(list: [AgoraChatMessage]) + func didSendMessages(list: [AgoraChatMessage]) + func didLocalMuteStateChanged(_ muted: Bool) + func didAllMuteStateChanged(_ muted: Bool) + func didReceiveAnnouncement(_ announcement: String?) + func didConnectionStateChaned(_ state: AgoraChatConnectionState) + func onEasemobLog(content: String, + extra: String?, + type: FcrEasemobLogType) + func didOccurError(type: AgoraChatErrorType) +} + + struct AgoraChatEasemoExtraInfo: Convertable { var avatarurl: String? } @@ -35,13 +59,19 @@ struct AgoraChatEasemobUserConfig { var fcrRoomId: String var password: String var role: Int + var sendRoomIds: Array + var recvRoomIds: Array + var chatGroupUuids: Array init(userName: String, nickName: String, avatarurl: String?, fcrRoomId: String, password: String? = "", - role: Int) { + role: Int, + sendRoomIds: Array? = [], + recvRoomIds: Array? = [], + chatGroupUuids: Array? = []) { var finalAvatarUrl = "https://download-sdk.oss-cn-beijing.aliyuncs.com/downloads/IMDemo/avatar/Image1.png" var finalPassword = userName if let avatarurl = avatarurl { @@ -56,6 +86,9 @@ struct AgoraChatEasemobUserConfig { self.fcrRoomId = fcrRoomId self.password = finalPassword self.role = role + self.sendRoomIds = sendRoomIds ?? [] + self.recvRoomIds = recvRoomIds ?? [] + self.chatGroupUuids = chatGroupUuids ?? [] } } diff --git a/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobWidget.swift b/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobWidget.swift index e672c07..13e2a0f 100644 --- a/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobWidget.swift +++ b/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobWidget.swift @@ -54,7 +54,8 @@ import AgoraChat override init(widgetInfo: AgoraWidgetInfo) { super.init(widgetInfo: widgetInfo) - + log(content: "AgoraWidget init >>>", + type: .info ) initData() } diff --git a/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobWidgetGroup.swift b/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobWidgetGroup.swift new file mode 100644 index 0000000..24eaace --- /dev/null +++ b/SDKs/AgoraWidgets/AgoraChat/Easemob/AgoraChatEasemobWidgetGroup.swift @@ -0,0 +1,407 @@ +// +// AgoraChatEssWidget.swift +// AgoraWidgets +// +// Created by LYY on 2022/7/17. +// + +import AgoraWidget +import CoreMedia +import AgoraChat + +@objcMembers public class AgoraChatEasemobWidgetGroup: AgoraNativeWidget { + private lazy var mainView = AgoraChatMainView() + + private var easemob: AgoraChatEasemobGroup? + + private var localMuted: Bool = false { + didSet { + guard localMuted != oldValue, + !isTeacher else { + return + } + mainView.updateBottomBarMuteState(islocalMuted: localMuted, + isAllMuted: allMuted, + localMuteAuth: isTeacher) + } + } + private var allMuted: Bool = false { + didSet { + guard allMuted != oldValue else { + return + } + mainView.updateBottomBarMuteState(islocalMuted: localMuted, + isAllMuted: allMuted, + localMuteAuth: isTeacher) + } + } + + private var launchCondition: (flag: Bool, + config: AgoraChatEasemobLaunchConfig?, + serverAPI: AgoraChatServerAPI?) = (flag: false, + config: nil, + serverAPI: nil) { + didSet { + guard !launchCondition.flag, + let config = launchCondition.config, + let serverAPI = launchCondition.serverAPI else { + return + } + fetchEsasemobToken(config: config, + serverAPI: serverAPI) + } + } + override init(widgetInfo: AgoraWidgetInfo) { + super.init(widgetInfo: widgetInfo) + log(content: "AgoraWidgetGroup init >>>", + type: .info ) + initData() + } + + public override func onLoad() { + super.onLoad() + + mainView.delegate = self + mainView.editAnnouncementEnabled = isTeacher + view.addSubview(mainView) + + mainView.mas_makeConstraints { make in + make?.left.right().top().bottom().equalTo()(0) + } + + let roomType = AgoraWidgetRoomType(rawValue: Int(info.roomInfo.roomType)) ?? .small + let roleType = AgoraWidgetRoleType(rawValue: info.localUserInfo.userRole) ?? .student + + var items = AgoraChatMainViewItem.allCases + + switch roomType { + case .oneToOne: + items.removeAll([.announcement, .muteAll]) + default: + break + } + + switch roleType { + case .student: + items.removeAll([.muteAll]) + case .observer: + items.removeAll([.muteAll, .input]) + default: + break + } + let extra = ["viewItem": items.agDescription] + log(content: "update view items", + extra: extra.agDescription, + type: .info ) + mainView.updateViewItems(items) + } + + public override func onMessageReceived(_ message: String) { + super.onMessageReceived(message) + + if let keys = message.toRequestKeys() { + let serverAPI = AgoraChatServerAPI(host: keys.host, + appId: keys.agoraAppId, + token: keys.token, + roomId: info.roomInfo.roomUuid, + userId: info.localUserInfo.userUuid, + logTube: logger) + launchCondition.serverAPI = serverAPI + } + } + + public override func onWidgetRoomPropertiesUpdated(_ properties: [String : Any], + cause: [String : Any]?, + keyPaths: [String], + operatorUser: AgoraWidgetUserInfo?) { + super.onWidgetRoomPropertiesUpdated(properties, + cause: cause, + keyPaths: keyPaths, + operatorUser: operatorUser) + + initData() + } + + public override func onWidgetUserPropertiesUpdated(_ properties: [String : Any], cause: [String : Any]?, keyPaths: [String], operatorUser: AgoraWidgetUserInfo?) { + super.onWidgetUserPropertiesUpdated(properties, cause: cause, keyPaths: keyPaths, operatorUser: operatorUser) + } + + deinit { + easemob?.logout() + } +} + + +// MARK: - view delegate +extension AgoraChatEasemobWidgetGroup: AgoraChatMainViewDelegate { + func onSendImageData(_ data: Data) { + easemob?.sendImageMessageData(data) + } + + func onSendTextMessage(_ message: String) { + easemob?.sendTextMessage(message) + } + + func onClickAllMuted(_ isAllMuted: Bool) { + guard isTeacher else { + return + } + easemob?.muteAll(mute: isAllMuted) + } + + func onSetAnnouncement(_ announcement: String?) { + easemob?.setAnnouncement(announcement) + } + + func onShowErrorMessage(_ errorMessage: String) { + sendSignal(.error(errorMessage)) + } +} + +// MARK: - private +private extension AgoraChatEasemobWidgetGroup { + func initData() { + guard launchCondition.config == nil, + let extra = info.roomProperties?.toObject(AgoraChatEasemobRoomProperties.self), + let userId = info.localUserProperties?["userId"] as? String else { + return + } + // 1. init easemob + var enableConsoleLog = false +#if DEBUG + enableConsoleLog = true +#endif + + var avatarUrl: String? + if let extraDic = info.extraInfo as? [String: Any], + let url = extraDic["avatarUrl"] as? String { + avatarUrl = url + } + var userName = info.localUserInfo.userUuid + + if let userId = info.localUserProperties?["userId"] as? String { + userName = userId + } + let chatGroupUuids = info.localUserProperties?["chatGroupUuids"] as? Array + let sendRoomIds = info.localUserProperties?["sendChatRoomIds"] as? Array + let recvRoomIds = info.localUserProperties?["receiveChatRoomIds"] as? Array + let userConfig = AgoraChatEasemobUserConfig(userName: userName, + nickName: info.localUserInfo.userName, + avatarurl: avatarUrl, + fcrRoomId: info.roomInfo.roomUuid, + role: info.localUserInfo.userRole.userRoleToInt(), + sendRoomIds: sendRoomIds, + recvRoomIds: recvRoomIds, + chatGroupUuids: chatGroupUuids) + easemob = AgoraChatEasemobGroup(appKey: extra.appKey, + chatRoomId: extra.chatRoomId, + userConfig: userConfig, + enableConsoleLog: enableConsoleLog, + delegate: self) + + let launchConfig = AgoraChatEasemobLaunchConfig(roomId: info.roomInfo.roomUuid, + userId: userId, + userName: info.localUserInfo.userName, + chatRoomId: extra.chatRoomId, + password: userId) + launchCondition.config = launchConfig + } + + func launchEsasemob(token: String) { + // 3. join easemob + let failureBlock: EasemobFailureCompletion = { [weak self] type in + self?.handleError(type: type) + } + + let joinSuccessBlock: (() -> Void) = { [weak self] in + guard let `self` = self else { + return + } + self.initEasemobState() + } + + let loginSuccessBlock: EasemobSuccessCompletion = { [weak easemob] in + guard let `easemob` = easemob else { + return + } + easemob.join(success: joinSuccessBlock, failure: failureBlock) + } + + // 2. login easemob + self.easemob?.login(token: token, + success: loginSuccessBlock, + failure: failureBlock) + + } + + func fetchEsasemobToken(config: AgoraChatEasemobLaunchConfig, + serverAPI: AgoraChatServerAPI) { + launchCondition.flag = true + + serverAPI.fetchEasemobIMToken(success: { [weak self] (bodyDic) in + guard let `self` = self, + let dataDic = bodyDic["data"] as? [String : Any], + let hxToken = dataDic["token"] as? String else { + return + } + self.launchEsasemob(token: hxToken) + }, failure: { [weak self] error in + self?.handleError(type: .loginFailed) + }) + } + + func initEasemobState() { + guard let `easemob` = easemob else { + return + } + let failureHandle: ((AgoraChatErrorType) -> Void) = { [weak self] type in + self?.handleError(type: type) + } + + easemob.getAllMutedState(success: { [weak self] muted in + guard let `self` = self else { + return + } + self.allMuted = muted + }, failure: failureHandle) + + if !isTeacher { + easemob.getLocalMutedState(success: { [weak self] muted in + guard let `self` = self else { + return + } + self.localMuted = muted + },failure: failureHandle) + } + + easemob.getAnnouncement(success: { [weak self] announcement in + guard let text = announcement, + text.count > 0 else { + self?.mainView.setAnnouncement(nil, + showRemind: false) + return + } + self?.mainView.setAnnouncement(text, + showRemind: false) + }, failure: failureHandle) + + fetchHistoryMessage() + } + + func fetchHistoryMessage() { + guard let `easemob` = easemob else { + return + } + + let failureHandle: ((AgoraChatErrorType) -> Void) = { [weak self] type in + self?.handleError(type: type) + } + + easemob.getHistoryMessages(success: { [weak self] messageList in + guard let list = messageList, + let `self` = self else { + return + } + var modelList = [AgoraChatMessageViewType]() + let localUserId = easemob.userConfig.userName ?? self.info.localUserInfo.userUuid + for chatMessage in list { + guard let model = chatMessage.toViewModel(localUserId: localUserId) else { + continue + } + modelList.append(model) + } + self.mainView.setupHistoryMessages(list: modelList) + }, failure: failureHandle) + } + + func handleError(type: AgoraChatErrorType) { + var errorLocalized: String? + switch type { + case .loginFailed: + errorLocalized = "fcr_hyphenate_im_login_faild".widgets_localized() + case .joinFailed: + errorLocalized = "fcr_hyphenate_im_join_faild".widgets_localized() + case .loginedFromRemote: + errorLocalized = "fcr_hyphenate_im_login_on_other_device".widgets_localized() + case .forcedLogOut: + errorLocalized = "fcr_hyphenate_im_logout_forced".widgets_localized() + default: + return + } + + guard let errorString = errorLocalized else { + return + } + self.sendSignal(.error(errorString)) + } + + func sendSignal(_ signal: AgoraChatInteractionSignal) { + guard let message = signal.toMessageString() else { + return + } + sendMessage(message) + } +} + +// MARK: - ChatManagerDelegate +extension AgoraChatEasemobWidgetGroup: AgoraChatEasemobDelegate { + func didReceiveMessages(list: [AgoraChatMessage]) { + let localUserId = easemob?.userConfig.userName ?? info.localUserInfo.userUuid + for message in list { + guard let viewModel = message.toViewModel(localUserId: localUserId) else { + continue + } + mainView.appendMessages([viewModel]) + } + + sendSignal(.messageReceived) + } + + func didSendMessages(list: [AgoraChatMessage]) { + let localUserId = easemob?.userConfig.userName ?? info.localUserInfo.userUuid + for message in list { + guard let viewModel = message.toViewModel(localUserId: localUserId) else { + continue + } + + mainView.appendMessages([viewModel]) + } + } + + func didLocalMuteStateChanged(_ muted: Bool) { + guard !isTeacher else { + return + } + localMuted = muted + } + + func didAllMuteStateChanged(_ muted: Bool) { + allMuted = muted + } + + func didReceiveAnnouncement(_ announcement: String?) { + guard let text = announcement, + text.count > 0 else { + mainView.setAnnouncement(nil) + return + } + sendSignal(.messageReceived) + mainView.setAnnouncement(text) + } + + func didConnectionStateChaned(_ state: AgoraChatConnectionState) { + + } + + func didOccurError(type: AgoraChatErrorType) { + handleError(type: type) + } + + func onEasemobLog(content: String, + extra: String?, + type: FcrEasemobLogType) { + log(content: content, + extra: extra, + type: type.agoraType) + } +} diff --git a/SDKs/AgoraWidgets/AgoraChat/Views/BottomBar/AgoraChatInputView.swift b/SDKs/AgoraWidgets/AgoraChat/Views/BottomBar/AgoraChatInputView.swift index 43e3e17..09f0fe6 100644 --- a/SDKs/AgoraWidgets/AgoraChat/Views/BottomBar/AgoraChatInputView.swift +++ b/SDKs/AgoraWidgets/AgoraChat/Views/BottomBar/AgoraChatInputView.swift @@ -80,6 +80,19 @@ extension AgoraChatInputView: UITextFieldDelegate { onClickSendMessage() return true } + + func textField(_ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String) -> Bool { + guard let currentText = textField.text else { + return true + } + + let newText = currentText.replacingCharacters(in: Range(range, in: currentText)!, + with: string) + + return newText.count <= 300 + } } // MARK: - Actions private extension AgoraChatInputView { @@ -234,4 +247,3 @@ extension AgoraChatInputView: AgoraUIContentContainer { sendButton.layer.cornerRadius = config.cornerRadius } } - diff --git a/SDKs/AgoraWidgets/AgoraChat/Views/Content/AgoraChatAnnouncementView.swift b/SDKs/AgoraWidgets/AgoraChat/Views/Content/AgoraChatAnnouncementView.swift index 9b97616..748ad1b 100644 --- a/SDKs/AgoraWidgets/AgoraChat/Views/Content/AgoraChatAnnouncementView.swift +++ b/SDKs/AgoraWidgets/AgoraChat/Views/Content/AgoraChatAnnouncementView.swift @@ -56,7 +56,6 @@ class AgoraChatAnnouncementView: UIView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - } // MARK: - AgoraUIContentContainer @@ -129,6 +128,7 @@ extension AgoraChatAnnouncementView: AgoraUIContentContainer { make?.left.equalTo()(13) make?.top.equalTo()(13) make?.width.height().lessThanOrEqualTo()(self)?.offset()(-14) + make?.bottom.lessThanOrEqualTo()(self)?.offset()(-34) } deleteButton.mas_makeConstraints { make in @@ -257,6 +257,11 @@ extension AgoraChatAnnouncementView: UITextViewDelegate { return false } + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + func textViewDidEndEditing(_ textView: UITextView) { inputTextView.resignFirstResponder() } diff --git a/SDKs/AgoraWidgets/AgoraChat/Views/Content/AgoraChatMessageView.swift b/SDKs/AgoraWidgets/AgoraChat/Views/Content/AgoraChatMessageView.swift index 486abd8..93945b6 100644 --- a/SDKs/AgoraWidgets/AgoraChat/Views/Content/AgoraChatMessageView.swift +++ b/SDKs/AgoraWidgets/AgoraChat/Views/Content/AgoraChatMessageView.swift @@ -199,17 +199,35 @@ extension AgoraChatMessageView: UITableViewDelegate, UITableViewDataSource { cell.messageImageView.size = size cell.updateFrame() } else { - let url = URL(string: model.imageRemoteUrl) + let urlString = model.imageRemoteUrl + let url = URL(string: urlString) let brokenImage = UIConfig.agoraChat.picture.brokenImage + cell.messageImageView.sd_setImage(with: url, placeholderImage: brokenImage) { [weak self] downloadImage, error, cacheType, url in guard let `self` = self else { return } - cell.messageImageView.image = downloadImage - let size = cell.sizeWithImage(downloadImage) - cell.messageImageView.size = size - cell.updateFrame() + + guard self.messageDataSource.count > indexPath.row else { + return + } + + let type = self.messageDataSource[indexPath.row] + + guard case .image(var model) = type else { + return + } + + guard model.imageRemoteUrl == urlString else { + return + } + + model.image = downloadImage + + let new = AgoraChatMessageViewType.image(model) + + self.messageDataSource[indexPath.row] = new tableView.reloadRows(at: [indexPath], with: .none) @@ -229,11 +247,16 @@ extension AgoraChatMessageView: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let type = messageDataSource[indexPath.row] + guard case .image(let model) = type else { return } - setFullScreenImage(urlString: model.imageRemoteUrl, - localImage: model.image) + + guard let image = model.image else { + return + } + + setFullScreenImage(localImage: image) } } @@ -289,23 +312,14 @@ private extension AgoraChatMessageView { func updateNoticeMessageCell(cell: AgoraChatNoticeMessageCell, notice: String) { cell.noticeLabel.text = notice - } + } - func setFullScreenImage(urlString: String?, - localImage:UIImage?) { + func setFullScreenImage(localImage: UIImage) { let topVc = UIViewController.agora_top_view_controller() - if let localImage = localImage { - topVc.view.bringSubviewToFront(fullScreenImageView) - fullScreenImageView.agora_visible = true - fullScreenImageView.image = localImage - } else if let url = URL(string: urlString) { - topVc.view.bringSubviewToFront(fullScreenImageView) - fullScreenImageView.agora_visible = true - fullScreenImageView.sd_setImage(with: url) - } else { - return - } + topVc.view.bringSubviewToFront(fullScreenImageView) + fullScreenImageView.agora_visible = true + fullScreenImageView.image = localImage } @objc func onClickCloseFullScreenImage() { diff --git a/SDKs/AgoraWidgets/Assets/others/en.lproj/Localizable.strings b/SDKs/AgoraWidgets/Assets/others/en.lproj/Localizable.strings index 798ade0..7908f1d 100644 --- a/SDKs/AgoraWidgets/Assets/others/en.lproj/Localizable.strings +++ b/SDKs/AgoraWidgets/Assets/others/en.lproj/Localizable.strings @@ -101,6 +101,11 @@ fcr_popup_quiz_my_answer = "My Answer"; fcr_popup_quiz_start_again = "restart"; fcr_popup_quiz_answer_time = "Time elapsed"; +//slide +fcr_board_slide_retry = "course load failure, retry now"; +fcr_board_slide_retry_failure = "course load failure, please load next page or reopen"; + + // poll fcr_poll_title = "Poll"; fcr_poll_submit = "Submit"; diff --git a/SDKs/AgoraWidgets/Assets/others/zh-Hans.lproj/Localizable.strings b/SDKs/AgoraWidgets/Assets/others/zh-Hans.lproj/Localizable.strings index d0f2bca..3180d85 100644 --- a/SDKs/AgoraWidgets/Assets/others/zh-Hans.lproj/Localizable.strings +++ b/SDKs/AgoraWidgets/Assets/others/zh-Hans.lproj/Localizable.strings @@ -90,6 +90,10 @@ fcr_cloud_upload_file = "上传文件"; fcr_cloud_upload_pictures = "上传图片"; fcr_cloud_uploading = "上传中"; +//slide +fcr_board_slide_retry = "课件加载失败,正在重试"; +fcr_board_slide_retry_failure = "课件加载失败,请切换下一页或关闭课件后重试"; + // AnswerSelector fcr_popup_quiz = "答题器"; fcr_popup_quiz_post = "提交答案"; diff --git a/SDKs/AgoraWidgets/CloudDrive/FcrCloudDriveServerAPI.swift b/SDKs/AgoraWidgets/CloudDrive/FcrCloudDriveServerAPI.swift index 25bba49..ceaaa6d 100644 --- a/SDKs/AgoraWidgets/CloudDrive/FcrCloudDriveServerAPI.swift +++ b/SDKs/AgoraWidgets/CloudDrive/FcrCloudDriveServerAPI.swift @@ -54,7 +54,7 @@ class FcrCloudDriveServerAPI: AgoraWidgetServerAPI { request(event: "cloud-drive-delete-file", url: urlString, method: .delete, - parameters: list) { json in + anyParameters: list) { json in success(resourceUuid) } failure: { error in failure(error) @@ -117,7 +117,7 @@ class FcrCloudDriveServerAPI: AgoraWidgetServerAPI { request(event: "cloud-drive", url: urlString, method: .post, - parameters: parameters) { json in + anyParameters: parameters) { json in if let data = (json["data"] as? [[String: Any]])?.first { success(data) } else { diff --git a/SDKs/AgoraWidgets/Common/AgoraNativeWidget.swift b/SDKs/AgoraWidgets/Common/AgoraNativeWidget.swift index 7deec75..74a1cb2 100644 --- a/SDKs/AgoraWidgets/Common/AgoraNativeWidget.swift +++ b/SDKs/AgoraWidgets/Common/AgoraNativeWidget.swift @@ -14,9 +14,9 @@ public class AgoraNativeWidget: AgoraBaseWidget, AgoraWidgetLogTube { let logger = AgoraWidgetLogger(widgetId: widgetInfo.widgetId, logId: widgetInfo.localUserInfo.userUuid) -#if DEBUG +//#if DEBUG logger.isPrintOnConsole = true -#endif +//#endif self.logger = logger super.init(widgetInfo: widgetInfo) diff --git a/SDKs/AgoraWidgets/Common/AgoraWidgetServerAPI.swift b/SDKs/AgoraWidgets/Common/AgoraWidgetServerAPI.swift index d8ccebf..6deeb73 100644 --- a/SDKs/AgoraWidgets/Common/AgoraWidgetServerAPI.swift +++ b/SDKs/AgoraWidgets/Common/AgoraWidgetServerAPI.swift @@ -85,7 +85,7 @@ public class AgoraWidgetServerAPI: NSObject { url: String, method: ArHttpMethod, header: [String: String]? = nil, - parameters: Any? = nil, + anyParameters: Any, success: JsonCompletion? = nil, failure: FailureCompletion? = nil) { var extra: [String: Any] = ["event": event] @@ -94,9 +94,7 @@ public class AgoraWidgetServerAPI: NSObject { extra["header"] = header.description } - if let `parameters` = parameters { - extra["parameters"] = parameters - } + extra["parameters"] = anyParameters self.armin.logTube?.log(info: "http request", extra: extra.description) @@ -114,10 +112,8 @@ public class AgoraWidgetServerAPI: NSObject { } // Parameters - if let params = parameters { - let jsonData = try? JSONSerialization.data(withJSONObject: params) - request.httpBody = jsonData - } + let jsonData = try? JSONSerialization.data(withJSONObject: anyParameters) + request.httpBody = jsonData request.allHTTPHeaderFields = ["x-agora-token": token, "x-agora-uid": userId, diff --git a/SDKs/AgoraWidgets/Whiteboard/FcrBoardWidget.swift b/SDKs/AgoraWidgets/Whiteboard/FcrBoardWidget.swift index 23aa21d..223a05f 100644 --- a/SDKs/AgoraWidgets/Whiteboard/FcrBoardWidget.swift +++ b/SDKs/AgoraWidgets/Whiteboard/FcrBoardWidget.swift @@ -10,6 +10,7 @@ import AgoraWidget import AgoraLog import Photos import Armin +import Whiteboard @objcMembers public class FcrBoardWidget: AgoraNativeWidget { // Views @@ -28,6 +29,7 @@ import Armin private var canJoin = false + private var retryTime = 0 // 教师角色加入房间成功时设置,学生角色监听grantedUsers变化设置 private var hasOperationPrivilege: Bool = false { didSet { @@ -57,6 +59,14 @@ import Armin analyzeGrantedUsersFromRoomProperties() } + func retry(slideId:String, slideIndex: Int){ + self.retryTime += 1 + self.boardRoom?.recoverSlide(slideId: slideId, slideIndex: slideIndex) + showToast("fcr_board_slide_retry".localized() + String(self.retryTime) + "/5", + type: .error) + } + + public override func onWidgetRoomPropertiesDeleted(_ properties: [String : Any]?, cause: [String : Any]?, keyPaths: [String], @@ -331,10 +341,10 @@ extension FcrBoardWidget { let backgroundColor = UIConfig.netlessBoard.backgroundColor let room = FcrBoardRoom(appId: config.boardAppId, region: boardRegion, - backgroundColor: backgroundColor) - room.delegate = self + backgroundColor: backgroundColor, + logTube: self) - room.logTube = self + room.delegate = self let ratio = view.ratio() @@ -665,6 +675,29 @@ extension FcrBoardWidget: FcrBoardRoomDelegate { break } } + func onSlideError(slideError: WhiteSlideErrorType, errorMessage: String, slideId: String, slideIndex: Int) { + if(slideError == WhiteSlideErrorType.resourceError || slideError == WhiteSlideErrorType.canvasCrash){ + if(self.retryTime == 0){ + self.retry(slideId: slideId, slideIndex: slideIndex) + }else if(self.retryTime < 5){ + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + self.retry(slideId: slideId, slideIndex: slideIndex) + } + }else{ + showToast("fcr_board_slide_retry_failure".localized(), + type: .error) + } + } else if(slideError == WhiteSlideErrorType.runtimeError){ + self.boardRoom?.recoverSlide(slideId: slideId, slideIndex: slideIndex + 1) + } else if(slideError == WhiteSlideErrorType.runtimeWarn) { + let slideExtra = ["slideId": slideId, + "slideIndex": slideIndex.agDescription] + log(content: "slide page error", + extra: slideExtra.agDescription, type: .warning, + fromClass: WhiteSDK.self, + funcName: "onSlideError") + } + } } // MARK: - FcrBoardMainWindowDelegate diff --git a/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardListener.swift b/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardListener.swift index fa9eae3..c7829bb 100644 --- a/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardListener.swift +++ b/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardListener.swift @@ -5,12 +5,125 @@ // Created by Cavan on 2022/6/8. // +import AgoraRtcKit import Foundation import Whiteboard class FcrBoardListener: NSObject { weak var roomNeedObserve: FcrBoardRoomNeedObserve? weak var mainWindowNeedObserve: FcrBoardMainWindowNeedObserve? + weak var rtc: AgoraRtcEngineKit! + weak var effectMixer: WhiteAudioEffectMixerBridge? + weak var logTube: FcrBoardLogTube? + + override init() { + super.init() + let getRtc = Notification.Name(rawValue: "rtc.engine.object") + + NotificationCenter.default.addObserver(self, + selector: #selector(getRtcObject(_:)), + name: getRtc, + object: nil) + + let audioFileInfo = Notification.Name(rawValue: "rtc.engine.audio.file.info") + + NotificationCenter.default.addObserver(self, + selector: #selector(rtcAudioFileInfo(_:)), + name: audioFileInfo, + object: nil) + + let audioEffectFinish = Notification.Name(rawValue: "rtc.engine.audio.effect.finish") + + NotificationCenter.default.addObserver(self, + selector: #selector(rtcAudioEffectFinish(_:)), + name: audioEffectFinish, + object: nil) + + let audioEffectState = Notification.Name(rawValue: "rtc.engine.audio.effect.state") + + NotificationCenter.default.addObserver(self, + selector: #selector(rtcAudioEffectStateChanged(_:)), + name: audioEffectState, + object: nil) + + let needRtc = Notification(name: Notification.Name(rawValue: "need.rtc.engine.object")) + + NotificationCenter.default.post(needRtc) + } + + deinit { + NotificationCenter.default.removeObserver(self) + + _ = stopAllEffects() + } + + @objc func getRtcObject(_ notification: Notification) { + rtc = notification.object as? AgoraRtcEngineKit + } + + @objc func rtcAudioFileInfo(_ notification: Notification) { + guard let object = notification.object as? [String: Any], + let info = object["info"] as? AgoraRtcAudioFileInfo else { + return + } + + log(content: #function, + extra: "filePath: \(info.filePath), durationMs: \(info.durationMs)", + type: .info) + + effectMixer?.setEffectDurationUpdate(info.filePath, + duration: Int(info.durationMs)) + } + + @objc func rtcAudioEffectFinish(_ notification: Notification) { + guard let object = notification.object as? [String: Any], + let soundId = object["soundId"] as? Int else { + return + } + + log(content: #function, + extra: "soundId: \(soundId)", + type: .info) + + effectMixer?.setEffectFinished(soundId) + } + + @objc func rtcAudioEffectStateChanged(_ notification: Notification) { + guard let object = notification.object as? [String: Any], + let soundId = object["soundId"] as? Int, + let state = object["state"] as? Int else { + return + } + + log(content: #function, + extra: "soundId: \(soundId), state: \(state)", + type: .info) + + effectMixer?.setEffectSoundId(soundId, + stateChanged: state) + } + + func log(content: String, + extra: String? = nil, + type: FcrBoardLogType, + fromClass: AnyClass? = nil, + funcName: String = #function, + line: Int = #line) { + var classType: AnyClass + + if let `fromClass` = fromClass { + classType = fromClass + } else { + classType = self.classForCoder + } + + logTube?.onBoardLog(content: content, + extra: extra, + type: type, + fromClass: classType, + funcName: funcName, + line: line) + } } extension FcrBoardListener: WhiteCommonCallbackDelegate { @@ -55,6 +168,7 @@ extension FcrBoardListener: WhiteAudioMixerBridgeDelegate { replace: replace, cycle: cycle) } + func stopAudioMixing() { mainWindowNeedObserve?.onStopAudioMixing() } @@ -71,3 +185,220 @@ extension FcrBoardListener: WhiteAudioMixerBridgeDelegate { mainWindowNeedObserve?.onAudioMixingPositionUpdated(position: position) } } + +extension FcrBoardListener: WhiteAudioEffectMixerBridgeDelegate { + func getEffectsVolume() -> Double { + let result = rtc.getEffectsVolume() + + log(content: #function, + extra: "result: \(result)", + type: .info, + fromClass: FcrBoardListener.self) + + return result + } + + func setEffectsVolume(_ volume: Double) -> Int32 { + let result = rtc.setEffectsVolume(volume) + + log(content: #function, + extra: "volume: \(volume), result: \(result)", + type: .info, + fromClass: FcrBoardListener.self) + + return result + } + + func setVolumeOfEffect(_ soundId: Int32, + withVolume volume: Double) -> Int32 { + let result = rtc.setVolumeOfEffect(soundId, + withVolume: volume) + + log(content: #function, + extra: "soundId: \(soundId), volume: \(volume), result: \(result)", + type: .info, + fromClass: WhiteSDK.self) + + return result + } + + func playEffect(_ soundId: Int32, + filePath: String?, + loopCount: Int32, + pitch: Double, + pan: Double, + gain: Double, + publish: Bool, + startPos: Int32, + identifier: String) -> Int32 { + var extra = "soundId: \(soundId), filePath: \(filePath ?? "nil"), loopCount: \(loopCount), pitch: \(pitch), pan: \(pan), gain: \(gain) publish: \(publish), startPos: \(startPos), identifier: \(identifier)" + + if identifier == "mediaPlayer" { + let tGain: Double = 300 + + extra += ", mediaPlayer final gain: \(tGain)" + + let result = rtc.playEffect(soundId, + filePath: filePath, + loopCount: loopCount, + pitch: pitch, + pan: pan, + gain: tGain, + publish: publish) + + extra += ", result: \(result)" + + log(content: #function, + extra: extra, + type: .info, + fromClass: FcrBoardListener.self) + + return result + } else { + let tGain: Double = 300 + + extra += ", ppt final gain: \(tGain)" + + let result = rtc.playEffect(soundId, + filePath: filePath, + loopCount: loopCount, + pitch: pitch, + pan: pan, + gain: tGain, + publish: publish, + startPos: startPos) + + extra += ", result: \(result)" + + log(content: #function, + extra: extra, + type: .info, + fromClass: FcrBoardListener.self) + + return result + } + } + + func stopEffect(_ soundId: Int32) -> Int32 { + let result = rtc.stopEffect(soundId) + + log(content: #function, + extra: "soundId: \(soundId), result: \(result)", + type: .info, + fromClass: FcrBoardListener.self) + + return result + } + + func stopAllEffects() -> Int32 { + let result = rtc.stopAllEffects() + + log(content: #function, + extra: "result: \(result)", + type: .info, + fromClass: WhiteSDK.self) + + return result + } + + func preloadEffect(_ soundId: Int32, + filePath: String?) -> Int32 { + let result = rtc.preloadEffect(soundId, + filePath: filePath) + + log(content: #function, + extra: "soundId: \(soundId), filePath: \(filePath ?? ""), result: \(result)", + type: .info, + fromClass: FcrBoardListener.self) + + return result + } + + func unloadEffect(_ soundId: Int32) -> Int32 { + let result = rtc.unloadEffect(soundId) + + log(content: #function, + extra: "soundId: \(soundId), result: \(result)", + type: .info, + fromClass: FcrBoardListener.self) + + return result + } + + func pauseEffect(_ soundId: Int32) -> Int32 { + let result = rtc.pauseEffect(soundId) + + log(content: #function, + extra: "soundId: \(soundId), result: \(result)", + type: .info, + fromClass: FcrBoardListener.self) + + return result + } + + func pauseAllEffects() -> Int32 { + let result = rtc.pauseAllEffects() + + log(content: #function, + extra: "result: \(result)", + type: .info, + fromClass: FcrBoardListener.self) + + return result + } + + func resumeEffect(_ soundId: Int32) -> Int32 { + let result = rtc.resumeEffect(soundId) + + log(content: #function, + extra: "result: \(result)", + type: .info, + fromClass: FcrBoardListener.self) + + return result + } + + func resumeAllEffects() -> Int32 { + let result = rtc.resumeAllEffects() + + log(content: #function, + extra: "result: \(result)", + type: .info, + fromClass: FcrBoardListener.self) + + return result + } + + func setEffectPosition(_ soundId: Int32, + pos: Int) -> Int32 { + let result = rtc.setEffectPosition(soundId, + pos: pos) + + log(content: #function, + extra: "soundId: \(soundId), pos: \(pos), result: \(result)", + type: .info, + fromClass: FcrBoardListener.self) + + return result + } + + func getEffectCurrentPosition(_ soundId: Int32) -> Int32 { + // log(content: #function, + // extra: "soundId: \(soundId)", + // type: .info, + // fromClass: WhiteSDK.self) + + return rtc.getEffectCurrentPosition(soundId) + } + + func getEffectDuration(_ filePath: String) -> Int32 { + let result = rtc.getEffectDuration(filePath) + + log(content: #function, + extra: "filePath: \(filePath), result: \(result)", + type: .info, + fromClass: FcrBoardListener.self) + + return result + } +} diff --git a/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardProtocols.swift b/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardProtocols.swift index 3071679..dfda98c 100644 --- a/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardProtocols.swift +++ b/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardProtocols.swift @@ -11,6 +11,7 @@ import Whiteboard // Public protocol FcrBoardRoomDelegate: NSObjectProtocol { func onConnectionStateUpdated(state: FcrBoardRoomConnectionState) + func onSlideError(slideError: WhiteSlideErrorType, errorMessage: String, slideId: String, slideIndex: Int) } protocol FcrBoardMainWindowDelegate: FcrBoardAudioMixingDelegate { diff --git a/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardRoom.swift b/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardRoom.swift index 1a7f25a..cb07092 100644 --- a/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardRoom.swift +++ b/SDKs/AgoraWidgets/Whiteboard/Wrapper/FcrBoardRoom.swift @@ -8,7 +8,7 @@ import Foundation import Whiteboard -class FcrBoardRoom: NSObject { +class FcrBoardRoom: NSObject, WhiteSlideDelegate { private weak var whiteRoom: WhiteRoom? private let whiteView: WhiteBoardView private let whiteSDK: WhiteSDK @@ -20,12 +20,18 @@ class FcrBoardRoom: NSObject { private var mainWindow: FcrBoardMainWindow? weak var delegate: FcrBoardRoomDelegate? - weak var logTube: FcrBoardLogTube? + weak var logTube: FcrBoardLogTube? { + didSet { + listener.logTube = logTube + } + } init(appId: String, region: FcrBoardRegion, - backgroundColor: UIColor?) { + backgroundColor: UIColor?, + logTube: FcrBoardLogTube?) { let listener = FcrBoardListener() + listener.logTube = logTube let whiteView = WhiteBoardView(frame: .zero, configuration: WKWebViewConfiguration.defaultConfig()) @@ -37,19 +43,28 @@ class FcrBoardRoom: NSObject { sdkConfig.region = region.netlessValue sdkConfig.useMultiViews = true sdkConfig.userCursor = true + sdkConfig.enableAppliancePlugin = true + sdkConfig.log = true + + sdkConfig.loggerOptions = ["printLevelMask": WhiteSDKLoggerOptionLevelKey.debug.rawValue] let whiteSDK = WhiteSDK(whiteBoardView: whiteView, config: sdkConfig, commonCallbackDelegate: listener, - audioMixerBridgeDelegate: listener) + effectMixerBridgeDelegate: listener) + + whiteSDK.setParameters(["effectMixingForMediaPlayer": true]) self.whiteView = whiteView self.whiteSDK = whiteSDK self.listener = listener + self.logTube = logTube super.init() + whiteSDK.setSlideDelegate(self) listener.roomNeedObserve = self + listener.effectMixer = whiteSDK.effectMixer registerH5App() @@ -73,12 +88,20 @@ class FcrBoardRoom: NSObject { funcName: "init") } + + func onSlideError(_ slideError: WhiteSlideErrorType, errorMessage: String, slideId: String, slideIndex: Int) { + delegate?.onSlideError(slideError: slideError, errorMessage: errorMessage, slideId: slideId, slideIndex: slideIndex) + } + + func recoverSlide(slideId:String, slideIndex:Int) { + self.whiteSDK.recoverSlide(slideId, slideIndex: slideIndex) + } + func join(config: FcrBoardRoomJoinConfig, superView: UIView, success: @escaping (FcrBoardMainWindow) -> Void, failure: @escaping (Error) -> Void) { hasLeft = false - joinConfig = config superView.addSubview(whiteView) @@ -163,6 +186,8 @@ private extension FcrBoardRoom { delegate?.onConnectionStateUpdated(state: state) } + + func getWhiteRoomConfig() -> WhiteRoomConfig? { guard let config = joinConfig else { log(content: "join config nil",