mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Update voice broadcast recorder according to the most recent voice broadcast state event
This commit is contained in:
parent
f436de1230
commit
763b60ee6b
@ -27,6 +27,7 @@ import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
|
||||
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayerImpl
|
||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorderQ
|
||||
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@ -36,9 +37,17 @@ abstract class VoiceModule {
|
||||
companion object {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesVoiceBroadcastRecorder(context: Context): VoiceBroadcastRecorder? {
|
||||
fun providesVoiceBroadcastRecorder(
|
||||
context: Context,
|
||||
sessionHolder: ActiveSessionHolder,
|
||||
getMostRecentVoiceBroadcastStateEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase,
|
||||
): VoiceBroadcastRecorder? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
VoiceBroadcastRecorderQ(context)
|
||||
VoiceBroadcastRecorderQ(
|
||||
context = context,
|
||||
sessionHolder = sessionHolder,
|
||||
getVoiceBroadcastEventUseCase = getMostRecentVoiceBroadcastStateEventUseCase
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package im.vector.app.features.voicebroadcast.recording
|
||||
|
||||
import androidx.annotation.IntRange
|
||||
import im.vector.app.features.voice.VoiceRecorder
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||
import java.io.File
|
||||
|
||||
interface VoiceBroadcastRecorder : VoiceRecorder {
|
||||
@ -31,7 +32,7 @@ interface VoiceBroadcastRecorder : VoiceRecorder {
|
||||
/** Current remaining time of recording, in seconds, if any. */
|
||||
val currentRemainingTime: Long?
|
||||
|
||||
fun startRecord(roomId: String, chunkLength: Int, maxLength: Int)
|
||||
fun startRecordVoiceBroadcast(voiceBroadcast: VoiceBroadcast, chunkLength: Int, maxLength: Int)
|
||||
fun addListener(listener: Listener)
|
||||
fun removeListener(listener: Listener)
|
||||
|
||||
|
@ -20,8 +20,17 @@ import android.content.Context
|
||||
import android.media.MediaRecorder
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import im.vector.app.features.voice.AbstractVoiceRecorderQ
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
|
||||
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
@ -30,10 +39,17 @@ import java.util.concurrent.TimeUnit
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
class VoiceBroadcastRecorderQ(
|
||||
context: Context,
|
||||
private val sessionHolder: ActiveSessionHolder,
|
||||
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase
|
||||
) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder {
|
||||
|
||||
private val session get() = sessionHolder.getActiveSession()
|
||||
private val sessionScope get() = session.coroutineScope
|
||||
|
||||
private var voiceBroadcastStateObserver: Job? = null
|
||||
|
||||
private var maxFileSize = 0L // zero or negative for no limit
|
||||
private var currentRoomId: String? = null
|
||||
private var currentVoiceBroadcast: VoiceBroadcast? = null
|
||||
private var currentMaxLength: Int = 0
|
||||
|
||||
override var currentSequence = 0
|
||||
@ -68,14 +84,16 @@ class VoiceBroadcastRecorderQ(
|
||||
}
|
||||
}
|
||||
|
||||
override fun startRecord(roomId: String, chunkLength: Int, maxLength: Int) {
|
||||
currentRoomId = roomId
|
||||
override fun startRecordVoiceBroadcast(voiceBroadcast: VoiceBroadcast, chunkLength: Int, maxLength: Int) {
|
||||
// Stop recording previous voice broadcast if any
|
||||
if (recordingState != VoiceBroadcastRecorder.State.Idle) stopRecord()
|
||||
|
||||
currentVoiceBroadcast = voiceBroadcast
|
||||
maxFileSize = (chunkLength * audioEncodingBitRate / 8).toLong()
|
||||
currentMaxLength = maxLength
|
||||
currentSequence = 1
|
||||
startRecord(roomId)
|
||||
recordingState = VoiceBroadcastRecorder.State.Recording
|
||||
recordingTicker.start()
|
||||
|
||||
observeVoiceBroadcastStateEvent(voiceBroadcast)
|
||||
}
|
||||
|
||||
override fun pauseRecord() {
|
||||
@ -88,7 +106,7 @@ class VoiceBroadcastRecorderQ(
|
||||
|
||||
override fun resumeRecord() {
|
||||
currentSequence++
|
||||
currentRoomId?.let { startRecord(it) }
|
||||
currentVoiceBroadcast?.let { startRecord(it.roomId) }
|
||||
recordingState = VoiceBroadcastRecorder.State.Recording
|
||||
recordingTicker.resume()
|
||||
}
|
||||
@ -104,11 +122,15 @@ class VoiceBroadcastRecorderQ(
|
||||
// Remove listeners
|
||||
listeners.clear()
|
||||
|
||||
// Do not observe anymore voice broadcast changes
|
||||
voiceBroadcastStateObserver?.cancel()
|
||||
voiceBroadcastStateObserver = null
|
||||
|
||||
// Reset data
|
||||
currentSequence = 0
|
||||
currentMaxLength = 0
|
||||
currentRemainingTime = null
|
||||
currentRoomId = null
|
||||
currentVoiceBroadcast = null
|
||||
}
|
||||
|
||||
override fun release() {
|
||||
@ -126,6 +148,26 @@ class VoiceBroadcastRecorderQ(
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
private fun observeVoiceBroadcastStateEvent(voiceBroadcast: VoiceBroadcast) {
|
||||
voiceBroadcastStateObserver = getVoiceBroadcastEventUseCase.execute(voiceBroadcast)
|
||||
.onEach { onVoiceBroadcastStateEventUpdated(voiceBroadcast, it.getOrNull()) }
|
||||
.launchIn(sessionScope)
|
||||
}
|
||||
|
||||
private fun onVoiceBroadcastStateEventUpdated(voiceBroadcast: VoiceBroadcast, event: VoiceBroadcastEvent?) {
|
||||
when (event?.content?.voiceBroadcastState) {
|
||||
VoiceBroadcastState.STARTED -> {
|
||||
startRecord(voiceBroadcast.roomId)
|
||||
recordingState = VoiceBroadcastRecorder.State.Recording
|
||||
recordingTicker.start()
|
||||
}
|
||||
VoiceBroadcastState.PAUSED -> pauseRecord()
|
||||
VoiceBroadcastState.RESUMED -> resumeRecord()
|
||||
VoiceBroadcastState.STOPPED,
|
||||
null -> stopRecord()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onMaxFileSizeApproaching(roomId: String) {
|
||||
setNextOutputFile(roomId)
|
||||
}
|
||||
|
@ -62,11 +62,5 @@ class PauseVoiceBroadcastUseCase @Inject constructor(
|
||||
lastChunkSequence = voiceBroadcastRecorder?.currentSequence,
|
||||
).toContent(),
|
||||
)
|
||||
|
||||
pauseRecording()
|
||||
}
|
||||
|
||||
private fun pauseRecording() {
|
||||
voiceBroadcastRecorder?.pauseRecord()
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
@ -31,8 +30,7 @@ import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class ResumeVoiceBroadcastUseCase @Inject constructor(
|
||||
private val session: Session,
|
||||
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
||||
private val session: Session
|
||||
) {
|
||||
|
||||
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
||||
@ -66,11 +64,5 @@ class ResumeVoiceBroadcastUseCase @Inject constructor(
|
||||
voiceBroadcastStateStr = VoiceBroadcastState.RESUMED.value,
|
||||
).toContent(),
|
||||
)
|
||||
|
||||
resumeRecording()
|
||||
}
|
||||
|
||||
private fun resumeRecording() {
|
||||
voiceBroadcastRecorder?.resumeRecord()
|
||||
}
|
||||
}
|
||||
|
@ -24,11 +24,13 @@ import im.vector.app.features.session.coroutineScope
|
||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastFailure
|
||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||
import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase
|
||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
@ -43,6 +45,8 @@ import org.matrix.android.sdk.api.session.room.getStateEvent
|
||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import org.matrix.android.sdk.flow.unwrap
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
@ -63,6 +67,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
||||
|
||||
assertCanStartVoiceBroadcast(room)
|
||||
startVoiceBroadcast(room)
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
private suspend fun startVoiceBroadcast(room: Room) {
|
||||
@ -79,13 +84,15 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
||||
).toContent()
|
||||
)
|
||||
|
||||
startRecording(room, eventId, chunkLength, maxLength)
|
||||
val voiceBroadcast = VoiceBroadcast(roomId = room.roomId, voiceBroadcastId = eventId)
|
||||
room.flow().liveTimelineEvent(eventId).unwrap().first() // wait for the event come back from the sync
|
||||
startRecording(room, voiceBroadcast, chunkLength, maxLength)
|
||||
}
|
||||
|
||||
private fun startRecording(room: Room, eventId: String, chunkLength: Int, maxLength: Int) {
|
||||
private fun startRecording(room: Room, voiceBroadcast: VoiceBroadcast, chunkLength: Int, maxLength: Int) {
|
||||
voiceBroadcastRecorder?.addListener(object : VoiceBroadcastRecorder.Listener {
|
||||
override fun onVoiceMessageCreated(file: File, sequence: Int) {
|
||||
sendVoiceFile(room, file, eventId, sequence)
|
||||
sendVoiceFile(room, file, voiceBroadcast, sequence)
|
||||
}
|
||||
|
||||
override fun onRemainingTimeUpdated(remainingTime: Long?) {
|
||||
@ -94,10 +101,10 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
||||
}
|
||||
}
|
||||
})
|
||||
voiceBroadcastRecorder?.startRecord(room.roomId, chunkLength, maxLength)
|
||||
voiceBroadcastRecorder?.startRecordVoiceBroadcast(voiceBroadcast, chunkLength, maxLength)
|
||||
}
|
||||
|
||||
private fun sendVoiceFile(room: Room, voiceMessageFile: File, referenceEventId: String, sequence: Int) {
|
||||
private fun sendVoiceFile(room: Room, voiceMessageFile: File, voiceBroadcast: VoiceBroadcast, sequence: Int) {
|
||||
val outputFileUri = FileProvider.getUriForFile(
|
||||
context,
|
||||
buildMeta.applicationId + ".fileProvider",
|
||||
@ -109,7 +116,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
||||
attachment = audioType.toContentAttachmentData(isVoiceMessage = true),
|
||||
compressBeforeSending = false,
|
||||
roomIds = emptySet(),
|
||||
relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId),
|
||||
relatesTo = RelationDefaultContent(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId),
|
||||
additionalContent = mapOf(
|
||||
VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY to VoiceBroadcastChunk(sequence = sequence).toContent()
|
||||
)
|
||||
|
@ -19,7 +19,6 @@ package im.vector.app.features.voicebroadcast.usecase
|
||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||
import im.vector.app.features.voicebroadcast.recording.usecase.ResumeVoiceBroadcastUseCase
|
||||
import im.vector.app.test.fakes.FakeRoom
|
||||
import im.vector.app.test.fakes.FakeRoomService
|
||||
@ -27,7 +26,6 @@ import im.vector.app.test.fakes.FakeSession
|
||||
import io.mockk.clearAllMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.shouldBe
|
||||
@ -47,8 +45,7 @@ class ResumeVoiceBroadcastUseCaseTest {
|
||||
|
||||
private val fakeRoom = FakeRoom()
|
||||
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
||||
private val fakeVoiceBroadcastRecorder = mockk<VoiceBroadcastRecorder>(relaxed = true)
|
||||
private val resumeVoiceBroadcastUseCase = ResumeVoiceBroadcastUseCase(fakeSession, fakeVoiceBroadcastRecorder)
|
||||
private val resumeVoiceBroadcastUseCase = ResumeVoiceBroadcastUseCase(fakeSession)
|
||||
|
||||
@Test
|
||||
fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is resumed or not`() = runTest {
|
||||
|
Loading…
Reference in New Issue
Block a user