// // LNRoomManager.swift // Gami // // Created by OneeChan on 2026/3/9. // import Foundation import AtomicXCore import RTCRoomEngine protocol LNRoomManagerNotify { func onUserRoomAbilityChanged() func onHallEntranceChanged() } extension LNRoomManagerNotify { func onUserRoomAbilityChanged() { } func onHallEntranceChanged() { } } func showTencentError(_ error: ErrorInfo) { let items = error.message.components(separatedBy: ",") for item in items { let parts = item.components(separatedBy: ":") if parts.first?.trimmingCharacters(in: .whitespaces) == "error_message" { showToast(parts.dropFirst().joined(separator: ":").trimmingCharacters(in: .whitespaces)) return } } showToast(error.message) } class LNRoomManager { static let shared = LNRoomManager() static let RoomNameMinInput = 2 static let RoomNameMaxInput = 20 static let MicNum = 10 static private(set) var hallEntrance = false { didSet { if oldValue != hallEntrance { LNEventDeliver.notifyEvent { ($0 as? LNRoomManagerNotify)?.onHallEntranceChanged() } } } } private(set) var roomAbility: LNRoomAbilityVO? private(set) weak var curRoom: LNRoomViewModel? let liveListStore = LiveListStore.shared init () { LNEventDeliver.addObserver(self) } } // MARK: 直播间管理 extension LNRoomManager { func getCreateRoomAbility(queue: DispatchQueue = .main, handler: ((LNRoomAbilityVO?) -> Void)?) { LNHttpManager.shared.getCreateRoomAbility { info, err in queue.asyncIfNotGlobal { if let err { showToast(err.errorDesc) handler?(nil) } else { self.roomAbility = info handler?(info) LNEventDeliver.notifyEvent { ($0 as? LNRoomManagerNotify)?.onUserRoomAbilityChanged() } } } } } func createRoom(roomName: String, cover: String, queue: DispatchQueue = .main, forbidAudio: Bool, handler: @escaping (String?) -> Void) { leaveRoom { _ in LNHttpManager.shared.createRoom(roomTitle: roomName, roomCover: cover, forbidAudio: forbidAudio) { room, err in queue.asyncIfNotGlobal { handler(room?.roomId) LNUserDefaults[.joinedRoomId] = room?.roomId } if let err { showToast(err.errorDesc) } } } } func joinRoom(roomId: String, queue: DispatchQueue = .main, handler: @escaping (String?) -> Void) { liveListStore.joinLive(liveID: roomId) { result in queue.asyncIfNotGlobal { switch result { case .success(let info): handler(info.liveID) LNUserDefaults[.joinedRoomId] = roomId case .failure(let errorInfo): handler(nil) showTencentError(errorInfo) self.reportRoomUnavailable(id: roomId, code: errorInfo.code, reason: errorInfo.message) } } } } func closeRoom(queue: DispatchQueue = .main, handler: @escaping (Bool) -> Void) { liveListStore.endLive { result in queue.asyncIfNotGlobal { switch result { case .success: handler(true) case .failure(let errorInfo): handler(false) showTencentError(errorInfo) } } } } func leaveRoom(queue: DispatchQueue = .main, handler: @escaping (Bool) -> Void) { let curLive: String = curRoom?.roomId ?? LNUserDefaults[.joinedRoomId, ""] if curLive.isEmpty { handler(true) } else { let json = "{\"api\":\"exitRoom\",\"params\":{\"roomId\":\"\(curLive)\",\"syncWaiting\":true,\"roomType\":2}}" TUIRoomEngine.sharedInstance().callExperimentalAPI(jsonStr: json) { error in handler(true) LNUserDefaults[.joinedRoomId] = "" } } } func updateCurRoom(_ room: LNRoomViewModel) { curRoom = room } } // MARK: 入口管理 extension LNRoomManager { func reloadHallEntrance() { LNHttpManager.shared.getHallEntrance { res, err in guard let res else { return } Self.hallEntrance = res.result } } func getRandomRoom(queue: DispatchQueue = .main, handler: @escaping (LNRoomItemVO?) -> Void) { LNHttpManager.shared.getRandomRoom { res, err in queue.asyncIfNotGlobal { handler(res?.room) } if let res, res.room?.roomId.isEmpty != false { Self.hallEntrance = false } if let err { showToast(err.errorDesc) } } } func searchRoom(keyword: String, next: String?, queue: DispatchQueue = .main, handler: @escaping (LNSearchRoomResponse?) -> Void) { LNHttpManager.shared.searchRoom(keyword: keyword, size: 30, next: next ?? "") { res, err in queue.asyncIfNotGlobal { handler(res) } if let err { showToast(err.errorDesc) } } } func getRoomList(next: String?, queue: DispatchQueue = .main, handler: @escaping (LNSearchRoomResponse?) -> Void) { LNHttpManager.shared.getRoomList(size: 30, next: next ?? "") { res, err in queue.asyncIfNotGlobal { handler(res) } if let err { showToast(err.errorDesc) } } } func reportRoomUnavailable(id: String, code: Int, reason: String) { LNHttpManager.shared.reportRoomUnavailable(id: id, code: "\(code)", reason: reason) { _ in } } } // MARK: 用户 extension LNRoomManager { func getUserCurRoom(uid: String, queue: DispatchQueue = .main, handler: @escaping (LNUserCurRoomResponse?) -> Void) { LNHttpManager.shared.getUserCurRoom(uid: uid) { res, err in queue.asyncIfNotGlobal { handler(res) } if let err { showToast(err.errorDesc) } } } } extension LNRoomManager: LNAccountManagerNotify, LNProfileManagerNotify { func onUserLogin() { getCreateRoomAbility(handler: nil) } }