| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- //
- // RoomVideoFloatViewModel.swift
- // TUIRoomKit
- //
- // Created by janejntang on 2023/7/11.
- //
- import Foundation
- import RTCRoomEngine
- import Factory
- import Combine
- protocol RoomVideoFloatViewResponder: NSObject {
- func updateUserStatus(user: UserEntity)
- func makeToast(text: String)
- func showAvatarImageView(isShow: Bool)
- func getRenderView() -> UIView
- func updateUserVolume(volume: Int)
- func updateUserAudio(hasAudio: Bool)
- }
- class RoomVideoFloatViewModel: NSObject {
- var displayUserId: String = ""
- var displayStreamType: TUIVideoStreamType = .cameraStream
- weak var viewResponder: RoomVideoFloatViewResponder?
- private var lastSwitchTime = 0
- private var switchIntervalTime = 5
- private let voiceVolumeMinLimit = 10
- 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.subscribeEngine(event: .onRemoteUserLeaveRoom, observer: self)
- EngineEventCenter.shared.subscribeEngine(event: .onUserInfoChanged, observer: 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.unsubscribeEngine(event: .onRemoteUserLeaveRoom, observer: self)
- EngineEventCenter.shared.unsubscribeEngine(event: .onUserInfoChanged, observer: 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() {
- if let userItem = getScreenUserItem() {
- switchScreenStreamUser(userItem: userItem)
- } else if getRoomOwnerUser() != nil {
- switchToRoomOwnerUser()
- } else {
- switchToSelfUser()
- }
- }
-
- func getUserEntity(userId: String) -> UserEntity? {
- return engineManager.store.attendeeList.first(where: { $0.userId == userId })
- }
-
- @objc private func dismissFloatViewForLogout() {
- RoomVideoFloatView.dismiss()
- RoomRouter.makeToastInWindow(toast: .logoutText, duration: 0.5)
- }
-
- func reportFloatWindowShow() {
- RoomKitReport.reportData(.metricsFloatWindowShow)
- }
-
- deinit {
- unsubscribeEngine()
- unsubLogoutNotification()
- debugPrint("deinit \(self)")
- }
-
- @Injected(\.conferenceStore) var operation: ConferenceStore
- }
- extension RoomVideoFloatViewModel {
- private func getScreenUserItem() -> UserEntity? {
- return engineManager.store.attendeeList.first(where: { $0.hasScreenStream == true })
- }
-
- private func showScreenStream(userItem: UserEntity) {
- startPlayVideo(userId: userItem.userId, streamType: .screenStream)
- changePlayingState(userId: userItem.userId, streamType: .screenStream)
- viewResponder?.updateUserStatus(user: userItem)
- viewResponder?.showAvatarImageView(isShow: false)
- }
-
- private func showCameraStream(userItem: UserEntity) {
- viewResponder?.updateUserStatus(user: userItem)
- if userItem.hasVideoStream {
- startPlayVideo(userId: userItem.userId, streamType: .cameraStream)
- } else {
- viewResponder?.showAvatarImageView(isShow: true)
- }
- changePlayingState(userId: userItem.userId, streamType: .cameraStream)
- }
-
- private func isArrivalSwitchUserTime() -> Bool {
- let currentTime = Int(Date().timeIntervalSince1970)
- return labs(currentTime - lastSwitchTime) > switchIntervalTime
- }
-
- private func getMaxVoiceVolumeMap(volumeMap: [String : NSNumber]) -> [String : Int]? {
- guard let maxElement = volumeMap.max(by: { $0.value.compare($1.value) == .orderedAscending }) else { return nil }
- return [maxElement.key : maxElement.value.intValue]
- }
-
- private func isSwitchOriginalUser(originalUserId: String, volumeMap: [String : NSNumber]) -> Bool {
- let originalUserVolume = volumeMap[originalUserId]?.intValue ?? 0
- guard originalUserVolume < voiceVolumeMinLimit else { return false }
- guard let maxVolume = getMaxVoiceVolumeMap(volumeMap: volumeMap)?.values.first else { return false }
- return maxVolume >= voiceVolumeMinLimit
- }
-
- private func switchToRoomOwnerUser() {
- guard let ownerUser = getRoomOwnerUser() else { return }
- if displayUserId.isEmpty {
- showCameraStream(userItem: ownerUser)
- } else if displayUserId != ownerUser.userId || displayStreamType != .cameraStream {
- stopPlayVideo(userId: displayUserId, streamType: displayStreamType)
- showCameraStream(userItem: ownerUser)
- }
- }
-
- private func switchToSelfUser() {
- if displayUserId.isEmpty {
- showCameraStream(userItem: currentUser)
- } else if displayUserId != currentUser.userId || displayStreamType != .cameraStream {
- stopPlayVideo(userId: displayUserId, streamType: displayStreamType)
- showCameraStream(userItem: currentUser)
- }
- }
-
- private func getRoomOwnerUser() -> UserEntity? {
- return engineManager.store.attendeeList.first(where: { $0.userRole == .roomOwner })
- }
-
- private func switchCameraStreamUser(volumeMap: [String : NSNumber]) {
- guard !volumeMap.isEmpty else { return }
- guard isArrivalSwitchUserTime() else { return }
- var latestDisplayUserId: String
- if !isSwitchOriginalUser(originalUserId: displayUserId, volumeMap: volumeMap) {
- latestDisplayUserId = displayUserId
- } else if let maxVolumeMap = getMaxVoiceVolumeMap(volumeMap: volumeMap)?.first, maxVolumeMap.value >= voiceVolumeMinLimit {
- latestDisplayUserId = maxVolumeMap.key
- } else if let user = getRoomOwnerUser() {
- latestDisplayUserId = user.userId
- } else {
- latestDisplayUserId = currentUser.userId
- }
- guard let latestDisplayUserItem = getUserEntity(userId: latestDisplayUserId) else { return }
- if displayUserId.isEmpty {
- showCameraStream(userItem: latestDisplayUserItem)
- lastSwitchTime = Int(Date().timeIntervalSince1970)
- } else if latestDisplayUserId != displayUserId || displayStreamType != .cameraStream {
- stopPlayVideo(userId: displayUserId, streamType: displayStreamType)
- showCameraStream(userItem: latestDisplayUserItem)
- lastSwitchTime = Int(Date().timeIntervalSince1970)
- }
- }
-
- private func switchScreenStreamUser(userItem: UserEntity) {
- if !displayUserId.isEmpty {
- stopPlayVideo(userId: displayUserId, streamType: displayStreamType)
- }
- if userItem.userId == currentUser.userId {
- showCameraStream(userItem: userItem)
- } else {
- showScreenStream(userItem: userItem)
- }
- lastSwitchTime = Int(Date().timeIntervalSince1970)
- }
-
- private func startPlayVideo(userId: String, streamType: TUIVideoStreamType) {
- if userId == currentUser.userId {
- engineManager.setLocalVideoView(viewResponder?.getRenderView())
- } else {
- engineManager.setRemoteVideoView(userId: userId, streamType: streamType, view: viewResponder?.getRenderView())
- engineManager.startPlayRemoteVideo(userId: userId, streamType: streamType)
- }
- viewResponder?.showAvatarImageView(isShow: false)
- }
-
- private func stopPlayVideo(userId: String, streamType: TUIVideoStreamType) {
- if userId == currentUser.userId {
- engineManager.setLocalVideoView(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.displayUserId = userId
- self.displayStreamType = streamType
- }
- }
- extension RoomVideoFloatViewModel: RoomEngineEventResponder {
- func onEngineEvent(name: EngineEventCenter.RoomEngineEvent, param: [String : Any]?) {
- switch name {
- case .onRoomDismissed:
- handleRoomDismissed()
- case .onKickedOutOfRoom:
- handleKickedOutOfRoom()
- 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 }
- handleUserVideoStateChanged(userId: userId, streamType: streamType, hasVideo: hasVideo)
- case .onUserAudioStateChanged:
- guard let userId = param?["userId"] as? String else { return }
- guard let hasAudio = param?["hasAudio"] as? Bool else { return }
- handleUserAudioStateChanged(userId: userId, hasAudio: hasAudio)
- case .onUserVoiceVolumeChanged:
- guard let volumeMap = param as? [String : NSNumber] else { return }
- handleUserVoiceVolumeChanged(volumeMap: volumeMap)
- case .onKickedOffLine:
- handleKickedOffLine()
- case .onRemoteUserLeaveRoom :
- guard let userInfo = param?["userInfo"] as? TUIUserInfo else { return }
- handleRemoteUserLeaveRoom(useInfo: userInfo)
- case .onUserInfoChanged:
- guard let userInfo = param?["userInfo"] as? TUIUserInfo else { return }
- guard let modifyFlag = param?["modifyFlag"] as? TUIUserInfoModifyFlag else { return }
- if modifyFlag.contains(.nameCard) {
- guard userInfo.userId == displayUserId else { return }
- guard let userItem = getUserEntity(userId: displayUserId) else { return }
- viewResponder?.updateUserStatus(user: userItem)
- } else if modifyFlag.contains(.userRole) {
- handleUserRoleChanged(userId: userInfo.userId, userRole: userInfo.userRole)
- }
- default: break
- }
- }
-
- private func handleRoomDismissed() {
- engineManager.destroyEngineManager()
- RoomVideoFloatView.dismiss()
- RoomRouter.makeToastInWindow(toast: .roomDismissedText, duration: 0.5)
- }
-
- private func handleKickedOutOfRoom() {
- engineManager.destroyEngineManager()
- RoomVideoFloatView.dismiss()
- operation.dispatch(action: RoomResponseActions.onExitSuccess())
- RoomRouter.makeToastInWindow(toast: .kickedOutOfRoomText, duration: 0.5)
- }
-
- private func handleUserVideoStateChanged(userId: String, streamType: TUIVideoStreamType, hasVideo: Bool) {
- if streamType == .screenStream {
- if hasVideo {
- guard let userItem = getUserEntity(userId: userId) else { return }
- switchScreenStreamUser(userItem: userItem)
- } else if getRoomOwnerUser() != nil {
- switchToRoomOwnerUser()
- } else {
- switchToSelfUser()
- }
- return
- }
- guard getScreenUserItem() == nil || getScreenUserItem()?.userId == currentUser.userId else { return }
- guard userId == displayUserId else { return }
- if hasVideo {
- startPlayVideo(userId: userId, streamType: streamType)
- } else {
- viewResponder?.showAvatarImageView(isShow: true)
- }
- }
-
- private func handleUserAudioStateChanged(userId: String, hasAudio: Bool) {
- guard userId == displayUserId else { return }
- viewResponder?.updateUserAudio(hasAudio: hasAudio)
- }
-
- private func handleUserVoiceVolumeChanged(volumeMap: [String : NSNumber]) {
- if getScreenUserItem() == nil {
- switchCameraStreamUser(volumeMap: volumeMap)
- }
- let userVolume = volumeMap[displayUserId]?.intValue ?? 0
- viewResponder?.updateUserVolume(volume: userVolume)
- }
-
- private func handleKickedOffLine() {
- RoomVideoFloatView.dismiss()
- operation.dispatch(action: RoomResponseActions.onExitSuccess())
- RoomRouter.makeToastInWindow(toast: .kickedOffLineText, duration: 0.5)
- }
-
- private func handleRemoteUserLeaveRoom(useInfo: TUIUserInfo) {
- guard displayUserId == useInfo.userId else { return }
- if getRoomOwnerUser() != nil {
- switchToRoomOwnerUser()
- } else {
- switchToSelfUser()
- }
- }
-
- private func handleUserRoleChanged(userId: String, userRole: TUIRole) {
- if getScreenUserItem() == nil, userRole == .roomOwner {
- switchToRoomOwnerUser()
- }
- guard let userItem = getUserEntity(userId: displayUserId) else { return }
- viewResponder?.updateUserStatus(user: userItem)
- }
- }
- private extension String {
- static let logoutText = localized("You are logged out")
- static let roomDismissedText = localized("The conference was closed.")
- static let kickedOutOfRoomText = localized("You were removed by the host.")
- static let kickedOffLineText = localized("You are already logged in elsewhere")
- }
|