From f8852856c6d60c6b9245d47b2393df5ed08dabc5 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 10 Jan 2023 10:34:45 +0100 Subject: [PATCH 01/20] Convert state enum to sealed interface --- .../MessageVoiceBroadcastListeningItem.kt | 20 +++--- .../listening/VoiceBroadcastPlayer.kt | 12 ++-- .../listening/VoiceBroadcastPlayerImpl.kt | 62 +++++++++---------- 3 files changed, 47 insertions(+), 47 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 b788d79214..d21e6771d9 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 @@ -63,10 +63,10 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem playPauseButton.setOnClickListener { if (player.currentVoiceBroadcast == voiceBroadcast) { when (player.playingState) { - VoiceBroadcastPlayer.State.PLAYING, - VoiceBroadcastPlayer.State.BUFFERING -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) - VoiceBroadcastPlayer.State.PAUSED, - VoiceBroadcastPlayer.State.IDLE -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) + VoiceBroadcastPlayer.State.Playing, + VoiceBroadcastPlayer.State.Buffering -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) + VoiceBroadcastPlayer.State.Paused, + VoiceBroadcastPlayer.State.Idle -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) } } else { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) @@ -100,17 +100,17 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem private fun renderPlayingState(holder: Holder, state: VoiceBroadcastPlayer.State) { with(holder) { - bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING - voiceBroadcastMetadata.isVisible = state != VoiceBroadcastPlayer.State.BUFFERING + bufferingView.isVisible = state == VoiceBroadcastPlayer.State.Buffering + voiceBroadcastMetadata.isVisible = state != VoiceBroadcastPlayer.State.Buffering when (state) { - VoiceBroadcastPlayer.State.PLAYING, - VoiceBroadcastPlayer.State.BUFFERING -> { + VoiceBroadcastPlayer.State.Playing, + VoiceBroadcastPlayer.State.Buffering -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) } - VoiceBroadcastPlayer.State.IDLE, - VoiceBroadcastPlayer.State.PAUSED -> { + 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) } 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 0de88e9992..5e5c320d43 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 @@ -26,7 +26,7 @@ interface VoiceBroadcastPlayer { val currentVoiceBroadcast: VoiceBroadcast? /** - * The current playing [State], [State.IDLE] by default. + * The current playing [State], [State.Idle] by default. */ val playingState: State @@ -68,11 +68,11 @@ interface VoiceBroadcastPlayer { /** * Player states. */ - enum class State { - PLAYING, - PAUSED, - BUFFERING, - IDLE + sealed interface State { + object Playing : State + object Paused : State + object Buffering : State + object Idle : State } /** 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 9cb894bb58..f00d657682 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 @@ -79,7 +79,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } - override var playingState = State.IDLE + override var playingState: State = State.Idle @MainThread set(value) { if (field != value) { @@ -96,7 +96,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val hasChanged = currentVoiceBroadcast != voiceBroadcast when { hasChanged -> startPlayback(voiceBroadcast) - playingState == State.PAUSED -> resumePlayback() + playingState == State.Paused -> resumePlayback() else -> Unit } } @@ -107,7 +107,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( override fun stop() { // Update state - playingState = State.IDLE + playingState = State.Idle // Stop and release media players stopPlayer() @@ -129,7 +129,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( listeners[voiceBroadcast.voiceBroadcastId]?.add(listener) ?: run { listeners[voiceBroadcast.voiceBroadcastId] = CopyOnWriteArrayList().apply { add(listener) } } - listener.onPlayingStateChanged(if (voiceBroadcast == currentVoiceBroadcast) playingState else State.IDLE) + listener.onPlayingStateChanged(if (voiceBroadcast == currentVoiceBroadcast) playingState else State.Idle) listener.onLiveModeChanged(voiceBroadcast == currentVoiceBroadcast) } @@ -139,11 +139,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun startPlayback(voiceBroadcast: VoiceBroadcast) { // Stop listening previous voice broadcast if any - if (playingState != State.IDLE) stop() + if (playingState != State.Idle) stop() currentVoiceBroadcast = voiceBroadcast - playingState = State.BUFFERING + playingState = State.Buffering observeVoiceBroadcastStateEvent(voiceBroadcast) } @@ -175,13 +175,13 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun onPlaylistUpdated() { when (playingState) { - State.PLAYING, - State.PAUSED -> { + State.Playing, + State.Paused -> { if (nextMediaPlayer == null && !isPreparingNextPlayer) { prepareNextMediaPlayer() } } - State.BUFFERING -> { + State.Buffering -> { val nextItem = if (isLiveListening && playlist.currentSequence == null) { // live listening, jump to the last item if playback has not started playlist.lastOrNull() @@ -193,7 +193,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( startPlayback(nextItem.startTime) } } - State.IDLE -> Unit // Should not happen + State.Idle -> Unit // Should not happen } } @@ -213,7 +213,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( if (sequencePosition > 0) { mp.seekTo(sequencePosition) } - playingState = State.PLAYING + playingState = State.Playing prepareNextMediaPlayer() } } catch (failure: Throwable) { @@ -224,7 +224,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } private fun pausePlayback() { - playingState = State.PAUSED // This will trigger a playing state update and save the current position + playingState = State.Paused // This will trigger a playing state update and save the current position if (currentMediaPlayer != null) { currentMediaPlayer?.pause() } else { @@ -234,7 +234,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun resumePlayback() { if (currentMediaPlayer != null) { - playingState = State.PLAYING + playingState = State.Playing currentMediaPlayer?.start() } else { val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } ?: 0 @@ -247,11 +247,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor( voiceBroadcast != currentVoiceBroadcast -> { playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) } - playingState == State.PLAYING || playingState == State.BUFFERING -> { + playingState == State.Playing || playingState == State.Buffering -> { updateLiveListeningMode(positionMillis) startPlayback(positionMillis) } - playingState == State.IDLE || playingState == State.PAUSED -> { + playingState == State.Idle || playingState == State.Paused -> { stopPlayer() playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) } @@ -267,15 +267,15 @@ class VoiceBroadcastPlayerImpl @Inject constructor( isPreparingNextPlayer = false nextMediaPlayer = mp when (playingState) { - State.PLAYING, - State.PAUSED -> { + State.Playing, + State.Paused -> { currentMediaPlayer?.setNextMediaPlayer(mp) } - State.BUFFERING -> { + State.Buffering -> { mp.start() onNextMediaPlayerStarted(mp) } - State.IDLE -> stopPlayer() + State.Idle -> stopPlayer() } } } @@ -327,10 +327,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor( currentVoiceBroadcast?.voiceBroadcastId?.let { voiceBroadcastId -> // Start or stop playback ticker when (playingState) { - State.PLAYING -> playbackTicker.startPlaybackTicker(voiceBroadcastId) - State.PAUSED, - State.BUFFERING, - State.IDLE -> playbackTicker.stopPlaybackTicker(voiceBroadcastId) + State.Playing -> playbackTicker.startPlaybackTicker(voiceBroadcastId) + State.Paused, + State.Buffering, + State.Idle -> playbackTicker.stopPlaybackTicker(voiceBroadcastId) } // Notify state change to all the listeners attached to the current voice broadcast id listeners[voiceBroadcastId]?.forEach { listener -> listener.onPlayingStateChanged(playingState) } @@ -348,7 +348,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( // the current voice broadcast is not live (ended) mostRecentVoiceBroadcastEvent?.isLive != true -> false // the player is stopped or paused - playingState == State.IDLE || playingState == State.PAUSED -> false + playingState == State.Idle || playingState == State.Paused -> false seekPosition != null -> { val seekDirection = seekPosition.compareTo(getCurrentPlaybackPosition() ?: 0) val newSequence = playlist.findByPosition(seekPosition)?.sequence @@ -374,13 +374,13 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun onLiveListeningChanged(isLiveListening: Boolean) { // Live has ended and last chunk has been reached, we can stop the playback - if (!isLiveListening && playingState == State.BUFFERING && playlist.currentSequence == mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence) { + if (!isLiveListening && playingState == State.Buffering && playlist.currentSequence == mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence) { stop() } } private fun onNextMediaPlayerStarted(mp: MediaPlayer) { - playingState = State.PLAYING + playingState = State.Playing playlist.currentSequence = playlist.currentSequence?.inc() currentMediaPlayer = mp nextMediaPlayer = null @@ -427,7 +427,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( stop() } else { // Enter in buffering mode and release current media player - playingState = State.BUFFERING + playingState = State.Buffering currentMediaPlayer?.release() currentMediaPlayer = null } @@ -462,18 +462,18 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val playbackTime = getCurrentPlaybackPosition() val percentage = getCurrentPlaybackPercentage() when (playingState) { - State.PLAYING -> { + State.Playing -> { if (playbackTime != null && percentage != null) { playbackTracker.updatePlayingAtPlaybackTime(id, playbackTime, percentage) } } - State.PAUSED, - State.BUFFERING -> { + State.Paused, + State.Buffering -> { if (playbackTime != null && percentage != null) { playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) } } - State.IDLE -> { + State.Idle -> { if (playbackTime == null || percentage == null || (playlist.duration - playbackTime) < 50) { playbackTracker.stopPlayback(id) } else { From 2d24eb1273d0aa72e9ea2e5f758484763628af34 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 10 Jan 2023 14:00:46 +0100 Subject: [PATCH 02/20] Handle playback error --- changelog.d/7829.bugfix | 1 + .../src/main/res/values/strings.xml | 1 + .../vector/app/core/error/ErrorFormatter.kt | 2 + .../voice/VoiceMessageRecorderView.kt | 1 + .../factory/VoiceBroadcastItemFactory.kt | 3 + .../helper/AudioMessagePlaybackTracker.kt | 30 ++++++--- .../item/AbsMessageVoiceBroadcastItem.kt | 3 + .../detail/timeline/item/MessageAudioItem.kt | 1 + .../MessageVoiceBroadcastListeningItem.kt | 35 ++++++++--- .../detail/timeline/item/MessageVoiceItem.kt | 1 + .../voicebroadcast/VoiceBroadcastFailure.kt | 11 ++++ .../listening/VoiceBroadcastPlayer.kt | 2 + .../listening/VoiceBroadcastPlayerImpl.kt | 63 +++++++++++-------- .../res/drawable/ic_voice_broadcast_error.xml | 10 +++ ...e_event_voice_broadcast_listening_stub.xml | 23 +++++++ 15 files changed, 144 insertions(+), 43 deletions(-) create mode 100644 changelog.d/7829.bugfix create mode 100644 vector/src/main/res/drawable/ic_voice_broadcast_error.xml diff --git a/changelog.d/7829.bugfix b/changelog.d/7829.bugfix new file mode 100644 index 0000000000..705f7310f0 --- /dev/null +++ b/changelog.d/7829.bugfix @@ -0,0 +1 @@ +Handle exceptions when listening a voice broadcast diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index d9f94ba27b..ce84ef61ad 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3121,6 +3121,7 @@ You don’t have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions. Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one. You are already recording a voice broadcast. Please end your current voice broadcast to start a new one. + Unable to play this voice broadcast. %1$s left Stop live broadcasting? diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index 380c80775b..78aaa058e9 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -157,6 +157,8 @@ class DefaultErrorFormatter @Inject constructor( RecordingError.BlockedBySomeoneElse -> stringProvider.getString(R.string.error_voice_broadcast_blocked_by_someone_else_message) RecordingError.NoPermission -> stringProvider.getString(R.string.error_voice_broadcast_permission_denied_message) RecordingError.UserAlreadyBroadcasting -> stringProvider.getString(R.string.error_voice_broadcast_already_in_progress_message) + is VoiceBroadcastFailure.ListeningError.UnableToPlay, + is VoiceBroadcastFailure.ListeningError.DownloadError -> stringProvider.getString(R.string.error_voice_broadcast_unable_to_play) } } 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 a7b926f29a..b5c4b4a537 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 @@ -229,6 +229,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( voiceMessageViews.renderPlaying(state) } is AudioMessagePlaybackTracker.Listener.State.Paused, + is AudioMessagePlaybackTracker.Listener.State.Error, is AudioMessagePlaybackTracker.Listener.State.Idle -> { voiceMessageViews.renderIdle() } 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 cc3a015120..ad8e163633 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 @@ -15,6 +15,7 @@ */ package im.vector.app.features.home.room.detail.timeline.factory +import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider import im.vector.app.features.displayname.getBestName @@ -45,6 +46,7 @@ class VoiceBroadcastItemFactory @Inject constructor( private val avatarSizeProvider: AvatarSizeProvider, private val colorProvider: ColorProvider, private val drawableProvider: DrawableProvider, + private val errorFormatter: ErrorFormatter, private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, private val voiceBroadcastPlayer: VoiceBroadcastPlayer, private val playbackTracker: AudioMessagePlaybackTracker, @@ -82,6 +84,7 @@ class VoiceBroadcastItemFactory @Inject constructor( roomItem = session.getRoom(params.event.roomId)?.roomSummary()?.toMatrixItem(), colorProvider = colorProvider, drawableProvider = drawableProvider, + errorFormatter = errorFormatter, ) return if (isRecording) { 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 c34cbbc74a..c598a99af7 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 @@ -50,8 +50,11 @@ class AudioMessagePlaybackTracker @Inject constructor() { listeners.remove(id) } - fun pauseAllPlaybacks() { - listeners.keys.forEach(::pausePlayback) + fun unregisterListeners() { + listeners.forEach { + it.value.onUpdate(Listener.State.Idle) + } + listeners.clear() } /** @@ -84,6 +87,10 @@ class AudioMessagePlaybackTracker @Inject constructor() { } } + fun pauseAllPlaybacks() { + listeners.keys.forEach(::pausePlayback) + } + fun pausePlayback(id: String) { val state = getPlaybackState(id) if (state is Listener.State.Playing) { @@ -94,7 +101,14 @@ class AudioMessagePlaybackTracker @Inject constructor() { } fun stopPlayback(id: String) { - setState(id, Listener.State.Idle) + val state = getPlaybackState(id) + if (state !is Listener.State.Error) { + setState(id, Listener.State.Idle) + } + } + + fun onError(id: String, error: Throwable) { + setState(id, Listener.State.Error(error)) } fun updatePlayingAtPlaybackTime(id: String, time: Int, percentage: Float) { @@ -116,6 +130,7 @@ class AudioMessagePlaybackTracker @Inject constructor() { is Listener.State.Playing -> state.playbackTime is Listener.State.Paused -> state.playbackTime is Listener.State.Recording, + is Listener.State.Error, Listener.State.Idle, null -> null } @@ -126,18 +141,12 @@ class AudioMessagePlaybackTracker @Inject constructor() { is Listener.State.Playing -> state.percentage is Listener.State.Paused -> state.percentage is Listener.State.Recording, + is Listener.State.Error, Listener.State.Idle, null -> null } } - fun unregisterListeners() { - listeners.forEach { - it.value.onUpdate(Listener.State.Idle) - } - listeners.clear() - } - companion object { const val RECORDING_ID = "RECORDING_ID" } @@ -148,6 +157,7 @@ class AudioMessagePlaybackTracker @Inject constructor() { sealed class State { object Idle : State() + data class Error(val failure: Throwable) : State() data class Playing(val playbackTime: Int, val percentage: Float) : State() data class Paused(val playbackTime: Int, val percentage: Float) : State() data class Recording(val amplitudeList: List) : State() 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 c6b90cdabe..7cde978e42 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 @@ -22,6 +22,7 @@ import androidx.annotation.IdRes import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import im.vector.app.R +import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.tintBackground import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider @@ -48,6 +49,7 @@ abstract class AbsMessageVoiceBroadcastItem() { private fun renderStateBasedOnAudioPlayback(holder: Holder) { audioMessagePlaybackTracker.track(attributes.informationData.eventId) { state -> when (state) { + is AudioMessagePlaybackTracker.Listener.State.Error, is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder) is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state) is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state) 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 d21e6771d9..0aa2aaad3b 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 @@ -20,11 +20,13 @@ import android.text.format.DateUtils import android.widget.ImageButton import android.widget.SeekBar import android.widget.TextView +import androidx.constraintlayout.widget.Group import androidx.core.view.isInvisible import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.onClick +import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer @@ -54,6 +56,16 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } } player.addListener(voiceBroadcast, playerListener) + + playbackTracker.track(voiceBroadcast.voiceBroadcastId) { playbackState -> + renderBackwardForwardButtons(holder, playbackState) + renderPlaybackError(holder, playbackState) + renderLiveIndicator(holder) + if (!isUserSeeking) { + holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) ?: 0 + } + } + bindSeekBar(holder) bindButtons(holder) } @@ -66,6 +78,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem VoiceBroadcastPlayer.State.Playing, VoiceBroadcastPlayer.State.Buffering -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) VoiceBroadcastPlayer.State.Paused, + is VoiceBroadcastPlayer.State.Error, VoiceBroadcastPlayer.State.Idle -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) } } else { @@ -109,6 +122,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) } + is VoiceBroadcastPlayer.State.Error, VoiceBroadcastPlayer.State.Idle, VoiceBroadcastPlayer.State.Paused -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_play) @@ -120,6 +134,18 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } } + private fun renderPlaybackError(holder: Holder, playbackState: State) { + with(holder) { + if (playbackState is State.Error) { + controlsGroup.isVisible = false + errorView.setTextOrHide(errorFormatter.toHumanReadable(playbackState.failure)) + } else { + errorView.isVisible = false + controlsGroup.isVisible = true + } + } + } + private fun bindSeekBar(holder: Holder) { with(holder) { remainingTimeView.text = formatRemainingTime(duration) @@ -141,13 +167,6 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } }) } - playbackTracker.track(voiceBroadcast.voiceBroadcastId) { playbackState -> - renderBackwardForwardButtons(holder, playbackState) - renderLiveIndicator(holder) - if (!isUserSeeking) { - holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) ?: 0 - } - } } private fun renderBackwardForwardButtons(holder: Holder, playbackState: State) { @@ -187,6 +206,8 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem val broadcasterNameMetadata by bind(R.id.broadcasterNameMetadata) val voiceBroadcastMetadata by bind(R.id.voiceBroadcastMetadata) val listenersCountMetadata by bind(R.id.listenersCountMetadata) + val errorView by bind(R.id.errorView) + val controlsGroup by bind(R.id.controlsGroup) } companion object { 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 d3f320db7d..a8e215b4a9 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 @@ -124,6 +124,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { audioMessagePlaybackTracker.track(attributes.informationData.eventId) { state -> when (state) { + is AudioMessagePlaybackTracker.Listener.State.Error, 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) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt index 76b50c78ab..75863dc042 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt @@ -16,10 +16,21 @@ package im.vector.app.features.voicebroadcast +import android.media.MediaPlayer + sealed class VoiceBroadcastFailure : Throwable() { sealed class RecordingError : VoiceBroadcastFailure() { object NoPermission : RecordingError() object BlockedBySomeoneElse : RecordingError() object UserAlreadyBroadcasting : RecordingError() } + + sealed class ListeningError : VoiceBroadcastFailure() { + /** + * @property what the type of error that has occurred, see [MediaPlayer.OnErrorListener.onError]. + * @property extra an extra code, specific to the error, see [MediaPlayer.OnErrorListener.onError]. + */ + data class UnableToPlay(val what: Int, val extra: Int) : ListeningError() + data class DownloadError(override val cause: Throwable?) : ListeningError() + } } 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 5e5c320d43..ad0ecf69b4 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,6 +16,7 @@ package im.vector.app.features.voicebroadcast.listening +import im.vector.app.features.voicebroadcast.VoiceBroadcastFailure import im.vector.app.features.voicebroadcast.model.VoiceBroadcast interface VoiceBroadcastPlayer { @@ -72,6 +73,7 @@ interface VoiceBroadcastPlayer { object Playing : State object Paused : State object Buffering : State + data class Error(val failure: VoiceBroadcastFailure.ListeningError) : State object Idle : State } 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 f00d657682..538b2f8da4 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,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.onFirst 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.VoiceBroadcastFailure 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 @@ -193,6 +193,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( startPlayback(nextItem.startTime) } } + is State.Error -> Unit State.Idle -> Unit // Should not happen } } @@ -205,20 +206,15 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val sequence = playlistItem.sequence ?: run { Timber.w("## Voice Broadcast | Playlist item has no sequence"); return } val sequencePosition = position - playlistItem.startTime sessionScope.launch { - try { - prepareMediaPlayer(content) { mp -> - currentMediaPlayer = mp - playlist.currentSequence = sequence - mp.start() - if (sequencePosition > 0) { - mp.seekTo(sequencePosition) - } - playingState = State.Playing - prepareNextMediaPlayer() + prepareMediaPlayer(content) { mp -> + currentMediaPlayer = mp + playlist.currentSequence = sequence + mp.start() + if (sequencePosition > 0) { + mp.seekTo(sequencePosition) } - } catch (failure: Throwable) { - Timber.e(failure, "## Voice Broadcast | Unable to start playback: $failure") - throw VoiceFailure.UnableToPlay(failure) + playingState = State.Playing + prepareNextMediaPlayer() } } } @@ -275,6 +271,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( mp.start() onNextMediaPlayerStarted(mp) } + is State.Error, State.Idle -> stopPlayer() } } @@ -288,11 +285,12 @@ class VoiceBroadcastPlayerImpl @Inject constructor( session.fileService().downloadFile(messageAudioContent) } catch (failure: Throwable) { Timber.e(failure, "Voice Broadcast | Download has failed: $failure") - throw VoiceFailure.UnableToPlay(failure) + throw VoiceBroadcastFailure.ListeningError.DownloadError(failure) } return audioFile.inputStream().use { fis -> MediaPlayer().apply { + setOnErrorListener(mediaPlayerListener) setAudioAttributes( AudioAttributes.Builder() // Do not use CONTENT_TYPE_SPEECH / USAGE_VOICE_COMMUNICATION because we want to play loud here @@ -302,10 +300,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor( ) setDataSource(fis.fd) setOnInfoListener(mediaPlayerListener) - setOnErrorListener(mediaPlayerListener) setOnPreparedListener(onPreparedListener) setOnCompletionListener(mediaPlayerListener) - prepare() + prepareAsync() } } } @@ -330,8 +327,15 @@ class VoiceBroadcastPlayerImpl @Inject constructor( State.Playing -> playbackTicker.startPlaybackTicker(voiceBroadcastId) State.Paused, State.Buffering, + is State.Error, State.Idle -> playbackTicker.stopPlaybackTicker(voiceBroadcastId) } + + // Notify playback tracker about error + if (playingState is State.Error) { + playbackTracker.onError(voiceBroadcastId, playingState.failure) + } + // Notify state change to all the listeners attached to the current voice broadcast id listeners[voiceBroadcastId]?.forEach { listener -> listener.onPlayingStateChanged(playingState) } } @@ -374,7 +378,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun onLiveListeningChanged(isLiveListening: Boolean) { // Live has ended and last chunk has been reached, we can stop the playback - if (!isLiveListening && playingState == State.Buffering && playlist.currentSequence == mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence) { + val hasReachedLastChunk = playlist.currentSequence == mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence + if (!isLiveListening && playingState == State.Buffering && hasReachedLastChunk) { stop() } } @@ -389,16 +394,16 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun getCurrentPlaybackPosition(): Int? { val voiceBroadcastId = currentVoiceBroadcast?.voiceBroadcastId ?: return null - val computedPosition = currentMediaPlayer?.currentPosition?.let { playlist.currentItem?.startTime?.plus(it) } + val computedPosition = tryOrNull { currentMediaPlayer?.currentPosition }?.let { playlist.currentItem?.startTime?.plus(it) } val savedPosition = playbackTracker.getPlaybackTime(voiceBroadcastId) 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 computedPosition = tryOrNull { currentMediaPlayer?.currentPosition }?.let { playlistPosition?.plus(it) } ?: playlistPosition + val duration = playlist.duration + val computedPercentage = if (computedPosition != null && duration > 0) computedPosition.toFloat() / duration else null val savedPercentage = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPercentage(it) } return computedPercentage ?: savedPercentage } @@ -416,6 +421,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } override fun onCompletion(mp: MediaPlayer) { + // Release media player as soon as it completed + mp.release() + currentMediaPlayer = null + // Next media player is already attached to this player and will start playing automatically if (nextMediaPlayer != null) return @@ -426,15 +435,16 @@ class VoiceBroadcastPlayerImpl @Inject constructor( // We'll not receive new chunks anymore so we can stop the live listening stop() } else { - // Enter in buffering mode and release current media player playingState = State.Buffering - currentMediaPlayer?.release() - currentMediaPlayer = null + prepareNextMediaPlayer() } } override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean { - stop() + Timber.d("## Voice Broadcast | onError: what=$what, extra=$extra") + if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) { + playingState = State.Error(VoiceBroadcastFailure.ListeningError.UnableToPlay(what, extra)) + } return true } } @@ -480,6 +490,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) } } + is State.Error -> Unit } } } diff --git a/vector/src/main/res/drawable/ic_voice_broadcast_error.xml b/vector/src/main/res/drawable/ic_voice_broadcast_error.xml new file mode 100644 index 0000000000..6cbd4592cb --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_broadcast_error.xml @@ -0,0 +1,10 @@ + + + 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 760293ee64..deec85e2ed 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 @@ -176,4 +176,27 @@ tools:ignore="NegativeMargin" tools:text="-0:12" /> + + + + From 3663f225905aabdfaa73550224cf210d6616fa96 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 10 Jan 2023 15:16:25 +0100 Subject: [PATCH 03/20] Handle download error during playback --- .../listening/VoiceBroadcastPlayerImpl.kt | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 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 538b2f8da4..ab4b6c2269 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 @@ -206,15 +206,19 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val sequence = playlistItem.sequence ?: run { Timber.w("## Voice Broadcast | Playlist item has no sequence"); return } val sequencePosition = position - playlistItem.startTime sessionScope.launch { - prepareMediaPlayer(content) { mp -> - currentMediaPlayer = mp - playlist.currentSequence = sequence - mp.start() - if (sequencePosition > 0) { - mp.seekTo(sequencePosition) + try { + prepareMediaPlayer(content) { mp -> + currentMediaPlayer = mp + playlist.currentSequence = sequence + mp.start() + if (sequencePosition > 0) { + mp.seekTo(sequencePosition) + } + playingState = State.Playing + prepareNextMediaPlayer() } - playingState = State.Playing - prepareNextMediaPlayer() + } catch (failure: VoiceBroadcastFailure.ListeningError.DownloadError) { + playingState = State.Error(failure) } } } @@ -259,20 +263,27 @@ class VoiceBroadcastPlayerImpl @Inject constructor( if (nextItem != null) { isPreparingNextPlayer = true sessionScope.launch { - prepareMediaPlayer(nextItem.audioEvent.content) { mp -> + try { + prepareMediaPlayer(nextItem.audioEvent.content) { mp -> + isPreparingNextPlayer = false + nextMediaPlayer = mp + when (playingState) { + State.Playing, + State.Paused -> { + currentMediaPlayer?.setNextMediaPlayer(mp) + } + State.Buffering -> { + mp.start() + onNextMediaPlayerStarted(mp) + } + is State.Error, + State.Idle -> stopPlayer() + } + } + } catch (failure: VoiceBroadcastFailure.ListeningError.DownloadError) { isPreparingNextPlayer = false - nextMediaPlayer = mp - when (playingState) { - State.Playing, - State.Paused -> { - currentMediaPlayer?.setNextMediaPlayer(mp) - } - State.Buffering -> { - mp.start() - onNextMediaPlayerStarted(mp) - } - is State.Error, - State.Idle -> stopPlayer() + if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) { + playingState = State.Error(failure) } } } From 9d3b5c5bbb7c0d2a457a11fa6a20214b3e788293 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 10 Jan 2023 16:00:19 +0100 Subject: [PATCH 04/20] Fix no display name for some voice broadcast recorder name --- .../room/detail/timeline/factory/VoiceBroadcastItemFactory.kt | 4 +--- 1 file changed, 1 insertion(+), 3 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 ad8e163633..3439fb1f57 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,7 +18,6 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.core.error.ErrorFormatter 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 @@ -37,7 +36,6 @@ import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom -import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -77,7 +75,7 @@ class VoiceBroadcastItemFactory @Inject constructor( voiceBroadcast = voiceBroadcast, voiceBroadcastState = voiceBroadcastContent.voiceBroadcastState, duration = voiceBroadcastEventsGroup.getDuration(), - recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName().orEmpty(), + recorderName = params.event.senderInfo.disambiguatedDisplayName, recorder = voiceBroadcastRecorder, player = voiceBroadcastPlayer, playbackTracker = playbackTracker, From 912d3e5055d301b5d4bd0c143b6ce9e21d5f9954 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 11 Jan 2023 15:57:39 +0300 Subject: [PATCH 05/20] Fix edited poll preview in room list. --- .../room/detail/timeline/format/DisplayableEventFormatter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index 5fa9576dd4..ef4846d822 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -130,7 +130,7 @@ class DisplayableEventFormatter @Inject constructor( span { } } in EventType.POLL_START.values -> { - timelineEvent.root.getClearContent().toModel(catchError = true)?.getBestPollCreationInfo()?.question?.getBestQuestion() + (timelineEvent.getVectorLastMessageContent() as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: stringProvider.getString(R.string.sent_a_poll) } in EventType.POLL_RESPONSE.values -> { From 61f7f12d7f40e4e33c937b02ee0a4a7910b3c06f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 11 Jan 2023 15:58:10 +0300 Subject: [PATCH 06/20] Fix edited rendering poll question in action preview. --- .../room/detail/timeline/action/MessageActionsViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 646cfa50d2..d442c1f1ba 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -216,8 +216,8 @@ class MessageActionsViewModel @AssistedInject constructor( noticeEventFormatter.format(timelineEvent, room?.roomSummary()?.isDirect.orFalse()) } in EventType.POLL_START.values -> { - timelineEvent.root.getClearContent().toModel(catchError = true) - ?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "" + (timelineEvent.getVectorLastMessageContent() as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() + ?: stringProvider.getString(R.string.message_reply_to_poll_preview) } else -> null } From 62e0c80a0633bfefde04d58b9c76c3ee2ea1aabf Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 11 Jan 2023 15:59:15 +0300 Subject: [PATCH 07/20] Fix rendering edited poll in timeline. --- .../sdk/api/session/room/timeline/TimelineEvent.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 6320ea964d..3aa480094c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -148,8 +148,8 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? { // Polls/Beacon are not message contents like others as there is no msgtype subtype to discriminate moshi parsing // so toModel won't parse them correctly // It's discriminated on event type instead. Maybe it shouldn't be MessageContent at all to avoid confusion? - in EventType.POLL_START.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() - in EventType.POLL_END.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() + in EventType.POLL_START.values -> (getLastPollEditNewContent() ?: root.getClearContent()).toModel() + in EventType.POLL_END.values -> (getLastPollEditNewContent() ?: root.getClearContent()).toModel() in EventType.STATE_ROOM_BEACON_INFO.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() in EventType.BEACON_LOCATION_DATA.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() else -> (getLastEditNewContent() ?: root.getClearContent()).toModel() @@ -160,6 +160,10 @@ fun TimelineEvent.getLastEditNewContent(): Content? { return annotations?.editSummary?.latestEdit?.getClearContent()?.toModel()?.newContent } +private fun TimelineEvent.getLastPollEditNewContent(): Content? { + return annotations?.editSummary?.latestEdit?.getClearContent()?.toModel()?.newContent +} + /** * Returns true if it's a reply. */ From ca99dc8a33c31a912d5e120721f6cc8d9f48e9d3 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 11 Jan 2023 16:10:06 +0300 Subject: [PATCH 08/20] Add changelog. --- changelog.d/7938.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7938.bugfix diff --git a/changelog.d/7938.bugfix b/changelog.d/7938.bugfix new file mode 100644 index 0000000000..70218edf8a --- /dev/null +++ b/changelog.d/7938.bugfix @@ -0,0 +1 @@ +Fix rendering of edited polls From a4a7fa69e81377ee6051983a3067f6ca87ff9591 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jan 2023 23:02:02 +0000 Subject: [PATCH 09/20] Bump appcompat from 1.5.1 to 1.6.0 Bumps appcompat from 1.5.1 to 1.6.0. --- updated-dependencies: - dependency-name: androidx.appcompat:appcompat dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 8b0933b943..23f0be97eb 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -50,7 +50,7 @@ ext.libs = [ ], androidx : [ 'activity' : "androidx.activity:activity-ktx:1.6.1", - 'appCompat' : "androidx.appcompat:appcompat:1.5.1", + 'appCompat' : "androidx.appcompat:appcompat:1.6.0", 'biometric' : "androidx.biometric:biometric:1.1.0", 'core' : "androidx.core:core-ktx:1.9.0", 'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1", From 90d9eaf9507ee5f456c452dc7d88c1485ca84d3e Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Mon, 9 Jan 2023 07:09:04 +0000 Subject: [PATCH 10/20] Translated using Weblate (Czech) Currently translated at 100.0% (2585 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- library/ui-strings/src/main/res/values-cs/strings.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index 0a7998deaa..2d2b91d645 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -2860,7 +2860,7 @@ Přihlásit se pomocí QR kódu Naskenovat QR kód Možnost nahrávat a odesílat hlasové vysílání na časové ose místnosti. - Povolit hlasové vysílání (v aktivním vývoji) + Povolit hlasové vysílání Domovský server nepodporuje přihlášení pomocí QR kódu. Přihlášení bylo na druhém zařízení zrušeno. Tento QR kód je neplatný. @@ -2946,4 +2946,13 @@ Odkaz Text Nastavit odkaz + Přístupový token umožňuje plný přístup k účtu. Nikomu ho nesdělujte. + Přístupový token + Přepnout na odrážky + Přepnout na číslovaný seznam + V této místnosti nejsou žádné předchozí hlasování + Předchozí hlasování + V této místnosti nejsou žádné aktivní hlasování + Aktivní hlasování + Historie hlasování \ No newline at end of file From 6cdd8096cd0889c68b2b68f792cf251c4ca40243 Mon Sep 17 00:00:00 2001 From: Christian Paul Date: Mon, 9 Jan 2023 09:39:16 +0000 Subject: [PATCH 11/20] Translated using Weblate (Esperanto) Currently translated at 75.8% (1961 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/eo/ --- library/ui-strings/src/main/res/values-eo/strings.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-eo/strings.xml b/library/ui-strings/src/main/res/values-eo/strings.xml index 4521e840a6..e417d183bf 100644 --- a/library/ui-strings/src/main/res/values-eo/strings.xml +++ b/library/ui-strings/src/main/res/values-eo/strings.xml @@ -1123,9 +1123,7 @@ Elektu landon Administri retpoŝtadresojn kaj telefonnumerojn ligitajn al via konto de Matrix Retpoŝtadresoj kaj telefonnumeroj - Ĉu montri ĉiujn mesaĝojn de %s\? -\n -\nSciu ke tiu ĉi ago reekigos la aplikaĵon, kaj tio povas daŭri iom da tempo. + Ĉu montri ĉiujn mesaĝojn de %s\? Via pasvorto ĝisdatiĝis La pasvorto ne validas Malsukcesis ĝisdatigi pasvorton @@ -2201,4 +2199,5 @@ Sonorante… Aroj - Iom uzantoj reatentita + \@room \ No newline at end of file From 07db45a1678d8cc7d989008b643f867389c602c6 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sat, 7 Jan 2023 20:46:31 +0000 Subject: [PATCH 12/20] Translated using Weblate (Hungarian) Currently translated at 100.0% (2585 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- library/ui-strings/src/main/res/values-hu/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index 1be136bb39..9fdad2dbf0 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -2890,4 +2890,13 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Hivatkozás Szöveg Hivatkozás beállítása + A hozzáférési kulcs teljes elérést biztosít a fiókhoz. Soha ne ossza meg mással. + Elérési kulcs + Lista ki-,bekapcsolása + Számozott lista ki-,bekapcsolása + Nincsenek régi szavazások ebben a szobában + Régi szavazások + Nincsenek aktív szavazások ebben a szobában + Aktív szavazások + Szavazás alakulása \ No newline at end of file From 6813571015c06959753d1aba647e12d2914e1c7e Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 12 Jan 2023 18:46:18 +0300 Subject: [PATCH 13/20] Fix rendering bug when poll is edited from another client. --- .../internal/session/room/EventEditValidator.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt index 41d0c3f6ab..5a66e7e62d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt @@ -16,13 +16,16 @@ package org.matrix.android.sdk.internal.session.room +import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import timber.log.Timber import javax.inject.Inject @@ -101,7 +104,7 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto if (originalDecrypted.type != replaceDecrypted.type) { return EditValidity.Invalid("replacement and original events must have the same type") } - if (replaceDecrypted.clearContent.toModel()?.newContent == null) { + if (!hasNewContent(replaceDecrypted.type, replaceDecrypted.clearContent)) { return EditValidity.Invalid("replacement event must have an m.new_content property") } } else { @@ -116,11 +119,18 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto if (originalEvent.type != replaceEvent.type) { return EditValidity.Invalid("replacement and original events must have the same type") } - if (replaceEvent.content.toModel()?.newContent == null) { + if (!hasNewContent(replaceEvent.type, replaceEvent.content)) { return EditValidity.Invalid("replacement event must have an m.new_content property") } } return EditValidity.Valid } + + private fun hasNewContent(eventType: String?, content: Content?): Boolean { + return when (eventType) { + in EventType.POLL_START.values -> content.toModel()?.newContent != null + else -> content.toModel()?.newContent != null + } + } } From 72e0dc4bd2c366b8dd7a30513900c5736b6075c3 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 12 Jan 2023 16:13:34 +0100 Subject: [PATCH 14/20] Voice Broadcast - only send a notification on the first chunk --- changelog.d/7845.wip | 1 + .../src/main/res/values/strings.xml | 1 + .../format/DisplayableEventFormatter.kt | 16 +++-- .../notifications/FilteredEventDetector.kt | 59 +++++++++++++++++++ .../NotificationDrawerManager.kt | 6 ++ 5 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 changelog.d/7845.wip create mode 100644 vector/src/main/java/im/vector/app/features/notifications/FilteredEventDetector.kt diff --git a/changelog.d/7845.wip b/changelog.d/7845.wip new file mode 100644 index 0000000000..8bce21499a --- /dev/null +++ b/changelog.d/7845.wip @@ -0,0 +1 @@ +[Voice Broadcast] Only display a notification on the first voice chunk diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index f1c2eb2c6d..6731248f83 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2295,6 +2295,7 @@ Verification Conclusion Shared their location Shared their live location + Started a voice broadcast Waiting… %s canceled diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index 5fa9576dd4..f57aa67800 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -27,6 +27,7 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.isLive +import im.vector.app.features.voicebroadcast.isVoiceBroadcast import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import me.gujun.android.span.image import me.gujun.android.span.span @@ -39,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageType +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getTextDisplayableContent @@ -86,10 +88,16 @@ class DisplayableEventFormatter @Inject constructor( simpleFormat(senderName, stringProvider.getString(R.string.sent_an_image), appendAuthor) } MessageType.MSGTYPE_AUDIO -> { - if ((messageContent as? MessageAudioContent)?.voiceMessageIndicator != null) { - simpleFormat(senderName, stringProvider.getString(R.string.sent_a_voice_message), appendAuthor) - } else { - simpleFormat(senderName, stringProvider.getString(R.string.sent_an_audio_file), appendAuthor) + when { + (messageContent as? MessageAudioContent)?.voiceMessageIndicator == null -> { + simpleFormat(senderName, stringProvider.getString(R.string.sent_an_audio_file), appendAuthor) + } + timelineEvent.root.asMessageAudioEvent().isVoiceBroadcast() -> { + simpleFormat(senderName, stringProvider.getString(R.string.started_a_voice_broadcast), appendAuthor) + } + else -> { + simpleFormat(senderName, stringProvider.getString(R.string.sent_a_voice_message), appendAuthor) + } } } MessageType.MSGTYPE_VIDEO -> { diff --git a/vector/src/main/java/im/vector/app/features/notifications/FilteredEventDetector.kt b/vector/src/main/java/im/vector/app/features/notifications/FilteredEventDetector.kt new file mode 100644 index 0000000000..e21462b182 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/notifications/FilteredEventDetector.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2023 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.notifications + +import im.vector.app.ActiveSessionDataSource +import im.vector.app.features.voicebroadcast.isVoiceBroadcast +import im.vector.app.features.voicebroadcast.sequence +import org.matrix.android.sdk.api.session.events.model.isVoiceMessage +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import javax.inject.Inject + +class FilteredEventDetector @Inject constructor( + private val activeSessionDataSource: ActiveSessionDataSource +) { + + /** + * Returns true if the given event should be ignored. + * Used to skip notifications if a non expected message is received. + */ + fun shouldBeIgnored(notifiableEvent: NotifiableEvent): Boolean { + val session = activeSessionDataSource.currentValue?.orNull() ?: return false + + if (notifiableEvent is NotifiableMessageEvent) { + val room = session.getRoom(notifiableEvent.roomId) ?: return false + val timelineEvent = room.getTimelineEvent(notifiableEvent.eventId) ?: return false + return timelineEvent.shouldBeIgnored() + } + return false + } + + /** + * Whether the timeline event should be ignored. + */ + private fun TimelineEvent.shouldBeIgnored(): Boolean { + if (root.isVoiceMessage()) { + val audioEvent = root.asMessageAudioEvent() + // if the event is a voice message related to a voice broadcast, only show the event on the first chunk. + return audioEvent.isVoiceBroadcast() && audioEvent?.sequence != 1 + } + + return false + } +} diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 4f05e83bd4..2d799034d9 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -47,6 +47,7 @@ class NotificationDrawerManager @Inject constructor( private val notifiableEventProcessor: NotifiableEventProcessor, private val notificationRenderer: NotificationRenderer, private val notificationEventPersistence: NotificationEventPersistence, + private val filteredEventDetector: FilteredEventDetector, private val buildMeta: BuildMeta, ) { @@ -100,6 +101,11 @@ class NotificationDrawerManager @Inject constructor( Timber.d("onNotifiableEventReceived(): is push: ${notifiableEvent.canBeReplaced}") } + if (filteredEventDetector.shouldBeIgnored(notifiableEvent)) { + Timber.d("onNotifiableEventReceived(): ignore the event") + return + } + add(notifiableEvent) } From 8a2f28bc376fd2f34c97270cdff7336332efe50e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 12 Jan 2023 18:29:56 +0100 Subject: [PATCH 15/20] Add comment to explain the error handling --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 4 ++++ 1 file changed, 4 insertions(+) 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 ab4b6c2269..1cb418163f 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 @@ -282,6 +282,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } catch (failure: VoiceBroadcastFailure.ListeningError.DownloadError) { isPreparingNextPlayer = false + // Do not change the playingState if the current player is still valid, + // the error will be thrown again when switching to the next player if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) { playingState = State.Error(failure) } @@ -453,6 +455,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean { Timber.d("## Voice Broadcast | onError: what=$what, extra=$extra") + // Do not change the playingState if the current player is still valid, + // the error will be thrown again when switching to the next player if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) { playingState = State.Error(VoiceBroadcastFailure.ListeningError.UnableToPlay(what, extra)) } From d55f1efd632d2426ba1ab2e0841b73d0948e8d60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jan 2023 00:17:34 +0000 Subject: [PATCH 16/20] Bump wysiwyg from 0.15.0 to 0.17.0 Bumps [wysiwyg](https://github.com/matrix-org/matrix-wysiwyg) from 0.15.0 to 0.17.0. - [Release notes](https://github.com/matrix-org/matrix-wysiwyg/releases) - [Changelog](https://github.com/matrix-org/matrix-rich-text-editor/blob/main/CHANGELOG.md) - [Commits](https://github.com/matrix-org/matrix-wysiwyg/compare/0.15.0...0.17.0) --- updated-dependencies: - dependency-name: io.element.android:wysiwyg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 8b0933b943..f9d2850e58 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -103,7 +103,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:0.15.0" + 'wysiwyg' : "io.element.android:wysiwyg:0.17.0" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", From e5801a4f19a00fd76755c898be0a5a041f343aac Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 13 Jan 2023 15:56:38 +0300 Subject: [PATCH 17/20] Make verification dialog cancelable. --- .../crypto/verification/VerificationBottomSheet.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index 38b72f2022..349dfc738d 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -17,6 +17,7 @@ package im.vector.app.features.crypto.verification import android.app.Activity import android.app.Dialog +import android.content.DialogInterface import android.os.Bundle import android.os.Parcelable import android.view.KeyEvent @@ -85,7 +86,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment Date: Fri, 13 Jan 2023 15:56:47 +0300 Subject: [PATCH 18/20] Add changelog. --- changelog.d/4025.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4025.bugfix diff --git a/changelog.d/4025.bugfix b/changelog.d/4025.bugfix new file mode 100644 index 0000000000..109da1c830 --- /dev/null +++ b/changelog.d/4025.bugfix @@ -0,0 +1 @@ +Fix can't get out of a verification dialog From 169c9b221ca69039406a50744749c5336c4af85a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 13 Jan 2023 15:46:48 +0100 Subject: [PATCH 19/20] Throw an error if the media player which has completed is not the expected one --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 6 +++++- 1 file changed, 5 insertions(+), 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 1cb418163f..2e1600e4e2 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 @@ -436,7 +436,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor( override fun onCompletion(mp: MediaPlayer) { // Release media player as soon as it completed mp.release() - currentMediaPlayer = null + if (currentMediaPlayer == mp) { + currentMediaPlayer = null + } else { + error("The media player which has completed mismatches the current media player instance.") + } // Next media player is already attached to this player and will start playing automatically if (nextMediaPlayer != null) return From 4a49f2ff9b6acda4cd879240034884d15ed9e31d Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 13 Jan 2023 18:15:52 +0300 Subject: [PATCH 20/20] Check if bottom sheet is cancellable. --- .../features/crypto/verification/VerificationBottomSheet.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index 349dfc738d..3b9de57be8 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -85,10 +85,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment