增加换肤功能

This commit is contained in:
启星
2025-08-14 10:07:49 +08:00
parent f6964c1e89
commit 4f9318d98e
8789 changed files with 978530 additions and 2 deletions

View File

@@ -0,0 +1,20 @@
//
// AudioModel.swift
// TUIRoomKit
//
// Created by janejntang on 2023/3/8.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
import RTCRoomEngine
class AudioModel {
var isMicOpened: Bool = false
var isSoundOnSpeaker: Bool = true
var captureVolume: Int = 100
var playVolume: Int = 100
var volumePrompt: Bool = true
var isRecord: Bool = false
var audioQuality: TUIAudioQuality = .default
}

View File

@@ -0,0 +1,41 @@
//
// ConferenceListManagerObserver.swift
// TUIRoomKit
//
// Created by janejntang on 2024/7/23.
//
import Foundation
import RTCRoomEngine
class ConferenceListObserver: NSObject, TUIConferenceListManagerObserver {
private var roomInfo: TUIRoomInfo {
EngineManager.shared.store.roomInfo
}
func onConferenceInfoChanged(conferenceInfo: TUIConferenceInfo, modifyFlag: TUIConferenceModifyFlag) {
guard conferenceInfo.basicRoomInfo.roomId == roomInfo.roomId else { return }
roomInfo.name = conferenceInfo.basicRoomInfo.name
EngineEventCenter.shared.notifyEngineEvent(event: .onConferenceInfoChanged, param: ["conferenceInfo": conferenceInfo, "modifyFlag": modifyFlag])
}
func onConferenceScheduled(conferenceInfo: TUIConferenceInfo) {
}
func onConferenceWillStart(conferenceInfo: TUIConferenceInfo) {
}
func onConferenceCancelled(roomId: String, reason: TUIConferenceCancelReason, operateUser: TUIUserInfo) {
}
func onScheduleAttendeesChanged(roomId: String, leftUsers: [TUIUserInfo], joinedUsers: [TUIUserInfo]) {
}
func onConferenceStatusChanged(roomId: String, status: TUIConferenceStatus) {
}
}

View File

@@ -0,0 +1,24 @@
//
// ConferenceParams.swift
// TUIRoomKit
//
// Created by janejntang on 2024/3/14.
//
import Foundation
@objcMembers public class ConferenceParams : NSObject {
public var isMuteMicrophone = false
public var isOpenCamera = false
public var isSoundOnSpeaker = true
public var name: String?
public var enableMicrophoneForAllUser = true
public var enableCameraForAllUser = true
public var enableMessageForAllUser = true
public var enableSeatControl = false
deinit {
debugPrint("deinit \(self)")
}
}

View File

@@ -0,0 +1,127 @@
//
// ConferenceSessionImp.swift
// TUIRoomKit
//
// Created by CY zhao on 2024/8/13.
//
import Foundation
import RTCRoomEngine
import Factory
class ConferenceSessionImp: NSObject {
private(set) var isEnableWaterMark = false;
private(set) var waterMarkText = "";
private var observers = NSHashTable<ConferenceObserver>.weakObjects()
// MARK: - Public
override init() {
super.init()
subscribeEngine()
}
func addObserver(observer: ConferenceObserver) {
guard !observers.contains(observer) else { return }
observers.add(observer)
}
func removeObserver(observer: ConferenceObserver) {
guard observers.contains(observer) else { return }
observers.remove(observer)
}
func destroy() {
unsubscribeEngine()
observers.removeAllObjects()
}
func enableWaterMark() {
self.isEnableWaterMark = true
}
func setWaterMarkText(waterMarkText: String) {
self.waterMarkText = waterMarkText
}
func setContactsViewProvider(_ provider: @escaping (ConferenceParticipants) -> ContactViewProtocol) {
Container.shared.contactViewController.register { participants in
provider(participants)
}
}
deinit {
unsubscribeEngine()
}
// MARK: - Private
private func subscribeEngine() {
EngineEventCenter.shared.subscribeEngine(event: .onExitedRoom, observer: self)
EngineEventCenter.shared.subscribeEngine(event: .onDestroyedRoom, observer: self)
EngineEventCenter.shared.subscribeEngine(event: .onStartedRoom, observer: self)
EngineEventCenter.shared.subscribeEngine(event: .onJoinedRoom, observer: self)
EngineEventCenter.shared.subscribeEngine(event: .onRoomDismissed, observer: self)
EngineEventCenter.shared.subscribeEngine(event: .onKickedOutOfRoom, observer: self)
}
private func unsubscribeEngine() {
EngineEventCenter.shared.unsubscribeEngine(event: .onExitedRoom, observer: self)
EngineEventCenter.shared.unsubscribeEngine(event: .onDestroyedRoom, observer: self)
}
}
// MARK: - callback
extension ConferenceSessionImp: RoomEngineEventResponder {
func onEngineEvent(name: EngineEventCenter.RoomEngineEvent, param: [String : Any]?) {
switch name {
case .onStartedRoom:
guard let roomInfo = param?["roomInfo"] as? TUIRoomInfo else { return }
guard let error = param?["error"] as? TUIError else { return }
guard let message = param?["mesasge"] as? String else { return }
handleRoomStarted(roomInfo: roomInfo, error: error, message: message)
case .onJoinedRoom:
guard let roomInfo = param?["roomInfo"] as? TUIRoomInfo else { return }
guard let error = param?["error"] as? TUIError else { return }
guard let message = param?["mesasge"] as? String else { return }
handleRoomJoined(roomInfo: roomInfo, error: error, message: message)
case .onDestroyedRoom, .onRoomDismissed:
guard let roomId = param?["roomId"] as? String else { return }
handleRoomFinished(roomId: roomId)
case .onExitedRoom, .onKickedOutOfRoom:
guard let roomId = param?["roomId"] as? String else { return }
handleRoomExited(roomId: roomId)
default: break
}
}
private func handleRoomStarted(roomInfo: TUIRoomInfo, error: TUIError, message: String) {
for observer in observers.allObjects {
observer.onConferenceStarted?(roomInfo: roomInfo, error: error, message: message)
}
}
private func handleRoomJoined(roomInfo: TUIRoomInfo, error: TUIError, message: String) {
for observer in observers.allObjects {
observer.onConferenceJoined?(roomInfo: roomInfo, error: error, message: message)
}
}
private func handleRoomFinished(roomId: String) {
for observer in observers.allObjects {
observer.onConferenceFinished?(roomId: roomId)
}
}
private func handleRoomExited(roomId: String) {
for observer in observers.allObjects {
observer.onConferenceExited?(roomId: roomId)
}
}
}
extension Container {
var contactViewController: ParameterFactory<ConferenceParticipants, ContactViewProtocol?> {
promised().scope(.unique)
}
}

View File

@@ -0,0 +1,183 @@
//
// EngineEventCenter.swift
// TUIRoomKit
//
// Created by aby on 2023/1/8.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
import TUICore
protocol RoomKitUIEventResponder: NSObject {
func onNotifyUIEvent(key: EngineEventCenter.RoomUIEvent, Object: Any?, info: [AnyHashable: Any]?)
}
protocol RoomEngineEventResponder: NSObject {
func onEngineEvent(name: EngineEventCenter.RoomEngineEvent, param: [String: Any]?)
}
class TUINotificationAdapter:NSObject ,TUINotificationProtocol {
weak var responder: RoomKitUIEventResponder?
init(responder: RoomKitUIEventResponder? = nil) {
self.responder = responder
}
func onNotifyEvent(_ key: String, subKey: String, object anObject: Any?, param: [AnyHashable : Any]?) {
guard let eventKey = EngineEventCenter.RoomUIEvent(rawValue: subKey) else { return }
responder?.onNotifyUIEvent(key: eventKey, Object: anObject, info: param)
}
deinit {
debugPrint("deinit \(self)")
}
}
class EngineEventCenter: NSObject {
// Weak Ref
typealias WeakArray<T> = [() -> T?]
static let shared = EngineEventCenter()
private var engineObserverMap: [RoomEngineEvent: WeakArray<RoomEngineEventResponder>] = [:]
private var uiEventObserverMap: [RoomUIEvent: [TUINotificationAdapter]] = [:]
private override init() {
super.init()
}
enum RoomEngineEvent: String {
case onKickedOffLine
case onRoomDismissed
case onKickedOutOfRoom
case onUserVideoStateChanged
case onUserAudioStateChanged
case onUserVoiceVolumeChanged
case onUserScreenCaptureStopped
case onRequestReceived
case onSendMessageForUserDisableChanged
case onRemoteUserEnterRoom
case onRemoteUserLeaveRoom
case onUserRoleChanged
case onSeatListChanged
case onAllUserCameraDisableChanged
case onAllUserMicrophoneDisableChanged
case onKickedOffSeat
case onStatistics
case onDeletedTakeSeatRequest
case onExitedRoom
case onDestroyedRoom
case onStartedRoom
case onJoinedRoom
case onConferenceInfoChanged
case onInitialSelfUserInfo
case onInitialRoomInfo
case onGetUserListFinished
}
enum RoomUIEvent: String {
case TUIRoomKitService
case TUIRoomKitService_RenewUserList
case TUIRoomKitService_SomeoneSharing
case TUIRoomKitService_RenewSeatList
case TUIRoomKitService_UserOnSeatChanged
case TUIRoomKitService_ShowRoomMainView
case TUIRoomKitService_ShowRoomVideoFloatView
case TUIRoomKitService_CurrentUserHasAudioStream
case TUIRoomKitService_CurrentUserHasVideoStream
case TUIRoomKitService_CurrentUserRoleChanged
case TUIRoomKitService_CurrentUserMuteMessage
case TUIRoomKitService_RoomOwnerChanged
case TUIRoomKitService_ChangeToolBarHiddenState
case TUIRoomKitService_SetToolBarDelayHidden
case TUIRoomKitService_HiddenChatWindow
case TUIRoomKitService_ShowExitRoomView
case TUIRoomKitService_RenewVideoSeatView
case TUIRoomKitService_DismissConferenceViewController
case TUIRoomKitService_ShowFloatChatView
}
func subscribeUIEvent(key: RoomUIEvent, responder: RoomKitUIEventResponder) {
let observer = TUINotificationAdapter(responder: responder)
if var observerArray = uiEventObserverMap[key] {
observerArray.append(observer)
uiEventObserverMap[key] = observerArray
} else {
uiEventObserverMap[key] = [observer]
}
DispatchQueue.main.async {
TUICore.registerEvent(RoomUIEvent.TUIRoomKitService.rawValue, subKey: key.rawValue, object: observer)
}
}
func unsubscribeUIEvent(key: RoomUIEvent, responder: RoomKitUIEventResponder) {
guard var observerArray = uiEventObserverMap[key] else { return }
observerArray = observerArray.filter({ observer in
guard let responderValue = observer.responder else {
DispatchQueue.main.async {
TUICore.unRegisterEvent(RoomUIEvent.TUIRoomKitService.rawValue, subKey: key.rawValue, object: observer)
}
return false
}
if responderValue.isEqual(responder) {
DispatchQueue.main.async {
TUICore.unRegisterEvent(RoomUIEvent.TUIRoomKitService.rawValue, subKey: key.rawValue, object: observer)
}
return false
} else {
return true
}
})
if observerArray.count == 0 {
uiEventObserverMap.removeValue(forKey: key)
} else {
uiEventObserverMap[key] = observerArray
}
}
func notifyUIEvent(key: RoomUIEvent, param: [AnyHashable : Any]) {
DispatchQueue.main.async {
TUICore.notifyEvent(RoomUIEvent.TUIRoomKitService.rawValue, subKey: key.rawValue, object: nil, param: param)
}
}
func subscribeEngine(event: RoomEngineEvent, observer: RoomEngineEventResponder) {
let weakObserver = { [weak observer] in return observer }
if var observerArray = engineObserverMap[event] {
let listenerObject = observerArray.first { weakObject in
guard let object = weakObject() else { return false }
return object.isEqual(observer)
}
guard listenerObject == nil else { return }
observerArray.append(weakObserver)
engineObserverMap[event] = observerArray
} else {
engineObserverMap[event] = [weakObserver]
}
}
func unsubscribeEngine(event: RoomEngineEvent, observer: RoomEngineEventResponder) {
guard var observerArray = engineObserverMap[event] else { return }
observerArray.removeAll { weakObject in
guard let object = weakObject() else { return true }
return object.isEqual(observer)
}
if observerArray.count == 0 {
engineObserverMap.removeValue(forKey: event)
} else {
engineObserverMap[event] = observerArray
}
}
func notifyEngineEvent(event: RoomEngineEvent, param: [String : Any]) {
guard let observers = engineObserverMap[event] else { return }
observers.forEach { responder in
responder()?.onEngineEvent(name: event, param: param)
}
}
deinit {
debugPrint("deinit \(self)")
}
}

View File

@@ -0,0 +1,818 @@
//
// EngineManager.swift
// TUIRoomKit
//
// Created by WesleyLei on 2022/9/22.
// Copyright © 2022 Tencent. All rights reserved.
//
import Foundation
import TUICore
import RTCRoomEngine
#if canImport(TXLiteAVSDK_TRTC)
import TXLiteAVSDK_TRTC
#elseif canImport(TXLiteAVSDK_Professional)
import TXLiteAVSDK_Professional
#endif
class EngineManager: NSObject {
static private(set) var shared = EngineManager()
private(set) lazy var store: RoomStore = {
let store = RoomStore()
return store
}()
private(set) var roomEngine = TUIRoomEngine.sharedInstance()
private lazy var eventDispatcher: RoomEventDispatcher = {
let eventDispatcher = RoomEventDispatcher()
return eventDispatcher
}()
private lazy var observer: TRTCObserver = {
let observer = TRTCObserver()
return observer
}()
private lazy var conferenceListObserver: ConferenceListObserver = {
return ConferenceListObserver()
}()
private lazy var conferenceListManager: TUIConferenceListManager? = {
guard let listManager = roomEngine.getExtension(extensionType: .conferenceListManager) as? TUIConferenceListManager else { return nil }
return listManager
}()
private let takeSeatTimeOutNumber: Double = 60
private let openRemoteDeviceTimeOutNumber: Double = 15
private let rootRouter: RoomRouter = RoomRouter.shared
private var isLoginEngine: Bool = false
private let appGroupString: String = "com.tencent.TUIRoomTXReplayKit-Screen"
override private init() {
super.init()
}
deinit {
debugPrint("deinit \(self)")
}
func setSelfInfo(userName: String, avatarURL: String, onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
store.currentUser.userName = userName
store.currentUser.avatarUrl = avatarURL
TUIRoomEngine.setSelfInfo(userName: userName, avatarUrl: avatarURL) {
onSuccess()
} onError: { code, message in
onError(code, message)
debugPrint("---setSelfInfo,code:\(code),message:\(message)")
}
}
func createRoom(roomInfo: TUIRoomInfo, onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
roomInfo.name = transferConferenceName(conferenceName: roomInfo.name)
if !isLoginEngine {
self.login(sdkAppId: Int(TUILogin.getSdkAppID()), userId: TUILogin.getUserID() ?? "", userSig: TUILogin.getUserSig() ?? "")
{ [weak self] in
guard let self = self else { return }
self.isLoginEngine = true
self.createEngineRoom(roomInfo: roomInfo, onSuccess: onSuccess, onError: onError)
} onError: { code, message in
onError(code, message)
}
} else {
createEngineRoom(roomInfo: roomInfo, onSuccess: onSuccess, onError: onError)
}
}
func enterRoom(roomId: String, options: TUIEnterRoomOptions? = nil, enableAudio: Bool, enableVideo: Bool, isSoundOnSpeaker: Bool,
onSuccess: @escaping TUIRoomInfoBlock, onError: @escaping TUIErrorBlock) {
store.videoSetting.isCameraOpened = enableVideo
store.audioSetting.isSoundOnSpeaker = isSoundOnSpeaker
setFramework()
if !isLoginEngine {
self.login(sdkAppId: Int(TUILogin.getSdkAppID()), userId: TUILogin.getUserID() ?? "", userSig: TUILogin.getUserSig() ?? "")
{ [weak self] in
guard let self = self else { return }
self.isLoginEngine = true
self.enterEngineRoom(roomId: roomId, options: options, enableAudio: enableAudio, onSuccess: onSuccess, onError: onError)
} onError: { code, message in
onError(code, message)
}
} else {
enterEngineRoom(roomId: roomId, options: options, enableAudio: enableAudio, onSuccess: onSuccess, onError: onError)
}
}
func exitRoom(onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
roomEngine.exitRoom(syncWaiting: false) { [weak self] in
guard let self = self else { return }
self.handleExitRoomResult()
onSuccess?()
} onError: { [weak self] code, message in
guard let self = self else { return }
self.handleExitRoomResult()
onError?(code, message)
}
}
func destroyRoom(onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
roomEngine.destroyRoom { [weak self] in
guard let self = self else { return }
self.handleDestroyRoomResult()
onSuccess?()
} onError: { [weak self] code, message in
guard let self = self else { return }
self.handleDestroyRoomResult()
onError?(code, message)
}
}
func destroyEngineManager() {
removeEngineObserver()
unsubLogoutNotification()
store = RoomStore()
}
func muteLocalAudio() {
roomEngine.muteLocalAudio()
}
func unmuteLocalAudio(onSuccess:TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
roomEngine.unmuteLocalAudio {
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
}
func openLocalMicrophone(onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
let actionBlock = { [weak self] in
guard let self = self else { return }
self.roomEngine.openLocalMicrophone(self.store.audioSetting.audioQuality) { [weak self] in
guard let self = self else { return }
self.store.audioSetting.isMicOpened = true
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
}
if RoomCommon.checkAuthorMicStatusIsDenied() {
actionBlock()
} else {
RoomCommon.micStateActionWithPopCompletion { granted in
if granted {
actionBlock()
}
}
}
}
func closeLocalCamera() {
store.videoSetting.isCameraOpened = false
roomEngine.closeLocalCamera()
}
func openLocalCamera(onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
let actionBlock = { [weak self] in
guard let self = self else { return }
self.store.videoSetting.isCameraOpened = true
self.roomEngine.openLocalCamera(isFront: self.store.videoSetting.isFrontCamera, quality:
self.store.videoSetting.videoQuality) {
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
}
if RoomCommon.checkAuthorCamaraStatusIsDenied() {
actionBlock()
} else {
RoomCommon.cameraStateActionWithPopCompletion { granted in
if granted {
actionBlock()
}
}
}
}
func switchCamera() {
store.videoSetting.isFrontCamera = !store.videoSetting.isFrontCamera
roomEngine.getMediaDeviceManager().switchCamera(store.videoSetting.isFrontCamera)
}
func switchMirror() {
store.videoSetting.isMirror = !store.videoSetting.isMirror
let params = TRTCRenderParams()
params.mirrorType = store.videoSetting.isMirror ? .enable : .disable
setLocalRenderParams(params: params)
}
func muteAllAudioAction(isMute: Bool, onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
store.roomInfo.isMicrophoneDisableForAllUser = isMute
roomEngine.disableDeviceForAllUserByAdmin(device: .microphone, isDisable:
store.roomInfo.isMicrophoneDisableForAllUser) {
onSuccess()
} onError: { code, message in
onError(code, message)
}
}
func muteAllVideoAction(isMute: Bool, onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
store.roomInfo.isCameraDisableForAllUser = isMute
roomEngine.disableDeviceForAllUserByAdmin(device: .camera, isDisable:
store.roomInfo.isCameraDisableForAllUser) {
onSuccess()
} onError: { code, message in
onError(code, message)
}
}
func takeUserOnSeatByAdmin(userId: String, timeout: Double,
onAccepted: @escaping TUIRequestAcceptedBlock,
onRejected: @escaping TUIRequestRejectedBlock,
onCancelled: @escaping TUIRequestCancelledBlock,
onTimeout: @escaping TUIRequestTimeoutBlock,
onError: @escaping TUIRequestErrorBlock) {
roomEngine.takeUserOnSeatByAdmin(-1, userId: userId, timeout: timeout) { requestId, userId in
onAccepted(requestId, userId)
} onRejected: { requestId, userId, message in
onRejected( requestId, userId, message)
} onCancelled: { requestId, userId in
onCancelled(requestId, userId)
} onTimeout: { requestId, userId in
onTimeout(requestId, userId)
} onError: { requestId, userId, code, message in
onError(requestId, userId, code, message)
}
}
func setAudioRoute(isSoundOnSpeaker: Bool) {
store.audioSetting.isSoundOnSpeaker = isSoundOnSpeaker
let route: TUIAudioRoute = isSoundOnSpeaker ? .speakerphone : .earpiece
roomEngine.getMediaDeviceManager().setAudioRoute(route)
}
func takeSeat(onAccepted: TUIRequestAcceptedBlock? = nil,
onRejected: TUIRequestRejectedBlock? = nil,
onCancelled: TUIRequestCancelledBlock? = nil,
onTimeout: TUIRequestTimeoutBlock? = nil,
onError: TUIRequestErrorBlock? = nil) -> TUIRequest {
let request = self.roomEngine.takeSeat(-1, timeout: takeSeatTimeOutNumber) { [weak self] requestId, userId in
guard let self = self else { return }
self.store.currentUser.isOnSeat = true
self.store.selfTakeSeatRequestId = nil
onAccepted?(requestId, userId)
} onRejected: { [weak self] requestId, userId, message in
guard let self = self else { return }
self.store.selfTakeSeatRequestId = nil
onRejected?(requestId, userId, message)
} onCancelled: { [weak self] requestId, userId in
guard let self = self else { return }
self.store.selfTakeSeatRequestId = nil
onCancelled?(requestId, userId)
} onTimeout: { [weak self] requestId, userId in
guard let self = self else { return }
self.store.selfTakeSeatRequestId = nil
onTimeout?(requestId, userId)
} onError: { [weak self] requestId, userId, code, message in
guard let self = self else { return }
self.store.selfTakeSeatRequestId = nil
onError?(requestId, userId, code, message)
}
store.selfTakeSeatRequestId = request.requestId
return request
}
func cancelTakeSeatRequest() {
guard let requestId = store.selfTakeSeatRequestId else { return }
cancelRequest(requestId)
store.selfTakeSeatRequestId = nil
}
func fetchRoomInfo(roomId: String ,onSuccess: TUIRoomInfoBlock? = nil, onError: TUIErrorBlock? = nil) {
roomEngine.fetchRoomInfo(roomId: roomId, roomType: .conference) { [weak self] roomInfo in
guard let self = self, let roomInfo = roomInfo else { return }
if roomId == self.store.roomInfo.roomId {
self.store.initialRoomInfo(roomInfo)
}
onSuccess?(roomInfo)
} onError: { code, message in
onError?(code, message)
debugPrint("fetchRoomInfo,code:\(code), message:\(message)")
}
}
func setLocalRenderParams(params: TRTCRenderParams) {
roomEngine.getTRTCCloud().setLocalRenderParams(params)
}
func setGSensorMode(mode: TRTCGSensorMode) {
roomEngine.getTRTCCloud().setGSensorMode(mode)
}
func setLocalVideoView(streamType: TUIVideoStreamType, view: UIView?) {
roomEngine.setLocalVideoView(streamType: streamType, view: view)
}
func changeUserRole(userId: String, role: TUIRole, onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
roomEngine.changeUserRole(userId: userId, role: role, onSuccess: onSuccess, onError: onError)
}
func responseRemoteRequest(_ requestId: String, agree: Bool, onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
roomEngine.responseRemoteRequest(requestId, agree: agree) {
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
}
func getUserInfo(_ userId: String, onSuccess: @escaping TUIUserInfoBlock, onError: @escaping TUIErrorBlock) {
roomEngine.getUserInfo(userId, onSuccess: onSuccess, onError: onError)
}
func stopScreenCapture() {
roomEngine.stopScreenCapture()
}
func setVideoEncoder(videoQuality: TUIVideoQuality? = nil, bitrate: Int? = nil, fps: Int? = nil) {
let param = TUIRoomVideoEncoderParams()
store.videoSetting.videoQuality = videoQuality ?? store.videoSetting.videoQuality
param.videoResolution = store.videoSetting.videoQuality
store.videoSetting.videoBitrate = bitrate ?? store.videoSetting.videoBitrate
param.bitrate = store.videoSetting.videoBitrate
store.videoSetting.videoFps = fps ?? store.videoSetting.videoFps
param.fps = store.videoSetting.videoFps
roomEngine.updateVideoQualityEx(streamType: .cameraStream, params: param)
}
func cancelRequest(_ requestId: String, onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
roomEngine.cancelRequest(requestId) {
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
}
func leaveSeat(onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
roomEngine.leaveSeat {
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
}
func startScreenCapture() {
roomEngine.startScreenCapture(appGroup: appGroupString)
}
func stopPlayRemoteVideo(userId: String, streamType: TUIVideoStreamType) {
roomEngine.stopPlayRemoteVideo(userId: userId, streamType: streamType)
}
func setRemoteVideoView(userId: String, streamType: TUIVideoStreamType, view: UIView?) {
roomEngine.setRemoteVideoView(userId: userId, streamType: streamType, view: view)
}
func startPlayRemoteVideo(userId: String, streamType: TUIVideoStreamType, onSuccess: TUISuccessBlock? = nil,
onLoading: TUIPlayOnLoadingBlock? = nil, onError: TUIPlayOnErrorBlock? = nil) {
roomEngine.startPlayRemoteVideo(userId: userId, streamType: streamType, onPlaying: { _ in
guard let onSuccess = onSuccess else { return }
onSuccess()
}, onLoading: { userId in
guard let onLoading = onLoading else { return }
onLoading(userId)
}, onError: { userId, code, message in
guard let onError = onError else { return }
onError(userId, code, message)
})
}
func setAudioCaptureVolume(_ captureVolume: Int) {
store.audioSetting.captureVolume = captureVolume
roomEngine.getTRTCCloud().setAudioCaptureVolume(captureVolume)
}
func setAudioPlayoutVolume(_ playVolume: Int) {
store.audioSetting.playVolume = playVolume
roomEngine.getTRTCCloud().setAudioPlayoutVolume(playVolume)
}
func enableAudioVolumeEvaluation(isVolumePrompt: Bool) {
store.audioSetting.volumePrompt = isVolumePrompt
if isVolumePrompt {
roomEngine.getTRTCCloud().enableAudioVolumeEvaluation(300, enable_vad: true)
} else {
roomEngine.getTRTCCloud().enableAudioVolumeEvaluation(0, enable_vad: false)
}
}
func closeRemoteDeviceByAdmin(userId: String, device: TUIMediaDevice, onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
roomEngine.closeRemoteDeviceByAdmin(userId: userId, device: device, onSuccess: onSuccess, onError: onError)
}
func openRemoteDeviceByAdmin(userId: String, device: TUIMediaDevice,
onAccepted: TUIRequestAcceptedBlock? = nil,
onRejected: TUIRequestRejectedBlock? = nil,
onCancelled: TUIRequestCancelledBlock? = nil,
onTimeout: TUIRequestTimeoutBlock? = nil,
onError: TUIRequestErrorBlock? = nil) {
roomEngine.openRemoteDeviceByAdmin(userId: userId, device: device, timeout: openRemoteDeviceTimeOutNumber, onAccepted: { requestId, userId in
onAccepted?(requestId, userId)
}, onRejected: { requestId, userId, message in
onRejected?(requestId, userId, message)
}, onCancelled: { requestId, userId in
onCancelled?(requestId, userId)
}, onTimeout: { requestId, userId in
onTimeout?(requestId, userId)
}) { requestId, userId, code, message in
onError?(requestId, userId, code, message)
}
}
func disableSendingMessageByAdmin(userId: String, isDisable: Bool, onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
roomEngine.disableSendingMessageByAdmin(userId: userId, isDisable: isDisable) {
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
}
func kickUserOffSeatByAdmin(userId: String, onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
roomEngine.kickUserOffSeatByAdmin(-1, userId: userId) {
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
}
func kickRemoteUserOutOfRoom(userId: String, onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
roomEngine.kickRemoteUserOutOfRoom(userId) {
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
}
func initUserList(onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
self.getUserList(nextSequence: 0, localUserList: []) { [weak self] in
guard let self = self else { return }
if self.store.roomInfo.isSeatEnabled {
self.getSeatList {
onSuccess?()
} onError: { code, message in
onError?(code, message)
}
} else {
onSuccess?()
}
} onError: { code, message in
onError?(code, message)
}
}
func updateVideoQuality(quality: TUIVideoQuality) {
roomEngine.updateVideoQuality(quality)
}
func enableGravitySensor(enable: Bool) {
roomEngine.enableGravitySensor(enable: enable)
}
func setVideoResolutionMode(streamType: TUIVideoStreamType, resolutionMode: TUIResolutionMode) {
roomEngine.setVideoResolutionMode(streamType: streamType, resolutionMode: resolutionMode)
}
func changeRaiseHandNoticeState(isShown: Bool) {
store.isShownRaiseHandNotice = isShown
}
func setRemoteRenderParams(userId: String, streamType: TRTCVideoStreamType, params: TRTCRenderParams) {
roomEngine.getTRTCCloud().setRemoteRenderParams(userId, streamType: streamType, params: params)
}
func updateSeatApplicationList() {
roomEngine.getSeatApplicationList { [weak self] list in
guard let self = self else { return }
self.store.setInviteSeatList(list: list)
} onError: { code, message in
debugPrint("getSeatApplicationList,code:\(code),message:\(message)")
}
}
}
// MARK: - Private
extension EngineManager {
private func login(sdkAppId: Int, userId: String, userSig: String, onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
V2TIMManager.sharedInstance().initSDK(Int32(sdkAppId), config: V2TIMSDKConfig())
store.currentUser.userId = userId
TUIRoomEngine.login(sdkAppId: sdkAppId, userId: userId, userSig: userSig) { [weak self] in
guard let self = self else { return }
self.isLoginEngine = true
onSuccess()
} onError: { code, message in
onError(code, message)
}
}
private func createEngineRoom(roomInfo: TUIRoomInfo, onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
guard !store.isEnteredRoom else {
if store.roomInfo.roomId == roomInfo.roomId {
RoomVideoFloatView.dismiss()
onSuccess()
} else {
onError(.failed, .inAnotherRoomText)
}
return
}
self.store.roomInfo = roomInfo
self.roomEngine.createRoom(roomInfo) {
onSuccess()
} onError: { [weak self] code, message in
guard let self = self else { return }
self.destroyEngineManager()
onError(code, message)
}
}
private func transferConferenceName(conferenceName: String?) -> String {
if let confName = conferenceName, !confName.isEmpty {
return confName
}
let selfInfo = TUIRoomEngine.getSelfInfo()
let name: String = selfInfo.userName.isEmpty ? selfInfo.userId : selfInfo.userName
return name + .quickConferenceText
}
private func enterEngineRoom(roomId: String, options: TUIEnterRoomOptions? = nil, enableAudio: Bool, onSuccess: @escaping TUIRoomInfoBlock, onError: @escaping TUIErrorBlock) {
guard !store.isEnteredRoom else {
if store.roomInfo.roomId == roomId {
onSuccess(store.roomInfo)
} else {
onError(.failed, .inAnotherRoomText)
}
return
}
if let options = options {
roomEngine.enterRoom(roomId, roomType: .conference, options: options) { [weak self] roomInfo in
guard let self = self else { return }
guard let roomInfo = roomInfo else { return }
self.handleEnterRoomResult(roomInfo: roomInfo, enableAudio: enableAudio, onSuccess: onSuccess, onError: onError)
} onError: { [weak self] code, message in
guard let self = self else { return }
self.destroyEngineManager()
onError(code, message)
}
} else {
roomEngine.enterRoom(roomId) { [weak self] roomInfo in
guard let self = self else { return }
guard let roomInfo = roomInfo else { return }
self.handleEnterRoomResult(roomInfo: roomInfo, enableAudio: enableAudio, onSuccess: onSuccess, onError: onError)
} onError: { [weak self] code, message in
guard let self = self else { return }
self.destroyEngineManager()
onError(code, message)
}
}
}
private func handleEnterRoomResult(roomInfo: TUIRoomInfo, enableAudio: Bool, onSuccess: @escaping TUIRoomInfoBlock, onError: @escaping TUIErrorBlock) {
//Update the room entry data stored
addEngineObserver()
store.initialRoomInfo(roomInfo)
store.initialRoomCurrentUser()
//Initialize user list
initUserList()
//Initialize video settings
initLocalVideoState()
subLogoutNotification()
updateSeatApplicationList()
if !isNeededAutoTakeSeat() {
operateLocalMicrophone(enableAudio: enableAudio)
updateCameraState()
store.initalEnterRoomMessage()
onSuccess(roomInfo)
} else {
autoTakeSeatForOwner { [weak self] in
guard let self = self else { return }
self.operateLocalMicrophone(enableAudio: enableAudio)
self.updateCameraState()
self.store.initalEnterRoomMessage()
onSuccess(roomInfo)
} onError: { [weak self] code, message in
guard let self = self else { return }
self.destroyEngineManager()
onError(code, message)
}
}
}
private func updateCameraState() {
if store.roomInfo.isSeatEnabled && !store.currentUser.isOnSeat {
store.videoSetting.isCameraOpened = false
return
}
operateLocalCamera()
}
private func isNeededAutoTakeSeat() -> Bool {
return store.roomInfo.isSeatEnabled && store.currentUser.userId == store.roomInfo.ownerId
}
private func autoTakeSeatForOwner(onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
_ = self.takeSeat() { _,_ in
onSuccess()
} onError: { _, _, code, message in
if code == .alreadyInSeat {
onSuccess()
} else {
onError(code, message)
}
}
}
private func isPushLocalAudioStream(enableAudio: Bool) -> Bool {
if !enableAudio {
return false
}
if store.roomInfo.isMicrophoneDisableForAllUser, store.currentUser.userId != store.roomInfo.ownerId {
return false
}
if store.roomInfo.isSeatEnabled, store.currentUser.userId != store.roomInfo.ownerId {
return false
}
return true
}
private func operateLocalMicrophone(enableAudio: Bool ,onSuccess: TUISuccessBlock? = nil, onError: TUIErrorBlock? = nil) {
if isPushLocalAudioStream(enableAudio: enableAudio) {
openLocalMicrophone()
} else if RoomCommon.checkAuthorMicStatusIsDenied() {
muteLocalAudio()
openLocalMicrophone()
}
}
private func operateLocalCamera() {
let openLocalCameraActionBlock = { [weak self] in
guard let self = self else { return }
setLocalVideoView(streamType: .cameraStream, view: nil)
openLocalCamera()
}
if store.videoSetting.isCameraOpened && !store.roomInfo.isCameraDisableForAllUser {
if RoomCommon.checkAuthorCamaraStatusIsDenied() {
openLocalCameraActionBlock()
} else {
RoomCommon.cameraStateActionWithPopCompletion { granted in
if granted {
openLocalCameraActionBlock()
}
}
}
}
}
private func initLocalVideoState() {
setVideoParam()
enableGravitySensor(enable: true)
setGSensorMode(mode: .uiFixLayout)
let resolutionMode: TUIResolutionMode = isLandscape ? .landscape : .portrait
setVideoResolutionMode(streamType: .cameraStream, resolutionMode: resolutionMode)
}
private func setVideoParam() {
setVideoEncoder(videoQuality: store.videoSetting.videoQuality, bitrate: store.videoSetting.videoBitrate,
fps: store.videoSetting.videoFps)
let params = TRTCRenderParams()
params.fillMode = .fill
params.rotation = ._0
setLocalRenderParams(params: params)
}
private func getUserList(nextSequence: Int, localUserList: [UserEntity], onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
roomEngine.getUserList(nextSequence: nextSequence) { [weak self] list, nextSequence in
guard let self = self else { return }
var localUserList = localUserList
list.forEach { userInfo in
if userInfo.userName.isEmpty {
userInfo.userName = userInfo.userId
}
let userModel = UserEntity()
userModel.update(userInfo: userInfo)
localUserList.append(userModel)
}
if nextSequence != 0 {
self.getUserList(nextSequence: nextSequence, localUserList: localUserList, onSuccess: onSuccess, onError: onError)
} else {
self.store.attendeeList = localUserList
onSuccess()
if !self.store.roomInfo.isSeatEnabled {
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewVideoSeatView, param: [:])
}
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewUserList, param: [:])
EngineEventCenter.shared.notifyEngineEvent(event: .onGetUserListFinished, param: [:])
}
} onError: { code, message in
onError(code, message)
debugPrint("getUserList:code:\(code),message:\(message)")
}
}
private func getSeatList(onSuccess: @escaping TUISuccessBlock, onError: @escaping TUIErrorBlock) {
roomEngine.getSeatList { [weak self] seatList in
guard let self = self else { return }
self.store.initialSeatList(seatList: seatList)
self.store.initialOffSeatList()
onSuccess()
} onError: { code, message in
onError(code, message)
debugPrint("getSeatList:code:\(code),message:\(message)")
}
}
private func addEngineObserver() {
roomEngine.addObserver(eventDispatcher)
roomEngine.getTRTCCloud().addDelegate(observer)
conferenceListManager?.addObserver(conferenceListObserver)
}
private func removeEngineObserver() {
roomEngine.removeObserver(eventDispatcher)
roomEngine.getTRTCCloud().removeDelegate(observer)
conferenceListManager?.removeObserver(conferenceListObserver)
}
private func subLogoutNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(handleLogout),
name: NSNotification.Name.TUILogoutSuccess, object: nil)
}
private func unsubLogoutNotification() {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.TUILogoutSuccess, object: nil)
}
@objc private func handleLogout() {
destroyEngineManager()
}
private func handleDestroyRoomResult() {
destroyEngineManager()
EngineEventCenter.shared.notifyEngineEvent(event: .onDestroyedRoom, param: [ "roomId" : store.roomInfo.roomId ])
}
private func handleExitRoomResult() {
destroyEngineManager()
EngineEventCenter.shared.notifyEngineEvent(event: .onExitedRoom, param: [ "roomId" : store.roomInfo.roomId ])
}
}
// MARK: - TUIExtensionProtocol
extension EngineManager: TUIExtensionProtocol {
func getExtensionInfo(_ key: String, param: [AnyHashable: Any]?) -> [AnyHashable: Any] {
guard let param = param else {
return [:]
}
guard let roomId: String = param["roomId"] as? String else {
return [:]
}
if key == gRoomEngineKey {
return [key: roomEngine]
} else if key == gRoomInfoKey {
return [key: store.roomInfo]
} else if key == gLocalUserInfoKey {
return [key: store.currentUser]
} else {
return [:]
}
}
}
// MARK: - setFramework
extension EngineManager {
fileprivate static let TUIRoomKitFrameworkValue = 1
fileprivate static let TUIRoomKitComponentValue = 18
fileprivate static let IMComponentValue = 19
fileprivate static let TUIRoomKitLanguageValue = 3
private func setFramework() {
let componentValue = store.isImAccess ? EngineManager.IMComponentValue : EngineManager.TUIRoomKitComponentValue
let jsonStr = """
{
"api":"setFramework",
"params":{
"framework":\(EngineManager.TUIRoomKitFrameworkValue),
"component":\(componentValue),
"language":\(EngineManager.TUIRoomKitLanguageValue)
}
}
"""
TUIRoomEngine.callExperimentalAPI(jsonStr: jsonStr)
}
}
private extension String {
static var inAnotherRoomText: String {
localized("You are already in another conference")
}
static var quickConferenceText: String {
localized("'s quick meeting")
}
}

View File

@@ -0,0 +1,29 @@
//
// RequestEntity.swift
// TUIRoomKit
//
// Created by janejntang on 2024/3/20.
//
import Foundation
class RequestEntity {
let requestId: String
let userId: String
var userName: String = ""
var avatarUrl: String = ""
let timestamp: TimeInterval
init(requestId: String, userId: String) {
timestamp = Date().timeIntervalSince1970
self.requestId = requestId
self.userId = userId
guard let userItem = EngineManager.shared.store.attendeeList.first(where: { $0.userId == userId }) else { return }
userName = userItem.userName
avatarUrl = userItem.avatarUrl
}
deinit {
debugPrint("deinit \(self)")
}
}

View File

@@ -0,0 +1,282 @@
//
// RoomEventDispatcher.swift
// TUIRoomKit
//
// Created by janejntang on 2023/8/14.
//
import Foundation
import RTCRoomEngine
class RoomEventDispatcher: NSObject {
var engineManager: EngineManager {
EngineManager.shared
}
var store: RoomStore {
engineManager.store
}
var roomInfo: TUIRoomInfo {
store.roomInfo
}
var currentUser: UserEntity {
store.currentUser
}
deinit {
debugPrint("deinit")
}
}
extension RoomEventDispatcher: TUIRoomObserver {
// MARK: - Room event callback
func onAllUserMicrophoneDisableChanged(roomId: String, isDisable: Bool) {
roomInfo.isMicrophoneDisableForAllUser = isDisable
let param = [
"roomId" : roomId,
"isDisable" : isDisable,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onAllUserMicrophoneDisableChanged, param: param)
}
func onAllUserCameraDisableChanged(roomId: String, isDisable: Bool) {
roomInfo.isCameraDisableForAllUser = isDisable
let param = [
"roomId" : roomId,
"isDisable" : isDisable,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onAllUserCameraDisableChanged, param: param)
}
func onSendMessageForAllUserDisableChanged(roomId: String, isDisable: Bool) {
roomInfo.isMessageDisableForAllUser = isDisable
}
func onRoomDismissed(roomId: String) {
EngineEventCenter.shared.notifyEngineEvent(event: .onRoomDismissed, param: ["roomId" : roomId,])
}
func onKickedOutOfRoom(roomId: String, reason: TUIKickedOutOfRoomReason, message: String) {
let param = [
"roomId" : roomId,
"reason" : reason,
"message" : message,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onKickedOutOfRoom, param: param)
}
func onRoomNameChanged(roomId: String, roomName: String) {
roomInfo.name = roomName
}
// MARK: - User event callback in the room
func onRemoteUserEnterRoom(roomId: String, userInfo: TUIUserInfo) {
store.remoteUserEnterRoom(userInfo: userInfo)
let param = [
"roomId" : roomId,
"userInfo" : userInfo,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onRemoteUserEnterRoom, param: param)
}
func onRemoteUserLeaveRoom(roomId: String, userInfo: TUIUserInfo) {
store.remoteUserLeaveRoom(userInfo: userInfo)
let param = [
"roomId" : roomId,
"userInfo" : userInfo,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onRemoteUserLeaveRoom, param: param)
}
func onUserRoleChanged(userId: String, userRole: TUIRole) {
userRoleChanged(userId: userId, userRole: userRole)
let param = [
"userId" : userId,
"userRole" : userRole,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onUserRoleChanged, param: param)
}
func onUserVideoStateChanged(userId: String, streamType: TUIVideoStreamType, hasVideo: Bool, reason: TUIChangeReason) {
userVideoStateChanged(userId: userId, streamType: streamType, hasVideo: hasVideo, reason: reason)
let param = [
"userId" : userId,
"streamType" : streamType,
"hasVideo" : hasVideo,
"reason" : reason,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onUserVideoStateChanged, param: param)
}
func onUserAudioStateChanged(userId: String, hasAudio: Bool, reason: TUIChangeReason) {
userAudioStateChanged(userId: userId, hasAudio: hasAudio, reason: reason)
let param = [
"userId" : userId,
"hasAudio" : hasAudio,
"reason" : reason,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onUserAudioStateChanged, param: param)
}
func onUserVoiceVolumeChanged(volumeMap: [String : NSNumber]) {
userVoiceVolumeChanged(volumeMap: volumeMap)
EngineEventCenter.shared.notifyEngineEvent(event: .onUserVoiceVolumeChanged, param: volumeMap)
}
func onUserScreenCaptureStopped(reason: Int) {
userScreenCaptureStopped()
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_SomeoneSharing, param: ["userId":currentUser.userId, "hasVideo": false])
}
func onSeatListChanged(seatList: [TUISeatInfo], seated seatedList: [TUISeatInfo], left leftList: [TUISeatInfo]) {
seatListChanged(seatList: seatList,seated: seatedList, left: leftList)
let param = [
"seatList": seatList,
"seated": seatedList,
"left": leftList,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onSeatListChanged, param: param)
}
func OnSendMessageForUserDisableChanged(roomId: String, userId: String, isDisable muted: Bool) {
store.updateUserDisableSendingMessage(userId: userId, isDisable: muted)
let param = [
"roomId": roomId,
"userId": userId,
"muted": muted,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onSendMessageForUserDisableChanged, param: param)
}
// MARK: - Signaling request related callbacks
func onRequestReceived(request: TUIRequest) {
requestReceived(request: request)
EngineEventCenter.shared.notifyEngineEvent(event: .onRequestReceived, param: ["request": request,])
}
func onRequestCancelled(requestId: String, userId: String) {
store.deleteTakeSeatRequest(requestId: requestId)
}
func onRequestProcessed(requestId: String, userId: String) {
store.deleteTakeSeatRequest(requestId: requestId)
}
func onKickedOffSeat(userId: String) {
let param = [
"userId": userId,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onKickedOffSeat, param: param)
}
func onKickedOffLine(message: String) {
kickedOffLine()
let param = [
"message": message,
] as [String : Any]
EngineEventCenter.shared.notifyEngineEvent(event: .onKickedOffLine, param: param)
}
}
extension RoomEventDispatcher {
private func seatListChanged(seatList: [TUISeatInfo], seated: [TUISeatInfo], left leftList: [TUISeatInfo]) {
store.updateLeftSeatList(leftList: leftList)
store.updateSeatedList(seatList: seated)
}
private func userAudioStateChanged(userId: String, hasAudio: Bool, reason: TUIChangeReason) {
if userId == currentUser.userId {
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_CurrentUserHasAudioStream, param: ["hasAudio": hasAudio, "reason": reason])
currentUser.hasAudioStream = hasAudio
}
guard let userModel = store.attendeeList.first(where: { $0.userId == userId }) else { return }
userModel.hasAudioStream = hasAudio
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewUserList, param: [:])
}
private func userVideoStateChanged(userId: String, streamType: TUIVideoStreamType, hasVideo: Bool, reason: TUIChangeReason) {
switch streamType {
case .screenStream:
if userId == currentUser.userId {
currentUser.hasScreenStream = hasVideo
}
guard let userModel = store.attendeeList.first(where: { $0.userId == userId }) else { return }
userModel.hasScreenStream = hasVideo
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_SomeoneSharing, param: ["userId": userId, "hasVideo": hasVideo])
case .cameraStream:
if userId == currentUser.userId {
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_CurrentUserHasVideoStream,
param: ["hasVideo": hasVideo, "reason": reason])
currentUser.hasVideoStream = hasVideo
store.videoSetting.isCameraOpened = hasVideo
}
guard let userModel = store.attendeeList.first(where: { $0.userId == userId }) else { return }
userModel.hasVideoStream = hasVideo
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewUserList, param: [:])
default: break
}
}
private func userRoleChanged(userId: String, userRole: TUIRole) {
let isSelfRoleChanged = userId == currentUser.userId
let isRoomOwnerChanged = userRole == .roomOwner
if let userInfo = store.attendeeList.first(where: { $0.userId == userId }) {
userInfo.userRole = userRole
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewUserList, param: ["userRole": userRole])
}
if isSelfRoleChanged {
store.currentUser.userRole = userRole
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_CurrentUserRoleChanged, param: ["userRole": userRole])
}
if isRoomOwnerChanged {
EngineManager.shared.fetchRoomInfo(roomId: roomInfo.roomId) { _ in
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RoomOwnerChanged, param: ["owner": userId])
}
}
if checkAutoTakeSeatForOwner(userId: userId, userRole: userRole) {
let _ = engineManager.takeSeat()
}
if checkAutoSendingMessageForOwner(userId: userId, userRole: userRole) {
engineManager.disableSendingMessageByAdmin(userId: userId, isDisable: false)
}
if isSelfRoleChanged, userRole != .generalUser {
engineManager.updateSeatApplicationList()
}
}
private func checkAutoTakeSeatForOwner(userId: String, userRole: TUIRole) -> Bool {
let isSelfRoleChanged = userId == currentUser.userId
let isRoomOwnerChanged = userRole == .roomOwner
return isSelfRoleChanged && isRoomOwnerChanged && roomInfo.isSeatEnabled && !currentUser.isOnSeat
}
private func checkAutoSendingMessageForOwner(userId: String, userRole: TUIRole) -> Bool {
let isSelfRoleChanged = userId == currentUser.userId
let isRoomOwnerChanged = userRole == .roomOwner
return isSelfRoleChanged && isRoomOwnerChanged && currentUser.disableSendingMessage
}
private func requestReceived(request: TUIRequest) {
switch request.requestAction {
case .takeSeat:
store.addInviteSeatUser(request: request)
default: break
}
}
private func userVoiceVolumeChanged(volumeMap: [String : NSNumber]) {
for (userId, volume) in volumeMap {
guard let userModel = store.attendeeList.first(where: { $0.userId == userId}) else { continue }
userModel.userVoiceVolume = volume.intValue
}
}
private func userScreenCaptureStopped() {
currentUser.hasScreenStream = false
guard let userModel = store.attendeeList.first(where: { $0.userId == currentUser.userId }) else { return }
userModel.hasScreenStream = false
}
private func kickedOffLine() {
engineManager.destroyEngineManager()
}
}

View File

@@ -0,0 +1,268 @@
//
// RoomStore.swift
// TUIRoomKit
//
// Created by aby on 2022/12/28.
// Copyright © 2022 Tencent. All rights reserved.
//
import Foundation
import TUICore
import RTCRoomEngine
import Factory
let roomHashNumber: Int = 0x3B9AC9FF
class RoomStore: NSObject {
var currentUser: UserEntity = UserEntity()
var roomInfo: TUIRoomInfo = TUIRoomInfo()
var videoSetting: VideoModel = VideoModel()
var audioSetting: AudioModel = AudioModel()
var attendeeList: [UserEntity] = [] // User list
var seatList: [UserEntity] = [] // List of users who have taken the stage
var offSeatList: [UserEntity] = [] // List of users who not on stage
var inviteSeatList: [RequestEntity] = [] // List of users who apply to be on stage
var isEnteredRoom: Bool = false
var timeStampOnEnterRoom: Int = 0 // Timestamp of entering the meeting
var isImAccess: Bool = false // Whether TUIRoomKit is entered by IM
var selfTakeSeatRequestId: String? // Self ID for applying on stage
var shouldShowFloatChatView = true
private let openCameraKey = "isOpenCamera"
private let openMicrophoneKey = "isOpenMicrophone"
private let shownRaiseHandNoticeKey = "isShownRaiseHandNotice"
weak var conferenceObserver: ConferenceObserver?
var isOpenMicrophone: Bool {
didSet {
UserDefaults.standard.set(isOpenMicrophone, forKey: openMicrophoneKey)
UserDefaults.standard.synchronize()
}
}
var isOpenCamera: Bool {
didSet {
UserDefaults.standard.set(isOpenCamera, forKey: openCameraKey)
UserDefaults.standard.synchronize()
}
}
var isShownRaiseHandNotice: Bool {
didSet {
UserDefaults.standard.set(isShownRaiseHandNotice, forKey: shownRaiseHandNoticeKey)
UserDefaults.standard.synchronize()
}
}
override init() {
if let isOpenMicrophoneValue = UserDefaults.standard.object(forKey: openMicrophoneKey) as? Bool {
isOpenMicrophone = isOpenMicrophoneValue
} else {
isOpenMicrophone = true
}
if let isOpenCameraValue = UserDefaults.standard.object(forKey: openCameraKey) as? Bool {
isOpenCamera = isOpenCameraValue
} else {
isOpenCamera = true
}
if let isShownRaiseHandNoticeValue = UserDefaults.standard.object(forKey: shownRaiseHandNoticeKey) as? Bool {
isShownRaiseHandNotice = isShownRaiseHandNoticeValue
} else {
isShownRaiseHandNotice = true
}
}
func initialRoomInfo(_ roomInfo: TUIRoomInfo) {
self.roomInfo = roomInfo
EngineEventCenter.shared.notifyEngineEvent(event: .onInitialRoomInfo, param: ["roomInfo": roomInfo])
}
func initalEnterRoomMessage() {
isEnteredRoom = true
timeStampOnEnterRoom = Int(Date().timeIntervalSince1970)
}
func initialRoomCurrentUser() {
currentUser.userId = TUILogin.getUserID() ?? ""
currentUser.userName = TUILogin.getNickName() ?? ""
EngineManager.shared.getUserInfo(currentUser.userId) { [weak self] userInfo in
guard let self = self else { return }
guard let userInfo = userInfo else { return }
self.currentUser.update(userInfo: userInfo)
EngineEventCenter.shared.notifyEngineEvent(event: .onInitialSelfUserInfo, param: [:])
} onError: { code, message in
debugPrint("getUserInfo,code:\(code),message:\(message)")
}
}
func initialSeatList(seatList: [TUISeatInfo]) {
var localSeatList = [UserEntity]()
for seatInfo in seatList {
guard let userId = seatInfo.userId, userId != "" else { continue }
guard let userModel = getUserItem(userId) else { continue }
userModel.isOnSeat = true
localSeatList.append(userModel)
}
self.seatList = localSeatList
if seatList.contains(where: { $0.userId == currentUser.userId }) {
updateSelfOnSeatState(isOnSeat: true)
}
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewSeatList, param: [:])
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewVideoSeatView, param: [:])
}
func initialOffSeatList() {
offSeatList = attendeeList.filter({ !$0.isOnSeat })
}
func updateSelfOnSeatState(isOnSeat: Bool) {
currentUser.isOnSeat = isOnSeat
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_UserOnSeatChanged, param: ["isOnSeat": isOnSeat])
}
func updateUserDisableSendingMessage(userId: String, isDisable: Bool) {
if userId == currentUser.userId {
currentUser.disableSendingMessage = isDisable
}
guard let userItem = getUserItem(userId) else { return }
userItem.disableSendingMessage = isDisable
}
func deleteTakeSeatRequest(requestId: String) {
let requestUserId = inviteSeatList.first(where: { $0.requestId == requestId })?.userId
inviteSeatList = inviteSeatList.filter { requestItem in
requestItem.requestId != requestId
}
EngineEventCenter.shared.notifyEngineEvent(event: .onDeletedTakeSeatRequest, param: ["userId": requestUserId ?? ""])
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewSeatList, param: [:])
}
func deleteInviteSeatUser(_ userId: String) {
inviteSeatList = inviteSeatList.filter { requestItem in
requestItem.userId != userId
}
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewSeatList, param: [:])
}
func addInviteSeatUser(request: TUIRequest) {
guard !inviteSeatList.contains(where: { $0.userId == request.userId }) else { return }
let requestEntity = RequestEntity(requestId: request.requestId, userId: request.userId)
inviteSeatList.append(requestEntity)
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewSeatList, param: [:])
}
func setCameraOpened(_ isCameraOpened: Bool) {
videoSetting.isCameraOpened = isCameraOpened
}
func setSoundOnSpeaker(_ isSoundOnSpeaker: Bool) {
audioSetting.isSoundOnSpeaker = isSoundOnSpeaker
}
func setConferenceObserver(_ observer: ConferenceObserver?) {
conferenceObserver = observer
}
func setInviteSeatList(list: [TUIRequest]) {
inviteSeatList = []
for request in list {
let requestEntity = RequestEntity(requestId: request.requestId, userId: request.userId)
inviteSeatList.append(requestEntity)
}
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewSeatList, param: [:])
}
func updateLeftSeatList(leftList: [TUISeatInfo]) {
guard leftList.count > 0 else { return }
if leftList.contains(where: { $0.userId == currentUser.userId }) {
currentUser.isOnSeat = false
audioSetting.isMicOpened = false
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_UserOnSeatChanged,
param: ["isOnSeat":false])
}
for seatInfo: TUISeatInfo in leftList {
guard let userId = seatInfo.userId else { continue }
if let userItem = attendeeList.first(where: { $0.userId == userId }) {
userItem.isOnSeat = false
addOffSeatItem(userItem)
}
deleteOnSeatItem(userId)
}
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewSeatList, param: [:])
}
func updateSeatedList(seatList: [TUISeatInfo]) {
guard seatList.count > 0 else { return }
if seatList.contains(where: { $0.userId == currentUser.userId }) {
currentUser.isOnSeat = true
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_UserOnSeatChanged,
param: ["isOnSeat":true])
}
for seatInfo: TUISeatInfo in seatList {
guard let userId = seatInfo.userId else { continue }
guard let userInfo = attendeeList.first(where: { $0.userId == userId }) else { continue }
userInfo.isOnSeat = true
addOnSeatItem(userInfo)
deleteOffSeatItem(userId)
}
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewSeatList, param: [:])
}
func remoteUserEnterRoom(userInfo: TUIUserInfo) {
let userItem = UserEntity()
userItem.update(userInfo: userInfo)
addUserItem(userItem)
if roomInfo.isSeatEnabled, !userItem.isOnSeat {
addOffSeatItem(userItem)
}
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewUserList, param: [:])
}
func remoteUserLeaveRoom(userInfo: TUIUserInfo) {
deleteUserItem(userInfo.userId)
deleteOffSeatItem(userInfo.userId)
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_RenewUserList, param: [:])
}
func updateFloatChatShowState(shouldShow: Bool) {
shouldShowFloatChatView = shouldShow
EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_ShowFloatChatView, param: ["shouldShow": shouldShow])
}
deinit {
debugPrint("self:\(self),deinit")
}
}
extension RoomStore {
private func getUserItem(_ userId: String) -> UserEntity? {
return attendeeList.first(where: {$0.userId == userId})
}
private func addUserItem(_ userItem: UserEntity) {
guard getUserItem(userItem.userId) == nil else { return }
if userItem.userName.isEmpty {
userItem.userName = userItem.userId
}
attendeeList.append(userItem)
}
private func deleteUserItem(_ userId: String) {
attendeeList.removeAll(where: { $0.userId == userId })
}
private func addOnSeatItem(_ userItem: UserEntity) {
guard !seatList.contains(where: { $0.userId == userItem.userId }) else { return }
seatList.append(userItem)
}
private func deleteOnSeatItem(_ userId: String) {
seatList.removeAll(where: { $0.userId == userId })
}
private func addOffSeatItem(_ userItem: UserEntity) {
guard !offSeatList.contains(where: { $0.userId == userItem.userId }) else { return }
offSeatList.append(userItem)
}
private func deleteOffSeatItem(_ userId: String) {
offSeatList.removeAll(where: { $0.userId == userId })
}
}

View File

@@ -0,0 +1,67 @@
//
// ButtonItemData.swift
// TUIRoomKit
//
// Created by janejntang on 2023/1/10.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
class ButtonItemData {
enum ButtonType {
case muteAudioItemType
case muteVideoItemType
case raiseHandItemType
case leaveSeatItemType
case shareScreenItemType
case moreItemType
case switchCamaraItemType
case raiseHandApplyItemType
case normal
}
enum Orientation {
case left
case right
}
var buttonType: ButtonType = .normal
var normalIcon: String = ""
var selectedIcon: String = ""
var disabledIcon: String = ""
var normalTitle: String = ""
var selectedTitle: String = ""
var titleFont: UIFont?
var titleColor: UIColor?
var resourceBundle: Bundle = Bundle.main
var action: ((Any)->Void)?
var normalImage: UIImage? {
return UIImage(named: normalIcon, in: resourceBundle, compatibleWith: nil)?.checkOverturn()
}
var selectedImage: UIImage? {
return UIImage(named: selectedIcon, in: resourceBundle, compatibleWith: nil)?.checkOverturn()
}
var disabledImage: UIImage? {
return UIImage(named: disabledIcon, in: resourceBundle, compatibleWith: nil)?.checkOverturn()
}
var hasNotice: Bool = false
var noticeText: String = ""
var cornerRadius: CGFloat?
var hasLineView: Bool = false
var orientation: Orientation = .left
var imageSize: CGSize?
var size: CGSize?
var backgroundColor: UIColor?
var isSelect: Bool = false
var isEnabled: Bool = true
var isHidden: Bool = false
var alpha: CGFloat = 1
}

View File

@@ -0,0 +1,42 @@
//
// ListCellItemData.swift
// TUIRoomKit
//
// Created by janejntang on 2023/1/6.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
class ListCellItemData {
enum ListCellType {
case resolutionType
case frameRateType
case normal
}
var type: ListCellType = .normal
var size: CGSize?
var backgroundColor: UIColor = .clear
//UILabel configuration
var titleText: String = ""
var messageText: String = ""
var titleColor: UIColor?
var messageColor: UIColor?
//UISwitch configuration
var hasSwitch: Bool = false
var isSwitchOn: Bool = false
//UIButton configuration
var hasRightButton: Bool = false
var buttonData: ButtonItemData?
//UISlider configuration
var hasSliderLabel: Bool = false
var hasSlider: Bool = false
var minimumValue: Float = 0
var maximumValue: Float = 100
var sliderStep: Float = 1
var sliderUnit: String = ""
var sliderDefault: Float = 0
var action: ((Any)->Void)?
var hasOverAllAction: Bool = false
var hasDownLineView: Bool = false
}

View File

@@ -0,0 +1,30 @@
//
// PrepareSettingItemData.swift
// TUIRoomKit
//
// Created by janejntang on 2023/1/6.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
import UIKit
class PrepareSettingItemData {
var titleText: String = ""
var messageText: String = ""
var fieldPlaceholderText: String = ""
var fieldText: String = ""
var fieldEnable: Bool = false
var isSwitchOn: Bool = false
var action: ((Any)->Void)?
var normalIcon: String = ""
var resourceBundle: Bundle = Bundle.main
var hasSwitch: Bool = false
var hasButton: Bool = false
var hasFieldView: Bool = false
var hasOverAllAction: Bool = false
var size: CGSize?
var backgroundColor: UIColor?
var hasDownLineView: Bool = true
}

View File

@@ -0,0 +1,27 @@
//
// TRTCObserver.swift
// TUIRoomKit
//
// Created by janejntang on 2024/4/1.
//
import Foundation
#if canImport(TXLiteAVSDK_TRTC)
import TXLiteAVSDK_TRTC
#elseif canImport(TXLiteAVSDK_Professional)
import TXLiteAVSDK_Professional
#endif
class TRTCObserver: NSObject, TRTCCloudDelegate {
var roomId: String {
EngineManager.shared.store.roomInfo.roomId
}
func onExitRoom(_ reason: Int) {
guard reason == 2 else { return }
EngineEventCenter.shared.notifyEngineEvent(event: .onRoomDismissed, param: ["roomId": roomId])
}
func onStatistics(_ statistics: TRTCStatistics) {
EngineEventCenter.shared.notifyEngineEvent(event: .onStatistics, param: ["statistics": statistics])
}
}

View File

@@ -0,0 +1,45 @@
//
// UserEntity.swift
// TUIRoomKit
//
// Created by WesleyLei on 2022/9/26.
// Copyright © 2022 Tencent. All rights reserved.
//
import Foundation
import RTCRoomEngine
class UserEntity {
var userId: String = ""
var userName: String = ""
var avatarUrl: String = ""
var userRole: TUIRole = .generalUser
var userVoiceVolume: Int = 0
var hasAudioStream: Bool = false
var hasVideoStream: Bool = false
var videoStreamType: TUIVideoStreamType = .cameraStream
var isOnSeat: Bool = false
var disableSendingMessage: Bool = false
var hasScreenStream: Bool = false
func update(userInfo: TUIUserInfo) {
userId = userInfo.userId
userName = userInfo.userName
avatarUrl = userInfo.avatarUrl
userRole = userInfo.userRole
hasAudioStream = userInfo.hasAudioStream
hasVideoStream = userInfo.hasVideoStream
hasScreenStream = userInfo.hasScreenStream
}
init(){}
init(invitation: TUIInvitation) {
self.userId = invitation.invitee.userId
self.userName = invitation.invitee.userName
self.avatarUrl = invitation.invitee.avatarUrl
}
deinit {
debugPrint("deinit \(self)")
}
}

View File

@@ -0,0 +1,19 @@
//
// VideoModel.swift
// TUIRoomKit
//
// Created by janejntang on 2023/3/8.
// Copyright © 2023 Tencent. All rights reserved.
//
import Foundation
import RTCRoomEngine
class VideoModel {
var isCameraOpened: Bool = true
var videoFps: Int = 15
var videoBitrate: Int = 1_200
var isMirror: Bool = true
var isFrontCamera: Bool = true
var videoQuality: TUIVideoQuality = .quality720P
}

View File

@@ -0,0 +1,38 @@
//
// VideoSeatItem.swift
// TUIVideoSeat
//
// Created by jack on 2023/3/6.
// Copyright © 2023 Tencent. All rights reserved.
import Foundation
import RTCRoomEngine
class VideoSeatItem: Equatable {
static func == (lhs: VideoSeatItem, rhs: VideoSeatItem) -> Bool {
let lhsType = lhs.videoStreamType == .screenStream
let rhsType = rhs.videoStreamType == .screenStream
return (lhs.userId == rhs.userId) && (lhsType == rhsType)
}
var userId: String = ""
var userName: String = ""
var avatarUrl: String = ""
var userRole: TUIRole = .generalUser
var userVoiceVolume: Int = 0
var hasAudioStream: Bool = false
var hasVideoStream: Bool = false
var videoStreamType: TUIVideoStreamType = .cameraStream
var isOnSeat: Bool = false
var disableSendingMessage: Bool = false
var isHasVideoStream: Bool {
return hasVideoStream || videoStreamType == .screenStream
}
func update(userInfo: UserEntity) {
userId = userInfo.userId
userName = userInfo.userName
avatarUrl = userInfo.avatarUrl
userRole = userInfo.userRole
hasAudioStream = userInfo.hasAudioStream
hasVideoStream = userInfo.hasVideoStream
}
}