Voice message MediaPlayer: wait until player is ready (#1772)

Change to `MediaPlayer` API to allow waiting for the player to be in a ready state.
This is needed in order to perform some tasks (e.g. read the media duration, seek) after changing the media file.
This commit is contained in:
Marco Romano 2023-11-09 15:34:38 +01:00 committed by GitHub
parent b83d8733e2
commit 878417f557
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 110 additions and 28 deletions

View file

@ -42,7 +42,7 @@ class VoiceMessageComposerPlayer @Inject constructor(
State(
isPlaying = state.isPlaying,
currentPosition = state.currentPosition,
duration = state.duration,
duration = state.duration ?: 0L,
)
}.distinctUntilChanged()
@ -52,16 +52,17 @@ class VoiceMessageComposerPlayer @Inject constructor(
* @param mediaPath The path to the media to be played.
* @param mimeType The mime type of the media file.
*/
fun play(mediaPath: String, mimeType: String) {
suspend fun play(mediaPath: String, mimeType: String) {
if (mediaPath == curPlayingMediaId) {
mediaPlayer.play()
} else {
lastPlayedMediaPath = mediaPath
mediaPlayer.acquireControlAndPlay(
mediaPlayer.setMedia(
uri = mediaPath,
mediaId = mediaPath,
mimeType = mimeType,
)
mediaPlayer.play()
}
}

View file

@ -120,10 +120,12 @@ class VoiceMessageComposerPresenter @Inject constructor(
VoiceMessagePlayerEvent.Play ->
when (val recording = recorderState) {
is VoiceRecorderState.Finished ->
player.play(
mediaPath = recording.file.path,
mimeType = recording.mimeType,
)
localCoroutineScope.launch {
player.play(
mediaPath = recording.file.path,
mimeType = recording.mimeType,
)
}
else -> Timber.e("Voice message player event received but no file to play")
}
VoiceMessagePlayerEvent.Pause -> {

View file

@ -151,11 +151,12 @@ class DefaultVoiceMessagePlayer(
} else {
if (eventId != null) {
repo.getMediaFile().mapCatching { mediaFile ->
mediaPlayer.acquireControlAndPlay(
mediaPlayer.setMedia(
uri = mediaFile.path,
mediaId = eventId.value,
mimeType = "audio/ogg" // Files in the voice cache have no extension so we need to set the mime type manually.
)
mediaPlayer.play()
}
} else {
Result.failure(IllegalStateException("Cannot play a voice message with no eventId"))

View file

@ -195,6 +195,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
awaitItem().apply { assertThat(voiceMessageState).isEqualTo(aLoadedState()) }
val finalState = awaitItem().also {
assertThat(it.voiceMessageState).isEqualTo(aPlayingState())
}
@ -213,6 +214,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
skipItems(1) // Loaded state
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Pause))
val finalState = awaitItem().also {
assertThat(it.voiceMessageState).isEqualTo(aPausedState())
@ -250,6 +252,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
skipItems(1) // Loaded state
awaitItem().eventSink(VoiceMessageComposerEvents.DeleteVoiceMessage)
awaitItem().apply {
assertThat(voiceMessageState).isEqualTo(aPausedState())
@ -321,6 +324,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
skipItems(1) // Loaded state
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
assertThat(awaitItem().voiceMessageState).isEqualTo(aPlayingState().toSendingState())
@ -635,6 +639,14 @@ class VoiceMessageComposerPresenterTest {
waveform = waveform.toImmutableList(),
)
private fun aLoadedState() =
aPreviewState(
isPlaying = false,
playbackProgress = 0.0f,
showCursor = true,
time = 0.seconds,
)
private fun aPlayingState() =
aPreviewState(
isPlaying = true,

View file

@ -47,6 +47,11 @@ class DefaultVoiceMessagePlayerTest {
player.state.test {
skipItems(1) // skip initial state.
Truth.assertThat(player.play().isSuccess).isTrue()
awaitItem().let {
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isMyMedia).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(0)
}
awaitItem().let {
Truth.assertThat(it.isPlaying).isEqualTo(true)
Truth.assertThat(it.isMyMedia).isEqualTo(true)
@ -85,7 +90,7 @@ class DefaultVoiceMessagePlayerTest {
player.state.test {
skipItems(1) // skip initial state.
Truth.assertThat(player.play().isSuccess).isTrue()
skipItems(1) // skip play state
skipItems(2) // skip play states
player.pause()
awaitItem().let {
Truth.assertThat(it.isPlaying).isEqualTo(false)
@ -101,7 +106,7 @@ class DefaultVoiceMessagePlayerTest {
player.state.test {
skipItems(1) // skip initial state.
Truth.assertThat(player.play().isSuccess).isTrue()
skipItems(1) // skip play state
skipItems(2) // skip play states
player.pause()
skipItems(1)
player.play()
@ -119,7 +124,7 @@ class DefaultVoiceMessagePlayerTest {
player.state.test {
skipItems(1) // skip initial state.
Truth.assertThat(player.play().isSuccess).isTrue()
skipItems(1) // skip play state
skipItems(2) // skip play states
player.seekTo(2000)
awaitItem().let {
Truth.assertThat(it.isPlaying).isEqualTo(true)

View file

@ -70,6 +70,11 @@ class VoiceMessagePresenterTest {
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
}
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:00")
}
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
Truth.assertThat(it.progress).isEqualTo(0.5f)
@ -128,7 +133,7 @@ class VoiceMessagePresenterTest {
}
initialState.eventSink(VoiceMessageEvents.PlayPause)
skipItems(1) // skip downloading state
skipItems(2) // skip downloading states
val playingState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
@ -177,7 +182,7 @@ class VoiceMessagePresenterTest {
initialState.eventSink(VoiceMessageEvents.PlayPause)
skipItems(1) // skip downloading state
skipItems(2) // skip downloading states
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)