From 6ee1e869513c7b2237d9e7e93119adc86142a508 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 10 Nov 2022 00:44:05 +0100 Subject: [PATCH 01/11] Improve live indicator --- .../item/AbsMessageVoiceBroadcastItem.kt | 31 ++++++++++--------- .../MessageVoiceBroadcastListeningItem.kt | 12 +++++++ .../MessageVoiceBroadcastRecordingItem.kt | 9 ++++++ .../listening/VoiceBroadcastPlayer.kt | 5 +++ .../listening/VoiceBroadcastPlayerImpl.kt | 25 ++++++++++++--- .../listening/VoiceBroadcastPlaylist.kt | 7 +++-- 6 files changed, 68 insertions(+), 21 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt index 0329adf12b..c6b90cdabe 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt @@ -68,25 +68,26 @@ abstract class AbsMessageVoiceBroadcastItem { - 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 - } - } + liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorError)) + liveIndicator.isVisible = true } } + 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 class Holder(@IdRes stubId: Int) : AbsMessageItem.Holder(stubId) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index 4b91bbfb0e..b114f95f97 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -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.timeline.helper.AudioMessagePlaybackTracker.Listener.State import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView @EpoxyModelClass @@ -82,6 +83,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) { with(holder) { bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING @@ -99,6 +108,8 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } VoiceBroadcastPlayer.State.BUFFERING -> Unit } + + renderLiveIndicator(holder) } } @@ -121,6 +132,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } playbackTracker.track(voiceBroadcast.voiceBroadcastId) { playbackState -> renderBackwardForwardButtons(holder, playbackState) + renderLiveIndicator(holder) if (!isUserSeeking) { holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt index 17aa1543c0..ed77452382 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -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) { with(holder) { listenersCountMetadata.isVisible = false diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt index 8c11db4f43..02e843965f 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt @@ -30,6 +30,11 @@ interface VoiceBroadcastPlayer { */ 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. */ diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 6a6dc6a9e8..573a178c78 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -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.model.VoiceBroadcast 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.lib.core.utils.timer.CountUpTimer import kotlinx.coroutines.Job @@ -70,6 +69,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private var currentVoiceBroadcastEvent: VoiceBroadcastEvent? = null override var currentVoiceBroadcast: VoiceBroadcast? = null + override var isLiveListening: Boolean = false override var playingState = State.IDLE @MainThread @@ -142,7 +142,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun observeVoiceBroadcastLiveState(voiceBroadcast: VoiceBroadcast) { voiceBroadcastStateObserver = getVoiceBroadcastEventUseCase.execute(voiceBroadcast) - .onEach { currentVoiceBroadcastEvent = it.getOrNull() } + .onEach { + currentVoiceBroadcastEvent = it.getOrNull() + updateLiveListeningMode() + } .launchIn(sessionScope) } @@ -190,7 +193,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( else -> playlist.firstOrNull() } 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 sessionScope.launch { try { @@ -241,6 +244,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) } playingState == State.PLAYING || playingState == State.BUFFERING -> { + updateLiveListeningMode(positionMillis) startPlayback(positionMillis) } playingState == State.IDLE || playingState == State.PAUSED -> { @@ -302,18 +306,31 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } 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 -> + // Start or stop playback ticker when (playingState) { State.PLAYING -> playbackTicker.startPlaybackTicker(voiceBroadcastId) State.PAUSED, State.BUFFERING, State.IDLE -> playbackTicker.stopPlaybackTicker(voiceBroadcastId) } + // Notify state change to all the listeners attached to the current voice broadcast id listeners[voiceBroadcastId]?.forEach { listener -> listener.onStateChanged(playingState) } } } + private fun updateLiveListeningMode(playbackPosition: Int? = null) { + isLiveListening = when { + !currentVoiceBroadcastEvent?.isLive.orFalse() -> false + playingState == State.IDLE || playingState == State.PAUSED -> false + playbackPosition != null -> playlist.findByPosition(playbackPosition)?.sequence == playlist.lastOrNull()?.sequence + else -> isLiveListening || playlist.currentSequence == playlist.lastOrNull()?.sequence + } + } + private fun getCurrentPlaybackPosition(): Int? { val playlistPosition = playlist.currentItem?.startTime val computedPosition = currentMediaPlayer?.currentPosition?.let { playlistPosition?.plus(it) } ?: playlistPosition diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlaylist.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlaylist.kt index ff388c2313..36b737f23f 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlaylist.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlaylist.kt @@ -56,7 +56,7 @@ class VoiceBroadcastPlaylist( } 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) @@ -64,4 +64,7 @@ class VoiceBroadcastPlaylist( 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 +} From 5eb260e674384cbcfa7e2771d6948a8b010ffb5e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 10 Nov 2022 14:11:34 +0100 Subject: [PATCH 02/11] Unregister listeners on recording tile --- .../timeline/item/MessageVoiceBroadcastRecordingItem.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt index ed77452382..9bd6fc45ec 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -113,6 +113,10 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem super.unbind(holder) recorderListener?.let { recorder?.removeListener(it) } recorderListener = null + with(holder) { + recordButton.onClick(null) + stopRecordButton.onClick(null) + } } override fun getViewStubId() = STUB_ID From 2d006f87256644087409bbc102156e7b1894055e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 10 Nov 2022 15:59:28 +0100 Subject: [PATCH 03/11] Bind listener to live playback flag --- .../MessageVoiceBroadcastListeningItem.kt | 10 +++++- .../listening/VoiceBroadcastPlayer.kt | 9 +++-- .../listening/VoiceBroadcastPlayerImpl.kt | 33 ++++++++++++++++--- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index b114f95f97..7c7e69f320 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -44,7 +44,15 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } 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) bindSeekBar(holder) bindButtons(holder) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt index 02e843965f..0de88e9992 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt @@ -78,10 +78,15 @@ interface VoiceBroadcastPlayer { /** * Listener related to [VoiceBroadcastPlayer]. */ - fun interface Listener { + interface Listener { /** * Notify about [VoiceBroadcastPlayer.playingState] changes. */ - fun onStateChanged(state: State) + fun onPlayingStateChanged(state: State) = Unit + + /** + * Notify about [VoiceBroadcastPlayer.isLiveListening] changes. + */ + fun onLiveModeChanged(isLive: Boolean) = Unit } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 573a178c78..56c80ddfb1 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -121,7 +121,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( listeners[voiceBroadcast.voiceBroadcastId]?.add(listener) ?: run { listeners[voiceBroadcast.voiceBroadcastId] = CopyOnWriteArrayList().apply { add(listener) } } - listener.onStateChanged(if (voiceBroadcast == currentVoiceBroadcast) playingState else State.IDLE) + listener.onPlayingStateChanged(if (voiceBroadcast == currentVoiceBroadcast) playingState else State.IDLE) + listener.onLiveModeChanged(if (voiceBroadcast == currentVoiceBroadcast) isLiveListening else false) } override fun removeListener(voiceBroadcast: VoiceBroadcast, listener: Listener) { @@ -318,17 +319,41 @@ class VoiceBroadcastPlayerImpl @Inject constructor( State.IDLE -> playbackTicker.stopPlaybackTicker(voiceBroadcastId) } // Notify state change to all the listeners attached to the current voice broadcast id - listeners[voiceBroadcastId]?.forEach { listener -> listener.onStateChanged(playingState) } + listeners[voiceBroadcastId]?.forEach { listener -> listener.onPlayingStateChanged(playingState) } } } - private fun updateLiveListeningMode(playbackPosition: Int? = null) { + /** + * Update the live listening state according to: + * - the voice broadcast state, + * - the playing state, + * - the potential seek position. + */ + private fun updateLiveListeningMode(seekPosition: Int? = null) { isLiveListening = when { + // the current voice broadcast is not live (ended) !currentVoiceBroadcastEvent?.isLive.orFalse() -> false + // the player is stopped or paused playingState == State.IDLE || playingState == State.PAUSED -> false - playbackPosition != null -> playlist.findByPosition(playbackPosition)?.sequence == playlist.lastOrNull()?.sequence + // the user has sought + seekPosition != null -> { + val seekDirection = seekPosition.compareTo(getCurrentPlaybackPosition() ?: 0) + when { + // backward + seekDirection < 0 -> false + // forward: check if new sequence is the last one + else -> playlist.findByPosition(seekPosition)?.sequence == playlist.lastOrNull()?.sequence + } + } + // otherwise, stay in live or go in live if we reached the last sequence else -> isLiveListening || playlist.currentSequence == playlist.lastOrNull()?.sequence } + + 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) } + + } } private fun getCurrentPlaybackPosition(): Int? { From a3cd0ee790d30bae0f2a5360119f0f3f7edadfce Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 10 Nov 2022 16:34:43 +0100 Subject: [PATCH 04/11] Fix fetch playlist task getting stopped event from other voice broadcast --- .../voicebroadcast/VoiceBroadcastExtensions.kt | 3 +++ .../usecase/GetLiveVoiceBroadcastChunksUseCase.kt | 14 ++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt index fa8033a211..6faec5a262 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt @@ -39,6 +39,9 @@ val MessageAudioEvent.sequence: Int? get() = getVoiceBroadcastChunk()?.sequence val MessageAudioEvent.duration get() = content.audioInfo?.duration ?: content.audioWaveformInfo?.duration ?: 0 +val VoiceBroadcastEvent.voiceBroadcastId + get() = reference?.eventId + val VoiceBroadcastEvent.isLive get() = content?.isLive.orFalse() diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt index d12a329142..16b15b9a77 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt @@ -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.sequence import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastEventUseCase +import im.vector.app.features.voicebroadcast.voiceBroadcastId import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow @@ -73,14 +74,15 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( // Observe new timeline events val listener = object : Timeline.Listener { - private var lastEventId: String? = null + private var latestEventId: String? = null private var lastSequence: Int? = null override fun onTimelineUpdated(snapshot: List) { - 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 - val stopEvent = newEvents.findStopEvent() + val stopEvent = newEvents.findStopEvent(voiceBroadcast) if (stopEvent != null) { lastSequence = stopEvent.content?.lastChunkSequence } @@ -98,7 +100,7 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( 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. */ - private fun List.findStopEvent(): VoiceBroadcastEvent? = - this.mapNotNull { it.root.asVoiceBroadcastEvent() } + private fun List.findStopEvent(voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? = + this.mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent()?.takeIf { it.voiceBroadcastId == voiceBroadcast.voiceBroadcastId } } .find { it.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED } /** From 73d62c944c5d53eedcdee09d6cc2ba3ce7b25380 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 10 Nov 2022 18:13:00 +0100 Subject: [PATCH 05/11] Emit first event on voice broadcast event flow --- .../usecase/GetVoiceBroadcastEventUseCase.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastEventUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastEventUseCase.kt index 696d300fc3..94eca2b54e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastEventUseCase.kt @@ -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.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.voiceBroadcastId import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onStart import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session 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.toOptional 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 javax.inject.Inject @@ -57,10 +58,10 @@ class GetVoiceBroadcastEventUseCase @Inject constructor( else -> { room.flow() .liveStateEvent(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(latestEvent.root.stateKey.orEmpty())) - .unwrap() - .mapNotNull { it.asVoiceBroadcastEvent() } - .filter { it.reference?.eventId == voiceBroadcast.voiceBroadcastId } - .map { it.toOptional() } + .onStart { emit(latestEvent.root.toOptional()) } + .distinctUntilChanged() + .filter { !it.hasValue() || it.getOrNull()?.asVoiceBroadcastEvent()?.voiceBroadcastId == voiceBroadcast.voiceBroadcastId } + .mapOptional { it.asVoiceBroadcastEvent() } } } } From 44608f080c6fd8777fd9ccad2b6877c0746a313e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 14 Nov 2022 10:24:22 +0100 Subject: [PATCH 06/11] Improve logs --- .../listening/VoiceBroadcastPlayerImpl.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 56c80ddfb1..9199de79cf 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -70,18 +70,26 @@ class VoiceBroadcastPlayerImpl @Inject constructor( 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 @MainThread set(value) { if (field != value) { - Timber.w("## VoiceBroadcastPlayer state: $field -> $value") + Timber.w("playingState: $field -> $value") field = value onPlayingStateChanged(value) } } - /** Map voiceBroadcastId to listeners.*/ + /** Map voiceBroadcastId to listeners. */ private val listeners: MutableMap> = mutableMapOf() override fun playOrResume(voiceBroadcast: VoiceBroadcast) { @@ -325,9 +333,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor( /** * Update the live listening state according to: - * - the voice broadcast state, - * - the playing state, - * - the potential seek position. + * - 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 { @@ -348,11 +356,12 @@ class VoiceBroadcastPlayerImpl @Inject constructor( // otherwise, stay in live or go in live if we reached the last 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) } - } } From 288fc354878638ca71a658a91122feb2b3daaef0 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 14 Nov 2022 10:46:40 +0100 Subject: [PATCH 07/11] Changelog --- changelog.d/7579.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7579.wip diff --git a/changelog.d/7579.wip b/changelog.d/7579.wip new file mode 100644 index 0000000000..08e6c2cdca --- /dev/null +++ b/changelog.d/7579.wip @@ -0,0 +1 @@ +[Voice Broadcast] Improve the live indicator icon rendering in the timeline From 403fd9260ee299f6142a55e4eb993d4e766a80fd Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 14 Nov 2022 11:57:05 +0100 Subject: [PATCH 08/11] improve boolean condition --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 9199de79cf..d04b46b842 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -130,7 +130,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( listeners[voiceBroadcast.voiceBroadcastId] = CopyOnWriteArrayList().apply { add(listener) } } listener.onPlayingStateChanged(if (voiceBroadcast == currentVoiceBroadcast) playingState else State.IDLE) - listener.onLiveModeChanged(if (voiceBroadcast == currentVoiceBroadcast) isLiveListening else false) + listener.onLiveModeChanged(voiceBroadcast == currentVoiceBroadcast && isLiveListening) } override fun removeListener(voiceBroadcast: VoiceBroadcast, listener: Listener) { From b85fcf9a005300ebf0c4116b2692bf0c982b3ca2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 15 Nov 2022 00:11:16 +0100 Subject: [PATCH 09/11] Remove debounce on player buttons --- .../timeline/item/MessageVoiceBroadcastListeningItem.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index 7c7e69f320..e5cb677763 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -60,7 +60,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem private fun bindButtons(holder: Holder) { with(holder) { - playPauseButton.onClick { + playPauseButton.setOnClickListener { if (player.currentVoiceBroadcast == voiceBroadcast) { when (player.playingState) { VoiceBroadcastPlayer.State.PLAYING -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) @@ -72,11 +72,11 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) } } - fastBackwardButton.onClick { + fastBackwardButton.setOnClickListener { val newPos = seekBar.progress.minus(30_000).coerceIn(0, duration) callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, newPos, duration)) } - fastForwardButton.onClick { + fastForwardButton.setOnClickListener { val newPos = seekBar.progress.plus(30_000).coerceIn(0, duration) callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, newPos, duration)) } @@ -163,7 +163,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem player.removeListener(voiceBroadcast, playerListener) playbackTracker.untrack(voiceBroadcast.voiceBroadcastId) with(holder) { - seekBar.onClick(null) + seekBar.setOnSeekBarChangeListener(null) playPauseButton.onClick(null) fastForwardButton.onClick(null) fastBackwardButton.onClick(null) From d9454af63ed8b35a98fa4075fdc1082ef4a99120 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 15 Nov 2022 00:30:55 +0100 Subject: [PATCH 10/11] Stay in live when moving playback position in the same chunk --- .../listening/VoiceBroadcastPlayerImpl.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index d04b46b842..f065ac4e44 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -343,17 +343,21 @@ class VoiceBroadcastPlayerImpl @Inject constructor( !currentVoiceBroadcastEvent?.isLive.orFalse() -> false // the player is stopped or paused playingState == State.IDLE || playingState == State.PAUSED -> false - // the user has sought seekPosition != null -> { val seekDirection = seekPosition.compareTo(getCurrentPlaybackPosition() ?: 0) - when { - // backward - seekDirection < 0 -> false - // forward: check if new sequence is the last one - else -> playlist.findByPosition(seekPosition)?.sequence == playlist.lastOrNull()?.sequence + 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 last sequence + // otherwise, stay in live or go in live if we reached the latest sequence else -> isLiveListening || playlist.currentSequence == playlist.lastOrNull()?.sequence } } From 3239ec5d1f6e0f676c98533a8e6e464e44208aad Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 15 Nov 2022 10:52:09 +0100 Subject: [PATCH 11/11] replace negation "!" with ".not()" --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index f065ac4e44..5b0e5b2b1c 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -340,7 +340,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun updateLiveListeningMode(seekPosition: Int? = null) { isLiveListening = when { // the current voice broadcast is not live (ended) - !currentVoiceBroadcastEvent?.isLive.orFalse() -> false + currentVoiceBroadcastEvent?.isLive?.not().orFalse() -> false // the player is stopped or paused playingState == State.IDLE || playingState == State.PAUSED -> false seekPosition != null -> {