Skip to content

Commit d6fd32b

Browse files
authored
Merge pull request #7629 from vector-im/feature/fre/voice_broadcast_handle_event_deletion
Voice Broadcast - Handle event deletion when listening or recording
2 parents 46fc0ac + 9840731 commit d6fd32b

File tree

13 files changed

+278
-122
lines changed

13 files changed

+278
-122
lines changed

changelog.d/7629.wip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Voice Broadcast - Handle redaction of the state events on the listener and recorder sides

vector/src/main/java/im/vector/app/core/di/VoiceModule.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
2727
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayerImpl
2828
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
2929
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorderQ
30+
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
3031
import javax.inject.Singleton
3132

3233
@InstallIn(SingletonComponent::class)
@@ -36,9 +37,17 @@ abstract class VoiceModule {
3637
companion object {
3738
@Provides
3839
@Singleton
39-
fun providesVoiceBroadcastRecorder(context: Context): VoiceBroadcastRecorder? {
40+
fun providesVoiceBroadcastRecorder(
41+
context: Context,
42+
sessionHolder: ActiveSessionHolder,
43+
getMostRecentVoiceBroadcastStateEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase,
44+
): VoiceBroadcastRecorder? {
4045
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
41-
VoiceBroadcastRecorderQ(context)
46+
VoiceBroadcastRecorderQ(
47+
context = context,
48+
sessionHolder = sessionHolder,
49+
getVoiceBroadcastEventUseCase = getMostRecentVoiceBroadcastStateEventUseCase
50+
)
4251
} else {
4352
null
4453
}

vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Stat
3030
import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase
3131
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
3232
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
33-
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastEventUseCase
33+
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
3434
import im.vector.lib.core.utils.timer.CountUpTimer
3535
import kotlinx.coroutines.Job
3636
import kotlinx.coroutines.flow.launchIn
@@ -48,7 +48,7 @@ import javax.inject.Singleton
4848
class VoiceBroadcastPlayerImpl @Inject constructor(
4949
private val sessionHolder: ActiveSessionHolder,
5050
private val playbackTracker: AudioMessagePlaybackTracker,
51-
private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastEventUseCase,
51+
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase,
5252
private val getLiveVoiceBroadcastChunksUseCase: GetLiveVoiceBroadcastChunksUseCase
5353
) : VoiceBroadcastPlayer {
5454

@@ -66,7 +66,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
6666
private var nextMediaPlayer: MediaPlayer? = null
6767
private var isPreparingNextPlayer: Boolean = false
6868

69-
private var currentVoiceBroadcastEvent: VoiceBroadcastEvent? = null
69+
private var mostRecentVoiceBroadcastEvent: VoiceBroadcastEvent? = null
7070

7171
override var currentVoiceBroadcast: VoiceBroadcast? = null
7272
override var isLiveListening: Boolean = false
@@ -121,7 +121,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
121121
// Clear playlist
122122
playlist.reset()
123123

124-
currentVoiceBroadcastEvent = null
124+
mostRecentVoiceBroadcastEvent = null
125125
currentVoiceBroadcast = null
126126
}
127127

@@ -145,19 +145,25 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
145145

146146
playingState = State.BUFFERING
147147

148-
observeVoiceBroadcastLiveState(voiceBroadcast)
148+
observeVoiceBroadcastStateEvent(voiceBroadcast)
149149
fetchPlaylistAndStartPlayback(voiceBroadcast)
150150
}
151151

152-
private fun observeVoiceBroadcastLiveState(voiceBroadcast: VoiceBroadcast) {
152+
private fun observeVoiceBroadcastStateEvent(voiceBroadcast: VoiceBroadcast) {
153153
voiceBroadcastStateObserver = getVoiceBroadcastEventUseCase.execute(voiceBroadcast)
154-
.onEach {
155-
currentVoiceBroadcastEvent = it.getOrNull()
156-
updateLiveListeningMode()
157-
}
154+
.onEach { onVoiceBroadcastStateEventUpdated(it.getOrNull()) }
158155
.launchIn(sessionScope)
159156
}
160157

158+
private fun onVoiceBroadcastStateEventUpdated(event: VoiceBroadcastEvent?) {
159+
if (event == null) {
160+
stop()
161+
} else {
162+
mostRecentVoiceBroadcastEvent = event
163+
updateLiveListeningMode()
164+
}
165+
}
166+
161167
private fun fetchPlaylistAndStartPlayback(voiceBroadcast: VoiceBroadcast) {
162168
fetchPlaylistTask = getLiveVoiceBroadcastChunksUseCase.execute(voiceBroadcast)
163169
.onEach {
@@ -198,7 +204,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
198204

199205
val playlistItem = when {
200206
position != null -> playlist.findByPosition(position)
201-
currentVoiceBroadcastEvent?.isLive.orFalse() -> playlist.lastOrNull()
207+
mostRecentVoiceBroadcastEvent?.isLive.orFalse() -> playlist.lastOrNull()
202208
else -> playlist.firstOrNull()
203209
}
204210
val content = playlistItem?.audioEvent?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return }
@@ -340,7 +346,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
340346
private fun updateLiveListeningMode(seekPosition: Int? = null) {
341347
isLiveListening = when {
342348
// the current voice broadcast is not live (ended)
343-
currentVoiceBroadcastEvent?.isLive?.not().orFalse() -> false
349+
mostRecentVoiceBroadcastEvent?.isLive != true -> false
344350
// the player is stopped or paused
345351
playingState == State.IDLE || playingState == State.PAUSED -> false
346352
seekPosition != null -> {
@@ -406,13 +412,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
406412
override fun onCompletion(mp: MediaPlayer) {
407413
if (nextMediaPlayer != null) return
408414

409-
val content = currentVoiceBroadcastEvent?.content
410-
val isLive = content?.isLive.orFalse()
411-
if (!isLive && content?.lastChunkSequence == playlist.currentSequence) {
415+
if (isLiveListening || mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence) {
416+
playingState = State.BUFFERING
417+
} else {
412418
// We'll not receive new chunks anymore so we can stop the live listening
413419
stop()
414-
} else {
415-
playingState = State.BUFFERING
416420
}
417421
}
418422

vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
2424
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
2525
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
2626
import im.vector.app.features.voicebroadcast.sequence
27-
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastEventUseCase
27+
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
2828
import im.vector.app.features.voicebroadcast.voiceBroadcastId
2929
import kotlinx.coroutines.channels.awaitClose
3030
import kotlinx.coroutines.flow.Flow
@@ -48,7 +48,7 @@ import javax.inject.Inject
4848
*/
4949
class GetLiveVoiceBroadcastChunksUseCase @Inject constructor(
5050
private val activeSessionHolder: ActiveSessionHolder,
51-
private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastEventUseCase,
51+
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase,
5252
) {
5353

5454
fun execute(voiceBroadcast: VoiceBroadcast): Flow<List<MessageAudioEvent>> {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package im.vector.app.features.voicebroadcast.recording
1818

1919
import androidx.annotation.IntRange
2020
import im.vector.app.features.voice.VoiceRecorder
21+
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
2122
import java.io.File
2223

2324
interface VoiceBroadcastRecorder : VoiceRecorder {
@@ -31,7 +32,7 @@ interface VoiceBroadcastRecorder : VoiceRecorder {
3132
/** Current remaining time of recording, in seconds, if any. */
3233
val currentRemainingTime: Long?
3334

34-
fun startRecord(roomId: String, chunkLength: Int, maxLength: Int)
35+
fun startRecordVoiceBroadcast(voiceBroadcast: VoiceBroadcast, chunkLength: Int, maxLength: Int)
3536
fun addListener(listener: Listener)
3637
fun removeListener(listener: Listener)
3738

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

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,17 @@ import android.content.Context
2020
import android.media.MediaRecorder
2121
import android.os.Build
2222
import androidx.annotation.RequiresApi
23+
import im.vector.app.core.di.ActiveSessionHolder
24+
import im.vector.app.features.session.coroutineScope
2325
import im.vector.app.features.voice.AbstractVoiceRecorderQ
26+
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
27+
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
28+
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
29+
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
2430
import im.vector.lib.core.utils.timer.CountUpTimer
31+
import kotlinx.coroutines.Job
32+
import kotlinx.coroutines.flow.launchIn
33+
import kotlinx.coroutines.flow.onEach
2534
import org.matrix.android.sdk.api.extensions.tryOrNull
2635
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
2736
import java.util.concurrent.CopyOnWriteArrayList
@@ -30,10 +39,17 @@ import java.util.concurrent.TimeUnit
3039
@RequiresApi(Build.VERSION_CODES.Q)
3140
class VoiceBroadcastRecorderQ(
3241
context: Context,
42+
private val sessionHolder: ActiveSessionHolder,
43+
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase
3344
) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder {
3445

46+
private val session get() = sessionHolder.getActiveSession()
47+
private val sessionScope get() = session.coroutineScope
48+
49+
private var voiceBroadcastStateObserver: Job? = null
50+
3551
private var maxFileSize = 0L // zero or negative for no limit
36-
private var currentRoomId: String? = null
52+
private var currentVoiceBroadcast: VoiceBroadcast? = null
3753
private var currentMaxLength: Int = 0
3854

3955
override var currentSequence = 0
@@ -68,17 +84,20 @@ class VoiceBroadcastRecorderQ(
6884
}
6985
}
7086

71-
override fun startRecord(roomId: String, chunkLength: Int, maxLength: Int) {
72-
currentRoomId = roomId
87+
override fun startRecordVoiceBroadcast(voiceBroadcast: VoiceBroadcast, chunkLength: Int, maxLength: Int) {
88+
// Stop recording previous voice broadcast if any
89+
if (recordingState != VoiceBroadcastRecorder.State.Idle) stopRecord()
90+
91+
currentVoiceBroadcast = voiceBroadcast
7392
maxFileSize = (chunkLength * audioEncodingBitRate / 8).toLong()
7493
currentMaxLength = maxLength
7594
currentSequence = 1
76-
startRecord(roomId)
77-
recordingState = VoiceBroadcastRecorder.State.Recording
78-
recordingTicker.start()
95+
96+
observeVoiceBroadcastStateEvent(voiceBroadcast)
7997
}
8098

8199
override fun pauseRecord() {
100+
if (recordingState != VoiceBroadcastRecorder.State.Recording) return
82101
tryOrNull { mediaRecorder?.stop() }
83102
mediaRecorder?.reset()
84103
recordingState = VoiceBroadcastRecorder.State.Paused
@@ -87,8 +106,9 @@ class VoiceBroadcastRecorderQ(
87106
}
88107

89108
override fun resumeRecord() {
109+
if (recordingState != VoiceBroadcastRecorder.State.Paused) return
90110
currentSequence++
91-
currentRoomId?.let { startRecord(it) }
111+
currentVoiceBroadcast?.let { startRecord(it.roomId) }
92112
recordingState = VoiceBroadcastRecorder.State.Recording
93113
recordingTicker.resume()
94114
}
@@ -104,11 +124,15 @@ class VoiceBroadcastRecorderQ(
104124
// Remove listeners
105125
listeners.clear()
106126

127+
// Do not observe anymore voice broadcast changes
128+
voiceBroadcastStateObserver?.cancel()
129+
voiceBroadcastStateObserver = null
130+
107131
// Reset data
108132
currentSequence = 0
109133
currentMaxLength = 0
110134
currentRemainingTime = null
111-
currentRoomId = null
135+
currentVoiceBroadcast = null
112136
}
113137

114138
override fun release() {
@@ -126,6 +150,26 @@ class VoiceBroadcastRecorderQ(
126150
listeners.remove(listener)
127151
}
128152

153+
private fun observeVoiceBroadcastStateEvent(voiceBroadcast: VoiceBroadcast) {
154+
voiceBroadcastStateObserver = getVoiceBroadcastEventUseCase.execute(voiceBroadcast)
155+
.onEach { onVoiceBroadcastStateEventUpdated(voiceBroadcast, it.getOrNull()) }
156+
.launchIn(sessionScope)
157+
}
158+
159+
private fun onVoiceBroadcastStateEventUpdated(voiceBroadcast: VoiceBroadcast, event: VoiceBroadcastEvent?) {
160+
when (event?.content?.voiceBroadcastState) {
161+
VoiceBroadcastState.STARTED -> {
162+
startRecord(voiceBroadcast.roomId)
163+
recordingState = VoiceBroadcastRecorder.State.Recording
164+
recordingTicker.start()
165+
}
166+
VoiceBroadcastState.PAUSED -> pauseRecord()
167+
VoiceBroadcastState.RESUMED -> resumeRecord()
168+
VoiceBroadcastState.STOPPED,
169+
null -> stopRecord()
170+
}
171+
}
172+
129173
private fun onMaxFileSizeApproaching(roomId: String) {
130174
setNextOutputFile(roomId)
131175
}

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,20 @@ class PauseVoiceBroadcastUseCase @Inject constructor(
5353

5454
private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?) {
5555
Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event")
56+
57+
// save the last sequence number and immediately pause the recording
58+
val lastSequence = voiceBroadcastRecorder?.currentSequence
59+
pauseRecording()
60+
5661
room.stateService().sendStateEvent(
5762
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
5863
stateKey = session.myUserId,
5964
body = MessageVoiceBroadcastInfoContent(
6065
relatesTo = reference,
6166
voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value,
62-
lastChunkSequence = voiceBroadcastRecorder?.currentSequence,
67+
lastChunkSequence = lastSequence,
6368
).toContent(),
6469
)
65-
66-
pauseRecording()
6770
}
6871

6972
private fun pauseRecording() {

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
2020
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
2121
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
2222
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
23-
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
2423
import org.matrix.android.sdk.api.query.QueryStringValue
2524
import org.matrix.android.sdk.api.session.Session
2625
import org.matrix.android.sdk.api.session.events.model.toContent
@@ -32,7 +31,6 @@ import javax.inject.Inject
3231

3332
class ResumeVoiceBroadcastUseCase @Inject constructor(
3433
private val session: Session,
35-
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
3634
) {
3735

3836
suspend fun execute(roomId: String): Result<Unit> = runCatching {
@@ -66,11 +64,5 @@ class ResumeVoiceBroadcastUseCase @Inject constructor(
6664
voiceBroadcastStateStr = VoiceBroadcastState.RESUMED.value,
6765
).toContent(),
6866
)
69-
70-
resumeRecording()
71-
}
72-
73-
private fun resumeRecording() {
74-
voiceBroadcastRecorder?.resumeRecord()
7567
}
7668
}

0 commit comments

Comments
 (0)