Fix bug where primary speaker wasn't reset after they leave room
正在显示
2 个修改的文件
包含
53 行增加
和
20 行删除
| @@ -66,7 +66,7 @@ class LiveKit { | @@ -66,7 +66,7 @@ class LiveKit { | ||
| 66 | token: String, | 66 | token: String, |
| 67 | options: ConnectOptions = ConnectOptions(), | 67 | options: ConnectOptions = ConnectOptions(), |
| 68 | roomOptions: RoomOptions = RoomOptions(), | 68 | roomOptions: RoomOptions = RoomOptions(), |
| 69 | - listener: RoomListener? | 69 | + listener: RoomListener? = null |
| 70 | ): Room { | 70 | ): Room { |
| 71 | val room = create(appContext, roomOptions) | 71 | val room = create(appContext, roomOptions) |
| 72 | 72 |
| @@ -9,7 +9,7 @@ import io.livekit.android.RoomOptions | @@ -9,7 +9,7 @@ import io.livekit.android.RoomOptions | ||
| 9 | import io.livekit.android.events.RoomEvent | 9 | import io.livekit.android.events.RoomEvent |
| 10 | import io.livekit.android.events.collect | 10 | import io.livekit.android.events.collect |
| 11 | import io.livekit.android.room.Room | 11 | import io.livekit.android.room.Room |
| 12 | -import io.livekit.android.room.RoomListener | 12 | +import io.livekit.android.room.participant.LocalParticipant |
| 13 | import io.livekit.android.room.participant.Participant | 13 | import io.livekit.android.room.participant.Participant |
| 14 | import io.livekit.android.room.participant.RemoteParticipant | 14 | import io.livekit.android.room.participant.RemoteParticipant |
| 15 | import io.livekit.android.room.track.* | 15 | import io.livekit.android.room.track.* |
| @@ -23,7 +23,7 @@ class CallViewModel( | @@ -23,7 +23,7 @@ class CallViewModel( | ||
| 23 | val url: String, | 23 | val url: String, |
| 24 | val token: String, | 24 | val token: String, |
| 25 | application: Application | 25 | application: Application |
| 26 | -) : AndroidViewModel(application), RoomListener { | 26 | +) : AndroidViewModel(application) { |
| 27 | private val mutableRoom = MutableStateFlow<Room?>(null) | 27 | private val mutableRoom = MutableStateFlow<Room?>(null) |
| 28 | val room: MutableStateFlow<Room?> = mutableRoom | 28 | val room: MutableStateFlow<Room?> = mutableRoom |
| 29 | val participants = mutableRoom.flatMapLatest { room -> | 29 | val participants = mutableRoom.flatMapLatest { room -> |
| @@ -84,8 +84,7 @@ class CallViewModel( | @@ -84,8 +84,7 @@ class CallViewModel( | ||
| 84 | application, | 84 | application, |
| 85 | url, | 85 | url, |
| 86 | token, | 86 | token, |
| 87 | - roomOptions = RoomOptions(adaptiveStream = true), | ||
| 88 | - listener = this@CallViewModel | 87 | + roomOptions = RoomOptions(adaptiveStream = true, dynacast = true), |
| 89 | ) | 88 | ) |
| 90 | 89 | ||
| 91 | // Create and publish audio/video tracks | 90 | // Create and publish audio/video tracks |
| @@ -99,7 +98,18 @@ class CallViewModel( | @@ -99,7 +98,18 @@ class CallViewModel( | ||
| 99 | 98 | ||
| 100 | mutablePrimarySpeaker.value = room.remoteParticipants.values.firstOrNull() ?: localParticipant | 99 | mutablePrimarySpeaker.value = room.remoteParticipants.values.firstOrNull() ?: localParticipant |
| 101 | 100 | ||
| 102 | - viewModelScope.launch { | 101 | + launch { |
| 102 | + combine(participants, activeSpeakers) { participants, speakers -> participants to speakers } | ||
| 103 | + .collect { (participantsList, speakers) -> | ||
| 104 | + handlePrimarySpeaker( | ||
| 105 | + participantsList, | ||
| 106 | + speakers, | ||
| 107 | + room | ||
| 108 | + ) | ||
| 109 | + } | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + launch { | ||
| 103 | room.events.collect { | 113 | room.events.collect { |
| 104 | when (it) { | 114 | when (it) { |
| 105 | is RoomEvent.FailedToConnect -> mutableError.value = it.error | 115 | is RoomEvent.FailedToConnect -> mutableError.value = it.error |
| @@ -117,6 +127,43 @@ class CallViewModel( | @@ -117,6 +127,43 @@ class CallViewModel( | ||
| 117 | } | 127 | } |
| 118 | } | 128 | } |
| 119 | 129 | ||
| 130 | + private fun handlePrimarySpeaker(participantsList: List<Participant>, speakers: List<Participant>, room: Room) { | ||
| 131 | + | ||
| 132 | + var speaker = mutablePrimarySpeaker.value | ||
| 133 | + | ||
| 134 | + // If speaker is local participant (due to defaults), | ||
| 135 | + // attempt to find another remote speaker to replace with. | ||
| 136 | + if (speaker is LocalParticipant) { | ||
| 137 | + val remoteSpeaker = participantsList | ||
| 138 | + .filterIsInstance<RemoteParticipant>() // Try not to display local participant as speaker. | ||
| 139 | + .firstOrNull() | ||
| 140 | + | ||
| 141 | + if (remoteSpeaker != null) { | ||
| 142 | + speaker = remoteSpeaker | ||
| 143 | + } | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + // If previous primary speaker leaves | ||
| 147 | + if (!participantsList.contains(speaker)) { | ||
| 148 | + // Default to another person in room, or local participant. | ||
| 149 | + speaker = participantsList.filterIsInstance<RemoteParticipant>() | ||
| 150 | + .firstOrNull() | ||
| 151 | + ?: room.localParticipant | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + if (speakers.isNotEmpty() && !speakers.contains(speaker)) { | ||
| 155 | + val remoteSpeaker = speakers | ||
| 156 | + .filterIsInstance<RemoteParticipant>() // Try not to display local participant as speaker. | ||
| 157 | + .firstOrNull() | ||
| 158 | + | ||
| 159 | + if (remoteSpeaker != null) { | ||
| 160 | + speaker = remoteSpeaker | ||
| 161 | + } | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + mutablePrimarySpeaker.value = speaker | ||
| 165 | + } | ||
| 166 | + | ||
| 120 | fun startScreenCapture(mediaProjectionPermissionResultData: Intent) { | 167 | fun startScreenCapture(mediaProjectionPermissionResultData: Intent) { |
| 121 | val localParticipant = room.value?.localParticipant ?: return | 168 | val localParticipant = room.value?.localParticipant ?: return |
| 122 | viewModelScope.launch { | 169 | viewModelScope.launch { |
| @@ -150,20 +197,6 @@ class CallViewModel( | @@ -150,20 +197,6 @@ class CallViewModel( | ||
| 150 | mutableRoom.value?.disconnect() | 197 | mutableRoom.value?.disconnect() |
| 151 | } | 198 | } |
| 152 | 199 | ||
| 153 | - override fun onDisconnect(room: Room, error: Exception?) { | ||
| 154 | - } | ||
| 155 | - | ||
| 156 | - override fun onActiveSpeakersChanged(speakers: List<Participant>, room: Room) { | ||
| 157 | - // If old active speaker is still active, don't change. | ||
| 158 | - if (speakers.isEmpty() || speakers.contains(mutablePrimarySpeaker.value)) { | ||
| 159 | - return | ||
| 160 | - } | ||
| 161 | - val newSpeaker = speakers | ||
| 162 | - .filter { it is RemoteParticipant } // Try not to display local participant as speaker. | ||
| 163 | - .firstOrNull() ?: return | ||
| 164 | - mutablePrimarySpeaker.value = newSpeaker | ||
| 165 | - } | ||
| 166 | - | ||
| 167 | fun setMicEnabled(enabled: Boolean) { | 200 | fun setMicEnabled(enabled: Boolean) { |
| 168 | viewModelScope.launch { | 201 | viewModelScope.launch { |
| 169 | val localParticipant = room.value?.localParticipant ?: return@launch | 202 | val localParticipant = room.value?.localParticipant ?: return@launch |
-
请 注册 或 登录 后发表评论