Update seek bar tick progress while playing

This commit is contained in:
Florian Renaud 2022-10-31 17:04:49 +01:00
parent ac0d823c88
commit b0a31304a1
4 changed files with 100 additions and 11 deletions

View file

@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.factory
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.DrawableProvider
import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
@ -44,6 +45,7 @@ class VoiceBroadcastItemFactory @Inject constructor(
private val drawableProvider: DrawableProvider,
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
private val voiceBroadcastPlayer: VoiceBroadcastPlayer,
private val playbackTracker: AudioMessagePlaybackTracker,
) {
fun create(
@ -71,6 +73,7 @@ class VoiceBroadcastItemFactory @Inject constructor(
recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName().orEmpty(),
recorder = voiceBroadcastRecorder,
player = voiceBroadcastPlayer,
playbackTracker = playbackTracker,
roomItem = session.getRoom(params.event.roomId)?.roomSummary()?.toMatrixItem(),
colorProvider = colorProvider,
drawableProvider = drawableProvider,

View file

@ -25,6 +25,7 @@ import im.vector.app.R
import im.vector.app.core.extensions.tintBackground
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.DrawableProvider
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
@ -40,6 +41,8 @@ abstract class AbsMessageVoiceBroadcastItem<H : AbsMessageVoiceBroadcastItem.Hol
protected val recorderName get() = voiceBroadcastAttributes.recorderName
protected val recorder get() = voiceBroadcastAttributes.recorder
protected val player get() = voiceBroadcastAttributes.player
protected val playbackTracker get() = voiceBroadcastAttributes.playbackTracker
protected val duration get() = voiceBroadcastAttributes.duration
protected val roomItem get() = voiceBroadcastAttributes.roomItem
protected val colorProvider get() = voiceBroadcastAttributes.colorProvider
protected val drawableProvider get() = voiceBroadcastAttributes.drawableProvider
@ -98,6 +101,7 @@ abstract class AbsMessageVoiceBroadcastItem<H : AbsMessageVoiceBroadcastItem.Hol
val recorderName: String,
val recorder: VoiceBroadcastRecorder?,
val player: VoiceBroadcastPlayer,
val playbackTracker: AudioMessagePlaybackTracker,
val roomItem: MatrixItem?,
val colorProvider: ColorProvider,
val drawableProvider: DrawableProvider,

View file

@ -27,6 +27,7 @@ import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
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
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
@ -34,6 +35,7 @@ import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem<MessageVoiceBroadcastListeningItem.Holder>() {
private lateinit var playerListener: VoiceBroadcastPlayer.Listener
private var isUserSeeking = false
override fun bind(holder: Holder) {
super.bind(holder)
@ -86,15 +88,36 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
}
private fun bindSeekBar(holder: Holder) {
holder.durationView.text = formatPlaybackTime(voiceBroadcastAttributes.duration)
holder.seekBar.max = voiceBroadcastAttributes.duration
holder.durationView.text = formatPlaybackTime(duration)
holder.seekBar.max = duration
holder.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) = Unit
override fun onStartTrackingTouch(seekBar: SeekBar) = Unit
override fun onStartTrackingTouch(seekBar: SeekBar) {
isUserSeeking = true
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcastId, seekBar.progress))
isUserSeeking = false
}
})
playbackTracker.track(voiceBroadcastId, object : AudioMessagePlaybackTracker.Listener {
override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) {
when (state) {
is AudioMessagePlaybackTracker.Listener.State.Paused -> {
if (!isUserSeeking) {
holder.seekBar.progress = state.playbackTime
}
}
is AudioMessagePlaybackTracker.Listener.State.Playing -> {
if (!isUserSeeking) {
holder.seekBar.progress = state.playbackTime
}
}
AudioMessagePlaybackTracker.Listener.State.Idle -> Unit
is AudioMessagePlaybackTracker.Listener.State.Recording -> Unit
}
}
})
}
@ -105,6 +128,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
super.unbind(holder)
player.removeListener(voiceBroadcastId, playerListener)
holder.seekBar.setOnSeekBarChangeListener(null)
playbackTracker.untrack(voiceBroadcastId)
}
override fun getViewStubId() = STUB_ID

View file

@ -29,6 +29,7 @@ import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroad
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.sequence
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastUseCase
import im.vector.lib.core.utils.timer.CountUpTimer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent
@ -60,6 +62,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
private var voiceBroadcastStateJob: Job? = null
private val mediaPlayerListener = MediaPlayerListener()
private val playbackTicker = PlaybackTicker()
private var currentMediaPlayer: MediaPlayer? = null
private var nextMediaPlayer: MediaPlayer? = null
@ -79,6 +82,24 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
field = value
// Notify state change to all the listeners attached to the current voice broadcast id
currentVoiceBroadcastId?.let { voiceBroadcastId ->
when (value) {
State.PLAYING -> {
playbackTracker.startPlayback(voiceBroadcastId)
playbackTicker.startPlaybackTicker(voiceBroadcastId)
}
State.PAUSED -> {
playbackTracker.pausePlayback(voiceBroadcastId)
playbackTicker.stopPlaybackTicker()
}
State.BUFFERING -> {
playbackTracker.pausePlayback(voiceBroadcastId)
playbackTicker.stopPlaybackTicker()
}
State.IDLE -> {
playbackTracker.stopPlayback(voiceBroadcastId)
playbackTicker.stopPlaybackTicker()
}
}
listeners[voiceBroadcastId]?.forEach { listener -> listener.onStateChanged(value) }
}
}
@ -99,15 +120,16 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
}
override fun pause() {
currentMediaPlayer?.pause()
currentVoiceBroadcastId?.let { playbackTracker.pausePlayback(it) }
playingState = State.PAUSED
currentMediaPlayer?.pause()
}
override fun stop() {
// Update state
playingState = State.IDLE
// Stop playback
currentMediaPlayer?.stop()
currentVoiceBroadcastId?.let { playbackTracker.stopPlayback(it) }
isLive = false
// Release current player
@ -126,9 +148,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
fetchPlaylistJob?.cancel()
fetchPlaylistJob = null
// Update state
playingState = State.IDLE
// Clear playlist
playlist = emptyList()
currentSequence = null
@ -218,7 +237,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
if (position > 0) {
currentMediaPlayer?.seekTo(position)
}
currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) }
currentSequence = computedSequence
withContext(Dispatchers.Main) { playingState = State.PLAYING }
nextMediaPlayer = prepareNextMediaPlayer()
@ -231,7 +249,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
private fun resumePlayback() {
currentMediaPlayer?.start()
currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) }
playingState = State.PLAYING
}
@ -352,4 +369,45 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
private fun getVoiceBroadcastDuration() = playlist.lastOrNull()?.let { it.startTime + it.audioEvent.duration } ?: 0
private data class PlaylistItem(val audioEvent: MessageAudioEvent, val startTime: Int)
private inner class PlaybackTicker(
private var playbackTicker: CountUpTimer? = null,
) {
fun startPlaybackTicker(id: String) {
playbackTicker?.stop()
playbackTicker = CountUpTimer().apply {
tickListener = object : CountUpTimer.TickListener {
override fun onTick(milliseconds: Long) {
onPlaybackTick(id)
}
}
resume()
}
onPlaybackTick(id)
}
private fun onPlaybackTick(id: String) {
if (currentMediaPlayer?.isPlaying.orFalse()) {
val itemStartPosition = currentSequence?.let { seq -> playlist.find { it.audioEvent.sequence == seq } }?.startTime
val currentVoiceBroadcastPosition = itemStartPosition?.plus(currentMediaPlayer?.currentPosition ?: 0)
if (currentVoiceBroadcastPosition != null) {
val totalDuration = getVoiceBroadcastDuration()
val percentage = currentVoiceBroadcastPosition.toFloat() / totalDuration
playbackTracker.updatePlayingAtPlaybackTime(id, currentVoiceBroadcastPosition, percentage)
} else {
playbackTracker.stopPlayback(id)
stopPlaybackTicker()
}
} else {
playbackTracker.stopPlayback(id)
stopPlaybackTicker()
}
}
fun stopPlaybackTicker() {
playbackTicker?.stop()
playbackTicker = null
}
}
}