Improve live indicator

This commit is contained in:
Florian Renaud 2022-11-10 00:44:05 +01:00
parent 0d3c779455
commit 6ee1e86951
6 changed files with 68 additions and 21 deletions

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
@ -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) { 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 +108,8 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
} }
VoiceBroadcastPlayer.State.BUFFERING -> Unit VoiceBroadcastPlayer.State.BUFFERING -> Unit
} }
renderLiveIndicator(holder)
} }
} }
@ -121,6 +132,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)
} }

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

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.
*/ */

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,6 +69,7 @@ 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
override var playingState = State.IDLE override var playingState = State.IDLE
@MainThread @MainThread
@ -142,7 +142,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 +193,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 +244,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,18 +306,31 @@ 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)
} }
// 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.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? { private fun getCurrentPlaybackPosition(): Int? {
val playlistPosition = playlist.currentItem?.startTime val playlistPosition = playlist.currentItem?.startTime
val computedPosition = currentMediaPlayer?.currentPosition?.let { playlistPosition?.plus(it) } ?: playlistPosition val computedPosition = currentMediaPlayer?.currentPosition?.let { playlistPosition?.plus(it) } ?: playlistPosition

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
}