Add test on DefaultMediaPlayer.
This commit is contained in:
committed by
Benoit Marty
parent
f0339bc252
commit
799fb1a676
@@ -15,7 +15,6 @@ import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.mediaplayer.api.MediaPlayer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -36,6 +35,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||
@SingleIn(RoomScope::class)
|
||||
class DefaultMediaPlayer @Inject constructor(
|
||||
private val player: SimplePlayer,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
) : MediaPlayer {
|
||||
private val listener = object : SimplePlayer.Listener {
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
@@ -47,7 +47,7 @@ class DefaultMediaPlayer @Inject constructor(
|
||||
)
|
||||
}
|
||||
if (isPlaying) {
|
||||
job = scope.launch { updateCurrentPosition() }
|
||||
job = coroutineScope.launch { updateCurrentPosition() }
|
||||
} else {
|
||||
job?.cancel()
|
||||
}
|
||||
@@ -79,7 +79,6 @@ class DefaultMediaPlayer @Inject constructor(
|
||||
player.addListener(listener)
|
||||
}
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||
private var job: Job? = null
|
||||
|
||||
private val _state = MutableStateFlow(
|
||||
@@ -102,7 +101,8 @@ class DefaultMediaPlayer @Inject constructor(
|
||||
mimeType: String,
|
||||
startPositionMs: Long,
|
||||
): MediaPlayer.State {
|
||||
player.pause() // Must pause here otherwise if the player was playing it would keep on playing the new media item.
|
||||
// Must pause here otherwise if the player was playing it would keep on playing the new media item.
|
||||
player.pause()
|
||||
player.clearMediaItems()
|
||||
player.setMediaItem(
|
||||
MediaItem.Builder()
|
||||
@@ -129,11 +129,9 @@ class DefaultMediaPlayer @Inject constructor(
|
||||
player.getCurrentMediaItem()?.let {
|
||||
player.setMediaItem(it, 0)
|
||||
player.prepare()
|
||||
player.play()
|
||||
}
|
||||
} else {
|
||||
player.play()
|
||||
}
|
||||
player.play()
|
||||
}
|
||||
|
||||
override fun pause() {
|
||||
|
||||
@@ -7,12 +7,396 @@
|
||||
|
||||
package io.element.android.libraries.mediaplayer.impl
|
||||
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.mediaplayer.api.MediaPlayer
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultMediaPlayerTest {
|
||||
private val aMediaId = "mediaId"
|
||||
private val aMediaItem = MediaItem.Builder().setMediaId(aMediaId).build()
|
||||
|
||||
@Test
|
||||
fun `default test`() = runTest {
|
||||
// TODO
|
||||
fun `initial state`() = runTest {
|
||||
val sut = createDefaultMediaPlayer()
|
||||
sut.state.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 0,
|
||||
duration = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `start player will update the current position and pause it will stop`() = runTest {
|
||||
val playLambda = lambdaRecorder<Unit> { }
|
||||
val pauseLambda = lambdaRecorder<Unit> { }
|
||||
val player = FakeSimplePlayer(
|
||||
playLambda = playLambda,
|
||||
pauseLambda = pauseLambda,
|
||||
)
|
||||
val sut = createDefaultMediaPlayer(
|
||||
simplePlayer = player,
|
||||
)
|
||||
sut.state.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 0,
|
||||
duration = null,
|
||||
)
|
||||
)
|
||||
sut.play()
|
||||
playLambda.assertions().isCalledOnce()
|
||||
player.durationResult = 123L
|
||||
player.simulateIsPlayingChanged(true)
|
||||
val playingState = awaitItem()
|
||||
assertThat(playingState).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = true,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 0,
|
||||
duration = 123,
|
||||
)
|
||||
)
|
||||
player.currentPositionResult = 1L
|
||||
assertThat(awaitItem()).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = true,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 1,
|
||||
duration = 123,
|
||||
)
|
||||
)
|
||||
player.currentPositionResult = 2L
|
||||
assertThat(awaitItem()).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = true,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 2,
|
||||
duration = 123,
|
||||
)
|
||||
)
|
||||
player.pause()
|
||||
pauseLambda.assertions().isCalledOnce()
|
||||
player.simulateIsPlayingChanged(false)
|
||||
assertThat(awaitItem()).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 2,
|
||||
duration = 123,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `start player on ended playback will not invoke more methods if current media item is null`() = runTest {
|
||||
val playLambda = lambdaRecorder<Unit> { }
|
||||
val getCurrentMediaItemLambda = lambdaRecorder<MediaItem?> { null }
|
||||
val player = FakeSimplePlayer(
|
||||
playLambda = playLambda,
|
||||
getCurrentMediaItemLambda = getCurrentMediaItemLambda,
|
||||
)
|
||||
val sut = createDefaultMediaPlayer(
|
||||
simplePlayer = player,
|
||||
)
|
||||
sut.state.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 0,
|
||||
duration = null,
|
||||
)
|
||||
)
|
||||
player.playbackStateResult = Player.STATE_ENDED
|
||||
sut.play()
|
||||
playLambda.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `start player on ended playback will invoke more methods if current media item is not null`() = runTest {
|
||||
val playLambda = lambdaRecorder<Unit> { }
|
||||
val prepareLambda = lambdaRecorder<Unit> { }
|
||||
val getCurrentMediaItemLambda = lambdaRecorder<MediaItem?> { aMediaItem }
|
||||
val setMediaItemLambda = lambdaRecorder<MediaItem, Long, Unit> { _, _ -> }
|
||||
val player = FakeSimplePlayer(
|
||||
playLambda = playLambda,
|
||||
prepareLambda = prepareLambda,
|
||||
setMediaItemLambda = setMediaItemLambda,
|
||||
getCurrentMediaItemLambda = getCurrentMediaItemLambda,
|
||||
)
|
||||
val sut = createDefaultMediaPlayer(
|
||||
simplePlayer = player,
|
||||
)
|
||||
sut.state.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 0,
|
||||
duration = null,
|
||||
)
|
||||
)
|
||||
player.playbackStateResult = Player.STATE_ENDED
|
||||
sut.play()
|
||||
setMediaItemLambda.assertions().isCalledOnce().with(
|
||||
value(aMediaItem),
|
||||
value(0L),
|
||||
)
|
||||
prepareLambda.assertions().isCalledOnce()
|
||||
playLambda.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `pause player invokes pause on the embedded player`() = runTest {
|
||||
val pauseLambda = lambdaRecorder<Unit> { }
|
||||
val player = FakeSimplePlayer(
|
||||
pauseLambda = pauseLambda,
|
||||
)
|
||||
val sut = createDefaultMediaPlayer(
|
||||
simplePlayer = player,
|
||||
)
|
||||
sut.pause()
|
||||
pauseLambda.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `close player invokes release on the embedded player`() = runTest {
|
||||
val releaseLambda = lambdaRecorder<Unit> { }
|
||||
val player = FakeSimplePlayer(
|
||||
releaseLambda = releaseLambda,
|
||||
)
|
||||
val sut = createDefaultMediaPlayer(
|
||||
simplePlayer = player,
|
||||
)
|
||||
sut.close()
|
||||
releaseLambda.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `seekTo invokes release on the embedded player`() = runTest {
|
||||
val seekToLambda = lambdaRecorder<Long, Unit> { }
|
||||
val player = FakeSimplePlayer(
|
||||
seekToLambda = seekToLambda,
|
||||
)
|
||||
val sut = createDefaultMediaPlayer(
|
||||
simplePlayer = player,
|
||||
)
|
||||
sut.state.test {
|
||||
awaitItem()
|
||||
player.currentPositionResult = 33L
|
||||
sut.seekTo(33L)
|
||||
seekToLambda.assertions().isCalledOnce().with(value(33L))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 33L,
|
||||
duration = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onPlaybackStateChanged update the state`() = runTest {
|
||||
val player = FakeSimplePlayer()
|
||||
val sut = createDefaultMediaPlayer(
|
||||
simplePlayer = player,
|
||||
)
|
||||
sut.state.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 0,
|
||||
duration = null,
|
||||
)
|
||||
)
|
||||
player.currentPositionResult = 44
|
||||
player.durationResult = 123L
|
||||
player.simulatePlaybackStateChanged(Player.STATE_READY)
|
||||
val readyState = awaitItem()
|
||||
assertThat(readyState).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = true,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 44,
|
||||
duration = 123,
|
||||
)
|
||||
)
|
||||
player.simulatePlaybackStateChanged(Player.STATE_ENDED)
|
||||
val endedState = awaitItem()
|
||||
assertThat(endedState).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = false,
|
||||
isEnded = true,
|
||||
mediaId = null,
|
||||
currentPosition = 44,
|
||||
duration = 123,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setMedia with timeout error`() = runTest {
|
||||
val pauseLambda = lambdaRecorder<Unit> { }
|
||||
val clearMediaItemsLambda = lambdaRecorder<Unit> { }
|
||||
val setMediaItemLambda = lambdaRecorder<MediaItem, Long, Unit> { _, _ -> }
|
||||
val prepareLambda = lambdaRecorder<Unit> { }
|
||||
val player = FakeSimplePlayer(
|
||||
pauseLambda = pauseLambda,
|
||||
clearMediaItemsLambda = clearMediaItemsLambda,
|
||||
setMediaItemLambda = setMediaItemLambda,
|
||||
prepareLambda = prepareLambda,
|
||||
)
|
||||
val sut = createDefaultMediaPlayer(
|
||||
simplePlayer = player,
|
||||
)
|
||||
sut.state.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 0,
|
||||
duration = null,
|
||||
)
|
||||
)
|
||||
val result = runCatching {
|
||||
sut.setMedia("uri", "mediaId", "mimeType", 12)
|
||||
}
|
||||
pauseLambda.assertions().isCalledOnce()
|
||||
clearMediaItemsLambda.assertions().isCalledOnce()
|
||||
setMediaItemLambda.assertions().isCalledOnce().with(
|
||||
value(MediaItem.Builder().setUri("uri").setMediaId("mediaId").setMimeType("mimeType").build()),
|
||||
value(12L),
|
||||
)
|
||||
prepareLambda.assertions().isCalledOnce()
|
||||
assertThat(result.isFailure).isTrue()
|
||||
assertThrows(TimeoutCancellationException::class.java) {
|
||||
result.getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setMedia success`() = runTest {
|
||||
var player: FakeSimplePlayer? = null
|
||||
val pauseLambda = lambdaRecorder<Unit> { }
|
||||
val clearMediaItemsLambda = lambdaRecorder<Unit> { }
|
||||
val setMediaItemLambda = lambdaRecorder<MediaItem, Long, Unit> { _, _ -> }
|
||||
val prepareLambda = lambdaRecorder<Unit> {
|
||||
player?.simulatePlaybackStateChanged(Player.STATE_READY)
|
||||
player?.simulateMediaItemTransition(aMediaItem)
|
||||
}
|
||||
player = FakeSimplePlayer(
|
||||
pauseLambda = pauseLambda,
|
||||
clearMediaItemsLambda = clearMediaItemsLambda,
|
||||
setMediaItemLambda = setMediaItemLambda,
|
||||
prepareLambda = prepareLambda,
|
||||
)
|
||||
val sut = createDefaultMediaPlayer(
|
||||
simplePlayer = player,
|
||||
)
|
||||
sut.state.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = false,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 0,
|
||||
duration = null,
|
||||
)
|
||||
)
|
||||
val state = sut.setMedia("uri", "mediaId", "mimeType", 12)
|
||||
pauseLambda.assertions().isCalledOnce()
|
||||
clearMediaItemsLambda.assertions().isCalledOnce()
|
||||
setMediaItemLambda.assertions().isCalledOnce().with(
|
||||
value(MediaItem.Builder().setUri("uri").setMediaId("mediaId").setMimeType("mimeType").build()),
|
||||
value(12L),
|
||||
)
|
||||
prepareLambda.assertions().isCalledOnce()
|
||||
|
||||
val finalState = MediaPlayer.State(
|
||||
isReady = true,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = "mediaId",
|
||||
currentPosition = 0,
|
||||
duration = 0,
|
||||
)
|
||||
assertThat(awaitItem()).isEqualTo(
|
||||
MediaPlayer.State(
|
||||
isReady = true,
|
||||
isPlaying = false,
|
||||
isEnded = false,
|
||||
mediaId = null,
|
||||
currentPosition = 0,
|
||||
duration = 0,
|
||||
)
|
||||
)
|
||||
assertThat(awaitItem()).isEqualTo(finalState)
|
||||
assertThat(state).isEqualTo(finalState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createDefaultMediaPlayer(
|
||||
simplePlayer: SimplePlayer = FakeSimplePlayer(),
|
||||
): DefaultMediaPlayer = DefaultMediaPlayer(
|
||||
simplePlayer,
|
||||
backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.mediaplayer.impl
|
||||
|
||||
import androidx.media3.common.MediaItem
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeSimplePlayer(
|
||||
private val clearMediaItemsLambda: () -> Unit = { lambdaError() },
|
||||
private val setMediaItemLambda: (MediaItem, Long) -> Unit = { _, _ -> lambdaError() },
|
||||
private val getCurrentMediaItemLambda: () -> MediaItem? = { lambdaError() },
|
||||
private val prepareLambda: () -> Unit = { lambdaError() },
|
||||
private val playLambda: () -> Unit = { lambdaError() },
|
||||
private val pauseLambda: () -> Unit = { lambdaError() },
|
||||
private val seekToLambda: (Long) -> Unit = { lambdaError() },
|
||||
private val releaseLambda: () -> Unit = { lambdaError() },
|
||||
) : SimplePlayer {
|
||||
private val listeners = mutableListOf<SimplePlayer.Listener>()
|
||||
override fun addListener(listener: SimplePlayer.Listener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
var currentPositionResult: Long = 0
|
||||
override val currentPosition: Long get() = currentPositionResult
|
||||
var playbackStateResult: Int = 0
|
||||
override val playbackState: Int get() = playbackStateResult
|
||||
var durationResult: Long = 0
|
||||
override val duration: Long get() = durationResult
|
||||
|
||||
override fun clearMediaItems() = clearMediaItemsLambda()
|
||||
override fun setMediaItem(mediaItem: MediaItem, startPositionMs: Long) {
|
||||
setMediaItemLambda(mediaItem, startPositionMs)
|
||||
}
|
||||
|
||||
override fun getCurrentMediaItem(): MediaItem? = getCurrentMediaItemLambda()
|
||||
override fun prepare() = prepareLambda()
|
||||
override fun play() = playLambda()
|
||||
override fun pause() = pauseLambda()
|
||||
override fun seekTo(positionMs: Long) = seekToLambda(positionMs)
|
||||
override fun release() = releaseLambda()
|
||||
|
||||
fun simulateIsPlayingChanged(isPlaying: Boolean) {
|
||||
listeners.forEach { it.onIsPlayingChanged(isPlaying) }
|
||||
}
|
||||
|
||||
fun simulateMediaItemTransition(mediaItem: MediaItem?) {
|
||||
listeners.forEach { it.onMediaItemTransition(mediaItem) }
|
||||
}
|
||||
|
||||
fun simulatePlaybackStateChanged(playbackState: Int) {
|
||||
listeners.forEach { it.onPlaybackStateChanged(playbackState) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user