From b0a31304a1ae0649c478884996013c70718486dc Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 31 Oct 2022 17:04:49 +0100 Subject: [PATCH 01/28] Update seek bar tick progress while playing --- .../factory/VoiceBroadcastItemFactory.kt | 3 + .../item/AbsMessageVoiceBroadcastItem.kt | 4 + .../MessageVoiceBroadcastListeningItem.kt | 30 +++++++- .../listening/VoiceBroadcastPlayerImpl.kt | 74 +++++++++++++++++-- 4 files changed, 100 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index 5d9c663210..06d3563303 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -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, 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 7ada0c71f2..9ea0a634c5 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 @@ -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() { 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 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 166e5a12e5..4fbaee8374 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 @@ -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 + } + } } From 20d62b14dea44cacfa8c770a339bd37b3bcaaf14 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 1 Nov 2022 10:28:24 +0100 Subject: [PATCH 02/28] Changelog --- changelog.d/7496.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7496.wip diff --git a/changelog.d/7496.wip b/changelog.d/7496.wip new file mode 100644 index 0000000000..49d15d084f --- /dev/null +++ b/changelog.d/7496.wip @@ -0,0 +1 @@ +[Voice Broadcast] Add seekbar in listening tile From 6d850b30306936b5b4602b48242c3524b5dfb730 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 3 Nov 2022 16:17:55 +0100 Subject: [PATCH 03/28] Create VoiceBroadcast model with roomId and eventId --- .../home/room/detail/RoomDetailAction.kt | 5 +- .../home/room/detail/TimelineViewModel.kt | 4 +- .../factory/VoiceBroadcastItemFactory.kt | 9 ++-- .../item/AbsMessageVoiceBroadcastItem.kt | 5 +- .../MessageVoiceBroadcastListeningItem.kt | 12 ++--- .../voicebroadcast/VoiceBroadcastHelper.kt | 7 +-- .../listening/VoiceBroadcastPlayer.kt | 16 +++--- .../listening/VoiceBroadcastPlayerImpl.kt | 49 +++++++++---------- .../GetLiveVoiceBroadcastChunksUseCase.kt | 15 +++--- .../voicebroadcast/model/VoiceBroadcast.kt | 22 +++++++++ ...se.kt => GetVoiceBroadcastEventUseCase.kt} | 14 +++--- 11 files changed, 93 insertions(+), 65 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcast.kt rename vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/{GetVoiceBroadcastUseCase.kt => GetVoiceBroadcastEventUseCase.kt} (72%) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index 8c49213a42..ba0f7dbdf8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -20,6 +20,7 @@ import android.net.Uri import android.view.View import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.call.conference.ConferenceEvent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcast import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent @@ -129,10 +130,10 @@ sealed class RoomDetailAction : VectorViewModelAction { } sealed class Listening : VoiceBroadcastAction() { - data class PlayOrResume(val voiceBroadcastId: String) : Listening() + data class PlayOrResume(val voiceBroadcast: VoiceBroadcast) : Listening() object Pause : Listening() object Stop : Listening() - data class SeekTo(val voiceBroadcastId: String, val positionMillis: Int) : Listening() + data class SeekTo(val voiceBroadcast: VoiceBroadcast, val positionMillis: Int) : Listening() } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 3f4fae1ce9..252823b2a6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -634,10 +634,10 @@ class TimelineViewModel @AssistedInject constructor( VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId) VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId) VoiceBroadcastAction.Recording.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) - is VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(room.roomId, action.voiceBroadcastId) + is VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(action.voiceBroadcast) VoiceBroadcastAction.Listening.Pause -> voiceBroadcastHelper.pausePlayback() VoiceBroadcastAction.Listening.Stop -> voiceBroadcastHelper.stopPlayback() - is VoiceBroadcastAction.Listening.SeekTo -> voiceBroadcastHelper.seekTo(action.voiceBroadcastId, action.positionMillis) + is VoiceBroadcastAction.Listening.SeekTo -> voiceBroadcastHelper.seekTo(action.voiceBroadcast, action.positionMillis) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index 06d3563303..e4f7bed72f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -29,6 +29,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadca import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem_ import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcast import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder @@ -60,14 +61,14 @@ class VoiceBroadcastItemFactory @Inject constructor( val voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null val voiceBroadcastEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent().root.asVoiceBroadcastEvent() ?: return null val voiceBroadcastContent = voiceBroadcastEvent.content ?: return null - val voiceBroadcastId = voiceBroadcastEventsGroup.voiceBroadcastId + val voiceBroadcast = VoiceBroadcast(voiceBroadcastId = voiceBroadcastEventsGroup.voiceBroadcastId, roomId = params.event.roomId) val isRecording = voiceBroadcastContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && voiceBroadcastEvent.root.stateKey == session.myUserId && messageContent.deviceId == session.sessionParams.deviceId val voiceBroadcastAttributes = AbsMessageVoiceBroadcastItem.Attributes( - voiceBroadcastId = voiceBroadcastId, + voiceBroadcast = voiceBroadcast, voiceBroadcastState = voiceBroadcastContent.voiceBroadcastState, duration = voiceBroadcastEventsGroup.getDuration(), recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName().orEmpty(), @@ -92,7 +93,7 @@ class VoiceBroadcastItemFactory @Inject constructor( voiceBroadcastAttributes: AbsMessageVoiceBroadcastItem.Attributes, ): MessageVoiceBroadcastRecordingItem { return MessageVoiceBroadcastRecordingItem_() - .id("voice_broadcast_${voiceBroadcastAttributes.voiceBroadcastId}") + .id("voice_broadcast_${voiceBroadcastAttributes.voiceBroadcast.voiceBroadcastId}") .attributes(attributes) .voiceBroadcastAttributes(voiceBroadcastAttributes) .highlighted(highlight) @@ -105,7 +106,7 @@ class VoiceBroadcastItemFactory @Inject constructor( voiceBroadcastAttributes: AbsMessageVoiceBroadcastItem.Attributes, ): MessageVoiceBroadcastListeningItem { return MessageVoiceBroadcastListeningItem_() - .id("voice_broadcast_${voiceBroadcastAttributes.voiceBroadcastId}") + .id("voice_broadcast_${voiceBroadcastAttributes.voiceBroadcast.voiceBroadcastId}") .attributes(attributes) .voiceBroadcastAttributes(voiceBroadcastAttributes) .highlighted(highlight) 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 9ea0a634c5..0329adf12b 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 @@ -27,6 +27,7 @@ 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.VoiceBroadcast import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder import org.matrix.android.sdk.api.util.MatrixItem @@ -36,7 +37,7 @@ abstract class AbsMessageVoiceBroadcastItem renderPlayingState(holder, state) } - player.addListener(voiceBroadcastId, playerListener) + player.addListener(voiceBroadcast, playerListener) bindSeekBar(holder) } @@ -77,7 +77,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem VoiceBroadcastPlayer.State.PAUSED -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_play) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) - playPauseButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) } + playPauseButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) } seekBar.isEnabled = false } VoiceBroadcastPlayer.State.BUFFERING -> { @@ -98,11 +98,11 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } override fun onStopTrackingTouch(seekBar: SeekBar) { - callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcastId, seekBar.progress)) + callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, seekBar.progress)) isUserSeeking = false } }) - playbackTracker.track(voiceBroadcastId, object : AudioMessagePlaybackTracker.Listener { + playbackTracker.track(voiceBroadcast.voiceBroadcastId, object : AudioMessagePlaybackTracker.Listener { override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) { when (state) { is AudioMessagePlaybackTracker.Listener.State.Paused -> { @@ -126,9 +126,9 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem override fun unbind(holder: Holder) { super.unbind(holder) - player.removeListener(voiceBroadcastId, playerListener) + player.removeListener(voiceBroadcast, playerListener) holder.seekBar.setOnSeekBarChangeListener(null) - playbackTracker.untrack(voiceBroadcastId) + playbackTracker.untrack(voiceBroadcast.voiceBroadcastId) } override fun getViewStubId() = STUB_ID diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt index 7864d3b4e3..6839056520 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -17,6 +17,7 @@ package im.vector.app.features.voicebroadcast import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer +import im.vector.app.features.voicebroadcast.model.VoiceBroadcast import im.vector.app.features.voicebroadcast.recording.usecase.PauseVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.recording.usecase.ResumeVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.recording.usecase.StartVoiceBroadcastUseCase @@ -41,14 +42,14 @@ class VoiceBroadcastHelper @Inject constructor( suspend fun stopVoiceBroadcast(roomId: String) = stopVoiceBroadcastUseCase.execute(roomId) - fun playOrResumePlayback(roomId: String, voiceBroadcastId: String) = voiceBroadcastPlayer.playOrResume(roomId, voiceBroadcastId) + fun playOrResumePlayback(voiceBroadcast: VoiceBroadcast) = voiceBroadcastPlayer.playOrResume(voiceBroadcast) fun pausePlayback() = voiceBroadcastPlayer.pause() fun stopPlayback() = voiceBroadcastPlayer.stop() - fun seekTo(voiceBroadcastId: String, positionMillis: Int) { - if (voiceBroadcastPlayer.currentVoiceBroadcastId == voiceBroadcastId) { + fun seekTo(voiceBroadcast: VoiceBroadcast, positionMillis: Int) { + if (voiceBroadcastPlayer.currentVoiceBroadcast == voiceBroadcast) { voiceBroadcastPlayer.seekTo(positionMillis) } } 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 2a2a549af0..b4806ba57d 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 @@ -16,12 +16,14 @@ package im.vector.app.features.voicebroadcast.listening +import im.vector.app.features.voicebroadcast.model.VoiceBroadcast + interface VoiceBroadcastPlayer { /** - * The current playing voice broadcast identifier, if any. + * The current playing voice broadcast, if any. */ - val currentVoiceBroadcastId: String? + val currentVoiceBroadcast: VoiceBroadcast? /** * The current playing [State], [State.IDLE] by default. @@ -31,7 +33,7 @@ interface VoiceBroadcastPlayer { /** * Start playback of the given voice broadcast. */ - fun playOrResume(roomId: String, voiceBroadcastId: String) + fun playOrResume(voiceBroadcast: VoiceBroadcast) /** * Pause playback of the current voice broadcast, if any. @@ -49,14 +51,14 @@ interface VoiceBroadcastPlayer { fun seekTo(positionMillis: Int) /** - * Add a [Listener] to the given voice broadcast id. + * Add a [Listener] to the given voice broadcast. */ - fun addListener(voiceBroadcastId: String, listener: Listener) + fun addListener(voiceBroadcast: VoiceBroadcast, listener: Listener) /** - * Remove a [Listener] from the given voice broadcast id. + * Remove a [Listener] from the given voice broadcast. */ - fun removeListener(voiceBroadcastId: String, listener: Listener) + fun removeListener(voiceBroadcast: VoiceBroadcast, listener: Listener) /** * Player states. 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 4fbaee8374..fc983e4112 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 @@ -26,9 +26,10 @@ import im.vector.app.features.voicebroadcast.duration import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Listener import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.State import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase +import im.vector.app.features.voicebroadcast.model.VoiceBroadcast 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.app.features.voicebroadcast.usecase.GetVoiceBroadcastEventUseCase import im.vector.lib.core.utils.timer.CountUpTimer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -51,7 +52,7 @@ import javax.inject.Singleton class VoiceBroadcastPlayerImpl @Inject constructor( private val sessionHolder: ActiveSessionHolder, private val playbackTracker: AudioMessagePlaybackTracker, - private val getVoiceBroadcastUseCase: GetVoiceBroadcastUseCase, + private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastEventUseCase, private val getLiveVoiceBroadcastChunksUseCase: GetLiveVoiceBroadcastChunksUseCase ) : VoiceBroadcastPlayer { @@ -73,7 +74,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private var isLive: Boolean = false - override var currentVoiceBroadcastId: String? = null + override var currentVoiceBroadcast: VoiceBroadcast? = null override var playingState = State.IDLE @MainThread @@ -81,7 +82,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( Timber.w("## VoiceBroadcastPlayer state: $field -> $value") field = value // Notify state change to all the listeners attached to the current voice broadcast id - currentVoiceBroadcastId?.let { voiceBroadcastId -> + currentVoiceBroadcast?.voiceBroadcastId?.let { voiceBroadcastId -> when (value) { State.PLAYING -> { playbackTracker.startPlayback(voiceBroadcastId) @@ -103,17 +104,16 @@ class VoiceBroadcastPlayerImpl @Inject constructor( listeners[voiceBroadcastId]?.forEach { listener -> listener.onStateChanged(value) } } } - private var currentRoomId: String? = null /** * Map voiceBroadcastId to listeners. */ private val listeners: MutableMap> = mutableMapOf() - override fun playOrResume(roomId: String, voiceBroadcastId: String) { - val hasChanged = currentVoiceBroadcastId != voiceBroadcastId + override fun playOrResume(voiceBroadcast: VoiceBroadcast) { + val hasChanged = currentVoiceBroadcast != voiceBroadcast when { - hasChanged -> startPlayback(roomId, voiceBroadcastId) + hasChanged -> startPlayback(voiceBroadcast) playingState == State.PAUSED -> resumePlayback() else -> Unit } @@ -152,37 +152,35 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playlist = emptyList() currentSequence = null - currentRoomId = null - currentVoiceBroadcastId = null + currentVoiceBroadcast = null } - override fun addListener(voiceBroadcastId: String, listener: Listener) { - listeners[voiceBroadcastId]?.add(listener) ?: run { - listeners[voiceBroadcastId] = CopyOnWriteArrayList().apply { add(listener) } + override fun addListener(voiceBroadcast: VoiceBroadcast, listener: Listener) { + listeners[voiceBroadcast.voiceBroadcastId]?.add(listener) ?: run { + listeners[voiceBroadcast.voiceBroadcastId] = CopyOnWriteArrayList().apply { add(listener) } } - if (voiceBroadcastId == currentVoiceBroadcastId) listener.onStateChanged(playingState) else listener.onStateChanged(State.IDLE) + listener.onStateChanged(if (voiceBroadcast == currentVoiceBroadcast) playingState else State.IDLE) } - override fun removeListener(voiceBroadcastId: String, listener: Listener) { - listeners[voiceBroadcastId]?.remove(listener) + override fun removeListener(voiceBroadcast: VoiceBroadcast, listener: Listener) { + listeners[voiceBroadcast.voiceBroadcastId]?.remove(listener) } - private fun startPlayback(roomId: String, eventId: String) { + private fun startPlayback(voiceBroadcast: VoiceBroadcast) { // Stop listening previous voice broadcast if any if (playingState != State.IDLE) stop() - currentRoomId = roomId - currentVoiceBroadcastId = eventId + currentVoiceBroadcast = voiceBroadcast playingState = State.BUFFERING - val voiceBroadcastState = getVoiceBroadcastUseCase.execute(roomId, eventId)?.content?.voiceBroadcastState + val voiceBroadcastState = getVoiceBroadcastEventUseCase.execute(voiceBroadcast)?.content?.voiceBroadcastState isLive = voiceBroadcastState != null && voiceBroadcastState != VoiceBroadcastState.STOPPED - fetchPlaylistAndStartPlayback(roomId, eventId) + fetchPlaylistAndStartPlayback(voiceBroadcast) } - private fun fetchPlaylistAndStartPlayback(roomId: String, voiceBroadcastId: String) { - fetchPlaylistJob = getLiveVoiceBroadcastChunksUseCase.execute(roomId, voiceBroadcastId) + private fun fetchPlaylistAndStartPlayback(voiceBroadcast: VoiceBroadcast) { + fetchPlaylistJob = getLiveVoiceBroadcastChunksUseCase.execute(voiceBroadcast) .onEach(this::updatePlaylist) .launchIn(coroutineScope) } @@ -347,9 +345,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( override fun onCompletion(mp: MediaPlayer) { if (nextMediaPlayer != null) return - val roomId = currentRoomId ?: return - val voiceBroadcastId = currentVoiceBroadcastId ?: return - val voiceBroadcastEventContent = getVoiceBroadcastUseCase.execute(roomId, voiceBroadcastId)?.content ?: return + val voiceBroadcast = currentVoiceBroadcast ?: return + val voiceBroadcastEventContent = getVoiceBroadcastEventUseCase.execute(voiceBroadcast)?.content ?: return isLive = voiceBroadcastEventContent.voiceBroadcastState != null && voiceBroadcastEventContent.voiceBroadcastState != VoiceBroadcastState.STOPPED if (!isLive && voiceBroadcastEventContent.lastChunkSequence == currentSequence) { 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 4f9f2de673..2e8fc31870 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 @@ -19,11 +19,12 @@ package im.vector.app.features.voicebroadcast.listening.usecase import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.voicebroadcast.getVoiceBroadcastEventId import im.vector.app.features.voicebroadcast.isVoiceBroadcast +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.sequence -import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastUseCase +import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastEventUseCase import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow @@ -44,19 +45,19 @@ import javax.inject.Inject */ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, - private val getVoiceBroadcastUseCase: GetVoiceBroadcastUseCase, + private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastEventUseCase, ) { - fun execute(roomId: String, voiceBroadcastId: String): Flow> { + fun execute(voiceBroadcast: VoiceBroadcast): Flow> { val session = activeSessionHolder.getSafeActiveSession() ?: return emptyFlow() - val room = session.roomService().getRoom(roomId) ?: return emptyFlow() + val room = session.roomService().getRoom(voiceBroadcast.roomId) ?: return emptyFlow() val timeline = room.timelineService().createTimeline(null, TimelineSettings(5)) // Get initial chunks - val existingChunks = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcastId) + val existingChunks = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) .mapNotNull { timelineEvent -> timelineEvent.root.asMessageAudioEvent().takeIf { it.isVoiceBroadcast() } } - val voiceBroadcastEvent = getVoiceBroadcastUseCase.execute(roomId, voiceBroadcastId) + val voiceBroadcastEvent = getVoiceBroadcastEventUseCase.execute(voiceBroadcast) val voiceBroadcastState = voiceBroadcastEvent?.content?.voiceBroadcastState return if (voiceBroadcastState == null || voiceBroadcastState == VoiceBroadcastState.STOPPED) { @@ -82,7 +83,7 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( lastSequence = stopEvent.content?.lastChunkSequence } - val newChunks = newEvents.mapToChunkEvents(voiceBroadcastId, voiceBroadcastEvent.root.senderId) + val newChunks = newEvents.mapToChunkEvents(voiceBroadcast.voiceBroadcastId, voiceBroadcastEvent.root.senderId) // Notify about new chunks if (newChunks.isNotEmpty()) { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcast.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcast.kt new file mode 100644 index 0000000000..62207d5b87 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcast.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.model + +data class VoiceBroadcast( + val voiceBroadcastId: String, + val roomId: String, +) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastEventUseCase.kt similarity index 72% rename from vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastUseCase.kt rename to vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastEventUseCase.kt index d08fa14a95..26ba3209b7 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastEventUseCase.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.features.voicebroadcast.model.VoiceBroadcast import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.session.Session @@ -24,17 +25,18 @@ import org.matrix.android.sdk.api.session.getRoom import timber.log.Timber import javax.inject.Inject -class GetVoiceBroadcastUseCase @Inject constructor( +class GetVoiceBroadcastEventUseCase @Inject constructor( private val session: Session, ) { - fun execute(roomId: String, eventId: String): VoiceBroadcastEvent? { - val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + fun execute(voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? { + val room = session.getRoom(voiceBroadcast.roomId) ?: error("Unknown roomId: ${voiceBroadcast.roomId}") - Timber.d("## GetVoiceBroadcastUseCase: get voice broadcast $eventId") + Timber.d("## GetVoiceBroadcastUseCase: get voice broadcast $voiceBroadcast") - val initialEvent = room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() // Fallback to initial event - val relatedEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId).sortedBy { it.root.originServerTs } + val initialEvent = room.timelineService().getTimelineEvent(voiceBroadcast.voiceBroadcastId)?.root?.asVoiceBroadcastEvent() + val relatedEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) + .sortedBy { it.root.originServerTs } return relatedEvents.mapNotNull { it.root.asVoiceBroadcastEvent() }.lastOrNull() ?: initialEvent } } From d89ef6988b6e894040da5e447cb781903b565767 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 11:35:50 +0100 Subject: [PATCH 04/28] Improve player seek --- .../MessageVoiceBroadcastListeningItem.kt | 7 +- .../VoiceBroadcastExtensions.kt | 5 + .../voicebroadcast/VoiceBroadcastHelper.kt | 4 +- .../listening/VoiceBroadcastPlayer.kt | 4 +- .../listening/VoiceBroadcastPlayerImpl.kt | 228 +++++++++--------- .../GetLiveVoiceBroadcastChunksUseCase.kt | 5 +- .../usecase/GetVoiceBroadcastEventUseCase.kt | 35 ++- 7 files changed, 157 insertions(+), 131 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 5e3cc6fba8..19caf3d8ba 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 @@ -71,18 +71,14 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) playPauseButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) } - seekBar.isEnabled = true } VoiceBroadcastPlayer.State.IDLE, VoiceBroadcastPlayer.State.PAUSED -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_play) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) playPauseButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) } - seekBar.isEnabled = false - } - VoiceBroadcastPlayer.State.BUFFERING -> { - seekBar.isEnabled = true } + VoiceBroadcastPlayer.State.BUFFERING -> Unit } } } @@ -112,6 +108,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } is AudioMessagePlaybackTracker.Listener.State.Playing -> { if (!isUserSeeking) { +// Timber.d("Voice Broadcast | AudioMessagePlaybackTracker.Listener.onUpdate - duration: $duration, playbackTime: ${state.playbackTime}") holder.seekBar.progress = state.playbackTime } } 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 48554f51d0..a1328c0ba3 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 @@ -17,6 +17,8 @@ package im.vector.app.features.voicebroadcast import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toModel @@ -34,3 +36,6 @@ fun MessageAudioEvent.getVoiceBroadcastChunk(): VoiceBroadcastChunk? { val MessageAudioEvent.sequence: Int? get() = getVoiceBroadcastChunk()?.sequence val MessageAudioEvent.duration get() = content.audioInfo?.duration ?: content.audioWaveformInfo?.duration ?: 0 + +val VoiceBroadcastEvent.isLive + get() = content?.voiceBroadcastState != null && content?.voiceBroadcastState != VoiceBroadcastState.STOPPED diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt index 6839056520..3661928fa5 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -49,8 +49,6 @@ class VoiceBroadcastHelper @Inject constructor( fun stopPlayback() = voiceBroadcastPlayer.stop() fun seekTo(voiceBroadcast: VoiceBroadcast, positionMillis: Int) { - if (voiceBroadcastPlayer.currentVoiceBroadcast == voiceBroadcast) { - voiceBroadcastPlayer.seekTo(positionMillis) - } + voiceBroadcastPlayer.seekTo(voiceBroadcast, positionMillis) } } 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 b4806ba57d..36e75236ad 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 @@ -46,9 +46,9 @@ interface VoiceBroadcastPlayer { fun stop() /** - * Seek to the given playback position, is milliseconds. + * Seek the given voice broadcast playback to the given position, is milliseconds. */ - fun seekTo(positionMillis: Int) + fun seekTo(voiceBroadcast: VoiceBroadcast, positionMillis: Int) /** * Add a [Listener] to 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 fc983e4112..773883d81a 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 @@ -18,16 +18,18 @@ package im.vector.app.features.voicebroadcast.listening import android.media.AudioAttributes import android.media.MediaPlayer +import android.media.MediaPlayer.OnPreparedListener import androidx.annotation.MainThread import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.voice.VoiceFailure import im.vector.app.features.voicebroadcast.duration +import im.vector.app.features.voicebroadcast.isLive import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Listener import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.State import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase import im.vector.app.features.voicebroadcast.model.VoiceBroadcast -import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +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 @@ -38,7 +40,6 @@ import kotlinx.coroutines.SupervisorJob 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 @@ -47,6 +48,7 @@ import timber.log.Timber import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject import javax.inject.Singleton +import kotlin.math.absoluteValue @Singleton class VoiceBroadcastPlayerImpl @Inject constructor( @@ -60,19 +62,20 @@ class VoiceBroadcastPlayerImpl @Inject constructor( get() = sessionHolder.getActiveSession() private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) - private var voiceBroadcastStateJob: Job? = null + private var fetchPlaylistTask: Job? = null + private var voiceBroadcastStateTask: Job? = null private val mediaPlayerListener = MediaPlayerListener() private val playbackTicker = PlaybackTicker() private var currentMediaPlayer: MediaPlayer? = null private var nextMediaPlayer: MediaPlayer? = null - private var currentSequence: Int? = null - private var fetchPlaylistJob: Job? = null private var playlist = emptyList() - - private var isLive: Boolean = false + private var currentSequence: Int? = null + private var currentVoiceBroadcastEvent: VoiceBroadcastEvent? = null + private val isLive get() = currentVoiceBroadcastEvent?.isLive.orFalse() + private val lastSequence get() = currentVoiceBroadcastEvent?.content?.lastChunkSequence override var currentVoiceBroadcast: VoiceBroadcast? = null @@ -81,33 +84,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor( set(value) { Timber.w("## VoiceBroadcastPlayer state: $field -> $value") field = value - // Notify state change to all the listeners attached to the current voice broadcast id - currentVoiceBroadcast?.voiceBroadcastId?.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) } - } + onPlayingStateChanged(value) } - /** - * Map voiceBroadcastId to listeners. - */ + /** Map voiceBroadcastId to listeners.*/ private val listeners: MutableMap> = mutableMapOf() override fun playOrResume(voiceBroadcast: VoiceBroadcast) { @@ -120,38 +100,28 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } override fun pause() { - playingState = State.PAUSED currentMediaPlayer?.pause() + playingState = State.PAUSED } override fun stop() { // Update state playingState = State.IDLE - // Stop playback - currentMediaPlayer?.stop() - isLive = false + // Stop and release media players + stopPlayer() - // Release current player - release(currentMediaPlayer) - currentMediaPlayer = null - - // Release next player - release(nextMediaPlayer) - nextMediaPlayer = null - - // Do not observe anymore voice broadcast state changes - voiceBroadcastStateJob?.cancel() - voiceBroadcastStateJob = null - - // Do not fetch the playlist anymore - fetchPlaylistJob?.cancel() - fetchPlaylistJob = null + // Do not observe anymore voice broadcast changes + fetchPlaylistTask?.cancel() + fetchPlaylistTask = null + voiceBroadcastStateTask?.cancel() + voiceBroadcastStateTask = null // Clear playlist playlist = emptyList() currentSequence = null + currentVoiceBroadcastEvent = null currentVoiceBroadcast = null } @@ -174,13 +144,18 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playingState = State.BUFFERING - val voiceBroadcastState = getVoiceBroadcastEventUseCase.execute(voiceBroadcast)?.content?.voiceBroadcastState - isLive = voiceBroadcastState != null && voiceBroadcastState != VoiceBroadcastState.STOPPED + observeVoiceBroadcastLiveState(voiceBroadcast) fetchPlaylistAndStartPlayback(voiceBroadcast) } + private fun observeVoiceBroadcastLiveState(voiceBroadcast: VoiceBroadcast) { + voiceBroadcastStateTask = getVoiceBroadcastEventUseCase.execute(voiceBroadcast) + .onEach { currentVoiceBroadcastEvent = it.getOrNull() } + .launchIn(coroutineScope) + } + private fun fetchPlaylistAndStartPlayback(voiceBroadcast: VoiceBroadcast) { - fetchPlaylistJob = getLiveVoiceBroadcastChunksUseCase.execute(voiceBroadcast) + fetchPlaylistTask = getLiveVoiceBroadcastChunksUseCase.execute(voiceBroadcast) .onEach(this::updatePlaylist) .launchIn(coroutineScope) } @@ -204,40 +179,51 @@ class VoiceBroadcastPlayerImpl @Inject constructor( when (playingState) { State.PLAYING -> { if (nextMediaPlayer == null) { - coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() } + prepareNextMediaPlayer() } } State.PAUSED -> { if (nextMediaPlayer == null) { - coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() } + prepareNextMediaPlayer() } } State.BUFFERING -> { val newMediaContent = getNextAudioContent() - if (newMediaContent != null) startPlayback() + if (newMediaContent != null) { + val savedPosition = currentVoiceBroadcast?.let { playbackTracker.getPlaybackTime(it.voiceBroadcastId) } + startPlayback(savedPosition) + } + } + State.IDLE -> { + val savedPosition = currentVoiceBroadcast?.let { playbackTracker.getPlaybackTime(it.voiceBroadcastId) } + startPlayback(savedPosition) } - State.IDLE -> startPlayback() } } - private fun startPlayback(sequence: Int? = null, position: Int = 0) { + private fun startPlayback(position: Int? = null) { + stopPlayer() + val playlistItem = when { - sequence != null -> playlist.find { it.audioEvent.sequence == sequence } + position != null -> playlist.lastOrNull { it.startTime <= position } isLive -> playlist.lastOrNull() else -> playlist.firstOrNull() } val content = playlistItem?.audioEvent?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return } - val computedSequence = playlistItem.audioEvent.sequence + val sequence = playlistItem.audioEvent.sequence + val sequencePosition = position?.let { it - playlistItem.startTime } ?: 0 coroutineScope.launch { try { - currentMediaPlayer = prepareMediaPlayer(content) - currentMediaPlayer?.start() - if (position > 0) { - currentMediaPlayer?.seekTo(position) + prepareMediaPlayer(content) { mp -> + currentMediaPlayer = mp + currentSequence = sequence + mp.start() + if (sequencePosition > 0) { + mp.seekTo(sequencePosition) + } + playingState = State.PLAYING + prepareNextMediaPlayer() } - currentSequence = computedSequence - withContext(Dispatchers.Main) { playingState = State.PLAYING } - nextMediaPlayer = prepareNextMediaPlayer() } catch (failure: Throwable) { Timber.e(failure, "Unable to start playback") throw VoiceFailure.UnableToPlay(failure) @@ -250,20 +236,12 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playingState = State.PLAYING } - override fun seekTo(positionMillis: Int) { - val duration = getVoiceBroadcastDuration() - val playlistItem = playlist.lastOrNull { it.startTime <= positionMillis } ?: return - val audioEvent = playlistItem.audioEvent - val eventPosition = positionMillis - playlistItem.startTime - - Timber.d("## Voice Broadcast | seekTo - duration=$duration, position=$positionMillis, sequence=${audioEvent.sequence}, sequencePosition=$eventPosition") - - tryOrNull { currentMediaPlayer?.stop() } - release(currentMediaPlayer) - tryOrNull { nextMediaPlayer?.stop() } - release(nextMediaPlayer) - - startPlayback(audioEvent.sequence, eventPosition) + override fun seekTo(voiceBroadcast: VoiceBroadcast, positionMillis: Int) { + if (voiceBroadcast != currentVoiceBroadcast) { + playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, 0f) + } else { + startPlayback(positionMillis) + } } private fun getNextAudioContent(): MessageAudioContent? { @@ -273,12 +251,24 @@ class VoiceBroadcastPlayerImpl @Inject constructor( return playlist.find { it.audioEvent.sequence == nextSequence }?.audioEvent?.content } - private suspend fun prepareNextMediaPlayer(): MediaPlayer? { - val nextContent = getNextAudioContent() ?: return null - return prepareMediaPlayer(nextContent) + private fun prepareNextMediaPlayer() { + nextMediaPlayer = null + val nextContent = getNextAudioContent() + if (nextContent != null) { + coroutineScope.launch { + prepareMediaPlayer(nextContent) { mp -> + if (nextMediaPlayer == null) { + nextMediaPlayer = mp + currentMediaPlayer?.setNextMediaPlayer(mp) + } else { + mp.release() + } + } + } + } } - private suspend fun prepareMediaPlayer(messageAudioContent: MessageAudioContent): MediaPlayer { + private suspend fun prepareMediaPlayer(messageAudioContent: MessageAudioContent, onPreparedListener: OnPreparedListener): MediaPlayer { // Download can fail val audioFile = try { session.fileService().downloadFile(messageAudioContent) @@ -299,57 +289,55 @@ class VoiceBroadcastPlayerImpl @Inject constructor( setDataSource(fis.fd) setOnInfoListener(mediaPlayerListener) setOnErrorListener(mediaPlayerListener) + setOnPreparedListener(onPreparedListener) setOnCompletionListener(mediaPlayerListener) prepare() } } } - private fun release(mp: MediaPlayer?) { - mp?.apply { - release() - setOnInfoListener(null) - setOnCompletionListener(null) - setOnErrorListener(null) + private fun stopPlayer() { + tryOrNull { currentMediaPlayer?.stop() } + currentMediaPlayer?.release() + currentMediaPlayer = null + + nextMediaPlayer?.release() + nextMediaPlayer = null + } + + private fun onPlayingStateChanged(playingState: State) { + // Notify state change to all the listeners attached to the current voice broadcast id + currentVoiceBroadcast?.voiceBroadcastId?.let { voiceBroadcastId -> + when (playingState) { + State.PLAYING -> playbackTicker.startPlaybackTicker(voiceBroadcastId) + State.PAUSED, + State.BUFFERING, + State.IDLE -> playbackTicker.stopPlaybackTicker(voiceBroadcastId) + } + listeners[voiceBroadcastId]?.forEach { listener -> listener.onStateChanged(playingState) } } } private inner class MediaPlayerListener : MediaPlayer.OnInfoListener, - MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean { when (what) { MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT -> { - release(currentMediaPlayer) - currentMediaPlayer = mp currentSequence = currentSequence?.plus(1) - coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() } + currentMediaPlayer = mp + prepareNextMediaPlayer() } } return false } - override fun onPrepared(mp: MediaPlayer) { - when (mp) { - currentMediaPlayer -> { - nextMediaPlayer?.let { mp.setNextMediaPlayer(it) } - } - nextMediaPlayer -> { - tryOrNull { currentMediaPlayer?.setNextMediaPlayer(mp) } - } - } - } - override fun onCompletion(mp: MediaPlayer) { if (nextMediaPlayer != null) return - val voiceBroadcast = currentVoiceBroadcast ?: return - val voiceBroadcastEventContent = getVoiceBroadcastEventUseCase.execute(voiceBroadcast)?.content ?: return - isLive = voiceBroadcastEventContent.voiceBroadcastState != null && voiceBroadcastEventContent.voiceBroadcastState != VoiceBroadcastState.STOPPED - if (!isLive && voiceBroadcastEventContent.lastChunkSequence == currentSequence) { + if (!isLive && lastSequence == currentSequence) { // We'll not receive new chunks anymore so we can stop the live listening stop() } else { @@ -388,23 +376,31 @@ class VoiceBroadcastPlayerImpl @Inject constructor( if (currentMediaPlayer?.isPlaying.orFalse()) { val itemStartPosition = currentSequence?.let { seq -> playlist.find { it.audioEvent.sequence == seq } }?.startTime val currentVoiceBroadcastPosition = itemStartPosition?.plus(currentMediaPlayer?.currentPosition ?: 0) + Timber.d("Voice Broadcast | VoiceBroadcastPlayerImpl - sequence: $currentSequence, itemStartPosition $itemStartPosition, currentMediaPlayer=$currentMediaPlayer, currentMediaPlayer?.currentPosition: ${currentMediaPlayer?.currentPosition}") if (currentVoiceBroadcastPosition != null) { val totalDuration = getVoiceBroadcastDuration() val percentage = currentVoiceBroadcastPosition.toFloat() / totalDuration playbackTracker.updatePlayingAtPlaybackTime(id, currentVoiceBroadcastPosition, percentage) } else { - playbackTracker.stopPlayback(id) - stopPlaybackTicker() + stopPlaybackTicker(id) } } else { - playbackTracker.stopPlayback(id) - stopPlaybackTicker() + stopPlaybackTicker(id) } } - fun stopPlaybackTicker() { + fun stopPlaybackTicker(id: String) { playbackTicker?.stop() playbackTicker = null + + val totalDuration = getVoiceBroadcastDuration() + val playbackTime = playbackTracker.getPlaybackTime(id) + val remainingTime = totalDuration - playbackTime + if (remainingTime < 1000) { + playbackTracker.updatePausedAtPlaybackTime(id, 0, 0f) + } else { + playbackTracker.pausePlayback(id) + } } } } 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 2e8fc31870..33e370e9bc 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 @@ -29,9 +29,12 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.lastOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.runningReduce +import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent @@ -57,7 +60,7 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( val existingChunks = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) .mapNotNull { timelineEvent -> timelineEvent.root.asMessageAudioEvent().takeIf { it.isVoiceBroadcast() } } - val voiceBroadcastEvent = getVoiceBroadcastEventUseCase.execute(voiceBroadcast) + val voiceBroadcastEvent = runBlocking { getVoiceBroadcastEventUseCase.execute(voiceBroadcast).firstOrNull()?.getOrNull() } val voiceBroadcastState = voiceBroadcastEvent?.content?.voiceBroadcastState return if (voiceBroadcastState == null || voiceBroadcastState == VoiceBroadcastState.STOPPED) { 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 26ba3209b7..7106322f06 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 @@ -16,12 +16,26 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants 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 kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +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 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 timber.log.Timber import javax.inject.Inject @@ -29,14 +43,27 @@ class GetVoiceBroadcastEventUseCase @Inject constructor( private val session: Session, ) { - fun execute(voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? { + fun execute(voiceBroadcast: VoiceBroadcast): Flow> { val room = session.getRoom(voiceBroadcast.roomId) ?: error("Unknown roomId: ${voiceBroadcast.roomId}") Timber.d("## GetVoiceBroadcastUseCase: get voice broadcast $voiceBroadcast") val initialEvent = room.timelineService().getTimelineEvent(voiceBroadcast.voiceBroadcastId)?.root?.asVoiceBroadcastEvent() - val relatedEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) - .sortedBy { it.root.originServerTs } - return relatedEvents.mapNotNull { it.root.asVoiceBroadcastEvent() }.lastOrNull() ?: initialEvent + val latestEvent = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) + .mapNotNull { it.root.asVoiceBroadcastEvent() } + .maxByOrNull { it.root.originServerTs ?: 0 } + ?: initialEvent + + return when (latestEvent?.content?.voiceBroadcastState) { + null, VoiceBroadcastState.STOPPED -> flowOf(latestEvent.toOptional()) + 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() } + } + } } } From 392fe6fa329d84805f553986b8a338018ee2c095 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 15:47:10 +0100 Subject: [PATCH 05/28] Transform TickListener to fun interface --- .../vector/lib/attachmentviewer/VideoViewHolder.kt | 14 ++++++-------- .../im/vector/lib/core/utils/timer/CountUpTimer.kt | 2 +- .../vector/app/features/call/webrtc/WebRtcCall.kt | 10 ++++------ .../room/detail/composer/AudioMessageHelper.kt | 12 ++---------- .../composer/voice/VoiceMessageRecorderView.kt | 8 +++----- .../location/live/map/LiveLocationUserItem.kt | 6 ++---- .../listening/VoiceBroadcastPlayerImpl.kt | 6 +----- 7 files changed, 19 insertions(+), 39 deletions(-) diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt index 92d28d26c9..07c7b4588f 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt @@ -103,14 +103,12 @@ class VideoViewHolder constructor(itemView: View) : views.videoView.setOnPreparedListener { stopTimer() countUpTimer = CountUpTimer(100).also { - it.tickListener = object : CountUpTimer.TickListener { - override fun onTick(milliseconds: Long) { - val duration = views.videoView.duration - val progress = views.videoView.currentPosition - val isPlaying = views.videoView.isPlaying -// Log.v("FOO", "isPlaying $isPlaying $progress/$duration") - eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration)) - } + it.tickListener = CountUpTimer.TickListener { + val duration = views.videoView.duration + val progress = views.videoView.currentPosition + val isPlaying = views.videoView.isPlaying + // Log.v("FOO", "isPlaying $isPlaying $progress/$duration") + eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration)) } it.resume() } diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt index e9d311fe03..a4fd8bb4e1 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt @@ -66,7 +66,7 @@ class CountUpTimer(private val intervalInMs: Long = 1_000) { coroutineScope.cancel() } - interface TickListener { + fun interface TickListener { fun onTick(milliseconds: Long) } } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index 00b9a76de7..0bf70690ba 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -167,12 +167,10 @@ class WebRtcCall( private var screenSender: RtpSender? = null private val timer = CountUpTimer(1000L).apply { - tickListener = object : CountUpTimer.TickListener { - override fun onTick(milliseconds: Long) { - val formattedDuration = formatDuration(Duration.ofMillis(milliseconds)) - listeners.forEach { - tryOrNull { it.onTick(formattedDuration) } - } + tickListener = CountUpTimer.TickListener { milliseconds -> + val formattedDuration = formatDuration(Duration.ofMillis(milliseconds)) + listeners.forEach { + tryOrNull { it.onTick(formattedDuration) } } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt index bede02c17f..eddfe500b3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt @@ -199,11 +199,7 @@ class AudioMessageHelper @Inject constructor( private fun startRecordingAmplitudes() { amplitudeTicker?.stop() amplitudeTicker = CountUpTimer(50).apply { - tickListener = object : CountUpTimer.TickListener { - override fun onTick(milliseconds: Long) { - onAmplitudeTick() - } - } + tickListener = CountUpTimer.TickListener { onAmplitudeTick() } resume() } } @@ -234,11 +230,7 @@ class AudioMessageHelper @Inject constructor( private fun startPlaybackTicker(id: String) { playbackTicker?.stop() playbackTicker = CountUpTimer().apply { - tickListener = object : CountUpTimer.TickListener { - override fun onTick(milliseconds: Long) { - onPlaybackTick(id) - } - } + tickListener = CountUpTimer.TickListener { onPlaybackTick(id) } resume() } onPlaybackTick(id) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt index 13e0477ab6..a7b926f29a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt @@ -189,11 +189,9 @@ class VoiceMessageRecorderView @JvmOverloads constructor( val startMs = ((clock.epochMillis() - startAt)).coerceAtLeast(0) recordingTicker?.stop() recordingTicker = CountUpTimer().apply { - tickListener = object : CountUpTimer.TickListener { - override fun onTick(milliseconds: Long) { - val isLocked = startFromLocked || lastKnownState is RecordingUiState.Locked - onRecordingTick(isLocked, milliseconds + startMs) - } + tickListener = CountUpTimer.TickListener { milliseconds -> + val isLocked = startFromLocked || lastKnownState is RecordingUiState.Locked + onRecordingTick(isLocked, milliseconds + startMs) } resume() } diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt index bab7f4c7f9..c108e83e76 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt @@ -79,10 +79,8 @@ abstract class LiveLocationUserItem : VectorEpoxyModel Date: Fri, 4 Nov 2022 15:54:28 +0100 Subject: [PATCH 06/28] VoiceBroadcastPlayerImpl - use session coroutine scope --- .../listening/VoiceBroadcastPlayerImpl.kt | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 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 bf8ff11043..6eb9cbc735 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 @@ -22,6 +22,7 @@ import android.media.MediaPlayer.OnPreparedListener import androidx.annotation.MainThread import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker +import im.vector.app.features.session.coroutineScope import im.vector.app.features.voice.VoiceFailure import im.vector.app.features.voicebroadcast.duration import im.vector.app.features.voicebroadcast.isLive @@ -33,10 +34,7 @@ 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.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -48,7 +46,6 @@ import timber.log.Timber import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject import javax.inject.Singleton -import kotlin.math.absoluteValue @Singleton class VoiceBroadcastPlayerImpl @Inject constructor( @@ -58,10 +55,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private val getLiveVoiceBroadcastChunksUseCase: GetLiveVoiceBroadcastChunksUseCase ) : VoiceBroadcastPlayer { - private val session - get() = sessionHolder.getActiveSession() + private val session get() = sessionHolder.getActiveSession() + private val sessionScope get() = session.coroutineScope - private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private var fetchPlaylistTask: Job? = null private var voiceBroadcastStateTask: Job? = null @@ -151,13 +147,13 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun observeVoiceBroadcastLiveState(voiceBroadcast: VoiceBroadcast) { voiceBroadcastStateTask = getVoiceBroadcastEventUseCase.execute(voiceBroadcast) .onEach { currentVoiceBroadcastEvent = it.getOrNull() } - .launchIn(coroutineScope) + .launchIn(sessionScope) } private fun fetchPlaylistAndStartPlayback(voiceBroadcast: VoiceBroadcast) { fetchPlaylistTask = getLiveVoiceBroadcastChunksUseCase.execute(voiceBroadcast) .onEach(this::updatePlaylist) - .launchIn(coroutineScope) + .launchIn(sessionScope) } private fun updatePlaylist(audioEvents: List) { @@ -212,7 +208,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val content = playlistItem?.audioEvent?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return } val sequence = playlistItem.audioEvent.sequence val sequencePosition = position?.let { it - playlistItem.startTime } ?: 0 - coroutineScope.launch { + sessionScope.launch { try { prepareMediaPlayer(content) { mp -> currentMediaPlayer = mp @@ -255,7 +251,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( nextMediaPlayer = null val nextContent = getNextAudioContent() if (nextContent != null) { - coroutineScope.launch { + sessionScope.launch { prepareMediaPlayer(nextContent) { mp -> if (nextMediaPlayer == null) { nextMediaPlayer = mp From c85b159952accaff1b7d65f027ced44fe790f0e4 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 17:12:02 +0100 Subject: [PATCH 07/28] VoiceBroadcastPlayer - Extract some code to VoiceBroadcastPlaylist --- .../VoiceBroadcastExtensions.kt | 7 +- .../listening/VoiceBroadcastPlayerImpl.kt | 60 +++++------------ .../listening/VoiceBroadcastPlaylist.kt | 67 +++++++++++++++++++ 3 files changed, 91 insertions(+), 43 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlaylist.kt 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 a1328c0ba3..fa8033a211 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 @@ -16,9 +16,11 @@ package im.vector.app.features.voicebroadcast +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toModel @@ -38,4 +40,7 @@ val MessageAudioEvent.sequence: Int? get() = getVoiceBroadcastChunk()?.sequence val MessageAudioEvent.duration get() = content.audioInfo?.duration ?: content.audioWaveformInfo?.duration ?: 0 val VoiceBroadcastEvent.isLive - get() = content?.voiceBroadcastState != null && content?.voiceBroadcastState != VoiceBroadcastState.STOPPED + get() = content?.isLive.orFalse() + +val MessageVoiceBroadcastInfoContent.isLive + get() = voiceBroadcastState != null && voiceBroadcastState != VoiceBroadcastState.STOPPED 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 6eb9cbc735..de4f965a59 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 @@ -24,7 +24,6 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.session.coroutineScope import im.vector.app.features.voice.VoiceFailure -import im.vector.app.features.voicebroadcast.duration import im.vector.app.features.voicebroadcast.isLive import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Listener import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.State @@ -41,7 +40,6 @@ import kotlinx.coroutines.launch 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 import timber.log.Timber import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject @@ -67,11 +65,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private var currentMediaPlayer: MediaPlayer? = null private var nextMediaPlayer: MediaPlayer? = null - private var playlist = emptyList() - private var currentSequence: Int? = null + private val playlist = VoiceBroadcastPlaylist() private var currentVoiceBroadcastEvent: VoiceBroadcastEvent? = null - private val isLive get() = currentVoiceBroadcastEvent?.isLive.orFalse() - private val lastSequence get() = currentVoiceBroadcastEvent?.content?.lastChunkSequence override var currentVoiceBroadcast: VoiceBroadcast? = null @@ -114,8 +109,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( voiceBroadcastStateTask = null // Clear playlist - playlist = emptyList() - currentSequence = null + playlist.reset() currentVoiceBroadcastEvent = null currentVoiceBroadcast = null @@ -152,25 +146,13 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun fetchPlaylistAndStartPlayback(voiceBroadcast: VoiceBroadcast) { fetchPlaylistTask = getLiveVoiceBroadcastChunksUseCase.execute(voiceBroadcast) - .onEach(this::updatePlaylist) + .onEach { + playlist.setItems(it) + onPlaylistUpdated() + } .launchIn(sessionScope) } - private fun updatePlaylist(audioEvents: List) { - val sorted = audioEvents.sortedBy { it.sequence?.toLong() ?: it.root.originServerTs } - val chunkPositions = sorted - .map { it.duration } - .runningFold(0) { acc, i -> acc + i } - .dropLast(1) - playlist = sorted.mapIndexed { index, messageAudioEvent -> - PlaylistItem( - audioEvent = messageAudioEvent, - startTime = chunkPositions.getOrNull(index) ?: 0 - ) - } - onPlaylistUpdated() - } - private fun onPlaylistUpdated() { when (playingState) { State.PLAYING -> { @@ -201,18 +183,18 @@ class VoiceBroadcastPlayerImpl @Inject constructor( stopPlayer() val playlistItem = when { - position != null -> playlist.lastOrNull { it.startTime <= position } - isLive -> playlist.lastOrNull() + position != null -> playlist.findByPosition(position) + currentVoiceBroadcastEvent?.isLive.orFalse() -> playlist.lastOrNull() else -> playlist.firstOrNull() } val content = playlistItem?.audioEvent?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return } - val sequence = playlistItem.audioEvent.sequence + val sequence = playlistItem.audioEvent.sequence ?: run { Timber.w("## VoiceBroadcastPlayer: playlist item has no sequence"); return } val sequencePosition = position?.let { it - playlistItem.startTime } ?: 0 sessionScope.launch { try { prepareMediaPlayer(content) { mp -> currentMediaPlayer = mp - currentSequence = sequence + playlist.currentSequence = sequence mp.start() if (sequencePosition > 0) { mp.seekTo(sequencePosition) @@ -241,10 +223,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } private fun getNextAudioContent(): MessageAudioContent? { - val nextSequence = currentSequence?.plus(1) - ?: playlist.lastOrNull()?.audioEvent?.sequence - ?: 1 - return playlist.find { it.audioEvent.sequence == nextSequence }?.audioEvent?.content + return playlist.getNextItem()?.audioEvent?.content } private fun prepareNextMediaPlayer() { @@ -322,7 +301,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean { when (what) { MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT -> { - currentSequence = currentSequence?.plus(1) + playlist.currentSequence++ currentMediaPlayer = mp prepareNextMediaPlayer() } @@ -333,7 +312,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor( override fun onCompletion(mp: MediaPlayer) { if (nextMediaPlayer != null) return - if (!isLive && lastSequence == currentSequence) { + val content = currentVoiceBroadcastEvent?.content + val isLive = content?.isLive.orFalse() + if (!isLive && content?.lastChunkSequence == playlist.currentSequence) { // We'll not receive new chunks anymore so we can stop the live listening stop() } else { @@ -347,10 +328,6 @@ 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, ) { @@ -366,12 +343,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun onPlaybackTick(id: String) { if (currentMediaPlayer?.isPlaying.orFalse()) { - val itemStartPosition = currentSequence?.let { seq -> playlist.find { it.audioEvent.sequence == seq } }?.startTime + val itemStartPosition = playlist.currentItem?.startTime val currentVoiceBroadcastPosition = itemStartPosition?.plus(currentMediaPlayer?.currentPosition ?: 0) Timber.d("Voice Broadcast | VoiceBroadcastPlayerImpl - sequence: $currentSequence, itemStartPosition $itemStartPosition, currentMediaPlayer=$currentMediaPlayer, currentMediaPlayer?.currentPosition: ${currentMediaPlayer?.currentPosition}") if (currentVoiceBroadcastPosition != null) { - val totalDuration = getVoiceBroadcastDuration() - val percentage = currentVoiceBroadcastPosition.toFloat() / totalDuration + val percentage = currentVoiceBroadcastPosition.toFloat() / playlist.duration playbackTracker.updatePlayingAtPlaybackTime(id, currentVoiceBroadcastPosition, percentage) } else { stopPlaybackTicker(id) @@ -385,7 +361,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playbackTicker?.stop() playbackTicker = null - val totalDuration = getVoiceBroadcastDuration() + val totalDuration = playlist.duration val playbackTime = playbackTracker.getPlaybackTime(id) val remainingTime = totalDuration - playbackTime if (remainingTime < 1000) { 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 new file mode 100644 index 0000000000..5cd6efc28a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlaylist.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.listening + +import im.vector.app.features.voicebroadcast.duration +import im.vector.app.features.voicebroadcast.sequence +import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent + +class VoiceBroadcastPlaylist( + private val items: MutableList = mutableListOf(), +) : List by items { + + var currentSequence = 1 + val currentItem get() = findBySequence(currentSequence) + + val duration + get() = items.lastOrNull()?.let { it.startTime + it.audioEvent.duration } ?: 0 + + fun setItems(audioEvents: List) { + items.clear() + val sorted = audioEvents.sortedBy { it.sequence?.toLong() ?: it.root.originServerTs } + val chunkPositions = sorted + .map { it.duration } + .runningFold(0) { acc, i -> acc + i } + .dropLast(1) + val newItems = sorted.mapIndexed { index, messageAudioEvent -> + PlaylistItem( + audioEvent = messageAudioEvent, + startTime = chunkPositions.getOrNull(index) ?: 0 + ) + } + items.addAll(newItems) + } + + fun reset() { + currentSequence = 1 + items.clear() + } + + fun findByPosition(positionMillis: Int): PlaylistItem? { + return items.lastOrNull { it.startTime <= positionMillis } + } + + fun findBySequence(sequenceNumber: Int): PlaylistItem? { + return items.find { it.audioEvent.sequence == sequenceNumber } + } + + fun getNextItem() = findBySequence(currentSequence.plus(1)) + + fun firstOrNull() = findBySequence(1) +} + +data class PlaylistItem(val audioEvent: MessageAudioEvent, val startTime: Int) From 37c75354bed4a884c03c2ca15a6f22e32d05f572 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 18:18:26 +0100 Subject: [PATCH 08/28] VoiceBroadcastPlayer - Reorganize some code --- .../listening/VoiceBroadcastPlayerImpl.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 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 de4f965a59..a38442a19a 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 @@ -56,16 +56,16 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private val session get() = sessionHolder.getActiveSession() private val sessionScope get() = session.coroutineScope - private var fetchPlaylistTask: Job? = null - private var voiceBroadcastStateTask: Job? = null - private val mediaPlayerListener = MediaPlayerListener() private val playbackTicker = PlaybackTicker() + private val playlist = VoiceBroadcastPlaylist() + + private var fetchPlaylistTask: Job? = null + private var voiceBroadcastStateObserver: Job? = null private var currentMediaPlayer: MediaPlayer? = null private var nextMediaPlayer: MediaPlayer? = null - private val playlist = VoiceBroadcastPlaylist() private var currentVoiceBroadcastEvent: VoiceBroadcastEvent? = null override var currentVoiceBroadcast: VoiceBroadcast? = null @@ -105,8 +105,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( // Do not observe anymore voice broadcast changes fetchPlaylistTask?.cancel() fetchPlaylistTask = null - voiceBroadcastStateTask?.cancel() - voiceBroadcastStateTask = null + voiceBroadcastStateObserver?.cancel() + voiceBroadcastStateObserver = null // Clear playlist playlist.reset() @@ -139,7 +139,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } private fun observeVoiceBroadcastLiveState(voiceBroadcast: VoiceBroadcast) { - voiceBroadcastStateTask = getVoiceBroadcastEventUseCase.execute(voiceBroadcast) + voiceBroadcastStateObserver = getVoiceBroadcastEventUseCase.execute(voiceBroadcast) .onEach { currentVoiceBroadcastEvent = it.getOrNull() } .launchIn(sessionScope) } @@ -345,7 +345,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( if (currentMediaPlayer?.isPlaying.orFalse()) { val itemStartPosition = playlist.currentItem?.startTime val currentVoiceBroadcastPosition = itemStartPosition?.plus(currentMediaPlayer?.currentPosition ?: 0) - Timber.d("Voice Broadcast | VoiceBroadcastPlayerImpl - sequence: $currentSequence, itemStartPosition $itemStartPosition, currentMediaPlayer=$currentMediaPlayer, currentMediaPlayer?.currentPosition: ${currentMediaPlayer?.currentPosition}") + Timber.d("Voice Broadcast | VoiceBroadcastPlayerImpl - sequence: ${playlist.currentSequence}, itemStartPosition $itemStartPosition, currentMediaPlayer=$currentMediaPlayer, currentMediaPlayer?.currentPosition: ${currentMediaPlayer?.currentPosition}") if (currentVoiceBroadcastPosition != null) { val percentage = currentVoiceBroadcastPosition.toFloat() / playlist.duration playbackTracker.updatePlayingAtPlaybackTime(id, currentVoiceBroadcastPosition, percentage) From b87b2cbb63aea881d753759482167e89f0c5ca0c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 18:28:26 +0100 Subject: [PATCH 09/28] Remove useless method --- .../listening/VoiceBroadcastPlayerImpl.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 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 a38442a19a..de01661431 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 @@ -166,8 +166,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } State.BUFFERING -> { - val newMediaContent = getNextAudioContent() - if (newMediaContent != null) { + val nextItem = playlist.getNextItem() + if (nextItem != null) { val savedPosition = currentVoiceBroadcast?.let { playbackTracker.getPlaybackTime(it.voiceBroadcastId) } startPlayback(savedPosition) } @@ -222,14 +222,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } - private fun getNextAudioContent(): MessageAudioContent? { - return playlist.getNextItem()?.audioEvent?.content - } - private fun prepareNextMediaPlayer() { nextMediaPlayer = null - val nextContent = getNextAudioContent() - if (nextContent != null) { + val nextItem = playlist.getNextItem() + if (nextItem != null) { sessionScope.launch { prepareMediaPlayer(nextContent) { mp -> if (nextMediaPlayer == null) { From a3cd861e15d09ec29ae29f84b3586523647c22e6 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 18:29:01 +0100 Subject: [PATCH 10/28] Add isPreparingNextPlayer flag --- .../listening/VoiceBroadcastPlayerImpl.kt | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 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 de01661431..adc1912140 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 @@ -65,6 +65,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private var currentMediaPlayer: MediaPlayer? = null private var nextMediaPlayer: MediaPlayer? = null + private var isPreparingNextPlayer: Boolean = false private var currentVoiceBroadcastEvent: VoiceBroadcastEvent? = null @@ -156,12 +157,12 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun onPlaylistUpdated() { when (playingState) { State.PLAYING -> { - if (nextMediaPlayer == null) { + if (nextMediaPlayer == null && !isPreparingNextPlayer) { prepareNextMediaPlayer() } } State.PAUSED -> { - if (nextMediaPlayer == null) { + if (nextMediaPlayer == null && !isPreparingNextPlayer) { prepareNextMediaPlayer() } } @@ -223,17 +224,14 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } private fun prepareNextMediaPlayer() { - nextMediaPlayer = null val nextItem = playlist.getNextItem() if (nextItem != null) { + isPreparingNextPlayer = true sessionScope.launch { - prepareMediaPlayer(nextContent) { mp -> - if (nextMediaPlayer == null) { - nextMediaPlayer = mp - currentMediaPlayer?.setNextMediaPlayer(mp) - } else { - mp.release() - } + prepareMediaPlayer(nextItem.audioEvent.content) { mp -> + nextMediaPlayer = mp + currentMediaPlayer?.setNextMediaPlayer(mp) + isPreparingNextPlayer = false } } } @@ -274,6 +272,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( nextMediaPlayer?.release() nextMediaPlayer = null + isPreparingNextPlayer = false } private fun onPlayingStateChanged(playingState: State) { From a3201555462586c837f883216170668e2ed7840a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 18:36:37 +0100 Subject: [PATCH 11/28] reset nextMediaPlayer when item has changed --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 1 + 1 file changed, 1 insertion(+) 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 adc1912140..da3a9559a4 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 @@ -298,6 +298,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT -> { playlist.currentSequence++ currentMediaPlayer = mp + nextMediaPlayer = null prepareNextMediaPlayer() } } From 43a112839f6bcd2bfb17dc3bf36c16f92cd3d58d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 18:49:10 +0100 Subject: [PATCH 12/28] Fix seek when playlist is not loaded --- .../listening/VoiceBroadcastPlayerImpl.kt | 14 ++++++++------ 1 file changed, 8 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 da3a9559a4..7b8d8c9547 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 @@ -358,12 +358,14 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playbackTicker = null val totalDuration = playlist.duration - val playbackTime = playbackTracker.getPlaybackTime(id) - val remainingTime = totalDuration - playbackTime - if (remainingTime < 1000) { - playbackTracker.updatePausedAtPlaybackTime(id, 0, 0f) - } else { - playbackTracker.pausePlayback(id) + if (totalDuration > 0) { + val playbackTime = playbackTracker.getPlaybackTime(id) + val remainingTime = totalDuration - playbackTime + if (remainingTime < 1000) { + playbackTracker.updatePausedAtPlaybackTime(id, 0, 0f) + } else { + playbackTracker.pausePlayback(id) + } } } } From 266236c1e5daee19bdb6e632ab74850a30ed3c7e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 23:20:22 +0100 Subject: [PATCH 13/28] set playlist.currentSequence null by default --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 2 +- .../voicebroadcast/listening/VoiceBroadcastPlaylist.kt | 8 ++++---- 2 files changed, 5 insertions(+), 5 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 7b8d8c9547..921e0e69ea 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 @@ -296,7 +296,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean { when (what) { MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT -> { - playlist.currentSequence++ + playlist.currentSequence = playlist.currentSequence?.inc() currentMediaPlayer = mp nextMediaPlayer = null prepareNextMediaPlayer() 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 5cd6efc28a..ff388c2313 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 @@ -24,8 +24,8 @@ class VoiceBroadcastPlaylist( private val items: MutableList = mutableListOf(), ) : List by items { - var currentSequence = 1 - val currentItem get() = findBySequence(currentSequence) + var currentSequence: Int? = null + val currentItem get() = currentSequence?.let { findBySequence(it) } val duration get() = items.lastOrNull()?.let { it.startTime + it.audioEvent.duration } ?: 0 @@ -47,7 +47,7 @@ class VoiceBroadcastPlaylist( } fun reset() { - currentSequence = 1 + currentSequence = null items.clear() } @@ -59,7 +59,7 @@ class VoiceBroadcastPlaylist( return items.find { it.audioEvent.sequence == sequenceNumber } } - fun getNextItem() = findBySequence(currentSequence.plus(1)) + fun getNextItem() = findBySequence(currentSequence?.plus(1) ?: 1) fun firstOrNull() = findBySequence(1) } From a47e3c1233999b8e5ebbd83974626b86d58488a0 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 23:20:54 +0100 Subject: [PATCH 14/28] Improve playing state updates --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 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 921e0e69ea..bfbc53fd78 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 @@ -74,9 +74,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor( override var playingState = State.IDLE @MainThread set(value) { - Timber.w("## VoiceBroadcastPlayer state: $field -> $value") - field = value - onPlayingStateChanged(value) + if (field != value) { + Timber.w("## VoiceBroadcastPlayer state: $field -> $value") + field = value + onPlayingStateChanged(value) + } } /** Map voiceBroadcastId to listeners.*/ @@ -299,6 +301,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playlist.currentSequence = playlist.currentSequence?.inc() currentMediaPlayer = mp nextMediaPlayer = null + playingState = State.PLAYING prepareNextMediaPlayer() } } From b2f35fa1352f1b3771d8d3aa9231340560bebdab Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 23:21:18 +0100 Subject: [PATCH 15/28] Improve PlaybackTicker --- .../helper/AudioMessagePlaybackTracker.kt | 2 +- .../listening/VoiceBroadcastPlayerImpl.kt | 51 ++++++++++--------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt index 6937cd3a46..7e40b92ac8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt @@ -127,7 +127,7 @@ class AudioMessagePlaybackTracker @Inject constructor() { } } - private fun getPercentage(id: String): Float { + fun getPercentage(id: String): Float { return when (val state = states[id]) { is Listener.State.Playing -> state.percentage is Listener.State.Paused -> state.percentage 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 bfbc53fd78..50a3002c0d 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,34 +340,37 @@ class VoiceBroadcastPlayerImpl @Inject constructor( onPlaybackTick(id) } - private fun onPlaybackTick(id: String) { - if (currentMediaPlayer?.isPlaying.orFalse()) { - val itemStartPosition = playlist.currentItem?.startTime - val currentVoiceBroadcastPosition = itemStartPosition?.plus(currentMediaPlayer?.currentPosition ?: 0) - Timber.d("Voice Broadcast | VoiceBroadcastPlayerImpl - sequence: ${playlist.currentSequence}, itemStartPosition $itemStartPosition, currentMediaPlayer=$currentMediaPlayer, currentMediaPlayer?.currentPosition: ${currentMediaPlayer?.currentPosition}") - if (currentVoiceBroadcastPosition != null) { - val percentage = currentVoiceBroadcastPosition.toFloat() / playlist.duration - playbackTracker.updatePlayingAtPlaybackTime(id, currentVoiceBroadcastPosition, percentage) - } else { - stopPlaybackTicker(id) - } - } else { - stopPlaybackTicker(id) - } - } - fun stopPlaybackTicker(id: String) { playbackTicker?.stop() playbackTicker = null + onPlaybackTick(id) + } - val totalDuration = playlist.duration - if (totalDuration > 0) { - val playbackTime = playbackTracker.getPlaybackTime(id) - val remainingTime = totalDuration - playbackTime - if (remainingTime < 1000) { - playbackTracker.updatePausedAtPlaybackTime(id, 0, 0f) - } else { - playbackTracker.pausePlayback(id) + private fun onPlaybackTick(id: String) { + val currentItem = playlist.currentItem ?: return + val itemStartTime = currentItem.startTime + val duration = playlist.duration + when (playingState) { + State.PLAYING, + State.PAUSED -> { + Timber.d("Voice Broadcast | VoiceBroadcastPlayerImpl - sequence: ${playlist.currentSequence}, itemStartTime $itemStartTime, currentMediaPlayer=$currentMediaPlayer, currentMediaPlayer?.currentPosition: ${currentMediaPlayer?.currentPosition}") + val position = itemStartTime + (currentMediaPlayer?.currentPosition ?: 0) + val percentage = position.toFloat() / playlist.duration + if (playingState == State.PLAYING) { + playbackTracker.updatePlayingAtPlaybackTime(id, position, percentage) + } else { + playbackTracker.updatePausedAtPlaybackTime(id, position, percentage) + } + } + State.BUFFERING, + State.IDLE -> { + val playbackTime = playbackTracker.getPlaybackTime(id) + val percentage = playbackTracker.getPercentage(id) + if (playingState == State.IDLE && duration > 0 && (duration - playbackTime) < 1000) { + playbackTracker.updatePausedAtPlaybackTime(id, 0, 0f) + } else { + playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) + } } } } From 436e76c7563f3f529f395630c75dd59ef32436aa Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 23:42:02 +0100 Subject: [PATCH 16/28] Fix seek on paused state --- .../home/room/detail/RoomDetailAction.kt | 2 +- .../home/room/detail/TimelineViewModel.kt | 2 +- .../MessageVoiceBroadcastListeningItem.kt | 2 +- .../voicebroadcast/VoiceBroadcastHelper.kt | 4 +- .../listening/VoiceBroadcastPlayer.kt | 2 +- .../listening/VoiceBroadcastPlayerImpl.kt | 40 ++++++++++++++----- 6 files changed, 37 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index ba0f7dbdf8..faee8f652c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -133,7 +133,7 @@ sealed class RoomDetailAction : VectorViewModelAction { data class PlayOrResume(val voiceBroadcast: VoiceBroadcast) : Listening() object Pause : Listening() object Stop : Listening() - data class SeekTo(val voiceBroadcast: VoiceBroadcast, val positionMillis: Int) : Listening() + data class SeekTo(val voiceBroadcast: VoiceBroadcast, val positionMillis: Int, val duration: Int) : Listening() } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 252823b2a6..ef238d56e6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -637,7 +637,7 @@ class TimelineViewModel @AssistedInject constructor( is VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(action.voiceBroadcast) VoiceBroadcastAction.Listening.Pause -> voiceBroadcastHelper.pausePlayback() VoiceBroadcastAction.Listening.Stop -> voiceBroadcastHelper.stopPlayback() - is VoiceBroadcastAction.Listening.SeekTo -> voiceBroadcastHelper.seekTo(action.voiceBroadcast, action.positionMillis) + is VoiceBroadcastAction.Listening.SeekTo -> voiceBroadcastHelper.seekTo(action.voiceBroadcast, action.positionMillis, action.duration) } } } 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 19caf3d8ba..ebbfe13730 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 @@ -94,7 +94,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } override fun onStopTrackingTouch(seekBar: SeekBar) { - callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, seekBar.progress)) + callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, seekBar.progress, duration)) isUserSeeking = false } }) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt index 3661928fa5..38fb157748 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -48,7 +48,7 @@ class VoiceBroadcastHelper @Inject constructor( fun stopPlayback() = voiceBroadcastPlayer.stop() - fun seekTo(voiceBroadcast: VoiceBroadcast, positionMillis: Int) { - voiceBroadcastPlayer.seekTo(voiceBroadcast, positionMillis) + fun seekTo(voiceBroadcast: VoiceBroadcast, positionMillis: Int, duration: Int) { + voiceBroadcastPlayer.seekTo(voiceBroadcast, positionMillis, duration) } } 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 36e75236ad..8c11db4f43 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 @@ -48,7 +48,7 @@ interface VoiceBroadcastPlayer { /** * Seek the given voice broadcast playback to the given position, is milliseconds. */ - fun seekTo(voiceBroadcast: VoiceBroadcast, positionMillis: Int) + fun seekTo(voiceBroadcast: VoiceBroadcast, positionMillis: Int, duration: Int) /** * Add a [Listener] to 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 50a3002c0d..0937977b70 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 @@ -94,8 +94,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } override fun pause() { - currentMediaPlayer?.pause() - playingState = State.PAUSED + pausePlayback() } override fun stop() { @@ -212,16 +211,39 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } - private fun resumePlayback() { - currentMediaPlayer?.start() - playingState = State.PLAYING + private fun pausePlayback(positionMillis: Int? = null) { + if (positionMillis == null) { + currentMediaPlayer?.pause() + } else { + stopPlayer() + currentVoiceBroadcast?.voiceBroadcastId?.let { id -> + playbackTracker.updatePausedAtPlaybackTime(id, positionMillis, positionMillis.toFloat() / playlist.duration) + } + } + playingState = State.PAUSED } - override fun seekTo(voiceBroadcast: VoiceBroadcast, positionMillis: Int) { - if (voiceBroadcast != currentVoiceBroadcast) { - playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, 0f) + private fun resumePlayback() { + if (currentMediaPlayer != null) { + currentMediaPlayer?.start() + playingState = State.PLAYING } else { - startPlayback(positionMillis) + val position = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } + startPlayback(position) + } + } + + override fun seekTo(voiceBroadcast: VoiceBroadcast, positionMillis: Int, duration: Int) { + when { + voiceBroadcast != currentVoiceBroadcast -> { + playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) + } + playingState == State.PLAYING || playingState == State.BUFFERING -> { + startPlayback(positionMillis) + } + playingState == State.IDLE || playingState == State.PAUSED -> { + pausePlayback(positionMillis) + } } } From 7d51a265222c5bcabee7fe3e92a7a956e6c37377 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 4 Nov 2022 23:45:38 +0100 Subject: [PATCH 17/28] Decrease tick interval --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 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 0937977b70..f7e296ffed 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 @@ -355,7 +355,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( fun startPlaybackTicker(id: String) { playbackTicker?.stop() - playbackTicker = CountUpTimer().apply { + playbackTicker = CountUpTimer(50L).apply { tickListener = CountUpTimer.TickListener { onPlaybackTick(id) } resume() } @@ -388,7 +388,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( State.IDLE -> { val playbackTime = playbackTracker.getPlaybackTime(id) val percentage = playbackTracker.getPercentage(id) - if (playingState == State.IDLE && duration > 0 && (duration - playbackTime) < 1000) { + if (playingState == State.IDLE && duration > 0 && (duration - playbackTime) < 100) { playbackTracker.updatePausedAtPlaybackTime(id, 0, 0f) } else { playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) From baa9cb39b0ff0755376b612e7d5c10fdcab439cb Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Sat, 5 Nov 2022 00:06:00 +0100 Subject: [PATCH 18/28] Fix broken live listening --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 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 f7e296ffed..b613d99d33 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 @@ -171,12 +171,12 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val nextItem = playlist.getNextItem() if (nextItem != null) { val savedPosition = currentVoiceBroadcast?.let { playbackTracker.getPlaybackTime(it.voiceBroadcastId) } - startPlayback(savedPosition) + startPlayback(savedPosition?.takeIf { it > 0 }) } } State.IDLE -> { val savedPosition = currentVoiceBroadcast?.let { playbackTracker.getPlaybackTime(it.voiceBroadcastId) } - startPlayback(savedPosition) + startPlayback(savedPosition?.takeIf { it > 0 }) } } } @@ -389,7 +389,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val playbackTime = playbackTracker.getPlaybackTime(id) val percentage = playbackTracker.getPercentage(id) if (playingState == State.IDLE && duration > 0 && (duration - playbackTime) < 100) { - playbackTracker.updatePausedAtPlaybackTime(id, 0, 0f) + playbackTracker.stopPlayback(id) } else { playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) } From c5e6eb0d0e131f86ba9da3af94fa8c0455ebbc6e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Sat, 5 Nov 2022 00:07:02 +0100 Subject: [PATCH 19/28] Remove some logs --- .../detail/timeline/item/MessageVoiceBroadcastListeningItem.kt | 1 - .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 1 - 2 files changed, 2 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 ebbfe13730..bdd0670029 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 @@ -108,7 +108,6 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } is AudioMessagePlaybackTracker.Listener.State.Playing -> { if (!isUserSeeking) { -// Timber.d("Voice Broadcast | AudioMessagePlaybackTracker.Listener.onUpdate - duration: $duration, playbackTime: ${state.playbackTime}") holder.seekBar.progress = state.playbackTime } } 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 b613d99d33..9fb7c3ccb5 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 @@ -375,7 +375,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor( when (playingState) { State.PLAYING, State.PAUSED -> { - Timber.d("Voice Broadcast | VoiceBroadcastPlayerImpl - sequence: ${playlist.currentSequence}, itemStartTime $itemStartTime, currentMediaPlayer=$currentMediaPlayer, currentMediaPlayer?.currentPosition: ${currentMediaPlayer?.currentPosition}") val position = itemStartTime + (currentMediaPlayer?.currentPosition ?: 0) val percentage = position.toFloat() / playlist.duration if (playingState == State.PLAYING) { From aa8eec221a1551b6d737e6a58feffe8f82fd3da7 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Sat, 5 Nov 2022 00:29:11 +0100 Subject: [PATCH 20/28] Enable fast backward/forward buttons --- .../MessageVoiceBroadcastListeningItem.kt | 59 +++++++++++++------ .../listening/VoiceBroadcastPlayerImpl.kt | 13 ++-- ...e_event_voice_broadcast_listening_stub.xml | 4 +- 3 files changed, 47 insertions(+), 29 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 bdd0670029..558f81b5fa 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 @@ -28,6 +28,7 @@ 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.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView @@ -48,6 +49,32 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } player.addListener(voiceBroadcast, playerListener) bindSeekBar(holder) + bindButtons(holder) + } + + private fun bindButtons(holder: Holder) { + with(holder) { + playPauseButton.onClick { + when (player.playingState) { + VoiceBroadcastPlayer.State.PLAYING -> { + callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) + } + VoiceBroadcastPlayer.State.PAUSED, + VoiceBroadcastPlayer.State.IDLE -> { + callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) + } + VoiceBroadcastPlayer.State.BUFFERING -> Unit + } + } + fastBackwardButton.onClick { + val newPos = seekBar.progress.minus(30_000).coerceIn(0, duration) + callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, newPos, duration)) + } + fastForwardButton.onClick { + val newPos = seekBar.progress.plus(30_000).coerceIn(0, duration) + callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, newPos, duration)) + } + } } override fun renderMetadata(holder: Holder) { @@ -63,20 +90,15 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING playPauseButton.isVisible = state != VoiceBroadcastPlayer.State.BUFFERING - fastBackwardButton.isInvisible = true - fastForwardButton.isInvisible = true - when (state) { VoiceBroadcastPlayer.State.PLAYING -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) - playPauseButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) } } VoiceBroadcastPlayer.State.IDLE, VoiceBroadcastPlayer.State.PAUSED -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_play) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) - playPauseButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) } } VoiceBroadcastPlayer.State.BUFFERING -> Unit } @@ -99,25 +121,24 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } }) playbackTracker.track(voiceBroadcast.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 + override fun onUpdate(state: State) { + renderBackwardForwardButtons(holder, state) + if (!isUserSeeking) { + holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) } } }) } + private fun renderBackwardForwardButtons(holder: Holder, playbackState: State) { + val isPlayingOrPaused = playbackState is State.Playing || playbackState is State.Paused + val playbackTime = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) + val canBackward = isPlayingOrPaused && playbackTime > 0 + val canForward = isPlayingOrPaused && playbackTime < duration + holder.fastBackwardButton.isInvisible = !canBackward + holder.fastForwardButton.isInvisible = !canForward + } + private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong()) override fun unbind(holder: Holder) { 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 9fb7c3ccb5..020edc283a 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 @@ -371,7 +371,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun onPlaybackTick(id: String) { val currentItem = playlist.currentItem ?: return val itemStartTime = currentItem.startTime - val duration = playlist.duration when (playingState) { State.PLAYING, State.PAUSED -> { @@ -383,15 +382,13 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playbackTracker.updatePausedAtPlaybackTime(id, position, percentage) } } - State.BUFFERING, - State.IDLE -> { + State.BUFFERING -> { val playbackTime = playbackTracker.getPlaybackTime(id) val percentage = playbackTracker.getPercentage(id) - if (playingState == State.IDLE && duration > 0 && (duration - playbackTime) < 100) { - playbackTracker.stopPlayback(id) - } else { - playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) - } + playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) + } + State.IDLE -> { + playbackTracker.stopPlayback(id) } } } diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml index bed9407dfa..150f1cb281 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml @@ -100,7 +100,7 @@ android:id="@+id/fastBackwardButton" android:layout_width="24dp" android:layout_height="24dp" - android:background="@android:color/transparent" + android:background="@drawable/bg_rounded_button" android:contentDescription="@string/a11y_voice_broadcast_fast_backward" android:src="@drawable/ic_player_backward_30" app:tint="?vctr_content_secondary" /> @@ -127,7 +127,7 @@ android:id="@+id/fastForwardButton" android:layout_width="24dp" android:layout_height="24dp" - android:background="@android:color/transparent" + android:background="@drawable/bg_rounded_button" android:contentDescription="@string/a11y_voice_broadcast_fast_forward" android:src="@drawable/ic_player_forward_30" app:tint="?vctr_content_secondary" /> From 1c40f9c5e82ca4128238a42c99652954c787d33e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 7 Nov 2022 11:40:57 +0100 Subject: [PATCH 21/28] Minor cleanup --- .../MessageVoiceBroadcastListeningItem.kt | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 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 558f81b5fa..a43783a626 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,9 +44,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } private fun bindVoiceBroadcastItem(holder: Holder) { - playerListener = VoiceBroadcastPlayer.Listener { state -> - renderPlayingState(holder, state) - } + playerListener = VoiceBroadcastPlayer.Listener { renderPlayingState(holder, it) } player.addListener(voiceBroadcast, playerListener) bindSeekBar(holder) bindButtons(holder) @@ -56,13 +54,9 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem with(holder) { playPauseButton.onClick { when (player.playingState) { - VoiceBroadcastPlayer.State.PLAYING -> { - callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) - } + VoiceBroadcastPlayer.State.PLAYING -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) VoiceBroadcastPlayer.State.PAUSED, - VoiceBroadcastPlayer.State.IDLE -> { - callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) - } + VoiceBroadcastPlayer.State.IDLE -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) VoiceBroadcastPlayer.State.BUFFERING -> Unit } } @@ -106,20 +100,22 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } private fun bindSeekBar(holder: Holder) { - 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 + with(holder) { + durationView.text = formatPlaybackTime(duration) + seekBar.max = duration + seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) = Unit - override fun onStartTrackingTouch(seekBar: SeekBar) { - isUserSeeking = true - } + override fun onStartTrackingTouch(seekBar: SeekBar) { + isUserSeeking = true + } - override fun onStopTrackingTouch(seekBar: SeekBar) { - callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, seekBar.progress, duration)) - isUserSeeking = false - } - }) + override fun onStopTrackingTouch(seekBar: SeekBar) { + callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcast, seekBar.progress, duration)) + isUserSeeking = false + } + }) + } playbackTracker.track(voiceBroadcast.voiceBroadcastId, object : AudioMessagePlaybackTracker.Listener { override fun onUpdate(state: State) { renderBackwardForwardButtons(holder, state) From 226e2026a12452884b05434815afe609e671a98d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 7 Nov 2022 11:42:04 +0100 Subject: [PATCH 22/28] Remove item listeners --- .../timeline/item/MessageVoiceBroadcastListeningItem.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 a43783a626..c83b1f6954 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 @@ -140,8 +140,13 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem override fun unbind(holder: Holder) { super.unbind(holder) player.removeListener(voiceBroadcast, playerListener) - holder.seekBar.setOnSeekBarChangeListener(null) playbackTracker.untrack(voiceBroadcast.voiceBroadcastId) + with(holder) { + seekBar.onClick(null) + playPauseButton.onClick(null) + fastForwardButton.onClick(null) + fastBackwardButton.onClick(null) + } } override fun getViewStubId() = STUB_ID From 6b57b1190cccbe8e73e8e5b3934c14ad399b57eb Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 7 Nov 2022 11:46:30 +0100 Subject: [PATCH 23/28] Make AudioMessagePlaybackTracker.Listener interface funny --- .../helper/AudioMessagePlaybackTracker.kt | 2 +- .../detail/timeline/item/MessageAudioItem.kt | 16 +++++++--------- .../item/MessageVoiceBroadcastListeningItem.kt | 12 +++++------- .../detail/timeline/item/MessageVoiceItem.kt | 16 +++++++--------- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt index 7e40b92ac8..91f27ce5a8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt @@ -148,7 +148,7 @@ class AudioMessagePlaybackTracker @Inject constructor() { const val RECORDING_ID = "RECORDING_ID" } - interface Listener { + fun interface Listener { fun onUpdate(state: State) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt index fda9a1465f..3e8d6cb487 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt @@ -140,16 +140,14 @@ abstract class MessageAudioItem : AbsMessageItem() { } private fun renderStateBasedOnAudioPlayback(holder: Holder) { - audioMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener { - override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) { - when (state) { - is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder) - is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state) - is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state) - is AudioMessagePlaybackTracker.Listener.State.Recording -> Unit - } + audioMessagePlaybackTracker.track(attributes.informationData.eventId) { state -> + when (state) { + is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder) + is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state) + is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state) + is AudioMessagePlaybackTracker.Listener.State.Recording -> Unit } - }) + } } private fun renderIdleState(holder: Holder) { 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 c83b1f6954..1076e06b7e 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 @@ -116,14 +116,12 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } }) } - playbackTracker.track(voiceBroadcast.voiceBroadcastId, object : AudioMessagePlaybackTracker.Listener { - override fun onUpdate(state: State) { - renderBackwardForwardButtons(holder, state) - if (!isUserSeeking) { - holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) - } + playbackTracker.track(voiceBroadcast.voiceBroadcastId) { playbackState -> + renderBackwardForwardButtons(holder, playbackState) + if (!isUserSeeking) { + holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) } - }) + } } private fun renderBackwardForwardButtons(holder: Holder, playbackState: State) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index e057950790..d3f320db7d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -122,16 +122,14 @@ abstract class MessageVoiceItem : AbsMessageItem() { true } - audioMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener { - override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) { - when (state) { - is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder, waveformColorIdle, waveformColorPlayed) - is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state, waveformColorIdle, waveformColorPlayed) - is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state, waveformColorIdle, waveformColorPlayed) - is AudioMessagePlaybackTracker.Listener.State.Recording -> Unit - } + audioMessagePlaybackTracker.track(attributes.informationData.eventId) { state -> + when (state) { + is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder, waveformColorIdle, waveformColorPlayed) + is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state, waveformColorIdle, waveformColorPlayed) + is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state, waveformColorIdle, waveformColorPlayed) + is AudioMessagePlaybackTracker.Listener.State.Recording -> Unit } - }) + } } private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = (motionEvent.x / view.width).coerceIn(0f, 1f) From 305a362e9ee15e6a915053a5457d70e6de087dc8 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 7 Nov 2022 12:04:30 +0100 Subject: [PATCH 24/28] Fix play action on other voice broadcast than the current one --- .../item/MessageVoiceBroadcastListeningItem.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 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 1076e06b7e..4b91bbfb0e 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 @@ -27,7 +27,6 @@ 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.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView @@ -53,11 +52,15 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem private fun bindButtons(holder: Holder) { with(holder) { playPauseButton.onClick { - when (player.playingState) { - VoiceBroadcastPlayer.State.PLAYING -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) - VoiceBroadcastPlayer.State.PAUSED, - VoiceBroadcastPlayer.State.IDLE -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) - VoiceBroadcastPlayer.State.BUFFERING -> Unit + if (player.currentVoiceBroadcast == voiceBroadcast) { + when (player.playingState) { + VoiceBroadcastPlayer.State.PLAYING -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) + VoiceBroadcastPlayer.State.PAUSED, + VoiceBroadcastPlayer.State.IDLE -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) + VoiceBroadcastPlayer.State.BUFFERING -> Unit + } + } else { + callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) } } fastBackwardButton.onClick { From be18f4ec78af8bb77f3ff99f65157f3c12aaca35 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 7 Nov 2022 14:09:15 +0100 Subject: [PATCH 25/28] remove unused imports --- .../listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt | 1 - .../voicebroadcast/usecase/GetVoiceBroadcastEventUseCase.kt | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) 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 33e370e9bc..d12a329142 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 @@ -31,7 +31,6 @@ import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.lastOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.runningReduce import kotlinx.coroutines.runBlocking 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 7106322f06..696d300fc3 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 @@ -22,9 +22,7 @@ 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 kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -56,7 +54,7 @@ class GetVoiceBroadcastEventUseCase @Inject constructor( return when (latestEvent?.content?.voiceBroadcastState) { null, VoiceBroadcastState.STOPPED -> flowOf(latestEvent.toOptional()) - else -> { + else -> { room.flow() .liveStateEvent(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(latestEvent.root.stateKey.orEmpty())) .unwrap() From 9e83d88f08ae492a2f1271f0df255edf66c546c4 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 7 Nov 2022 14:52:53 +0100 Subject: [PATCH 26/28] Fix seek position when listening another voice broadcast --- .../listening/VoiceBroadcastPlayerImpl.kt | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 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 020edc283a..977b3906e0 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 @@ -216,8 +216,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor( currentMediaPlayer?.pause() } else { stopPlayer() - currentVoiceBroadcast?.voiceBroadcastId?.let { id -> - playbackTracker.updatePausedAtPlaybackTime(id, positionMillis, positionMillis.toFloat() / playlist.duration) + val voiceBroadcastId = currentVoiceBroadcast?.voiceBroadcastId + val duration = playlist.duration.takeIf { it > 0 } + if (voiceBroadcastId != null && duration != null) { + playbackTracker.updatePausedAtPlaybackTime(voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) } } playingState = State.PAUSED @@ -312,6 +314,22 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } + private fun getCurrentPlaybackPosition(): Int? { + val playlistPosition = playlist.currentItem?.startTime + val computedPosition = currentMediaPlayer?.currentPosition?.let { playlistPosition?.plus(it) } ?: playlistPosition + val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } + return computedPosition ?: savedPosition + } + + private fun getCurrentPlaybackPercentage(): Float? { + val playlistPosition = playlist.currentItem?.startTime + val computedPosition = currentMediaPlayer?.currentPosition?.let { playlistPosition?.plus(it) } ?: playlistPosition + val duration = playlist.duration.takeIf { it > 0 } + val computedPercentage = if (computedPosition != null && duration != null) computedPosition.toFloat() / duration else null + val savedPercentage = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPercentage(it) } + return computedPercentage ?: savedPercentage + } + private inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, @@ -369,26 +387,26 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } private fun onPlaybackTick(id: String) { - val currentItem = playlist.currentItem ?: return - val itemStartTime = currentItem.startTime + val playbackTime = getCurrentPlaybackPosition() + val percentage = getCurrentPlaybackPercentage() when (playingState) { - State.PLAYING, - State.PAUSED -> { - val position = itemStartTime + (currentMediaPlayer?.currentPosition ?: 0) - val percentage = position.toFloat() / playlist.duration - if (playingState == State.PLAYING) { - playbackTracker.updatePlayingAtPlaybackTime(id, position, percentage) - } else { - playbackTracker.updatePausedAtPlaybackTime(id, position, percentage) + State.PLAYING -> { + if (playbackTime != null && percentage != null) { + playbackTracker.updatePlayingAtPlaybackTime(id, playbackTime, percentage) } } + State.PAUSED, State.BUFFERING -> { - val playbackTime = playbackTracker.getPlaybackTime(id) - val percentage = playbackTracker.getPercentage(id) - playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) + if (playbackTime != null && percentage != null) { + playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) + } } State.IDLE -> { - playbackTracker.stopPlayback(id) + if (playbackTime == null || percentage == null || playbackTime == playlist.duration) { + playbackTracker.stopPlayback(id) + } else { + playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) + } } } } From 4e533667278c5a5f6900edebc58c5d6cad4c7725 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 7 Nov 2022 15:46:52 +0100 Subject: [PATCH 27/28] Fix default visibility of fast backward/forward buttons --- ...timeline_event_voice_broadcast_listening_stub.xml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml index 150f1cb281..1d31afba99 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml @@ -103,7 +103,9 @@ android:background="@drawable/bg_rounded_button" android:contentDescription="@string/a11y_voice_broadcast_fast_backward" android:src="@drawable/ic_player_backward_30" - app:tint="?vctr_content_secondary" /> + android:visibility="invisible" + app:tint="?vctr_content_secondary" + tools:visibility="visible" /> + android:indeterminateTint="?vctr_content_secondary" + android:visibility="gone" + tools:visibility="visible" /> + android:visibility="invisible" + app:tint="?vctr_content_secondary" + tools:visibility="visible" /> Date: Mon, 7 Nov 2022 16:05:06 +0100 Subject: [PATCH 28/28] improve end of voice broadcast check --- .../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 977b3906e0..6a6dc6a9e8 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 @@ -402,7 +402,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } State.IDLE -> { - if (playbackTime == null || percentage == null || playbackTime == playlist.duration) { + if (playbackTime == null || percentage == null || (playlist.duration - playbackTime) < 50) { playbackTracker.stopPlayback(id) } else { playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage)