LocalAudioState.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. //
  2. // LocalAudioState.swift
  3. // Pods
  4. //
  5. // Created by janejntang on 2024/12/26.
  6. //
  7. import Foundation
  8. import Combine
  9. import RTCRoomEngine
  10. import TUICore
  11. struct LocalAudioState {
  12. var volume: Int = 0
  13. var hasAudio: Bool = false
  14. var isHidden: Bool = false
  15. }
  16. protocol LocalAudioStore {
  17. var localAudioState: LocalAudioState { get }
  18. func subscribe<Value>(_ selector: Selector<LocalAudioState, Value>) -> AnyPublisher<Value, Never>
  19. func muteLocalAudio()
  20. func unmuteLocalAudio()
  21. }
  22. class LocalAudioProvider: NSObject {
  23. private var selfUserId = TUILogin.getUserID() ?? ""
  24. private lazy var store: Store<LocalAudioState, ServiceCenter> = {
  25. let store = Store.init(initialState: LocalAudioState(), environment: ServiceCenter(), reducers: [LocalAudioStateUpdater])
  26. store.register(effects: localAudioEffects())
  27. return store
  28. }()
  29. private var cancellables = Set<AnyCancellable>()
  30. private(set) var roomEngine = TUIRoomEngine.sharedInstance()
  31. // TODO: remove shared RoomStore
  32. var roomStore: RoomStore {
  33. EngineManager.shared.store
  34. }
  35. private lazy var isSeatEnabled = {
  36. self.roomStore.roomInfo.isSeatEnabled
  37. }()
  38. private lazy var isSelfOnSeat = {
  39. self.roomStore.currentUser.isOnSeat
  40. }()
  41. private lazy var selfRole: TUIRole = {
  42. self.roomStore.currentUser.userRole
  43. }()
  44. override init() {
  45. super.init()
  46. roomEngine.addObserver(self)
  47. updateHasAudio(roomStore.currentUser.hasAudioStream)
  48. }
  49. deinit {
  50. roomEngine.removeObserver(self)
  51. store.unregisterEffects(withId: localAudioEffects.id)
  52. store.unregister(reducer: LocalAudioStateUpdater)
  53. }
  54. func updateVolume(_ volume: Int) {
  55. store.dispatch(action: LocalAudioActions.updateVolume(payload: volume))
  56. }
  57. func updateHasAudio(_ hasAudio: Bool) {
  58. store.dispatch(action: LocalAudioActions.updateHasAudio(payload: hasAudio))
  59. }
  60. func updateIsHidden(_ enableShow: Bool) {
  61. store.dispatch(action: LocalAudioActions.updateIsHidden(payload: enableShow))
  62. }
  63. }
  64. extension LocalAudioProvider: LocalAudioStore {
  65. var localAudioState: LocalAudioState {
  66. return store.state
  67. }
  68. func subscribe<Value>(_ selector: Selector<LocalAudioState, Value>) -> AnyPublisher<Value, Never> {
  69. return store.select(selector)
  70. }
  71. func muteLocalAudio() {
  72. store.dispatch(action: LocalAudioActions.muteLocalAudio())
  73. }
  74. func unmuteLocalAudio() {
  75. store.dispatch(action: LocalAudioActions.unmuteLocalAudio(payload: roomStore))
  76. }
  77. }
  78. enum LocalAudioActions {
  79. static let key = "local.audio.action"
  80. static let updateVolume = ActionTemplate(id: key.appending(".updateVolume"), payloadType: Int.self)
  81. static let updateHasAudio = ActionTemplate(id: key.appending(".updateHasAudio"), payloadType: Bool.self)
  82. static let updateIsHidden = ActionTemplate(id: key.appending(".updateIsHidden"), payloadType: Bool.self)
  83. static let muteLocalAudio = ActionTemplate(id: key.appending(".muteLocalAudio"))
  84. static let unmuteLocalAudio = ActionTemplate(id: key.appending(".unmuteLocalAudio"), payloadType: RoomStore.self)
  85. }
  86. let LocalAudioStateUpdater = Reducer<LocalAudioState> (
  87. ReduceOn(LocalAudioActions.updateVolume, reduce: { state, action in
  88. state.volume = action.payload
  89. }),
  90. ReduceOn(LocalAudioActions.updateHasAudio, reduce: { state, action in
  91. state.hasAudio = action.payload
  92. }),
  93. ReduceOn(LocalAudioActions.updateIsHidden, reduce: { state, action in
  94. state.isHidden = action.payload
  95. })
  96. )
  97. extension LocalAudioProvider: TUIRoomObserver {
  98. func onUserAudioStateChanged(userId: String, hasAudio: Bool, reason: TUIChangeReason) {
  99. guard userId == selfUserId else { return }
  100. updateHasAudio(hasAudio)
  101. }
  102. func onUserVoiceVolumeChanged(volumeMap: [String : NSNumber]) {
  103. let selfVolume = volumeMap[selfUserId] ?? 0
  104. updateVolume(Int(truncating: selfVolume))
  105. }
  106. func onUserInfoChanged(userInfo: TUIUserInfo, modifyFlag: TUIUserInfoModifyFlag) {
  107. guard modifyFlag.contains(.userRole) else { return }
  108. guard userInfo.userId == selfUserId else { return }
  109. self.selfRole = userInfo.userRole
  110. updateIsHidden(checkIsHidden())
  111. }
  112. func onSeatListChanged(seatList: [TUISeatInfo], seated seatedList: [TUISeatInfo], left leftList: [TUISeatInfo]) {
  113. isSelfOnSeat = seatList.contains(where: { $0.userId == selfUserId })
  114. updateIsHidden(checkIsHidden())
  115. }
  116. private func checkIsHidden() -> Bool {
  117. return isSeatEnabled && selfRole == .generalUser && !isSelfOnSeat
  118. }
  119. }
  120. class localAudioEffects: Effects {
  121. typealias Environment = ServiceCenter
  122. let muteLocalAudio = Effect<Environment>.nonDispatching { actions, environment in
  123. actions
  124. .wasCreated(from: LocalAudioActions.muteLocalAudio)
  125. .sink { action in
  126. environment.audioService.muteLocalAudio()
  127. }
  128. }
  129. let unmuteLocalAudio = Effect<Environment>.nonDispatching { actions, environment in
  130. actions
  131. .wasCreated(from: LocalAudioActions.unmuteLocalAudio)
  132. .sink { action in
  133. let roomInfo = action.payload.roomInfo
  134. let currentUser = action.payload.currentUser
  135. if roomInfo.isMicrophoneDisableForAllUser && currentUser.userId != roomInfo.ownerId {
  136. environment.store?.dispatch(action: ViewActions.showToast(payload: ToastInfo(message: .muteAudioRoomReasonText)))
  137. return
  138. }
  139. if roomInfo.isSeatEnabled, !currentUser.isOnSeat {
  140. environment.store?.dispatch(action: ViewActions.showToast(payload: ToastInfo(message: .muteSeatReasonText)))
  141. return
  142. }
  143. _ = environment.audioService.unmuteLocalAudio()
  144. guard !action.payload.audioSetting.isMicOpened else { return }
  145. _ = environment.audioService.openLocalMicrophone()
  146. }
  147. }
  148. }
  149. private extension String {
  150. static var muteAudioRoomReasonText: String {
  151. localized("All on mute audio, unable to turn on microphone")
  152. }
  153. static var muteSeatReasonText: String {
  154. localized("Can be turned on after taking the stage")
  155. }
  156. }