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 +}