// // MediaSettingViewModel.swift // TUIRoomKit // // Created by janejntang on 2023/1/16. // Copyright © 2023 Tencent. All rights reserved. // import Foundation import RTCRoomEngine #if canImport(TXLiteAVSDK_TRTC) import TXLiteAVSDK_TRTC #elseif canImport(TXLiteAVSDK_Professional) import TXLiteAVSDK_Professional #endif protocol MediaSettingViewEventResponder: AnyObject { func showResolutionAlert() func showFrameRateAlert() func showQualityView() func updateStackView(item: ListCellItemData) func makeToast(text: String) } class MediaSettingViewModel { private(set) var videoItems: [ListCellItemData] = [] private(set) var audioItems: [ListCellItemData] = [] private(set) var otherItems: [ListCellItemData] = [] weak var viewResponder: MediaSettingViewEventResponder? = nil var engineManager: EngineManager { EngineManager.shared } var store: RoomStore { engineManager.store } var videoSetting: VideoModel { engineManager.store.videoSetting } var audioSetting: AudioModel { engineManager.store.audioSetting } let resolutionNameItems: [String] = [.smoothResolutionText, .standardResolutionText, .highResolutionText, .superResolutionText] private let resolutionItems: [TUIVideoQuality] = [.quality360P, .quality540P, .quality720P, .quality1080P] private let bitrateArray = [550, 850, 1_200, 2_000] let topItems: [String] = [.videoText, .audioText, .otherText] let frameRateArray = ["15", "20"] init() { createVideoItem() createAudioItem() createOtherItem() } private func createVideoItem() { let resolutionItem = ListCellItemData() resolutionItem.titleText = .resolutionText resolutionItem.hasOverAllAction = true resolutionItem.type = .resolutionType resolutionItem.hasDownLineView = true resolutionItem.hasRightButton = true let buttonData = ButtonItemData() if let resolutionName = getResolutionName(videoQuality: videoSetting.videoQuality) { buttonData.normalTitle = resolutionName } buttonData.orientation = .right buttonData.normalIcon = "room_down_arrow1" buttonData.resourceBundle = tuiRoomKitBundle() buttonData.titleFont = UIFont.systemFont(ofSize: 16, weight: .regular) buttonData.titleColor = UIColor(0xD1D9EC) buttonData.size = CGSize(width: 80, height: 30) buttonData.isEnabled = false resolutionItem.buttonData = buttonData resolutionItem.action = { [weak self] sender in guard let self = self else { return } self.resolutionAction() } videoItems.append(resolutionItem) let frameRateItem = ListCellItemData() frameRateItem.titleText = .frameRateText frameRateItem.hasOverAllAction = true frameRateItem.type = .frameRateType frameRateItem.hasRightButton = true let frameRateButtonData = ButtonItemData() frameRateButtonData.orientation = .right frameRateButtonData.normalIcon = "room_down_arrow1" frameRateButtonData.normalTitle = String(videoSetting.videoFps) frameRateButtonData.resourceBundle = tuiRoomKitBundle() frameRateButtonData.titleFont = UIFont.systemFont(ofSize: 16, weight: .regular) frameRateButtonData.titleColor = UIColor(0xD1D9EC) frameRateButtonData.size = CGSize(width: 80, height: 30) frameRateButtonData.isEnabled = false frameRateItem.buttonData = frameRateButtonData frameRateItem.action = { [weak self] sender in guard let self = self else { return } self.frameRateAction() } videoItems.append(frameRateItem) } private func createAudioItem() { let captureVolumeItem = ListCellItemData() captureVolumeItem.titleText = .captureVolumeText captureVolumeItem.hasSlider = true captureVolumeItem.hasSliderLabel = true captureVolumeItem.minimumValue = 0 captureVolumeItem.maximumValue = 100 captureVolumeItem.sliderStep = 1 captureVolumeItem.sliderDefault = Float(audioSetting.captureVolume) captureVolumeItem.hasDownLineView = true captureVolumeItem.action = { [weak self] sender in guard let self = self, let view = sender as? UISlider else { return } self.captureVolumeAction(sender: view) } audioItems.append(captureVolumeItem) let playingVolumeItem = ListCellItemData() playingVolumeItem.titleText = .playVolumeText playingVolumeItem.hasSlider = true playingVolumeItem.hasSliderLabel = true playingVolumeItem.minimumValue = 0 playingVolumeItem.maximumValue = 100 playingVolumeItem.sliderStep = 1 playingVolumeItem.sliderDefault = Float(audioSetting.playVolume) playingVolumeItem.hasDownLineView = true playingVolumeItem.action = { [weak self] sender in guard let self = self, let view = sender as? UISlider else { return } self.playingVolumeAction(sender: view) } audioItems.append(playingVolumeItem) let volumePromptItem = ListCellItemData() volumePromptItem.titleText = .volumePromptText volumePromptItem.hasSwitch = true volumePromptItem.isSwitchOn = audioSetting.volumePrompt volumePromptItem.action = { [weak self] sender in guard let self = self, let view = sender as? UISwitch else { return } self.volumePromptAction(sender: view) } audioItems.append(volumePromptItem) } private func resolutionAction() { viewResponder?.showResolutionAlert() } private func createOtherItem() { let qualityItem = ListCellItemData() qualityItem.titleText = .qualityInspectionText qualityItem.hasOverAllAction = true qualityItem.hasRightButton = true let buttonData = ButtonItemData() buttonData.orientation = .right buttonData.normalIcon = "room_right_arrow1" buttonData.resourceBundle = tuiRoomKitBundle() buttonData.titleFont = UIFont.systemFont(ofSize: 16, weight: .regular) buttonData.titleColor = UIColor(0xD1D9EC) buttonData.size = CGSize(width: 80, height: 30) buttonData.isEnabled = false qualityItem.buttonData = buttonData qualityItem.action = { [weak self] sender in guard let self = self else { return } self.showQualityAction() } otherItems.append(qualityItem) let floatChatItem = ListCellItemData() floatChatItem.titleText = .floatChatText floatChatItem.hasSwitch = true floatChatItem.isSwitchOn = store.shouldShowFloatChatView floatChatItem.action = { [weak self] sender in guard let self = self, let view = sender as? UISwitch else { return } self.floatChatShowAction(shouldShow: view.isOn) } otherItems.append(floatChatItem) } private func showQualityAction() { viewResponder?.showQualityView() } private func floatChatShowAction(shouldShow: Bool) { store.updateFloatChatShowState(shouldShow: shouldShow) } private func frameRateAction() { viewResponder?.showFrameRateAlert() } private func captureVolumeAction(sender: UISlider) { engineManager.setAudioCaptureVolume(Int(sender.value)) } private func playingVolumeAction(sender: UISlider) { engineManager.setAudioPlayoutVolume(Int(sender.value)) } private func volumePromptAction(sender: UISwitch) { engineManager.enableAudioVolumeEvaluation(isVolumePrompt: sender.isOn) } func changeResolutionAction(index: Int) { guard let videoItem = videoItems.first(where: { $0.type == .resolutionType }) else { return } guard let quality = resolutionItems[safe: index] else { return } guard let resolutionName = getResolutionName(videoQuality: quality) else { return } videoItem.buttonData?.normalTitle = resolutionName viewResponder?.updateStackView(item: videoItem) engineManager.setVideoEncoder(videoQuality: quality, bitrate: getBitrate(videoQuality: quality)) } func changeFrameRateAction(index: Int) { guard let videoItem = videoItems.first(where: { $0.type == .frameRateType }) else { return } guard let frameRate = frameRateArray[safe: index] else { return } videoItem.buttonData?.normalTitle = frameRate viewResponder?.updateStackView(item: videoItem) engineManager.setVideoEncoder(fps: Int(frameRate)) } func getCurrentResolutionIndex() -> Int { guard let index = resolutionItems.firstIndex(where: { $0 == videoSetting.videoQuality }) else { return 0 } return index } func getCurrentFrameRateIndex() -> Int { let frameRateString = String(videoSetting.videoFps) guard let index = frameRateArray.firstIndex(where: { $0 == frameRateString }) else { return 0 } return index } private func getResolutionName(videoQuality: TUIVideoQuality) -> String? { guard let index = resolutionItems.firstIndex(of: videoQuality) else { return nil } guard let resolutionName = resolutionNameItems[safe: index] else { return nil } return resolutionName } private func getBitrate(videoQuality: TUIVideoQuality) -> Int? { guard let index = resolutionItems.firstIndex(of: videoQuality) else { return nil } guard let bitrate = bitrateArray[safe: index] else { return nil } return bitrate } deinit { debugPrint("deinit \(self)") } } private extension String { static var videoText: String { localized("Video Settings") } static var audioText: String { localized("Audio Settings") } static var otherText: String { localized("Other Settings") } static var versionLowToastText: String { localized("Your system version is below 12.0. Please update.") } static var resolutionText: String { localized("Resolution") } static var frameRateText: String { localized("Frame Rate") } static var bitrateText: String { localized("Bitrate") } static var localMirrorText: String { localized("Local Mirror") } static var captureVolumeText: String { localized("Capture Volume") } static var playVolumeText: String { localized("Playback Volume") } static var volumePromptText: String { localized("Volume Reminder") } static var audioRecordingText: String { localized("Audio Recording") } static var smoothResolutionText: String { localized("Smooth") } static var standardResolutionText: String { localized("Standard Definition") } static var highResolutionText: String { localized("High Definition") } static var superResolutionText: String { localized("Full High Definition") } static var qualityInspectionText: String { localized("Network Quality Monitoring") } static var floatChatText: String { localized("Floating Chat") } }