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:
parent
b83d8733e2
commit
878417f557
10 changed files with 110 additions and 28 deletions
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue