Files
sodalive-ios/SodaLive/Sources/Agora/Agora.swift
2025-10-30 17:08:23 +09:00

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
}
}