315 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			315 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
//
 | 
						|
//  Agora.swift
 | 
						|
//  SodaLive
 | 
						|
//
 | 
						|
//  Created by klaus on 2023/08/14.
 | 
						|
//
 | 
						|
 | 
						|
import Foundation
 | 
						|
import AgoraRtcKit
 | 
						|
import AgoraRtmKit
 | 
						|
 | 
						|
final class Agora {
 | 
						|
    static let shared = Agora()
 | 
						|
    
 | 
						|
    func initialize() {
 | 
						|
        initRtcEngine()
 | 
						|
        initRtmClient()
 | 
						|
    }
 | 
						|
    
 | 
						|
    func deInit() {
 | 
						|
        deInitRtcEngine()
 | 
						|
        deInitRtmClient()
 | 
						|
    }
 | 
						|
    
 | 
						|
    // MARK: RTC
 | 
						|
    private var rtcEngine: AgoraRtcEngineKit?
 | 
						|
    var rtcEngineDelegate: AgoraRtcEngineDelegate?
 | 
						|
    
 | 
						|
    func initRtcEngine() {
 | 
						|
        rtcEngine = AgoraRtcEngineKit.sharedEngine(withAppId: AGORA_APP_ID, delegate: rtcEngineDelegate)
 | 
						|
        rtcEngine?.setChannelProfile(.liveBroadcasting)
 | 
						|
        rtcEngine?.enableAudio()
 | 
						|
        rtcEngine?.enableAudioVolumeIndication(500, smooth: 3, reportVad: true)
 | 
						|
    }
 | 
						|
    
 | 
						|
    func deInitRtcEngine() {
 | 
						|
        if let rtcEngine = rtcEngine {
 | 
						|
            rtcEngine.leaveChannel(nil)
 | 
						|
            
 | 
						|
            DispatchQueue.global(qos: .userInitiated).async {
 | 
						|
                AgoraRtcEngineKit.destroy()
 | 
						|
            }
 | 
						|
        }
 | 
						|
        rtcEngine = nil
 | 
						|
    }
 | 
						|
    
 | 
						|
    func joinRtcChannel(rtcToken: String, channelName: String) {
 | 
						|
        let userId = UserDefaults.int(forKey: .userId)
 | 
						|
        
 | 
						|
        rtcEngine?.joinChannel(
 | 
						|
            byToken: rtcToken,
 | 
						|
            channelId: channelName,
 | 
						|
            info: nil,
 | 
						|
            uid: UInt(userId),
 | 
						|
            joinSuccess: nil
 | 
						|
        )
 | 
						|
        
 | 
						|
        rtcEngine?.setAudioProfile(.musicHighQualityStereo)
 | 
						|
        rtcEngine?.setAudioScenario(.gameStreaming)
 | 
						|
    }
 | 
						|
    
 | 
						|
    func setRole(role: AgoraClientRole) {
 | 
						|
        self.rtcEngine?.setClientRole(role)
 | 
						|
    }
 | 
						|
    
 | 
						|
    func mute(_ isMute: Bool) {
 | 
						|
        rtcEngine?.muteLocalAudioStream(isMute)
 | 
						|
    }
 | 
						|
    
 | 
						|
    func speakerMute(_ isMute: Bool) {
 | 
						|
        rtcEngine?.muteAllRemoteAudioStreams(isMute)
 | 
						|
    }
 | 
						|
    
 | 
						|
    func getRtcConnectionState() -> AgoraConnectionState {
 | 
						|
        return rtcEngine!.getConnectionState()
 | 
						|
    }
 | 
						|
    
 | 
						|
    // MARK: RTM
 | 
						|
    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() {
 | 
						|
        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() {
 | 
						|
        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,
 | 
						|
        onConnectSuccess: @escaping (Bool) -> Void,
 | 
						|
        onConnectFail: @escaping () -> Void
 | 
						|
    ) {
 | 
						|
        if rtmLoggedIn && roomChannelName == channelName {
 | 
						|
            DEBUG_LOG("rtmLogin - already logged in and subscribed. skip")
 | 
						|
            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)
 | 
						|
        
 | 
						|
        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)
 | 
						|
        
 | 
						|
        if let jsonMessageData = jsonMessageData {
 | 
						|
            rtmKit?.publish(channelName: "inbox_\(peerId)", data: jsonMessageData, option: nil, completion: completion)
 | 
						|
        } else {
 | 
						|
            if let fail = fail {
 | 
						|
                fail()
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    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: AgoraRtmOperationBlock? = nil, fail: (() -> Void)? = nil) {
 | 
						|
        let encoder = JSONEncoder()
 | 
						|
        let jsonMessageData = try? encoder.encode(rawMessage)
 | 
						|
        
 | 
						|
        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
 | 
						|
    }
 | 
						|
}
 |