Add voice message recording duration indicator and limit (#1628)
--------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
8c0d9cc6a0
commit
f1b142f002
22 changed files with 263 additions and 35 deletions
|
|
@ -37,15 +37,19 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.yield
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.TimeSource
|
||||
|
||||
@SingleIn(RoomScope::class)
|
||||
@ContributesBinding(RoomScope::class)
|
||||
class VoiceRecorderImpl @Inject constructor(
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val timeSource: TimeSource,
|
||||
private val audioReaderFactory: AudioReader.Factory,
|
||||
private val encoder: Encoder,
|
||||
private val fileManager: VoiceFileManager,
|
||||
|
|
@ -74,16 +78,27 @@ class VoiceRecorderImpl @Inject constructor(
|
|||
val audioRecorder = audioReaderFactory.create(config, dispatchers).also { audioReader = it }
|
||||
|
||||
recordingJob = voiceCoroutineScope.launch {
|
||||
val startedAt = timeSource.markNow()
|
||||
audioRecorder.record { audio ->
|
||||
yield()
|
||||
|
||||
val elapsedTime = startedAt.elapsedNow()
|
||||
|
||||
if (elapsedTime >= 30.minutes) {
|
||||
Timber.w("Voice message time limit reached")
|
||||
stopRecord(false)
|
||||
return@record
|
||||
}
|
||||
|
||||
when (audio) {
|
||||
is Audio.Data -> {
|
||||
val audioLevel = audioLevelCalculator.calculateAudioLevel(audio.buffer)
|
||||
_state.emit(VoiceRecorderState.Recording(audioLevel))
|
||||
_state.emit(VoiceRecorderState.Recording(elapsedTime, audioLevel))
|
||||
encoder.encode(audio.buffer, audio.readSize)
|
||||
}
|
||||
is Audio.Error -> {
|
||||
Timber.e("Voice message error: code=${audio.audioRecordErrorCode}")
|
||||
_state.emit(VoiceRecorderState.Recording(0.0))
|
||||
_state.emit(VoiceRecorderState.Recording(elapsedTime, 0.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,9 +37,13 @@ import kotlinx.coroutines.test.runTest
|
|||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.TestTimeSource
|
||||
|
||||
class VoiceRecorderImplTest {
|
||||
private val fakeFileSystem = FakeFileSystem()
|
||||
private val timeSource = TestTimeSource()
|
||||
|
||||
@Test
|
||||
fun `it emits the initial state`() = runTest {
|
||||
|
|
@ -56,9 +60,27 @@ class VoiceRecorderImplTest {
|
|||
assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Idle)
|
||||
|
||||
voiceRecorder.startRecord()
|
||||
assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Recording(1.0))
|
||||
assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Recording(0.0))
|
||||
assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Recording(1.0))
|
||||
assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Recording(0.seconds, 1.0))
|
||||
timeSource += 1.seconds
|
||||
assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Recording(1.seconds,0.0))
|
||||
timeSource += 1.seconds
|
||||
assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Recording(2.seconds, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when elapsed time reaches 30 minutes, it stops recording`() = runTest {
|
||||
val voiceRecorder = createVoiceRecorder()
|
||||
voiceRecorder.state.test {
|
||||
assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Idle)
|
||||
|
||||
voiceRecorder.startRecord()
|
||||
assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Recording(0.minutes, 1.0))
|
||||
timeSource += 29.minutes
|
||||
assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Recording(29.minutes, 0.0))
|
||||
timeSource += 1.minutes
|
||||
|
||||
assertThat(awaitItem()).isEqualTo(VoiceRecorderState.Finished(File(FILE_PATH), "audio/ogg"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +116,7 @@ class VoiceRecorderImplTest {
|
|||
val fileConfig = VoiceRecorderModule.provideVoiceFileConfig()
|
||||
return VoiceRecorderImpl(
|
||||
dispatchers = testCoroutineDispatchers(),
|
||||
timeSource = timeSource,
|
||||
audioReaderFactory = FakeAudioRecorderFactory(
|
||||
audio = AUDIO,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class FakeAudioReader(
|
|||
while (audios.hasNext()) {
|
||||
if (!isRecording) break
|
||||
onAudio(audios.next())
|
||||
yield()
|
||||
}
|
||||
while (isActive) {
|
||||
// do not return from the coroutine until it is cancelled
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue