feature(agora): rtm version 1.4.10 -> 2.2.4

This commit is contained in:
Yu Sung
2025-10-30 15:58:57 +09:00
parent c7d165989c
commit 1159e5e53a
5 changed files with 423 additions and 298 deletions

16
Podfile
View File

@@ -7,6 +7,7 @@ target 'SodaLive' do
# Pods for SodaLive # Pods for SodaLive
pod 'BootpayUI', '4.4.10' pod 'BootpayUI', '4.4.10'
pod 'AgoraRtm', '2.2.4'
end end
@@ -16,9 +17,24 @@ target 'SodaLive-dev' do
# Pods for SodaLive-dev # Pods for SodaLive-dev
pod 'BootpayUI', '4.4.10' pod 'BootpayUI', '4.4.10'
pod 'AgoraRtm', '2.2.4'
end 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| post_install do |installer|
installer.generated_projects.each do |project| installer.generated_projects.each do |project|
project.targets.each do |target| project.targets.each do |target|

View File

@@ -1,4 +1,9 @@
PODS: 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) - Alamofire (5.10.2)
- Bootpay (4.4.6): - Bootpay (4.4.6):
- CryptoSwift - CryptoSwift
@@ -20,10 +25,12 @@ PODS:
- SwiftyJSON (5.0.2) - SwiftyJSON (5.0.2)
DEPENDENCIES: DEPENDENCIES:
- AgoraRtm (= 2.2.4)
- BootpayUI (= 4.4.10) - BootpayUI (= 4.4.10)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- AgoraRtm
- Alamofire - Alamofire
- Bootpay - Bootpay
- BootpayUI - BootpayUI
@@ -34,6 +41,7 @@ SPEC REPOS:
- SwiftyJSON - SwiftyJSON
SPEC CHECKSUMS: SPEC CHECKSUMS:
AgoraRtm: 534144434383d41b3b0ebfae2a961ef0f51b0645
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496 Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
Bootpay: cd7f0542b096ab0af0b09a6e12a6b87f2cbbb531 Bootpay: cd7f0542b096ab0af0b09a6e12a6b87f2cbbb531
BootpayUI: beec5b0bba002b4dbced8c0ecace571ed6a017bc BootpayUI: beec5b0bba002b4dbced8c0ecace571ed6a017bc
@@ -43,6 +51,6 @@ SPEC CHECKSUMS:
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a
PODFILE CHECKSUM: a99afeba13301c2139b142c1e1592fe314698d39 PODFILE CHECKSUM: 197d8c8b434dbcc335438281fc68e94718f6a8e1
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

View File

@@ -1,5 +1,5 @@
{ {
"originHash" : "021219245a6febf5a4891182273209d32ef45b11dd68b857e2eef1f7a8ca439d", "originHash" : "1f28da3687662a2a9efe60ffc2ca2499be411b5b0a1e07f72559059c40728121",
"pins" : [ "pins" : [
{ {
"identity" : "abseil-cpp-binary", "identity" : "abseil-cpp-binary",
@@ -28,15 +28,6 @@
"version" : "4.6.0" "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", "identity" : "alamofire",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@@ -23,7 +23,7 @@ final class Agora {
} }
// MARK: RTC // MARK: RTC
var rtcEngine: AgoraRtcEngineKit? private var rtcEngine: AgoraRtcEngineKit?
var rtcEngineDelegate: AgoraRtcEngineDelegate? var rtcEngineDelegate: AgoraRtcEngineDelegate?
func initRtcEngine() { func initRtcEngine() {
@@ -71,85 +71,216 @@ final class Agora {
rtcEngine?.muteAllRemoteAudioStreams(isMute) rtcEngine?.muteAllRemoteAudioStreams(isMute)
} }
func getRtcConnectionState() -> AgoraConnectionState {
return rtcEngine!.getConnectionState()
}
// MARK: RTM // MARK: RTM
var rtmKit: AgoraRtmKit? private var rtmKit: AgoraRtmClientKit?
var rtmChannel: AgoraRtmChannel? private var roomChannelName: String? = nil
var rtmDelegate: AgoraRtmDelegate?
// : RTM
private var rtmLoggedIn: Bool = false
// : RTM
private var rtmLoginInProgress: Bool = false
var rtmClientDelegate: AgoraRtmClientDelegate?
func initRtmClient() { 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() { func deInitRtmClient() {
rtmChannel?.leave(completion: nil) let userId = UserDefaults.int(forKey: .userId)
rtmKit?.logout(completion: nil) let group = DispatchGroup()
rtmChannel = nil
rtmKit = nil 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( func rtmLogin(
creatorId: Int, creatorId: Int,
rtmToken: String, rtmToken: String,
channelName: String, channelName: String,
rtmChannelDelegate: AgoraRtmChannelDelegate,
onConnectSuccess: @escaping (Bool) -> Void, onConnectSuccess: @escaping (Bool) -> Void,
onConnectFail: @escaping () -> Void onConnectFail: @escaping () -> Void
) { ) {
if rtmChannel != nil { if rtmLoggedIn && roomChannelName == channelName {
DEBUG_LOG("rtmLogin - already logged in and subscribed. skip")
return return
} }
//
if (rtmLoginInProgress) {
DEBUG_LOG("rtmLogin - already in progress. skip")
return
}
roomChannelName = channelName
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 {
DEBUG_LOG("rtmClient login - success (attempt=\(attempt))")
//
self?.subscribeChannel(
creatorId: creatorId,
onConnectSuccess: onConnectSuccess,
onConnectFail: onConnectFail
)
}
}
}
rtmLoginInProgress = true
attemptLogin(1)
}
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) let userId = UserDefaults.int(forKey: .userId)
rtmChannel = rtmKit?.createChannel( func completeSuccessIfReady() {
withId: channelName, if (!completed && roomSubscribed && inboxSubscribed) {
delegate: rtmChannelDelegate completed = true
) rtmLoggedIn = true
rtmLoginInProgress = false
rtmKit?.login( DEBUG_LOG("RTM subscribe - both channels subscribed")
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 { if userId == creatorId {
self.setRole(role: .broadcaster) self.setRole(role: .broadcaster)
} else { } else {
self.setRole(role: .audience) self.setRole(role: .audience)
} }
onConnectSuccess(userId == creatorId) onConnectSuccess(userId == creatorId)
} else {
onConnectFail()
} }
}) }
func failOnce(_ reason: String?) {
if (!completed) {
completed = true
if let reason = reason {
DEBUG_LOG("RTM subscribe failed: \(reason)")
} else { } else {
DEBUG_LOG("RTM subscribe failed: nil")
}
onConnectFail() 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 sendMessageToPeer(peerId: String, rawMessage: Data, completion: AgoraRtmSendPeerMessageBlock?) { func subscribeInbox(_ attempt: Int) {
let option = AgoraRtmSendMessageOptions() let inboxChannel = "inbox_\(userId)"
option.enableOfflineMessaging = false DEBUG_LOG("RTM subscribe(inbox: \(inboxChannel)) attempt=\(attempt)")
option.enableHistoricalMessaging = false rtmKit?.subscribe(channelName: inboxChannel, option: nil) { _, error in
if error != nil {
let message = AgoraRtmRawMessage(rawData: rawMessage, description: "") DEBUG_LOG("RTM subscribe(inbox) failure at attempt=\(attempt) operation=\(error!.operation) reason=\(error!.reason) code=\(error!.errorCode)")
rtmKit?.send(message, toPeer: peerId, completion: completion) 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()
}
}
} }
func sendRawMessageToPeer(peerId: String, rawMessage: LiveRoomChatRawMessage, completion: AgoraRtmSendPeerMessageBlock? = nil, fail: (() -> Void)? = nil) { //
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 encoder = JSONEncoder()
let jsonMessageData = try? encoder.encode(rawMessage) let jsonMessageData = try? encoder.encode(rawMessage)
let option = AgoraRtmSendMessageOptions()
option.enableOfflineMessaging = false
option.enableHistoricalMessaging = false
if let jsonMessageData = jsonMessageData { if let jsonMessageData = jsonMessageData {
let message = AgoraRtmRawMessage(rawData: jsonMessageData, description: "") rtmKit?.publish(channelName: "inbox_\(peerId)", data: jsonMessageData, option: nil, completion: completion)
rtmKit?.send(message, toPeer: peerId, sendMessageOptions: option, completion: completion)
} else { } else {
if let fail = fail { if let fail = fail {
fail() fail()
@@ -157,22 +288,27 @@ final class Agora {
} }
} }
func sendMessageToGroup(textMessage: String, completion: @escaping AgoraRtmSendChannelMessageBlock) { func sendMessageToGroup(textMessage: String, completion: @escaping AgoraRtmOperationBlock) {
let message = AgoraRtmMessage(text: textMessage) guard !textMessage.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
rtmChannel?.send(message, completion: completion) 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 encoder = JSONEncoder()
let jsonMessageData = try? encoder.encode(rawMessage) let jsonMessageData = try? encoder.encode(rawMessage)
if let jsonMessageData = jsonMessageData { if let jsonMessageData = jsonMessageData, let channelName = roomChannelName {
let message = AgoraRtmRawMessage(rawData: jsonMessageData, description: "") rtmKit?.publish(channelName: channelName, data: jsonMessageData, option: nil, completion: completion)
rtmChannel?.send(message, completion: completion)
} else { } else {
if let fail = fail { if let fail = fail {
fail() fail()
} }
} }
} }
func isRtmLoggedIn() -> Bool {
return rtmLoggedIn
}
} }

View File

@@ -225,6 +225,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
private var blockedMemberIdList = Set<Int>() private var blockedMemberIdList = Set<Int>()
private var hasInvokedJoinChannel = false
func getBlockedMemberIdList() { func getBlockedMemberIdList() {
userRepository.getBlockedMemberIdList() userRepository.getBlockedMemberIdList()
.sink { result in .sink { result in
@@ -265,7 +267,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
func initAgoraEngine() { func initAgoraEngine() {
agora.rtcEngineDelegate = self agora.rtcEngineDelegate = self
agora.rtmDelegate = self agora.rtmClientDelegate = self
agora.initialize() agora.initialize()
} }
@@ -370,15 +372,23 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
self.isActiveRoulette = data.isActiveRoulette self.isActiveRoulette = data.isActiveRoulette
self.isLoading = true self.isLoading = true
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.joinRtcChannel(rtcToken: data.rtcToken, channelName: data.channelName)
self.agora.rtmLogin( self.agora.rtmLogin(
creatorId: data.creatorId, creatorId: data.creatorId,
rtmToken: data.rtmToken, rtmToken: data.rtmToken,
channelName: data.channelName, channelName: data.channelName,
rtmChannelDelegate: self,
onConnectSuccess: self.agoraConnectSuccess, onConnectSuccess: self.agoraConnectSuccess,
onConnectFail: self.agoraConnectFail onConnectFail: self.agoraConnectFail
) )
} else {
DEBUG_LOG("joinChannel - skip (rtcConnected=\(rtcConnected), rtmLoggedIn=\(rtmLoggedIn), hasInvokedJoinChannel=\(hasInvokedJoinChannel))")
}
getTotalDonationCan() getTotalDonationCan()
getTotalHeartCount() getTotalHeartCount()
@@ -435,20 +445,17 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
self.popupContent = "\(remainingNoChattingTime)초 동안 채팅하실 수 없습니다" self.popupContent = "\(remainingNoChattingTime)초 동안 채팅하실 수 없습니다"
self.isShowPopup = true self.isShowPopup = true
} else if chatMessage.count > 0 { } else if chatMessage.count > 0 {
agora.sendMessageToGroup(textMessage: chatMessage, completion: { [unowned self] errorCode in agora.sendMessageToGroup(textMessage: chatMessage) { _, error in
if errorCode == .errorOk { if error == nil {
let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) 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.messages.append(LiveRoomNormalChat(userId: UserDefaults.int(forKey: .userId), profileUrl: profileUrl, nickname: nickname, rank: rank, chat: chatMessage))
self.messageChangeFlag.toggle() self.invalidateChat()
if self.messages.count > 100 {
self.messages.remove(at: 0)
}
} }
onSuccess() onSuccess()
}) }
} }
} }
} }
@@ -501,10 +508,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
UserDefaults.set(UserDefaults.int(forKey: .can) - can, forKey: .can) UserDefaults.set(UserDefaults.int(forKey: .can) - can, forKey: .can)
if isSecret { if isSecret {
agora.sendRawMessageToPeer( agora.sendRawMessageToPeer(peerId: String(liveRoomInfo!.creatorId), rawMessage: donationRawMessage) { [unowned self] _, error in
peerId: String(liveRoomInfo!.creatorId), rawMessage: donationRawMessage, if error == nil {
completion: { [unowned self] errorCode in
if errorCode == .ok {
let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId))
self.messages.append( self.messages.append(
LiveRoomDonationChat( LiveRoomDonationChat(
@@ -519,23 +524,16 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
addSignature(signature: decoded.data) addSignature(signature: decoded.data)
self.messageChangeFlag.toggle() self.invalidateChat()
if self.messages.count > 100 {
self.messages.remove(at: 0)
}
} else { } else {
refundDonation() refundDonation()
} }
}, } fail: { [unowned self] in
fail: { [unowned self] in
refundDonation() refundDonation()
} }
)
} else { } else {
agora.sendRawMessageToGroup( agora.sendRawMessageToGroup(rawMessage: donationRawMessage) { [unowned self] _, error in
rawMessage: donationRawMessage, if error == nil {
completion: { [unowned self] errorCode in
if errorCode == .errorOk {
let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId))
self.messages.append( self.messages.append(
LiveRoomDonationChat( LiveRoomDonationChat(
@@ -551,18 +549,13 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
totalDonationCan += can totalDonationCan += can
addSignature(signature: decoded.data) addSignature(signature: decoded.data)
self.messageChangeFlag.toggle() self.invalidateChat()
if self.messages.count > 100 {
self.messages.remove(at: 0)
}
} else { } else {
refundDonation() refundDonation()
} }
}, } fail: { [unowned self] in
fail: { [unowned self] in
refundDonation() refundDonation()
} }
)
} }
} else { } else {
if let message = decoded.message { if let message = decoded.message {
@@ -625,17 +618,17 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
} }
func inviteSpeaker(peerId: Int) { func inviteSpeaker(peerId: Int) {
agora.sendMessageToPeer(peerId: String(peerId), rawMessage: LiveRoomRequestType.INVITE_SPEAKER.rawValue.data(using: .utf8)!, completion: { [unowned self] errorCode in agora.sendMessageToPeer(peerId: String(peerId), rawMessage: LiveRoomRequestType.INVITE_SPEAKER.rawValue.data(using: .utf8)!) { [unowned self] _, error in
if errorCode == .ok { if error == nil {
self.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요." self.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요."
self.isShowPopup = true self.isShowPopup = true
} }
}) }
} }
func changeListener(peerId: Int, isFromManager: Bool = false) { 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 agora.sendMessageToPeer(peerId: String(peerId), rawMessage: LiveRoomRequestType.CHANGE_LISTENER.rawValue.data(using: .utf8)!) { [unowned self] _, error in
if errorCode == .ok { if error == nil {
if isFromManager { if isFromManager {
getRoomInfo() getRoomInfo()
setManagerMessage() setManagerMessage()
@@ -647,7 +640,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
self.isShowPopup = true self.isShowPopup = true
} }
}) }
} }
private func getUserNicknameAndProfileUrl(accountId: Int) -> (nickname: String, profileUrl: String) { private func getUserNicknameAndProfileUrl(accountId: Int) -> (nickname: String, profileUrl: String) {
@@ -977,12 +970,10 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
.store(in: &subscription) .store(in: &subscription)
let nickname = getUserNicknameAndProfileUrl(accountId: kickOutId).nickname let nickname = getUserNicknameAndProfileUrl(accountId: kickOutId).nickname
agora.sendMessageToPeer(peerId: String(kickOutId), rawMessage: LiveRoomRequestType.KICK_OUT.rawValue.data(using: .utf8)!, completion: { [unowned self] errorCode in agora.sendMessageToPeer(peerId: String(kickOutId), rawMessage: LiveRoomRequestType.KICK_OUT.rawValue.data(using: .utf8)!) { [unowned self] _, error in
if errorCode == .ok {
self.popupContent = "\(nickname)님을 내보냈습니다." self.popupContent = "\(nickname)님을 내보냈습니다."
self.isShowPopup = true self.isShowPopup = true
} }
})
} }
if let index = muteSpeakers.firstIndex(of: UInt(kickOutId)) { if let index = muteSpeakers.firstIndex(of: UInt(kickOutId)) {
@@ -1424,11 +1415,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
} }
func setNoChatting() { func setNoChatting() {
agora.sendMessageToPeer( agora.sendMessageToPeer(peerId: String(noChattingUserId), rawMessage: LiveRoomRequestType.NO_CHATTING.rawValue.data(using: .utf8)!) { [unowned self] _, error in
peerId: String(noChattingUserId), if error == nil {
rawMessage: LiveRoomRequestType.NO_CHATTING.rawValue.data(using: .utf8)!,
completion: { [unowned self] errorCode in
if errorCode == .ok {
self.popupContent = "\(noChattingUserNickname)님을 3분간 채팅금지를 하였습니다." self.popupContent = "\(noChattingUserNickname)님을 3분간 채팅금지를 하였습니다."
self.isShowPopup = true self.isShowPopup = true
@@ -1439,7 +1427,6 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
} }
} }
} }
)
} }
private func setManagerMessage() { private func setManagerMessage() {
@@ -1627,7 +1614,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
private func removeNoChatRoom() { private func removeNoChatRoom() {
var noChatRoomList = getNoChatRoomListFromUserDefaults() 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) noChatRoomList.remove(at: index)
} }
saveNoChatRoomListToUserDefaults(noChatRoomList: noChatRoomList) saveNoChatRoomListToUserDefaults(noChatRoomList: noChatRoomList)
@@ -1760,10 +1748,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
donationMessage: "" donationMessage: ""
) )
self.agora.sendRawMessageToGroup( agora.sendRawMessageToGroup(rawMessage: rouletteRawMessage) { [unowned self] _, error in
rawMessage: rouletteRawMessage, if error == nil {
completion: { [unowned self] errorCode in
if errorCode == .errorOk {
let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId))
self.messages.append( self.messages.append(
LiveRoomRouletteDonationChat( LiveRoomRouletteDonationChat(
@@ -1778,18 +1764,13 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
self.rouletteSelectedItem = "" self.rouletteSelectedItem = ""
self.rouletteCan = 0 self.rouletteCan = 0
self.messageChangeFlag.toggle() self.invalidateChat()
if self.messages.count > 100 {
self.messages.remove(at: 0)
}
} else { } else {
self.refundRouletteDonation() self.refundRouletteDonation()
} }
}, } fail: { [unowned self] in
fail: { [unowned self] in
self.refundRouletteDonation() self.refundRouletteDonation()
} }
)
} }
func stopPeriodicPlaybackValidation() { func stopPeriodicPlaybackValidation() {
@@ -1949,28 +1930,21 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
donationMessage: nil donationMessage: nil
) )
agora.sendRawMessageToGroup( agora.sendRawMessageToGroup(rawMessage: donationRawMessage) { [unowned self] _, error in
rawMessage: donationRawMessage, if error == nil {
completion: { [unowned self] errorCode in
if errorCode == .errorOk {
let (nickname, _) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId)) let (nickname, _) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId))
self.addHeartMessage(nickname: nickname) self.addHeartMessage(nickname: nickname)
totalHeartCount += 1 totalHeartCount += 1
addHeart() addHeart()
self.messageChangeFlag.toggle() self.invalidateChat()
if self.messages.count > 100 {
self.messages.remove(at: 0)
}
} else { } else {
refundDonation() self.refundDonation()
} }
}, } fail: { [unowned self] in
fail: { [unowned self] in self.refundDonation()
refundDonation()
} }
)
} }
} catch { } catch {
refundDonation() refundDonation()
@@ -2073,6 +2047,13 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
heartTimer?.cancel() heartTimer?.cancel()
heartTimer = nil heartTimer = nil
} }
private func invalidateChat() {
messageChangeFlag.toggle()
if messages.count > 100 {
messages.remove(at: 0)
}
}
} }
extension LiveRoomViewModel: AgoraRtcEngineDelegate { extension LiveRoomViewModel: AgoraRtcEngineDelegate {
@@ -2081,7 +2062,6 @@ extension LiveRoomViewModel: AgoraRtcEngineDelegate {
.filter { $0.volume > 0 } .filter { $0.volume > 0 }
.map { $0.uid } .map { $0.uid }
DEBUG_LOG("activeSpeakerIds::: \(activeSpeakerIds)")
activeSpeakers.removeAll() activeSpeakers.removeAll()
activeSpeakers.append(contentsOf: activeSpeakerIds) activeSpeakers.append(contentsOf: activeSpeakerIds)
} }
@@ -2115,10 +2095,17 @@ extension LiveRoomViewModel: AgoraRtcEngineDelegate {
} }
} }
extension LiveRoomViewModel: AgoraRtmDelegate { extension LiveRoomViewModel: AgoraRtmClientDelegate {
func rtmKit(_ kit: AgoraRtmKit, messageReceived message: AgoraRtmMessage, fromPeer peerId: String) { func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) {
if message.type == .raw, let rawMessage = message as? AgoraRtmRawMessage { DEBUG_LOG("Message received.\n channel: \(event.channelName), publisher: \(event.publisher)")
let rawMessageString = String(data: rawMessage.rawData, encoding: .utf8)
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 DispatchQueue.main.async { [unowned self] in
if rawMessageString == LiveRoomRequestType.CHANGE_LISTENER.rawValue { if rawMessageString == LiveRoomRequestType.CHANGE_LISTENER.rawValue {
@@ -2127,7 +2114,7 @@ extension LiveRoomViewModel: AgoraRtmDelegate {
} }
if rawMessageString == LiveRoomRequestType.REQUEST_SPEAKER.rawValue { if rawMessageString == LiveRoomRequestType.REQUEST_SPEAKER.rawValue {
self.popupContent = "\(getUserNicknameAndProfileUrl(accountId: Int(peerId)!).nickname)님이 스피커 요청을 했어요!\n스퍼커로 초대할까요?" self.popupContent = "\(nickname)님이 스피커 요청을 했어요!\n스퍼커로 초대할까요?"
self.popupCancelTitle = "건너뛰기" self.popupCancelTitle = "건너뛰기"
self.popupCancelAction = { self.popupCancelAction = {
self.isShowPopup = false self.isShowPopup = false
@@ -2136,7 +2123,7 @@ extension LiveRoomViewModel: AgoraRtmDelegate {
self.popupConfirmAction = { self.popupConfirmAction = {
self.isShowPopup = false self.isShowPopup = false
if self.liveRoomInfo!.speakerList.count <= 5 { if self.liveRoomInfo!.speakerList.count <= 5 {
self.requestSpeakerAllow(peerId) self.requestSpeakerAllow(publisher)
} else { } else {
self.errorMessage = "스피커 정원이 초과되었습니다." self.errorMessage = "스피커 정원이 초과되었습니다."
self.isShowErrorPopup = true self.isShowErrorPopup = true
@@ -2214,13 +2201,12 @@ extension LiveRoomViewModel: AgoraRtmDelegate {
do { do {
let jsonDecoder = JSONDecoder() let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(LiveRoomChatRawMessage.self, from: rawMessage.rawData) let decoded = try jsonDecoder.decode(LiveRoomChatRawMessage.self, from: message)
let (nickname, profileUrl) = getUserNicknameAndProfileUrl(accountId: Int(peerId)!)
if decoded.type == .SECRET_DONATION { if decoded.type == .SECRET_DONATION {
self.messages.append( self.messages.append(
LiveRoomDonationChat( LiveRoomDonationChat(
memberId: Int(peerId)!, memberId: Int(publisher)!,
profileUrl: profileUrl, profileUrl: profileUrl,
nickname: nickname, nickname: nickname,
chat: decoded.message, chat: decoded.message,
@@ -2234,27 +2220,10 @@ extension LiveRoomViewModel: AgoraRtmDelegate {
} else if let imageUrl = decoded.signatureImageUrl { } else if let imageUrl = decoded.signatureImageUrl {
self.addSignatureImage(imageUrl: imageUrl) self.addSignatureImage(imageUrl: imageUrl)
} }
} } else if decoded.type == .DONATION {
} 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( self.messages.append(
LiveRoomDonationChat( LiveRoomDonationChat(
memberId: Int(member.userId)!, memberId: Int(publisher)!,
profileUrl: profileUrl, profileUrl: profileUrl,
nickname: nickname, nickname: nickname,
chat: decoded.message, chat: decoded.message,
@@ -2291,41 +2260,46 @@ extension LiveRoomViewModel: AgoraRtmChannelDelegate {
} }
} catch { } 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) let rank = getUserRank(userId: memberId)
if !chat.trimmingCharacters(in: .whitespaces).isEmpty && !blockedMemberIdList.contains(memberId) { if !message.trimmingCharacters(in: .whitespaces).isEmpty && !blockedMemberIdList.contains(memberId) {
messages.append(LiveRoomNormalChat(userId: memberId, profileUrl: profileUrl, nickname: nickname, rank: rank, chat: chat)) messages.append(LiveRoomNormalChat(userId: memberId, profileUrl: profileUrl, nickname: nickname, rank: rank, chat: message))
} }
} }
DispatchQueue.main.async { [unowned self] in DispatchQueue.main.async { [unowned self] in
self.messageChangeFlag.toggle() self.invalidateChat()
if self.messages.count > 100 {
self.messages.remove(at: 0)
}
} }
} }
func channel(_ channel: AgoraRtmChannel, memberJoined member: AgoraRtmMember) { func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceivePresenceEvent event: AgoraRtmPresenceEvent) {
getRoomInfo(userId: Int(member.userId)!) { [unowned self] nickname in 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 { if !nickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && isEntryMessageEnabled {
DispatchQueue.main.async { [unowned self] in DispatchQueue.main.async { [unowned self] in
self.messages.append(LiveRoomJoinChat(nickname: nickname)) self.messages.append(LiveRoomJoinChat(nickname: nickname))
self.messageChangeFlag.toggle() self.invalidateChat()
if self.messages.count > 100 {
self.messages.remove(at: 0)
} }
} }
} }
} } else if eventType == .remoteLeaveChannel {
} if let liveRoomInfo = liveRoomInfo, liveRoomInfo.creatorId != Int(memberId)! {
func channel(_ channel: AgoraRtmChannel, memberLeft member: AgoraRtmMember) {
if let liveRoomInfo = liveRoomInfo, liveRoomInfo.creatorId != Int(member.userId)! {
getRoomInfo() 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)")
}
}