From 1159e5e53aa84b71e822849de59e7516566fbdba Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Thu, 30 Oct 2025 15:58:57 +0900 Subject: [PATCH] feature(agora): rtm version 1.4.10 -> 2.2.4 --- Podfile | 16 + Podfile.lock | 10 +- .../xcshareddata/swiftpm/Package.resolved | 11 +- SodaLive/Sources/Agora/Agora.swift | 250 +++++++--- .../Sources/Live/Room/LiveRoomViewModel.swift | 434 ++++++++---------- 5 files changed, 423 insertions(+), 298 deletions(-) diff --git a/Podfile b/Podfile index 821684e..e4799e2 100644 --- a/Podfile +++ b/Podfile @@ -7,6 +7,7 @@ target 'SodaLive' do # Pods for SodaLive pod 'BootpayUI', '4.4.10' + pod 'AgoraRtm', '2.2.4' end @@ -16,9 +17,24 @@ target 'SodaLive-dev' do # Pods for SodaLive-dev pod 'BootpayUI', '4.4.10' + pod 'AgoraRtm', '2.2.4' end +pre_install do |installer| + # Path to the AgoraRtm Pod directory inside the CocoaPods sandbox + rtm_pod_path = File.join(installer.sandbox.root, 'AgoraRtm') + # Full path to aosl.xcframework + aosl_xcframework_path = File.join(rtm_pod_path, 'aosl.xcframework') + + if File.exist?(aosl_xcframework_path) + puts "Deleting aosl.xcframework from #{aosl_xcframework_path}" + FileUtils.rm_rf(aosl_xcframework_path) + else + puts "aosl.xcframework not found, skipping deletion." + end +end + post_install do |installer| installer.generated_projects.each do |project| project.targets.each do |target| diff --git a/Podfile.lock b/Podfile.lock index 3af2c89..9cbddd8 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,4 +1,9 @@ PODS: + - AgoraRtm (2.2.4): + - AgoraRtm/RtmBasic (= 2.2.4) + - AgoraRtm/RtmKit (= 2.2.4) + - AgoraRtm/RtmBasic (2.2.4) + - AgoraRtm/RtmKit (2.2.4) - Alamofire (5.10.2) - Bootpay (4.4.6): - CryptoSwift @@ -20,10 +25,12 @@ PODS: - SwiftyJSON (5.0.2) DEPENDENCIES: + - AgoraRtm (= 2.2.4) - BootpayUI (= 4.4.10) SPEC REPOS: trunk: + - AgoraRtm - Alamofire - Bootpay - BootpayUI @@ -34,6 +41,7 @@ SPEC REPOS: - SwiftyJSON SPEC CHECKSUMS: + AgoraRtm: 534144434383d41b3b0ebfae2a961ef0f51b0645 Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496 Bootpay: cd7f0542b096ab0af0b09a6e12a6b87f2cbbb531 BootpayUI: beec5b0bba002b4dbced8c0ecace571ed6a017bc @@ -43,6 +51,6 @@ SPEC CHECKSUMS: SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a -PODFILE CHECKSUM: a99afeba13301c2139b142c1e1592fe314698d39 +PODFILE CHECKSUM: 197d8c8b434dbcc335438281fc68e94718f6a8e1 COCOAPODS: 1.16.2 diff --git a/SodaLive.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SodaLive.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6822a73..7baeae5 100644 --- a/SodaLive.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SodaLive.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "021219245a6febf5a4891182273209d32ef45b11dd68b857e2eef1f7a8ca439d", + "originHash" : "1f28da3687662a2a9efe60ffc2ca2499be411b5b0a1e07f72559059c40728121", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -28,15 +28,6 @@ "version" : "4.6.0" } }, - { - "identity" : "agorartm_ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/AgoraIO/AgoraRtm_iOS", - "state" : { - "revision" : "8d8d126da7e420798f39d1d95b6148eeb93971aa", - "version" : "1.4.10" - } - }, { "identity" : "alamofire", "kind" : "remoteSourceControl", diff --git a/SodaLive/Sources/Agora/Agora.swift b/SodaLive/Sources/Agora/Agora.swift index 1027671..45b889b 100644 --- a/SodaLive/Sources/Agora/Agora.swift +++ b/SodaLive/Sources/Agora/Agora.swift @@ -23,7 +23,7 @@ final class Agora { } // MARK: RTC - var rtcEngine: AgoraRtcEngineKit? + private var rtcEngine: AgoraRtcEngineKit? var rtcEngineDelegate: AgoraRtcEngineDelegate? func initRtcEngine() { @@ -71,85 +71,216 @@ final class Agora { rtcEngine?.muteAllRemoteAudioStreams(isMute) } + func getRtcConnectionState() -> AgoraConnectionState { + return rtcEngine!.getConnectionState() + } + // MARK: RTM - var rtmKit: AgoraRtmKit? - var rtmChannel: AgoraRtmChannel? - var rtmDelegate: AgoraRtmDelegate? + private var rtmKit: AgoraRtmClientKit? + private var roomChannelName: String? = nil + + // 상태 플래그: RTM 로그인 완료 여부 + private var rtmLoggedIn: Bool = false + + // 상태 플래그: RTM 로그인 진행 중 여부 + private var rtmLoginInProgress: Bool = false + + var rtmClientDelegate: AgoraRtmClientDelegate? func initRtmClient() { - rtmKit = AgoraRtmKit(appId: AGORA_APP_ID, delegate: rtmDelegate) + if rtmKit != nil { + rtmKit?.logout() + rtmKit?.destroy() + rtmKit = nil + } + let userId = UserDefaults.int(forKey: .userId) + let config = AgoraRtmClientConfig(appId: AGORA_APP_ID, userId: String(userId)) + rtmKit = try? AgoraRtmClientKit(config, delegate: rtmClientDelegate) } func deInitRtmClient() { - rtmChannel?.leave(completion: nil) - rtmKit?.logout(completion: nil) - rtmChannel = nil - rtmKit = nil + let userId = UserDefaults.int(forKey: .userId) + let group = DispatchGroup() + + if let channel = roomChannelName { + group.enter() + rtmKit?.unsubscribe(channel) { [weak self] _, error in + if let error = error { + DEBUG_LOG("RTM unsubscribe fail - \(error.operation)") + DEBUG_LOG("RTM unsubscribe fail - \(error.errorCode)") + DEBUG_LOG("RTM unsubscribe fail - \(error.reason)") + } else { + DEBUG_LOG("RTM unsubscribe - \(channel)") + self?.roomChannelName = nil + } + group.leave() + } + } + + group.enter() + rtmKit?.unsubscribe("inbox_\(userId)") { _, error in + if let error = error { + DEBUG_LOG("RTM unsubscribe fail - \(error.operation)") + DEBUG_LOG("RTM unsubscribe fail - \(error.errorCode)") + DEBUG_LOG("RTM unsubscribe fail - \(error.reason)") + } else { + DEBUG_LOG("RTM unsubscribe - inbox_\(userId)") + } + group.leave() + } + + group.notify(queue: .global(qos: .userInitiated)) { [weak self] in + guard let self = self else { return } + self.rtmKit?.logout() + self.rtmKit?.destroy() + self.rtmKit = nil + self.rtmLoggedIn = false + self.rtmLoginInProgress = false + } } func rtmLogin( creatorId: Int, rtmToken: String, channelName: String, - rtmChannelDelegate: AgoraRtmChannelDelegate, onConnectSuccess: @escaping (Bool) -> Void, onConnectFail: @escaping () -> Void ) { - if rtmChannel != nil { + if rtmLoggedIn && roomChannelName == channelName { + DEBUG_LOG("rtmLogin - already logged in and subscribed. skip") return } - let userId = UserDefaults.int(forKey: .userId) + // 로그인 시도 중이면 재호출 방지 + if (rtmLoginInProgress) { + DEBUG_LOG("rtmLogin - already in progress. skip") + return + } - rtmChannel = rtmKit?.createChannel( - withId: channelName, - delegate: rtmChannelDelegate - ) + roomChannelName = channelName - rtmKit?.login( - byToken: rtmToken, - user: String(userId), - completion: { [unowned self] loginErrorCode in - if loginErrorCode == .ok { - self.rtmChannel?.join(completion: { joinChannelErrorCode in - if joinChannelErrorCode == .channelErrorOk { - if userId == creatorId { - self.setRole(role: .broadcaster) - } else { - self.setRole(role: .audience) - } - - onConnectSuccess(userId == creatorId) - } else { - onConnectFail() - } - }) + func attemptLogin(_ attempt: Int) { + rtmKit?.login(rtmToken) { [weak self] response, error in + if let error = error { + DEBUG_LOG("rtmClient login - fail (attempt=\(attempt)), \(error.reason)") + if attempt < 4 { + + } else { + self?.rtmLoginInProgress = false + onConnectFail() + } } else { - onConnectFail() + DEBUG_LOG("rtmClient login - success (attempt=\(attempt))") + // 로그인 성공 후 두 채널 구독 시도 + self?.subscribeChannel( + creatorId: creatorId, + onConnectSuccess: onConnectSuccess, + onConnectFail: onConnectFail + ) } } - ) - } - - func sendMessageToPeer(peerId: String, rawMessage: Data, completion: AgoraRtmSendPeerMessageBlock?) { - let option = AgoraRtmSendMessageOptions() - option.enableOfflineMessaging = false - option.enableHistoricalMessaging = false + } - let message = AgoraRtmRawMessage(rawData: rawMessage, description: "") - rtmKit?.send(message, toPeer: peerId, completion: completion) + rtmLoginInProgress = true + attemptLogin(1) } - func sendRawMessageToPeer(peerId: String, rawMessage: LiveRoomChatRawMessage, completion: AgoraRtmSendPeerMessageBlock? = nil, fail: (() -> Void)? = nil) { + private func subscribeChannel( + creatorId: Int, + onConnectSuccess: @escaping (Bool) -> Void, + onConnectFail: @escaping () -> Void + ) { + let targetRoom = roomChannelName + if (targetRoom == nil) { + DEBUG_LOG("subscribeChannel - roomChannelName is nil") + onConnectFail() + return + } + + var completed = false + var roomSubscribed = false + var inboxSubscribed = false + let userId = UserDefaults.int(forKey: .userId) + + func completeSuccessIfReady() { + if (!completed && roomSubscribed && inboxSubscribed) { + completed = true + rtmLoggedIn = true + rtmLoginInProgress = false + DEBUG_LOG("RTM subscribe - both channels subscribed") + if userId == creatorId { + self.setRole(role: .broadcaster) + } else { + self.setRole(role: .audience) + } + onConnectSuccess(userId == creatorId) + } + } + + func failOnce(_ reason: String?) { + if (!completed) { + completed = true + if let reason = reason { + DEBUG_LOG("RTM subscribe failed: \(reason)") + } else { + DEBUG_LOG("RTM subscribe failed: nil") + } + onConnectFail() + } + } + + func subscribeRoom(_ attempt: Int) { + DEBUG_LOG("RTM subscribe(room: \(targetRoom!)) attempt=\(attempt)") + rtmKit?.subscribe(channelName: targetRoom!, option: nil) { _, error in + if error != nil { + DEBUG_LOG("RTM subscribe(room) failure at attempt=\(attempt) operation=\(error!.operation) reason=\(error!.reason) code=\(error!.errorCode)") + if (attempt < 4) { + subscribeRoom(attempt + 1) + } else { + failOnce("room subscribe failed after 3 retries (4 attempts)") + } + } else { + DEBUG_LOG("RTM subscribe(room) success at attempt=\(attempt)") + roomSubscribed = true + completeSuccessIfReady() + } + } + } + + func subscribeInbox(_ attempt: Int) { + let inboxChannel = "inbox_\(userId)" + DEBUG_LOG("RTM subscribe(inbox: \(inboxChannel)) attempt=\(attempt)") + rtmKit?.subscribe(channelName: inboxChannel, option: nil) { _, error in + if error != nil { + DEBUG_LOG("RTM subscribe(inbox) failure at attempt=\(attempt) operation=\(error!.operation) reason=\(error!.reason) code=\(error!.errorCode)") + if (attempt < 4) { + subscribeInbox(attempt + 1) + } else { + failOnce("room subscribe failed after 3 retries (4 attempts)") + } + } else { + DEBUG_LOG("RTM subscribe(inbox) success at attempt=\(attempt)") + inboxSubscribed = true + completeSuccessIfReady() + } + } + } + + // 두 채널 구독을 병렬로 시도 + subscribeRoom(1) + subscribeInbox(1) + } + + func sendMessageToPeer(peerId: String, rawMessage: Data, completion: AgoraRtmOperationBlock?) { + rtmKit?.publish(channelName: "inbox_\(peerId)", data: rawMessage, option: nil, completion: completion) + } + + func sendRawMessageToPeer(peerId: String, rawMessage: LiveRoomChatRawMessage, completion: AgoraRtmOperationBlock? = nil, fail: (() -> Void)? = nil) { let encoder = JSONEncoder() let jsonMessageData = try? encoder.encode(rawMessage) - let option = AgoraRtmSendMessageOptions() - option.enableOfflineMessaging = false - option.enableHistoricalMessaging = false if let jsonMessageData = jsonMessageData { - let message = AgoraRtmRawMessage(rawData: jsonMessageData, description: "") - rtmKit?.send(message, toPeer: peerId, sendMessageOptions: option, completion: completion) + rtmKit?.publish(channelName: "inbox_\(peerId)", data: jsonMessageData, option: nil, completion: completion) } else { if let fail = fail { fail() @@ -157,22 +288,27 @@ final class Agora { } } - func sendMessageToGroup(textMessage: String, completion: @escaping AgoraRtmSendChannelMessageBlock) { - let message = AgoraRtmMessage(text: textMessage) - rtmChannel?.send(message, completion: completion) + func sendMessageToGroup(textMessage: String, completion: @escaping AgoraRtmOperationBlock) { + guard !textMessage.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return } + guard let channelName = roomChannelName else { return } + + rtmKit?.publish(channelName: channelName, message: textMessage, option: nil, completion: completion) } - func sendRawMessageToGroup(rawMessage: LiveRoomChatRawMessage, completion: AgoraRtmSendChannelMessageBlock? = nil, fail: (() -> Void)? = nil) { + func sendRawMessageToGroup(rawMessage: LiveRoomChatRawMessage, completion: AgoraRtmOperationBlock? = nil, fail: (() -> Void)? = nil) { let encoder = JSONEncoder() let jsonMessageData = try? encoder.encode(rawMessage) - if let jsonMessageData = jsonMessageData { - let message = AgoraRtmRawMessage(rawData: jsonMessageData, description: "") - rtmChannel?.send(message, completion: completion) + if let jsonMessageData = jsonMessageData, let channelName = roomChannelName { + rtmKit?.publish(channelName: channelName, data: jsonMessageData, option: nil, completion: completion) } else { if let fail = fail { fail() } } } + + func isRtmLoggedIn() -> Bool { + return rtmLoggedIn + } } diff --git a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift index 74c5f93..07dfd22 100644 --- a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift +++ b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift @@ -225,6 +225,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject { private var blockedMemberIdList = Set() + private var hasInvokedJoinChannel = false + func getBlockedMemberIdList() { userRepository.getBlockedMemberIdList() .sink { result in @@ -265,7 +267,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject { func initAgoraEngine() { agora.rtcEngineDelegate = self - agora.rtmDelegate = self + agora.rtmClientDelegate = self agora.initialize() } @@ -370,15 +372,23 @@ final class LiveRoomViewModel: NSObject, ObservableObject { self.isActiveRoulette = data.isActiveRoulette self.isLoading = true - self.agora.joinRtcChannel(rtcToken: data.rtcToken, channelName: data.channelName) - self.agora.rtmLogin( - creatorId: data.creatorId, - rtmToken: data.rtmToken, - channelName: data.channelName, - rtmChannelDelegate: self, - onConnectSuccess: self.agoraConnectSuccess, - onConnectFail: self.agoraConnectFail - ) + let rtcState = self.agora.getRtcConnectionState() + let rtcConnected = rtcState == AgoraConnectionState.connected + let rtmLoggedIn = self.agora.isRtmLoggedIn() + + if (!hasInvokedJoinChannel && !(rtcConnected && rtmLoggedIn)) { + hasInvokedJoinChannel = true + self.agora.joinRtcChannel(rtcToken: data.rtcToken, channelName: data.channelName) + self.agora.rtmLogin( + creatorId: data.creatorId, + rtmToken: data.rtmToken, + channelName: data.channelName, + onConnectSuccess: self.agoraConnectSuccess, + onConnectFail: self.agoraConnectFail + ) + } else { + DEBUG_LOG("joinChannel - skip (rtcConnected=\(rtcConnected), rtmLoggedIn=\(rtmLoggedIn), hasInvokedJoinChannel=\(hasInvokedJoinChannel))") + } getTotalDonationCan() getTotalHeartCount() @@ -435,20 +445,17 @@ final class LiveRoomViewModel: NSObject, ObservableObject { self.popupContent = "\(remainingNoChattingTime)초 동안 채팅하실 수 없습니다" self.isShowPopup = true } else if chatMessage.count > 0 { - agora.sendMessageToGroup(textMessage: chatMessage, completion: { [unowned self] errorCode in - if errorCode == .errorOk { + agora.sendMessageToGroup(textMessage: chatMessage) { _, error in + if error == nil { let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) - let rank = getUserRank(userId: UserDefaults.int(forKey: .userId)) + let rank = self.getUserRank(userId: UserDefaults.int(forKey: .userId)) self.messages.append(LiveRoomNormalChat(userId: UserDefaults.int(forKey: .userId), profileUrl: profileUrl, nickname: nickname, rank: rank, chat: chatMessage)) - self.messageChangeFlag.toggle() - if self.messages.count > 100 { - self.messages.remove(at: 0) - } + self.invalidateChat() } onSuccess() - }) + } } } } @@ -501,68 +508,54 @@ final class LiveRoomViewModel: NSObject, ObservableObject { UserDefaults.set(UserDefaults.int(forKey: .can) - can, forKey: .can) if isSecret { - agora.sendRawMessageToPeer( - peerId: String(liveRoomInfo!.creatorId), rawMessage: donationRawMessage, - completion: { [unowned self] errorCode in - if errorCode == .ok { - let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) - self.messages.append( - LiveRoomDonationChat( - memberId: UserDefaults.int(forKey: .userId), - profileUrl: profileUrl, - nickname: nickname, - chat: rawMessage, - can: can, - donationMessage: message - ) + agora.sendRawMessageToPeer(peerId: String(liveRoomInfo!.creatorId), rawMessage: donationRawMessage) { [unowned self] _, error in + if error == nil { + let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) + self.messages.append( + LiveRoomDonationChat( + memberId: UserDefaults.int(forKey: .userId), + profileUrl: profileUrl, + nickname: nickname, + chat: rawMessage, + can: can, + donationMessage: message ) - - addSignature(signature: decoded.data) - - self.messageChangeFlag.toggle() - if self.messages.count > 100 { - self.messages.remove(at: 0) - } - } else { - refundDonation() - } - }, - fail: { [unowned self] in + ) + + addSignature(signature: decoded.data) + + self.invalidateChat() + } else { refundDonation() } - ) + } fail: { [unowned self] in + refundDonation() + } } else { - agora.sendRawMessageToGroup( - rawMessage: donationRawMessage, - completion: { [unowned self] errorCode in - if errorCode == .errorOk { - let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) - self.messages.append( - LiveRoomDonationChat( - memberId: UserDefaults.int(forKey: .userId), - profileUrl: profileUrl, - nickname: nickname, - chat: rawMessage, - can: can, - donationMessage: message - ) + agora.sendRawMessageToGroup(rawMessage: donationRawMessage) { [unowned self] _, error in + if error == nil { + let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) + self.messages.append( + LiveRoomDonationChat( + memberId: UserDefaults.int(forKey: .userId), + profileUrl: profileUrl, + nickname: nickname, + chat: rawMessage, + can: can, + donationMessage: message ) - - totalDonationCan += can - addSignature(signature: decoded.data) - - self.messageChangeFlag.toggle() - if self.messages.count > 100 { - self.messages.remove(at: 0) - } - } else { - refundDonation() - } - }, - fail: { [unowned self] in + ) + + totalDonationCan += can + addSignature(signature: decoded.data) + + self.invalidateChat() + } else { refundDonation() } - ) + } fail: { [unowned self] in + refundDonation() + } } } else { if let message = decoded.message { @@ -625,17 +618,17 @@ final class LiveRoomViewModel: NSObject, ObservableObject { } func inviteSpeaker(peerId: Int) { - agora.sendMessageToPeer(peerId: String(peerId), rawMessage: LiveRoomRequestType.INVITE_SPEAKER.rawValue.data(using: .utf8)!, completion: { [unowned self] errorCode in - if errorCode == .ok { + agora.sendMessageToPeer(peerId: String(peerId), rawMessage: LiveRoomRequestType.INVITE_SPEAKER.rawValue.data(using: .utf8)!) { [unowned self] _, error in + if error == nil { self.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요." self.isShowPopup = true } - }) + } } func changeListener(peerId: Int, isFromManager: Bool = false) { - agora.sendMessageToPeer(peerId: String(peerId), rawMessage: LiveRoomRequestType.CHANGE_LISTENER.rawValue.data(using: .utf8)!, completion: { [unowned self] errorCode in - if errorCode == .ok { + agora.sendMessageToPeer(peerId: String(peerId), rawMessage: LiveRoomRequestType.CHANGE_LISTENER.rawValue.data(using: .utf8)!) { [unowned self] _, error in + if error == nil { if isFromManager { getRoomInfo() setManagerMessage() @@ -647,7 +640,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject { self.isShowPopup = true } - }) + } } private func getUserNicknameAndProfileUrl(accountId: Int) -> (nickname: String, profileUrl: String) { @@ -977,12 +970,10 @@ final class LiveRoomViewModel: NSObject, ObservableObject { .store(in: &subscription) let nickname = getUserNicknameAndProfileUrl(accountId: kickOutId).nickname - agora.sendMessageToPeer(peerId: String(kickOutId), rawMessage: LiveRoomRequestType.KICK_OUT.rawValue.data(using: .utf8)!, completion: { [unowned self] errorCode in - if errorCode == .ok { - self.popupContent = "\(nickname)님을 내보냈습니다." - self.isShowPopup = true - } - }) + agora.sendMessageToPeer(peerId: String(kickOutId), rawMessage: LiveRoomRequestType.KICK_OUT.rawValue.data(using: .utf8)!) { [unowned self] _, error in + self.popupContent = "\(nickname)님을 내보냈습니다." + self.isShowPopup = true + } } if let index = muteSpeakers.firstIndex(of: UInt(kickOutId)) { @@ -1424,22 +1415,18 @@ final class LiveRoomViewModel: NSObject, ObservableObject { } func setNoChatting() { - agora.sendMessageToPeer( - peerId: String(noChattingUserId), - rawMessage: LiveRoomRequestType.NO_CHATTING.rawValue.data(using: .utf8)!, - completion: { [unowned self] errorCode in - if errorCode == .ok { - self.popupContent = "\(noChattingUserNickname)님을 3분간 채팅금지를 하였습니다." - self.isShowPopup = true - - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { - self.noChattingUserId = 0 - self.noChattingUserNickname = "" - self.noChattingUserProfileUrl = "" - } + agora.sendMessageToPeer(peerId: String(noChattingUserId), rawMessage: LiveRoomRequestType.NO_CHATTING.rawValue.data(using: .utf8)!) { [unowned self] _, error in + if error == nil { + self.popupContent = "\(noChattingUserNickname)님을 3분간 채팅금지를 하였습니다." + self.isShowPopup = true + + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + self.noChattingUserId = 0 + self.noChattingUserNickname = "" + self.noChattingUserProfileUrl = "" } } - ) + } } private func setManagerMessage() { @@ -1627,7 +1614,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject { private func removeNoChatRoom() { var noChatRoomList = getNoChatRoomListFromUserDefaults() - if let index = noChatRoomList.firstIndex(of: liveRoomInfo!.roomId) { + guard let roomId = liveRoomInfo?.roomId else { return } + if let index = noChatRoomList.firstIndex(of: roomId) { noChatRoomList.remove(at: index) } saveNoChatRoomListToUserDefaults(noChatRoomList: noChatRoomList) @@ -1760,36 +1748,29 @@ final class LiveRoomViewModel: NSObject, ObservableObject { donationMessage: "" ) - self.agora.sendRawMessageToGroup( - rawMessage: rouletteRawMessage, - completion: { [unowned self] errorCode in - if errorCode == .errorOk { - let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) - self.messages.append( - LiveRoomRouletteDonationChat( - profileUrl: profileUrl, - nickname: nickname, - rouletteResult: rouletteSelectedItem - ) + agora.sendRawMessageToGroup(rawMessage: rouletteRawMessage) { [unowned self] _, error in + if error == nil { + let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) + self.messages.append( + LiveRoomRouletteDonationChat( + profileUrl: profileUrl, + nickname: nickname, + rouletteResult: rouletteSelectedItem ) - - totalDonationCan += rouletteCan - self.rouletteItems.removeAll() - self.rouletteSelectedItem = "" - self.rouletteCan = 0 - - self.messageChangeFlag.toggle() - if self.messages.count > 100 { - self.messages.remove(at: 0) - } - } else { - self.refundRouletteDonation() - } - }, - fail: { [unowned self] in + ) + + totalDonationCan += rouletteCan + self.rouletteItems.removeAll() + self.rouletteSelectedItem = "" + self.rouletteCan = 0 + + self.invalidateChat() + } else { self.refundRouletteDonation() } - ) + } fail: { [unowned self] in + self.refundRouletteDonation() + } } func stopPeriodicPlaybackValidation() { @@ -1949,28 +1930,21 @@ final class LiveRoomViewModel: NSObject, ObservableObject { donationMessage: nil ) - agora.sendRawMessageToGroup( - rawMessage: donationRawMessage, - completion: { [unowned self] errorCode in - if errorCode == .errorOk { - let (nickname, _) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) - self.addHeartMessage(nickname: nickname) - - totalHeartCount += 1 - addHeart() - - self.messageChangeFlag.toggle() - if self.messages.count > 100 { - self.messages.remove(at: 0) - } - } else { - refundDonation() - } - }, - fail: { [unowned self] in - refundDonation() + agora.sendRawMessageToGroup(rawMessage: donationRawMessage) { [unowned self] _, error in + if error == nil { + let (nickname, _) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) + self.addHeartMessage(nickname: nickname) + + totalHeartCount += 1 + addHeart() + + self.invalidateChat() + } else { + self.refundDonation() } - ) + } fail: { [unowned self] in + self.refundDonation() + } } } catch { refundDonation() @@ -2073,6 +2047,13 @@ final class LiveRoomViewModel: NSObject, ObservableObject { heartTimer?.cancel() heartTimer = nil } + + private func invalidateChat() { + messageChangeFlag.toggle() + if messages.count > 100 { + messages.remove(at: 0) + } + } } extension LiveRoomViewModel: AgoraRtcEngineDelegate { @@ -2081,7 +2062,6 @@ extension LiveRoomViewModel: AgoraRtcEngineDelegate { .filter { $0.volume > 0 } .map { $0.uid } - DEBUG_LOG("activeSpeakerIds::: \(activeSpeakerIds)") activeSpeakers.removeAll() activeSpeakers.append(contentsOf: activeSpeakerIds) } @@ -2115,10 +2095,17 @@ extension LiveRoomViewModel: AgoraRtcEngineDelegate { } } -extension LiveRoomViewModel: AgoraRtmDelegate { - func rtmKit(_ kit: AgoraRtmKit, messageReceived message: AgoraRtmMessage, fromPeer peerId: String) { - if message.type == .raw, let rawMessage = message as? AgoraRtmRawMessage { - let rawMessageString = String(data: rawMessage.rawData, encoding: .utf8) +extension LiveRoomViewModel: AgoraRtmClientDelegate { + func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) { + DEBUG_LOG("Message received.\n channel: \(event.channelName), publisher: \(event.publisher)") + + let rawMessage = event.message.rawData + let textMessage = event.message.stringData + let publisher = event.publisher + let (nickname, profileUrl) = getUserNicknameAndProfileUrl(accountId: Int(publisher)!) + + if let message = rawMessage { + let rawMessageString = String(data: message, encoding: .utf8) DispatchQueue.main.async { [unowned self] in if rawMessageString == LiveRoomRequestType.CHANGE_LISTENER.rawValue { @@ -2127,7 +2114,7 @@ extension LiveRoomViewModel: AgoraRtmDelegate { } if rawMessageString == LiveRoomRequestType.REQUEST_SPEAKER.rawValue { - self.popupContent = "\(getUserNicknameAndProfileUrl(accountId: Int(peerId)!).nickname)님이 스피커 요청을 했어요!\n스퍼커로 초대할까요?" + self.popupContent = "\(nickname)님이 스피커 요청을 했어요!\n스퍼커로 초대할까요?" self.popupCancelTitle = "건너뛰기" self.popupCancelAction = { self.isShowPopup = false @@ -2136,7 +2123,7 @@ extension LiveRoomViewModel: AgoraRtmDelegate { self.popupConfirmAction = { self.isShowPopup = false if self.liveRoomInfo!.speakerList.count <= 5 { - self.requestSpeakerAllow(peerId) + self.requestSpeakerAllow(publisher) } else { self.errorMessage = "스피커 정원이 초과되었습니다." self.isShowErrorPopup = true @@ -2214,13 +2201,12 @@ extension LiveRoomViewModel: AgoraRtmDelegate { do { let jsonDecoder = JSONDecoder() - let decoded = try jsonDecoder.decode(LiveRoomChatRawMessage.self, from: rawMessage.rawData) - let (nickname, profileUrl) = getUserNicknameAndProfileUrl(accountId: Int(peerId)!) + let decoded = try jsonDecoder.decode(LiveRoomChatRawMessage.self, from: message) if decoded.type == .SECRET_DONATION { self.messages.append( LiveRoomDonationChat( - memberId: Int(peerId)!, + memberId: Int(publisher)!, profileUrl: profileUrl, nickname: nickname, chat: decoded.message, @@ -2234,98 +2220,86 @@ extension LiveRoomViewModel: AgoraRtmDelegate { } else if let imageUrl = decoded.signatureImageUrl { self.addSignatureImage(imageUrl: imageUrl) } + } else if decoded.type == .DONATION { + self.messages.append( + LiveRoomDonationChat( + memberId: Int(publisher)!, + profileUrl: profileUrl, + nickname: nickname, + chat: decoded.message, + can: decoded.can, + donationMessage: decoded.donationMessage ?? "" + ) + ) + + self.totalDonationCan += decoded.can + + if let signature = decoded.signature { + self.addSignature(signature: signature) + } else if let imageUrl = decoded.signatureImageUrl { + self.addSignatureImage(imageUrl: imageUrl) + } + } else if decoded.type == .ROULETTE_DONATION { + self.messages.append( + LiveRoomRouletteDonationChat( + profileUrl: profileUrl, + nickname: nickname, + rouletteResult: decoded.message + ) + ) + + self.totalDonationCan += decoded.can + } else if decoded.type == .TOGGLE_ROULETTE && decoded.isActiveRoulette != nil { + self.isActiveRoulette = decoded.isActiveRoulette! + } else if decoded.type == .EDIT_ROOM_INFO || decoded.type == .SET_MANAGER { + self.getRoomInfo() + } else if decoded.type == .HEART_DONATION { + self.addHeartMessage(nickname: nickname) + self.totalHeartCount += decoded.can + self.addHeart() } } catch { } } } - } -} - -extension LiveRoomViewModel: AgoraRtmChannelDelegate { - func channel(_ channel: AgoraRtmChannel, messageReceived message: AgoraRtmMessage, from member: AgoraRtmMember) { - let (nickname, profileUrl) = getUserNicknameAndProfileUrl(accountId: Int(member.userId)!) - if message.type == .raw, let rawMessage = message as? AgoraRtmRawMessage { - do { - let jsonDecoder = JSONDecoder() - let decoded = try jsonDecoder.decode(LiveRoomChatRawMessage.self, from: rawMessage.rawData) - - if decoded.type == .DONATION { - self.messages.append( - LiveRoomDonationChat( - memberId: Int(member.userId)!, - profileUrl: profileUrl, - nickname: nickname, - chat: decoded.message, - can: decoded.can, - donationMessage: decoded.donationMessage ?? "" - ) - ) - - self.totalDonationCan += decoded.can - - if let signature = decoded.signature { - self.addSignature(signature: signature) - } else if let imageUrl = decoded.signatureImageUrl { - self.addSignatureImage(imageUrl: imageUrl) - } - } else if decoded.type == .ROULETTE_DONATION { - self.messages.append( - LiveRoomRouletteDonationChat( - profileUrl: profileUrl, - nickname: nickname, - rouletteResult: decoded.message - ) - ) - - self.totalDonationCan += decoded.can - } else if decoded.type == .TOGGLE_ROULETTE && decoded.isActiveRoulette != nil { - self.isActiveRoulette = decoded.isActiveRoulette! - } else if decoded.type == .EDIT_ROOM_INFO || decoded.type == .SET_MANAGER { - self.getRoomInfo() - } else if decoded.type == .HEART_DONATION { - self.addHeartMessage(nickname: nickname) - self.totalHeartCount += decoded.can - self.addHeart() - } - } catch { - } - } else { - let memberId = Int(member.userId) ?? 0 - let chat = message.text + if let message = textMessage { + let memberId = Int(publisher) ?? 0 let rank = getUserRank(userId: memberId) - if !chat.trimmingCharacters(in: .whitespaces).isEmpty && !blockedMemberIdList.contains(memberId) { - messages.append(LiveRoomNormalChat(userId: memberId, profileUrl: profileUrl, nickname: nickname, rank: rank, chat: chat)) + if !message.trimmingCharacters(in: .whitespaces).isEmpty && !blockedMemberIdList.contains(memberId) { + messages.append(LiveRoomNormalChat(userId: memberId, profileUrl: profileUrl, nickname: nickname, rank: rank, chat: message)) } } DispatchQueue.main.async { [unowned self] in - self.messageChangeFlag.toggle() - if self.messages.count > 100 { - self.messages.remove(at: 0) - } + self.invalidateChat() } } - func channel(_ channel: AgoraRtmChannel, memberJoined member: AgoraRtmMember) { - getRoomInfo(userId: Int(member.userId)!) { [unowned self] nickname in - if !nickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && isEntryMessageEnabled { - DispatchQueue.main.async { [unowned self] in - self.messages.append(LiveRoomJoinChat(nickname: nickname)) - self.messageChangeFlag.toggle() - if self.messages.count > 100 { - self.messages.remove(at: 0) + func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceivePresenceEvent event: AgoraRtmPresenceEvent) { + DEBUG_LOG("didReceivePresenceEvent - \(event.type) - \(String(describing: event.publisher))") + let eventType = event.type + + if let memberId = event.publisher { + if eventType == .remoteJoinChannel { + getRoomInfo(userId: Int(memberId)!) { [unowned self] nickname in + if !nickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && isEntryMessageEnabled { + DispatchQueue.main.async { [unowned self] in + self.messages.append(LiveRoomJoinChat(nickname: nickname)) + self.invalidateChat() + } } } + } else if eventType == .remoteLeaveChannel { + if let liveRoomInfo = liveRoomInfo, liveRoomInfo.creatorId != Int(memberId)! { + getRoomInfo() + } } } } - func channel(_ channel: AgoraRtmChannel, memberLeft member: AgoraRtmMember) { - if let liveRoomInfo = liveRoomInfo, liveRoomInfo.creatorId != Int(member.userId)! { - getRoomInfo() - } + func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveLinkStateEvent event: AgoraRtmLinkStateEvent) { + DEBUG_LOG("Signaling link state change current state is: \(event.currentState.rawValue) previous state is :\(event.previousState.rawValue)") } }