Merge pull request from vector-im/feature/fre/voice_broadcast_live_indicator

Voice Broadcast - Improve live indicator icon rendering
This commit is contained in:
Florian Renaud 2022-11-15 17:25:38 +01:00 committed by GitHub
commit 4ac9c8d0e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 154 additions and 45 deletions

1
changelog.d/7579.wip Normal file
View file

@ -0,0 +1 @@
[Voice Broadcast] Improve the live indicator icon rendering in the timeline

View file

@ -68,25 +68,26 @@ abstract class AbsMessageVoiceBroadcastItem<H : AbsMessageVoiceBroadcastItem.Hol
renderMetadata(holder) renderMetadata(holder)
} }
private fun renderLiveIndicator(holder: H) { abstract fun renderLiveIndicator(holder: H)
protected fun renderPlayingLiveIndicator(holder: H) {
with(holder) { with(holder) {
when (voiceBroadcastState) { liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorError))
VoiceBroadcastState.STARTED, liveIndicator.isVisible = true
VoiceBroadcastState.RESUMED -> {
liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorError))
liveIndicator.isVisible = true
}
VoiceBroadcastState.PAUSED -> {
liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary))
liveIndicator.isVisible = true
}
VoiceBroadcastState.STOPPED, null -> {
liveIndicator.isVisible = false
}
}
} }
} }
protected fun renderPausedLiveIndicator(holder: H) {
with(holder) {
liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary))
liveIndicator.isVisible = true
}
}
protected fun renderNoLiveIndicator(holder: H) {
holder.liveIndicator.isVisible = false
}
abstract fun renderMetadata(holder: H) abstract fun renderMetadata(holder: H)
abstract class Holder(@IdRes stubId: Int) : AbsMessageItem.Holder(stubId) { abstract class Holder(@IdRes stubId: Int) : AbsMessageItem.Holder(stubId) {

View file

@ -29,6 +29,7 @@ import im.vector.app.core.epoxy.onClick
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
@EpoxyModelClass @EpoxyModelClass
@ -43,7 +44,15 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
} }
private fun bindVoiceBroadcastItem(holder: Holder) { private fun bindVoiceBroadcastItem(holder: Holder) {
playerListener = VoiceBroadcastPlayer.Listener { renderPlayingState(holder, it) } playerListener = object : VoiceBroadcastPlayer.Listener {
override fun onPlayingStateChanged(state: VoiceBroadcastPlayer.State) {
renderPlayingState(holder, state)
}
override fun onLiveModeChanged(isLive: Boolean) {
renderLiveIndicator(holder)
}
}
player.addListener(voiceBroadcast, playerListener) player.addListener(voiceBroadcast, playerListener)
bindSeekBar(holder) bindSeekBar(holder)
bindButtons(holder) bindButtons(holder)
@ -51,7 +60,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
private fun bindButtons(holder: Holder) { private fun bindButtons(holder: Holder) {
with(holder) { with(holder) {
playPauseButton.onClick { playPauseButton.setOnClickListener {
if (player.currentVoiceBroadcast == voiceBroadcast) { if (player.currentVoiceBroadcast == voiceBroadcast) {
when (player.playingState) { when (player.playingState) {
VoiceBroadcastPlayer.State.PLAYING -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) VoiceBroadcastPlayer.State.PLAYING -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause)
@ -63,11 +72,11 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast))
} }
} }
fastBackwardButton.onClick { fastBackwardButton.setOnClickListener {
val newPos = seekBar.progress.minus(30_000).coerceIn(0, duration) val newPos = seekBar.progress.minus(30_000).coerceIn(0, duration)
callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, newPos, duration)) callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, newPos, duration))
} }
fastForwardButton.onClick { fastForwardButton.setOnClickListener {
val newPos = seekBar.progress.plus(30_000).coerceIn(0, duration) val newPos = seekBar.progress.plus(30_000).coerceIn(0, duration)
callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, newPos, duration)) callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, newPos, duration))
} }
@ -82,6 +91,14 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
} }
} }
override fun renderLiveIndicator(holder: Holder) {
when {
voiceBroadcastState == null || voiceBroadcastState == VoiceBroadcastState.STOPPED -> renderNoLiveIndicator(holder)
voiceBroadcastState == VoiceBroadcastState.PAUSED || !player.isLiveListening -> renderPausedLiveIndicator(holder)
else -> renderPlayingLiveIndicator(holder)
}
}
private fun renderPlayingState(holder: Holder, state: VoiceBroadcastPlayer.State) { private fun renderPlayingState(holder: Holder, state: VoiceBroadcastPlayer.State) {
with(holder) { with(holder) {
bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING
@ -99,6 +116,8 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
} }
VoiceBroadcastPlayer.State.BUFFERING -> Unit VoiceBroadcastPlayer.State.BUFFERING -> Unit
} }
renderLiveIndicator(holder)
} }
} }
@ -121,6 +140,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
} }
playbackTracker.track(voiceBroadcast.voiceBroadcastId) { playbackState -> playbackTracker.track(voiceBroadcast.voiceBroadcastId) { playbackState ->
renderBackwardForwardButtons(holder, playbackState) renderBackwardForwardButtons(holder, playbackState)
renderLiveIndicator(holder)
if (!isUserSeeking) { if (!isUserSeeking) {
holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId)
} }
@ -143,7 +163,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
player.removeListener(voiceBroadcast, playerListener) player.removeListener(voiceBroadcast, playerListener)
playbackTracker.untrack(voiceBroadcast.voiceBroadcastId) playbackTracker.untrack(voiceBroadcast.voiceBroadcastId)
with(holder) { with(holder) {
seekBar.onClick(null) seekBar.setOnSeekBarChangeListener(null)
playPauseButton.onClick(null) playPauseButton.onClick(null)
fastForwardButton.onClick(null) fastForwardButton.onClick(null)
fastBackwardButton.onClick(null) fastBackwardButton.onClick(null)

View file

@ -48,6 +48,15 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
} }
} }
override fun renderLiveIndicator(holder: Holder) {
when (voiceBroadcastState) {
VoiceBroadcastState.STARTED,
VoiceBroadcastState.RESUMED -> renderPlayingLiveIndicator(holder)
VoiceBroadcastState.PAUSED -> renderPausedLiveIndicator(holder)
VoiceBroadcastState.STOPPED, null -> renderNoLiveIndicator(holder)
}
}
override fun renderMetadata(holder: Holder) { override fun renderMetadata(holder: Holder) {
with(holder) { with(holder) {
listenersCountMetadata.isVisible = false listenersCountMetadata.isVisible = false
@ -104,6 +113,10 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
super.unbind(holder) super.unbind(holder)
recorderListener?.let { recorder?.removeListener(it) } recorderListener?.let { recorder?.removeListener(it) }
recorderListener = null recorderListener = null
with(holder) {
recordButton.onClick(null)
stopRecordButton.onClick(null)
}
} }
override fun getViewStubId() = STUB_ID override fun getViewStubId() = STUB_ID

View file

@ -39,6 +39,9 @@ val MessageAudioEvent.sequence: Int? get() = getVoiceBroadcastChunk()?.sequence
val MessageAudioEvent.duration get() = content.audioInfo?.duration ?: content.audioWaveformInfo?.duration ?: 0 val MessageAudioEvent.duration get() = content.audioInfo?.duration ?: content.audioWaveformInfo?.duration ?: 0
val VoiceBroadcastEvent.voiceBroadcastId
get() = reference?.eventId
val VoiceBroadcastEvent.isLive val VoiceBroadcastEvent.isLive
get() = content?.isLive.orFalse() get() = content?.isLive.orFalse()

View file

@ -30,6 +30,11 @@ interface VoiceBroadcastPlayer {
*/ */
val playingState: State val playingState: State
/**
* Tells whether the player is listening a live voice broadcast in "live" position.
*/
val isLiveListening: Boolean
/** /**
* Start playback of the given voice broadcast. * Start playback of the given voice broadcast.
*/ */
@ -73,10 +78,15 @@ interface VoiceBroadcastPlayer {
/** /**
* Listener related to [VoiceBroadcastPlayer]. * Listener related to [VoiceBroadcastPlayer].
*/ */
fun interface Listener { interface Listener {
/** /**
* Notify about [VoiceBroadcastPlayer.playingState] changes. * Notify about [VoiceBroadcastPlayer.playingState] changes.
*/ */
fun onStateChanged(state: State) fun onPlayingStateChanged(state: State) = Unit
/**
* Notify about [VoiceBroadcastPlayer.isLiveListening] changes.
*/
fun onLiveModeChanged(isLive: Boolean) = Unit
} }
} }

View file

@ -30,7 +30,6 @@ import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Stat
import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
import im.vector.app.features.voicebroadcast.sequence
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastEventUseCase import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastEventUseCase
import im.vector.lib.core.utils.timer.CountUpTimer import im.vector.lib.core.utils.timer.CountUpTimer
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -70,18 +69,27 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
private var currentVoiceBroadcastEvent: VoiceBroadcastEvent? = null private var currentVoiceBroadcastEvent: VoiceBroadcastEvent? = null
override var currentVoiceBroadcast: VoiceBroadcast? = null override var currentVoiceBroadcast: VoiceBroadcast? = null
override var isLiveListening: Boolean = false
@MainThread
set(value) {
if (field != value) {
Timber.w("isLiveListening: $field -> $value")
field = value
onLiveListeningChanged(value)
}
}
override var playingState = State.IDLE override var playingState = State.IDLE
@MainThread @MainThread
set(value) { set(value) {
if (field != value) { if (field != value) {
Timber.w("## VoiceBroadcastPlayer state: $field -> $value") Timber.w("playingState: $field -> $value")
field = value field = value
onPlayingStateChanged(value) onPlayingStateChanged(value)
} }
} }
/** Map voiceBroadcastId to listeners.*/ /** Map voiceBroadcastId to listeners. */
private val listeners: MutableMap<String, CopyOnWriteArrayList<Listener>> = mutableMapOf() private val listeners: MutableMap<String, CopyOnWriteArrayList<Listener>> = mutableMapOf()
override fun playOrResume(voiceBroadcast: VoiceBroadcast) { override fun playOrResume(voiceBroadcast: VoiceBroadcast) {
@ -121,7 +129,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
listeners[voiceBroadcast.voiceBroadcastId]?.add(listener) ?: run { listeners[voiceBroadcast.voiceBroadcastId]?.add(listener) ?: run {
listeners[voiceBroadcast.voiceBroadcastId] = CopyOnWriteArrayList<Listener>().apply { add(listener) } listeners[voiceBroadcast.voiceBroadcastId] = CopyOnWriteArrayList<Listener>().apply { add(listener) }
} }
listener.onStateChanged(if (voiceBroadcast == currentVoiceBroadcast) playingState else State.IDLE) listener.onPlayingStateChanged(if (voiceBroadcast == currentVoiceBroadcast) playingState else State.IDLE)
listener.onLiveModeChanged(voiceBroadcast == currentVoiceBroadcast && isLiveListening)
} }
override fun removeListener(voiceBroadcast: VoiceBroadcast, listener: Listener) { override fun removeListener(voiceBroadcast: VoiceBroadcast, listener: Listener) {
@ -142,7 +151,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
private fun observeVoiceBroadcastLiveState(voiceBroadcast: VoiceBroadcast) { private fun observeVoiceBroadcastLiveState(voiceBroadcast: VoiceBroadcast) {
voiceBroadcastStateObserver = getVoiceBroadcastEventUseCase.execute(voiceBroadcast) voiceBroadcastStateObserver = getVoiceBroadcastEventUseCase.execute(voiceBroadcast)
.onEach { currentVoiceBroadcastEvent = it.getOrNull() } .onEach {
currentVoiceBroadcastEvent = it.getOrNull()
updateLiveListeningMode()
}
.launchIn(sessionScope) .launchIn(sessionScope)
} }
@ -190,7 +202,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
else -> playlist.firstOrNull() else -> playlist.firstOrNull()
} }
val content = playlistItem?.audioEvent?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return } val content = playlistItem?.audioEvent?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return }
val sequence = playlistItem.audioEvent.sequence ?: run { Timber.w("## VoiceBroadcastPlayer: playlist item has no sequence"); return } val sequence = playlistItem.sequence ?: run { Timber.w("## VoiceBroadcastPlayer: playlist item has no sequence"); return }
val sequencePosition = position?.let { it - playlistItem.startTime } ?: 0 val sequencePosition = position?.let { it - playlistItem.startTime } ?: 0
sessionScope.launch { sessionScope.launch {
try { try {
@ -241,6 +253,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration)
} }
playingState == State.PLAYING || playingState == State.BUFFERING -> { playingState == State.PLAYING || playingState == State.BUFFERING -> {
updateLiveListeningMode(positionMillis)
startPlayback(positionMillis) startPlayback(positionMillis)
} }
playingState == State.IDLE || playingState == State.PAUSED -> { playingState == State.IDLE || playingState == State.PAUSED -> {
@ -302,15 +315,57 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
} }
private fun onPlayingStateChanged(playingState: State) { private fun onPlayingStateChanged(playingState: State) {
// Notify state change to all the listeners attached to the current voice broadcast id // Update live playback flag
updateLiveListeningMode()
currentVoiceBroadcast?.voiceBroadcastId?.let { voiceBroadcastId -> currentVoiceBroadcast?.voiceBroadcastId?.let { voiceBroadcastId ->
// Start or stop playback ticker
when (playingState) { when (playingState) {
State.PLAYING -> playbackTicker.startPlaybackTicker(voiceBroadcastId) State.PLAYING -> playbackTicker.startPlaybackTicker(voiceBroadcastId)
State.PAUSED, State.PAUSED,
State.BUFFERING, State.BUFFERING,
State.IDLE -> playbackTicker.stopPlaybackTicker(voiceBroadcastId) State.IDLE -> playbackTicker.stopPlaybackTicker(voiceBroadcastId)
} }
listeners[voiceBroadcastId]?.forEach { listener -> listener.onStateChanged(playingState) } // Notify state change to all the listeners attached to the current voice broadcast id
listeners[voiceBroadcastId]?.forEach { listener -> listener.onPlayingStateChanged(playingState) }
}
}
/**
* Update the live listening state according to:
* - the voice broadcast state (started/paused/resumed/stopped),
* - the playing state (IDLE, PLAYING, PAUSED, BUFFERING),
* - the potential seek position (backward/forward).
*/
private fun updateLiveListeningMode(seekPosition: Int? = null) {
isLiveListening = when {
// the current voice broadcast is not live (ended)
currentVoiceBroadcastEvent?.isLive?.not().orFalse() -> false
// the player is stopped or paused
playingState == State.IDLE || playingState == State.PAUSED -> false
seekPosition != null -> {
val seekDirection = seekPosition.compareTo(getCurrentPlaybackPosition() ?: 0)
val newSequence = playlist.findByPosition(seekPosition)?.sequence
// the user has sought forward
if (seekDirection >= 0) {
// stay in live or latest sequence reached
isLiveListening || newSequence == playlist.lastOrNull()?.sequence
}
// the user has sought backward
else {
// was in live and stay in the same sequence
isLiveListening && newSequence == playlist.currentSequence
}
}
// otherwise, stay in live or go in live if we reached the latest sequence
else -> isLiveListening || playlist.currentSequence == playlist.lastOrNull()?.sequence
}
}
private fun onLiveListeningChanged(isLiveListening: Boolean) {
currentVoiceBroadcast?.voiceBroadcastId?.let { voiceBroadcastId ->
// Notify live mode change to all the listeners attached to the current voice broadcast id
listeners[voiceBroadcastId]?.forEach { listener -> listener.onLiveModeChanged(isLiveListening) }
} }
} }

View file

@ -56,7 +56,7 @@ class VoiceBroadcastPlaylist(
} }
fun findBySequence(sequenceNumber: Int): PlaylistItem? { fun findBySequence(sequenceNumber: Int): PlaylistItem? {
return items.find { it.audioEvent.sequence == sequenceNumber } return items.find { it.sequence == sequenceNumber }
} }
fun getNextItem() = findBySequence(currentSequence?.plus(1) ?: 1) fun getNextItem() = findBySequence(currentSequence?.plus(1) ?: 1)
@ -64,4 +64,7 @@ class VoiceBroadcastPlaylist(
fun firstOrNull() = findBySequence(1) fun firstOrNull() = findBySequence(1)
} }
data class PlaylistItem(val audioEvent: MessageAudioEvent, val startTime: Int) data class PlaylistItem(val audioEvent: MessageAudioEvent, val startTime: Int) {
val sequence: Int?
get() = audioEvent.sequence
}

View file

@ -25,6 +25,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import im.vector.app.features.voicebroadcast.sequence import im.vector.app.features.voicebroadcast.sequence
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastEventUseCase import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastEventUseCase
import im.vector.app.features.voicebroadcast.voiceBroadcastId
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
@ -73,14 +74,15 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor(
// Observe new timeline events // Observe new timeline events
val listener = object : Timeline.Listener { val listener = object : Timeline.Listener {
private var lastEventId: String? = null private var latestEventId: String? = null
private var lastSequence: Int? = null private var lastSequence: Int? = null
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) { override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val newEvents = lastEventId?.let { eventId -> snapshot.subList(0, snapshot.indexOfFirst { it.eventId == eventId }) } ?: snapshot val latestEventIndex = latestEventId?.let { eventId -> snapshot.indexOfFirst { it.eventId == eventId } }
val newEvents = if (latestEventIndex != null) snapshot.subList(0, latestEventIndex) else snapshot
// Detect a potential stopped voice broadcast state event // Detect a potential stopped voice broadcast state event
val stopEvent = newEvents.findStopEvent() val stopEvent = newEvents.findStopEvent(voiceBroadcast)
if (stopEvent != null) { if (stopEvent != null) {
lastSequence = stopEvent.content?.lastChunkSequence lastSequence = stopEvent.content?.lastChunkSequence
} }
@ -98,7 +100,7 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor(
timeline.dispose() timeline.dispose()
} }
lastEventId = snapshot.firstOrNull()?.eventId latestEventId = snapshot.firstOrNull()?.eventId
} }
} }
@ -117,8 +119,8 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor(
/** /**
* Find a [VoiceBroadcastEvent] with a [VoiceBroadcastState.STOPPED] state. * Find a [VoiceBroadcastEvent] with a [VoiceBroadcastState.STOPPED] state.
*/ */
private fun List<TimelineEvent>.findStopEvent(): VoiceBroadcastEvent? = private fun List<TimelineEvent>.findStopEvent(voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? =
this.mapNotNull { it.root.asVoiceBroadcastEvent() } this.mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent()?.takeIf { it.voiceBroadcastId == voiceBroadcast.voiceBroadcastId } }
.find { it.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED } .find { it.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED }
/** /**

View file

@ -21,11 +21,12 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import im.vector.app.features.voicebroadcast.voiceBroadcastId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.mapNotNull
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.RelationType
@ -33,7 +34,7 @@ import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.flow.mapOptional
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -57,10 +58,10 @@ class GetVoiceBroadcastEventUseCase @Inject constructor(
else -> { else -> {
room.flow() room.flow()
.liveStateEvent(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(latestEvent.root.stateKey.orEmpty())) .liveStateEvent(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(latestEvent.root.stateKey.orEmpty()))
.unwrap() .onStart { emit(latestEvent.root.toOptional()) }
.mapNotNull { it.asVoiceBroadcastEvent() } .distinctUntilChanged()
.filter { it.reference?.eventId == voiceBroadcast.voiceBroadcastId } .filter { !it.hasValue() || it.getOrNull()?.asVoiceBroadcastEvent()?.voiceBroadcastId == voiceBroadcast.voiceBroadcastId }
.map { it.toOptional() } .mapOptional { it.asVoiceBroadcastEvent() }
} }
} }
} }