Skip to content

Commit 31e599f

Browse files
authored
Merge pull request #7919 from vector-im/bugfix/fre/handle_vb_playback_crash
Voice Broadcast - Handle exceptions during playback
2 parents 25edcaf + 169c9b2 commit 31e599f

File tree

15 files changed

+208
-90
lines changed

15 files changed

+208
-90
lines changed

changelog.d/7829.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Handle exceptions when listening a voice broadcast

library/ui-strings/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3122,6 +3122,7 @@
31223122
<string name="error_voice_broadcast_permission_denied_message">You don’t have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.</string>
31233123
<string name="error_voice_broadcast_blocked_by_someone_else_message">Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.</string>
31243124
<string name="error_voice_broadcast_already_in_progress_message">You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.</string>
3125+
<string name="error_voice_broadcast_unable_to_play">Unable to play this voice broadcast.</string>
31253126
<!-- Examples of usage: 6h 15min 30sec left / 15min 30sec left / 30sec left -->
31263127
<string name="voice_broadcast_recording_time_left">%1$s left</string>
31273128
<string name="stop_voice_broadcast_dialog_title">Stop live broadcasting?</string>

vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ class DefaultErrorFormatter @Inject constructor(
157157
RecordingError.BlockedBySomeoneElse -> stringProvider.getString(R.string.error_voice_broadcast_blocked_by_someone_else_message)
158158
RecordingError.NoPermission -> stringProvider.getString(R.string.error_voice_broadcast_permission_denied_message)
159159
RecordingError.UserAlreadyBroadcasting -> stringProvider.getString(R.string.error_voice_broadcast_already_in_progress_message)
160+
is VoiceBroadcastFailure.ListeningError.UnableToPlay,
161+
is VoiceBroadcastFailure.ListeningError.DownloadError -> stringProvider.getString(R.string.error_voice_broadcast_unable_to_play)
160162
}
161163
}
162164

vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
229229
voiceMessageViews.renderPlaying(state)
230230
}
231231
is AudioMessagePlaybackTracker.Listener.State.Paused,
232+
is AudioMessagePlaybackTracker.Listener.State.Error,
232233
is AudioMessagePlaybackTracker.Listener.State.Idle -> {
233234
voiceMessageViews.renderIdle()
234235
}

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
*/
1616
package im.vector.app.features.home.room.detail.timeline.factory
1717

18+
import im.vector.app.core.error.ErrorFormatter
1819
import im.vector.app.core.resources.ColorProvider
1920
import im.vector.app.core.resources.DrawableProvider
20-
import im.vector.app.features.displayname.getBestName
2121
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
2222
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
2323
import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup
@@ -36,7 +36,6 @@ import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
3636
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
3737
import org.matrix.android.sdk.api.session.Session
3838
import org.matrix.android.sdk.api.session.getRoom
39-
import org.matrix.android.sdk.api.session.getUserOrDefault
4039
import org.matrix.android.sdk.api.util.toMatrixItem
4140
import javax.inject.Inject
4241

@@ -45,6 +44,7 @@ class VoiceBroadcastItemFactory @Inject constructor(
4544
private val avatarSizeProvider: AvatarSizeProvider,
4645
private val colorProvider: ColorProvider,
4746
private val drawableProvider: DrawableProvider,
47+
private val errorFormatter: ErrorFormatter,
4848
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
4949
private val voiceBroadcastPlayer: VoiceBroadcastPlayer,
5050
private val playbackTracker: AudioMessagePlaybackTracker,
@@ -75,13 +75,14 @@ class VoiceBroadcastItemFactory @Inject constructor(
7575
voiceBroadcast = voiceBroadcast,
7676
voiceBroadcastState = voiceBroadcastContent.voiceBroadcastState,
7777
duration = voiceBroadcastEventsGroup.getDuration(),
78-
recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName().orEmpty(),
78+
recorderName = params.event.senderInfo.disambiguatedDisplayName,
7979
recorder = voiceBroadcastRecorder,
8080
player = voiceBroadcastPlayer,
8181
playbackTracker = playbackTracker,
8282
roomItem = session.getRoom(params.event.roomId)?.roomSummary()?.toMatrixItem(),
8383
colorProvider = colorProvider,
8484
drawableProvider = drawableProvider,
85+
errorFormatter = errorFormatter,
8586
)
8687

8788
return if (isRecording) {

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ class AudioMessagePlaybackTracker @Inject constructor() {
5050
listeners.remove(id)
5151
}
5252

53-
fun pauseAllPlaybacks() {
54-
listeners.keys.forEach(::pausePlayback)
53+
fun unregisterListeners() {
54+
listeners.forEach {
55+
it.value.onUpdate(Listener.State.Idle)
56+
}
57+
listeners.clear()
5558
}
5659

5760
/**
@@ -84,6 +87,10 @@ class AudioMessagePlaybackTracker @Inject constructor() {
8487
}
8588
}
8689

90+
fun pauseAllPlaybacks() {
91+
listeners.keys.forEach(::pausePlayback)
92+
}
93+
8794
fun pausePlayback(id: String) {
8895
val state = getPlaybackState(id)
8996
if (state is Listener.State.Playing) {
@@ -94,7 +101,14 @@ class AudioMessagePlaybackTracker @Inject constructor() {
94101
}
95102

96103
fun stopPlayback(id: String) {
97-
setState(id, Listener.State.Idle)
104+
val state = getPlaybackState(id)
105+
if (state !is Listener.State.Error) {
106+
setState(id, Listener.State.Idle)
107+
}
108+
}
109+
110+
fun onError(id: String, error: Throwable) {
111+
setState(id, Listener.State.Error(error))
98112
}
99113

100114
fun updatePlayingAtPlaybackTime(id: String, time: Int, percentage: Float) {
@@ -116,6 +130,7 @@ class AudioMessagePlaybackTracker @Inject constructor() {
116130
is Listener.State.Playing -> state.playbackTime
117131
is Listener.State.Paused -> state.playbackTime
118132
is Listener.State.Recording,
133+
is Listener.State.Error,
119134
Listener.State.Idle,
120135
null -> null
121136
}
@@ -126,18 +141,12 @@ class AudioMessagePlaybackTracker @Inject constructor() {
126141
is Listener.State.Playing -> state.percentage
127142
is Listener.State.Paused -> state.percentage
128143
is Listener.State.Recording,
144+
is Listener.State.Error,
129145
Listener.State.Idle,
130146
null -> null
131147
}
132148
}
133149

134-
fun unregisterListeners() {
135-
listeners.forEach {
136-
it.value.onUpdate(Listener.State.Idle)
137-
}
138-
listeners.clear()
139-
}
140-
141150
companion object {
142151
const val RECORDING_ID = "RECORDING_ID"
143152
}
@@ -148,6 +157,7 @@ class AudioMessagePlaybackTracker @Inject constructor() {
148157

149158
sealed class State {
150159
object Idle : State()
160+
data class Error(val failure: Throwable) : State()
151161
data class Playing(val playbackTime: Int, val percentage: Float) : State()
152162
data class Paused(val playbackTime: Int, val percentage: Float) : State()
153163
data class Recording(val amplitudeList: List<Int>) : State()

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.annotation.IdRes
2222
import androidx.core.view.isVisible
2323
import com.airbnb.epoxy.EpoxyAttribute
2424
import im.vector.app.R
25+
import im.vector.app.core.error.ErrorFormatter
2526
import im.vector.app.core.extensions.tintBackground
2627
import im.vector.app.core.resources.ColorProvider
2728
import im.vector.app.core.resources.DrawableProvider
@@ -48,6 +49,7 @@ abstract class AbsMessageVoiceBroadcastItem<H : AbsMessageVoiceBroadcastItem.Hol
4849
protected val colorProvider get() = voiceBroadcastAttributes.colorProvider
4950
protected val drawableProvider get() = voiceBroadcastAttributes.drawableProvider
5051
protected val avatarRenderer get() = attributes.avatarRenderer
52+
protected val errorFormatter get() = voiceBroadcastAttributes.errorFormatter
5153
protected val callback get() = attributes.callback
5254

5355
override fun isCacheable(): Boolean = false
@@ -107,5 +109,6 @@ abstract class AbsMessageVoiceBroadcastItem<H : AbsMessageVoiceBroadcastItem.Hol
107109
val roomItem: MatrixItem?,
108110
val colorProvider: ColorProvider,
109111
val drawableProvider: DrawableProvider,
112+
val errorFormatter: ErrorFormatter,
110113
)
111114
}

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ abstract class MessageAudioItem : AbsMessageItem<MessageAudioItem.Holder>() {
142142
private fun renderStateBasedOnAudioPlayback(holder: Holder) {
143143
audioMessagePlaybackTracker.track(attributes.informationData.eventId) { state ->
144144
when (state) {
145+
is AudioMessagePlaybackTracker.Listener.State.Error,
145146
is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder)
146147
is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state)
147148
is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state)

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import android.text.format.DateUtils
2020
import android.widget.ImageButton
2121
import android.widget.SeekBar
2222
import android.widget.TextView
23+
import androidx.constraintlayout.widget.Group
2324
import androidx.core.view.isInvisible
2425
import androidx.core.view.isVisible
2526
import com.airbnb.epoxy.EpoxyModelClass
2627
import im.vector.app.R
2728
import im.vector.app.core.epoxy.onClick
29+
import im.vector.app.core.extensions.setTextOrHide
2830
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
2931
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State
3032
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
@@ -54,6 +56,16 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
5456
}
5557
}
5658
player.addListener(voiceBroadcast, playerListener)
59+
60+
playbackTracker.track(voiceBroadcast.voiceBroadcastId) { playbackState ->
61+
renderBackwardForwardButtons(holder, playbackState)
62+
renderPlaybackError(holder, playbackState)
63+
renderLiveIndicator(holder)
64+
if (!isUserSeeking) {
65+
holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) ?: 0
66+
}
67+
}
68+
5769
bindSeekBar(holder)
5870
bindButtons(holder)
5971
}
@@ -63,10 +75,11 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
6375
playPauseButton.setOnClickListener {
6476
if (player.currentVoiceBroadcast == voiceBroadcast) {
6577
when (player.playingState) {
66-
VoiceBroadcastPlayer.State.PLAYING,
67-
VoiceBroadcastPlayer.State.BUFFERING -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause)
68-
VoiceBroadcastPlayer.State.PAUSED,
69-
VoiceBroadcastPlayer.State.IDLE -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast))
78+
VoiceBroadcastPlayer.State.Playing,
79+
VoiceBroadcastPlayer.State.Buffering -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause)
80+
VoiceBroadcastPlayer.State.Paused,
81+
is VoiceBroadcastPlayer.State.Error,
82+
VoiceBroadcastPlayer.State.Idle -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast))
7083
}
7184
} else {
7285
callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast))
@@ -100,17 +113,18 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
100113

101114
private fun renderPlayingState(holder: Holder, state: VoiceBroadcastPlayer.State) {
102115
with(holder) {
103-
bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING
104-
voiceBroadcastMetadata.isVisible = state != VoiceBroadcastPlayer.State.BUFFERING
116+
bufferingView.isVisible = state == VoiceBroadcastPlayer.State.Buffering
117+
voiceBroadcastMetadata.isVisible = state != VoiceBroadcastPlayer.State.Buffering
105118

106119
when (state) {
107-
VoiceBroadcastPlayer.State.PLAYING,
108-
VoiceBroadcastPlayer.State.BUFFERING -> {
120+
VoiceBroadcastPlayer.State.Playing,
121+
VoiceBroadcastPlayer.State.Buffering -> {
109122
playPauseButton.setImageResource(R.drawable.ic_play_pause_pause)
110123
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast)
111124
}
112-
VoiceBroadcastPlayer.State.IDLE,
113-
VoiceBroadcastPlayer.State.PAUSED -> {
125+
is VoiceBroadcastPlayer.State.Error,
126+
VoiceBroadcastPlayer.State.Idle,
127+
VoiceBroadcastPlayer.State.Paused -> {
114128
playPauseButton.setImageResource(R.drawable.ic_play_pause_play)
115129
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast)
116130
}
@@ -120,6 +134,18 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
120134
}
121135
}
122136

137+
private fun renderPlaybackError(holder: Holder, playbackState: State) {
138+
with(holder) {
139+
if (playbackState is State.Error) {
140+
controlsGroup.isVisible = false
141+
errorView.setTextOrHide(errorFormatter.toHumanReadable(playbackState.failure))
142+
} else {
143+
errorView.isVisible = false
144+
controlsGroup.isVisible = true
145+
}
146+
}
147+
}
148+
123149
private fun bindSeekBar(holder: Holder) {
124150
with(holder) {
125151
remainingTimeView.text = formatRemainingTime(duration)
@@ -141,13 +167,6 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
141167
}
142168
})
143169
}
144-
playbackTracker.track(voiceBroadcast.voiceBroadcastId) { playbackState ->
145-
renderBackwardForwardButtons(holder, playbackState)
146-
renderLiveIndicator(holder)
147-
if (!isUserSeeking) {
148-
holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) ?: 0
149-
}
150-
}
151170
}
152171

153172
private fun renderBackwardForwardButtons(holder: Holder, playbackState: State) {
@@ -187,6 +206,8 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
187206
val broadcasterNameMetadata by bind<VoiceBroadcastMetadataView>(R.id.broadcasterNameMetadata)
188207
val voiceBroadcastMetadata by bind<VoiceBroadcastMetadataView>(R.id.voiceBroadcastMetadata)
189208
val listenersCountMetadata by bind<VoiceBroadcastMetadataView>(R.id.listenersCountMetadata)
209+
val errorView by bind<TextView>(R.id.errorView)
210+
val controlsGroup by bind<Group>(R.id.controlsGroup)
190211
}
191212

192213
companion object {

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
124124

125125
audioMessagePlaybackTracker.track(attributes.informationData.eventId) { state ->
126126
when (state) {
127+
is AudioMessagePlaybackTracker.Listener.State.Error,
127128
is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder, waveformColorIdle, waveformColorPlayed)
128129
is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state, waveformColorIdle, waveformColorPlayed)
129130
is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state, waveformColorIdle, waveformColorPlayed)

0 commit comments

Comments
 (0)