// // RoomVideoFloatViewModel.swift // TUIRoomKit // // Created by janejntang on 2023/7/11. // import Foundation import RTCRoomEngine protocol RoomVideoFloatViewResponder: NSObject { func updateUserStatus(user: UserEntity) func updateUserAudioVolume(hasAudio: Bool, volume: Int) func makeToast(text: String) func showAvatarImageView(isShow: Bool) } class RoomVideoFloatViewModel: NSObject { var userId: String = "" var streamType: TUIVideoStreamType = .cameraStream weak var renderView: UIView? weak var viewResponder: RoomVideoFloatViewResponder? var engineManager: EngineManager { EngineManager.shared } var roomInfo: TUIRoomInfo { engineManager.store.roomInfo } var currentUser: UserEntity { engineManager.store.currentUser } override init() { super.init() subscribeEngine() subLogoutNotification() } private func subscribeEngine() { EngineEventCenter.shared.subscribeEngine(event: .onUserVideoStateChanged, observer: self) EngineEventCenter.shared.subscribeEngine(event: .onRoomDismissed, observer: self) EngineEventCenter.shared.subscribeEngine(event: .onKickedOutOfRoom, observer: self) EngineEventCenter.shared.subscribeEngine(event: .onUserAudioStateChanged, observer: self) EngineEventCenter.shared.subscribeEngine(event: .onUserVoiceVolumeChanged, observer: self) EngineEventCenter.shared.subscribeEngine(event: .onKickedOffLine, observer: self) EngineEventCenter.shared.subscribeUIEvent(key: .TUIRoomKitService_RoomOwnerChanged, responder: self) } private func unsubscribeEngine() { EngineEventCenter.shared.unsubscribeEngine(event: .onUserVideoStateChanged, observer: self) EngineEventCenter.shared.unsubscribeEngine(event: .onRoomDismissed, observer: self) EngineEventCenter.shared.unsubscribeEngine(event: .onKickedOutOfRoom, observer: self) EngineEventCenter.shared.unsubscribeEngine(event: .onUserAudioStateChanged, observer: self) EngineEventCenter.shared.unsubscribeEngine(event: .onUserVoiceVolumeChanged, observer: self) EngineEventCenter.shared.unsubscribeEngine(event: .onKickedOffLine, observer: self) EngineEventCenter.shared.unsubscribeUIEvent(key: .TUIRoomKitService_RoomOwnerChanged, responder: self) } private func subLogoutNotification() { NotificationCenter.default.addObserver(self, selector: #selector(dismissFloatViewForLogout), name: NSNotification.Name.TUILogoutSuccess, object: nil) } private func unsubLogoutNotification() { NotificationCenter.default.removeObserver(self, name: NSNotification.Name.TUILogoutSuccess, object: nil) } func showRoomMainView() { if engineManager.store.isEnteredRoom { EngineEventCenter.shared.notifyUIEvent(key: .TUIRoomKitService_ShowRoomMainView, param: [:]) } } func showFloatWindowViewVideo(renderView: UIView?) { self.renderView = renderView if let userModel = getScreenUserModel() { //If someone is screen sharing, show the screen share first showScreenStream(userModel: userModel) } else { //Show host without screen sharing showCameraStream() } } func getUserEntity(userId: String) -> UserEntity? { return engineManager.store.attendeeList.first(where: { $0.userId == userId }) } @objc private func dismissFloatViewForLogout() { RoomVideoFloatView.dismiss() } deinit { unsubscribeEngine() unsubLogoutNotification() debugPrint("deinit \(self)") } } extension RoomVideoFloatViewModel { private func getScreenUserModel() -> UserEntity? { return engineManager.store.attendeeList.first(where: { $0.hasScreenStream == true }) } private func showScreenStream(userModel: UserEntity) { let streamType: TUIVideoStreamType = userModel.userId == currentUser.userId ? .cameraStream : .screenStream startPlayVideo(userId: userModel.userId, streamType: streamType) changePlayingState(userId: userModel.userId, streamType: streamType) viewResponder?.updateUserStatus(user: userModel) viewResponder?.showAvatarImageView(isShow: false) } private func showCameraStream() { guard let userModel = getUserEntity(userId: roomInfo.ownerId) ?? getUserEntity(userId: currentUser.userId) else { return } changePlayingState(userId: userModel.userId, streamType: .cameraStream) viewResponder?.updateUserStatus(user: userModel) if userModel.hasVideoStream { startPlayVideo(userId: userModel.userId, streamType: .cameraStream) } else { viewResponder?.showAvatarImageView(isShow: true) } } private func startPlayVideo(userId: String, streamType: TUIVideoStreamType) { if userId == currentUser.userId { engineManager.setLocalVideoView(streamType: streamType, view: renderView) } else { engineManager.setRemoteVideoView(userId: userId, streamType: streamType, view: renderView) engineManager.startPlayRemoteVideo(userId: userId, streamType: streamType) } viewResponder?.showAvatarImageView(isShow: false) } private func stopPlayVideo(userId: String, streamType: TUIVideoStreamType) { if userId == currentUser.userId { engineManager.setLocalVideoView(streamType: streamType, view: nil) return } engineManager.setRemoteVideoView(userId: userId, streamType: streamType, view: nil) guard let userItem = getUserEntity(userId: userId) else { return } if streamType == .screenStream, userItem.hasScreenStream { engineManager.stopPlayRemoteVideo(userId: userId, streamType: .screenStream) } else if streamType == .cameraStream, userItem.hasVideoStream { engineManager.stopPlayRemoteVideo(userId: userId, streamType: .cameraStream) } } private func changePlayingState(userId: String, streamType: TUIVideoStreamType) { self.userId = userId self.streamType = streamType } } extension RoomVideoFloatViewModel: RoomEngineEventResponder { func onEngineEvent(name: EngineEventCenter.RoomEngineEvent, param: [String : Any]?) { switch name { case .onKickedOutOfRoom, .onRoomDismissed: engineManager.destroyEngineManager() RoomVideoFloatView.dismiss() case .onUserVideoStateChanged: guard let userId = param?["userId"] as? String else { return } guard let streamType = param?["streamType"] as? TUIVideoStreamType else { return } guard let hasVideo = param?["hasVideo"] as? Bool else { return } if streamType == .screenStream { if hasVideo { stopPlayVideo(userId: roomInfo.ownerId, streamType: .cameraStream) guard let userModel = getUserEntity(userId: userId) else { return } showScreenStream(userModel: userModel) } else { stopPlayVideo(userId: self.userId, streamType: .screenStream) showCameraStream() } return } guard getScreenUserModel() == nil else { return } //If someone is screen sharing, don't show the host screen guard userId == roomInfo.ownerId else { return } if hasVideo { startPlayVideo(userId: userId, streamType: streamType) } else { viewResponder?.showAvatarImageView(isShow: true) } case .onUserAudioStateChanged: guard let userId = param?["userId"] as? String else { return } guard let hasAudio = param?["hasAudio"] as? Bool else { return } guard userId == self.userId else { return } var volume = 0 if let userModel = getUserEntity(userId: self.userId) { volume = userModel.userVoiceVolume } viewResponder?.updateUserAudioVolume(hasAudio: hasAudio, volume: volume) case .onUserVoiceVolumeChanged: guard let volumeNumber = param?[self.userId] as? NSNumber else { return } guard let userModel = getUserEntity(userId: self.userId) else { return } viewResponder?.updateUserAudioVolume(hasAudio: userModel.hasAudioStream, volume: volumeNumber.intValue) case .onKickedOffLine: RoomVideoFloatView.dismiss() default: break } } } extension RoomVideoFloatViewModel: RoomKitUIEventResponder { func onNotifyUIEvent(key: EngineEventCenter.RoomUIEvent, Object: Any?, info: [AnyHashable : Any]?) { switch key { case .TUIRoomKitService_RoomOwnerChanged: guard getScreenUserModel() == nil else { return } //If someone is screen sharing, don't show the host screen stopPlayVideo(userId: self.userId, streamType: .cameraStream) showCameraStream() default: break } } }