Skip to content

Commit 71b7fbd

Browse files
authored
Merge pull request #7993 from vector-im/feature/fre/vb_handle_connection_error
Pause voice broadcast if there is no network
2 parents 7eec435 + f0eae52 commit 71b7fbd

File tree

10 files changed

+212
-85
lines changed

10 files changed

+212
-85
lines changed

changelog.d/7890.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Voice Broadcast] Handle connection errors while recording

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3125,6 +3125,7 @@
31253125
<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>
31263126
<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>
31273127
<string name="error_voice_broadcast_unable_to_play">Unable to play this voice broadcast.</string>
3128+
<string name="error_voice_broadcast_no_connection_recording">Connection error - Recording paused</string>
31283129
<!-- Examples of usage: 6h 15min 30sec left / 15min 30sec left / 30sec left -->
31293130
<string name="voice_broadcast_recording_time_left">%1$s left</string>
31303131
<string name="stop_voice_broadcast_dialog_title">Stop live broadcasting?</string>

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package im.vector.app.features.home.room.detail.timeline.item
1818

1919
import android.widget.ImageButton
20+
import android.widget.TextView
21+
import androidx.constraintlayout.widget.Group
2022
import androidx.core.view.isVisible
2123
import com.airbnb.epoxy.EpoxyModelClass
2224
import im.vector.app.R
@@ -55,11 +57,11 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
5557
}
5658

5759
override fun renderLiveIndicator(holder: Holder) {
58-
when (voiceBroadcastState) {
59-
VoiceBroadcastState.STARTED,
60-
VoiceBroadcastState.RESUMED -> renderPlayingLiveIndicator(holder)
61-
VoiceBroadcastState.PAUSED -> renderPausedLiveIndicator(holder)
62-
VoiceBroadcastState.STOPPED, null -> renderNoLiveIndicator(holder)
60+
when (recorder?.recordingState) {
61+
VoiceBroadcastRecorder.State.Recording -> renderPlayingLiveIndicator(holder)
62+
VoiceBroadcastRecorder.State.Error,
63+
VoiceBroadcastRecorder.State.Paused -> renderPausedLiveIndicator(holder)
64+
VoiceBroadcastRecorder.State.Idle, null -> renderNoLiveIndicator(holder)
6365
}
6466
}
6567

@@ -85,7 +87,9 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
8587
VoiceBroadcastRecorder.State.Recording -> renderRecordingState(holder)
8688
VoiceBroadcastRecorder.State.Paused -> renderPausedState(holder)
8789
VoiceBroadcastRecorder.State.Idle -> renderStoppedState(holder)
90+
VoiceBroadcastRecorder.State.Error -> renderErrorState(holder, true)
8891
}
92+
renderLiveIndicator(holder)
8993
}
9094

9195
private fun renderVoiceBroadcastState(holder: Holder) {
@@ -101,6 +105,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
101105
private fun renderRecordingState(holder: Holder) = with(holder) {
102106
stopRecordButton.isEnabled = true
103107
recordButton.isEnabled = true
108+
renderErrorState(holder, false)
104109

105110
val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
106111
val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor)
@@ -113,6 +118,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
113118
private fun renderPausedState(holder: Holder) = with(holder) {
114119
stopRecordButton.isEnabled = true
115120
recordButton.isEnabled = true
121+
renderErrorState(holder, false)
116122

117123
recordButton.setImageResource(R.drawable.ic_recording_dot)
118124
recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record)
@@ -123,6 +129,12 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
123129
private fun renderStoppedState(holder: Holder) = with(holder) {
124130
recordButton.isEnabled = false
125131
stopRecordButton.isEnabled = false
132+
renderErrorState(holder, false)
133+
}
134+
135+
private fun renderErrorState(holder: Holder, isOnError: Boolean) = with(holder) {
136+
controlsGroup.isVisible = !isOnError
137+
errorView.isVisible = isOnError
126138
}
127139

128140
override fun unbind(holder: Holder) {
@@ -142,6 +154,8 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem
142154
val remainingTimeMetadata by bind<VoiceBroadcastMetadataView>(R.id.remainingTimeMetadata)
143155
val recordButton by bind<ImageButton>(R.id.recordButton)
144156
val stopRecordButton by bind<ImageButton>(R.id.stopRecordButton)
157+
val errorView by bind<TextView>(R.id.errorView)
158+
val controlsGroup by bind<Group>(R.id.controlsGroup)
145159
}
146160

147161
companion object {

vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorder.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ interface VoiceBroadcastRecorder : VoiceRecorder {
3333
val currentRemainingTime: Long?
3434

3535
fun startRecordVoiceBroadcast(voiceBroadcast: VoiceBroadcast, chunkLength: Int, maxLength: Int)
36+
37+
fun pauseOnError()
3638
fun addListener(listener: Listener)
3739
fun removeListener(listener: Listener)
3840

@@ -46,5 +48,6 @@ interface VoiceBroadcastRecorder : VoiceRecorder {
4648
Recording,
4749
Paused,
4850
Idle,
51+
Error,
4952
}
5053
}

vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorderQ.kt

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
2929
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
3030
import im.vector.lib.core.utils.timer.CountUpTimer
3131
import kotlinx.coroutines.Job
32+
import kotlinx.coroutines.flow.distinctUntilChanged
33+
import kotlinx.coroutines.flow.filter
3234
import kotlinx.coroutines.flow.launchIn
3335
import kotlinx.coroutines.flow.onEach
3436
import org.matrix.android.sdk.api.extensions.tryOrNull
3537
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
38+
import org.matrix.android.sdk.api.session.sync.SyncState
39+
import org.matrix.android.sdk.flow.flow
3640
import java.util.concurrent.CopyOnWriteArrayList
3741
import java.util.concurrent.TimeUnit
3842

@@ -47,6 +51,7 @@ class VoiceBroadcastRecorderQ(
4751
private val sessionScope get() = session.coroutineScope
4852

4953
private var voiceBroadcastStateObserver: Job? = null
54+
private var syncStateObserver: Job? = null
5055

5156
private var maxFileSize = 0L // zero or negative for no limit
5257
private var currentVoiceBroadcast: VoiceBroadcast? = null
@@ -96,21 +101,36 @@ class VoiceBroadcastRecorderQ(
96101
observeVoiceBroadcastStateEvent(voiceBroadcast)
97102
}
98103

99-
override fun pauseRecord() {
104+
override fun startRecord(roomId: String) {
105+
super.startRecord(roomId)
106+
observeConnectionState()
107+
}
108+
109+
override fun pauseOnError() {
100110
if (recordingState != VoiceBroadcastRecorder.State.Recording) return
101-
tryOrNull { mediaRecorder?.stop() }
102-
mediaRecorder?.reset()
111+
112+
pauseRecorder()
113+
stopObservingConnectionState()
114+
recordingState = VoiceBroadcastRecorder.State.Error
115+
}
116+
117+
override fun pauseRecord() {
118+
if (recordingState !in arrayOf(VoiceBroadcastRecorder.State.Recording, VoiceBroadcastRecorder.State.Error)) return
119+
120+
pauseRecorder()
121+
stopObservingConnectionState()
103122
recordingState = VoiceBroadcastRecorder.State.Paused
104-
recordingTicker.pause()
105123
notifyOutputFileCreated()
106124
}
107125

108126
override fun resumeRecord() {
109127
if (recordingState != VoiceBroadcastRecorder.State.Paused) return
128+
110129
currentSequence++
111130
currentVoiceBroadcast?.let { startRecord(it.roomId) }
112131
recordingState = VoiceBroadcastRecorder.State.Recording
113132
recordingTicker.resume()
133+
observeConnectionState()
114134
}
115135

116136
override fun stopRecord() {
@@ -128,6 +148,8 @@ class VoiceBroadcastRecorderQ(
128148
voiceBroadcastStateObserver?.cancel()
129149
voiceBroadcastStateObserver = null
130150

151+
stopObservingConnectionState()
152+
131153
// Reset data
132154
currentSequence = 0
133155
currentMaxLength = 0
@@ -197,6 +219,27 @@ class VoiceBroadcastRecorderQ(
197219
}
198220
}
199221

222+
private fun pauseRecorder() {
223+
if (recordingState != VoiceBroadcastRecorder.State.Recording) return
224+
225+
tryOrNull { mediaRecorder?.stop() }
226+
mediaRecorder?.reset()
227+
recordingTicker.pause()
228+
}
229+
230+
private fun observeConnectionState() {
231+
syncStateObserver = session.flow().liveSyncState()
232+
.distinctUntilChanged()
233+
.filter { it is SyncState.NoNetwork }
234+
.onEach { pauseOnError() }
235+
.launchIn(sessionScope)
236+
}
237+
238+
private fun stopObservingConnectionState() {
239+
syncStateObserver?.cancel()
240+
syncStateObserver = null
241+
}
242+
200243
private inner class RecordingTicker(
201244
private var recordingTicker: CountUpTimer? = null,
202245
) {

vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/PauseVoiceBroadcastUseCase.kt

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,25 @@
1616

1717
package im.vector.app.features.voicebroadcast.recording.usecase
1818

19+
import im.vector.app.features.session.coroutineScope
1920
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
2021
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
2122
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
2223
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
2324
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
25+
import kotlinx.coroutines.flow.filter
26+
import kotlinx.coroutines.flow.launchIn
27+
import kotlinx.coroutines.flow.onEach
28+
import kotlinx.coroutines.flow.take
29+
import org.matrix.android.sdk.api.failure.Failure
2430
import org.matrix.android.sdk.api.query.QueryStringValue
2531
import org.matrix.android.sdk.api.session.Session
2632
import org.matrix.android.sdk.api.session.events.model.toContent
2733
import org.matrix.android.sdk.api.session.getRoom
2834
import org.matrix.android.sdk.api.session.room.Room
2935
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
36+
import org.matrix.android.sdk.api.session.sync.SyncState
37+
import org.matrix.android.sdk.flow.flow
3038
import timber.log.Timber
3139
import javax.inject.Inject
3240

@@ -51,25 +59,35 @@ class PauseVoiceBroadcastUseCase @Inject constructor(
5159
}
5260
}
5361

54-
private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?) {
62+
private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?, remainingRetry: Int = 3) {
5563
Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event")
5664

57-
// save the last sequence number and immediately pause the recording
58-
val lastSequence = voiceBroadcastRecorder?.currentSequence
59-
pauseRecording()
65+
try {
66+
// save the last sequence number and immediately pause the recording
67+
val lastSequence = voiceBroadcastRecorder?.currentSequence
6068

61-
room.stateService().sendStateEvent(
62-
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
63-
stateKey = session.myUserId,
64-
body = MessageVoiceBroadcastInfoContent(
65-
relatesTo = reference,
66-
voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value,
67-
lastChunkSequence = lastSequence,
68-
).toContent(),
69-
)
70-
}
69+
room.stateService().sendStateEvent(
70+
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
71+
stateKey = session.myUserId,
72+
body = MessageVoiceBroadcastInfoContent(
73+
relatesTo = reference,
74+
voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value,
75+
lastChunkSequence = lastSequence,
76+
).toContent(),
77+
)
7178

72-
private fun pauseRecording() {
73-
voiceBroadcastRecorder?.pauseRecord()
79+
voiceBroadcastRecorder?.pauseRecord()
80+
} catch (e: Failure) {
81+
if (remainingRetry > 0) {
82+
voiceBroadcastRecorder?.pauseOnError()
83+
// Retry if there is no network issue (sync is running well)
84+
session.flow().liveSyncState()
85+
.filter { it is SyncState.Running }
86+
.take(1)
87+
.onEach { pauseVoiceBroadcast(room, reference, remainingRetry - 1) }
88+
.launchIn(session.coroutineScope)
89+
}
90+
throw e
91+
}
7492
}
7593
}

vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
5858
private val buildMeta: BuildMeta,
5959
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
6060
private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase,
61+
private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase,
6162
) {
6263

6364
suspend fun execute(roomId: String): Result<Unit> = runCatching {
@@ -103,6 +104,14 @@ class StartVoiceBroadcastUseCase @Inject constructor(
103104
session.coroutineScope.launch { stopVoiceBroadcastUseCase.execute(room.roomId) }
104105
}
105106
}
107+
108+
override fun onStateUpdated(state: VoiceBroadcastRecorder.State) {
109+
if (state == VoiceBroadcastRecorder.State.Error) {
110+
session.coroutineScope.launch {
111+
pauseVoiceBroadcastUseCase.execute(room.roomId)
112+
}
113+
}
114+
}
106115
})
107116
voiceBroadcastRecorder?.startRecordVoiceBroadcast(voiceBroadcast, chunkLength, maxLength)
108117
}

0 commit comments

Comments
 (0)