From c966d5678c3bcff16a35ce1934aa7b7df9b1c397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 24 Jan 2024 09:06:24 +0100 Subject: [PATCH 1/8] Better name function --- .../player/TestCurrentMediaItemTracker.kt | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestCurrentMediaItemTracker.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestCurrentMediaItemTracker.kt index 90793b8b6..1a9ccbb43 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestCurrentMediaItemTracker.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestCurrentMediaItemTracker.kt @@ -50,15 +50,15 @@ class TestCurrentMediaItemTracker { @Test fun testAreEqualsDifferentMediaItem() { - val mediaItem = createMediaItem("M1") - val mediaItem2 = createMediaItem("M2") + val mediaItem = createMediaItemWithMediaId("M1") + val mediaItem2 = createMediaItemWithMediaId("M2") Assert.assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) } @Test fun testAreEqualsSameMediaId() { - val mediaItem = createMediaItem("M1") - val mediaItem2 = createMediaItem("M1") + val mediaItem = createMediaItemWithMediaId("M1") + val mediaItem2 = createMediaItemWithMediaId("M1") Assert.assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) } @@ -95,7 +95,7 @@ class TestCurrentMediaItemTracker { @Test fun testStartEnd() = runTest { - val mediaItem = createMediaItem("M1") + val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.EOF) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemEnd(mediaItem) @@ -105,7 +105,7 @@ class TestCurrentMediaItemTracker { @Test fun testStartAsyncLoadEnd() = runTest { val mediaItemEmpty = MediaItem.Builder().setMediaId("M1").build() - val mediaItemLoaded = createMediaItem("M1") + val mediaItemLoaded = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.EOF) analyticsCommander.simulateItemStart(mediaItemEmpty) analyticsCommander.simulateItemLoaded(mediaItemLoaded) @@ -116,7 +116,7 @@ class TestCurrentMediaItemTracker { @Test fun testStartAsyncLoadRelease() = runTest { val mediaItemEmpty = MediaItem.Builder().setMediaId("M1").build() - val mediaItemLoaded = createMediaItem("M1") + val mediaItemLoaded = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.END) analyticsCommander.simulateItemStart(mediaItemEmpty) analyticsCommander.simulateItemLoaded(mediaItemLoaded) @@ -126,7 +126,7 @@ class TestCurrentMediaItemTracker { @Test fun testStartReleased() = runTest { - val mediaItem = createMediaItem("M1") + val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.END) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateRelease(mediaItem) @@ -135,7 +135,7 @@ class TestCurrentMediaItemTracker { @Test fun testRelease() = runTest { - val mediaItem = createMediaItem("M1") + val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE) analyticsCommander.simulateRelease(mediaItem) Assert.assertEquals(expected, tracker.stateList) @@ -143,7 +143,7 @@ class TestCurrentMediaItemTracker { @Test fun testRestartAfterEnd() = runTest { - val mediaItem = createMediaItem("M1") + val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.EOF, EventState.START, EventState.EOF) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemEnd(mediaItem) @@ -157,15 +157,15 @@ class TestCurrentMediaItemTracker { @Test fun testMediaTransitionSeekToNext() = runTest { val expectedStates = listOf(EventState.IDLE, EventState.START, EventState.END, EventState.START, EventState.EOF) - val mediaItem = createMediaItem("M1") - val mediaItem2 = createMediaItem("M2") + val mediaItem = createMediaItemWithMediaId("M1") + val mediaItem2 = createMediaItemWithMediaId("M2") analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionSeek(mediaItem, mediaItem2) analyticsCommander.simulateItemEnd(mediaItem2) Assert.assertEquals("Different Item", expectedStates, tracker.stateList) tracker.clear() - val mediaItem3 = createMediaItem("M1") + val mediaItem3 = createMediaItemWithMediaId("M1") analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionSeek(mediaItem, mediaItem3) analyticsCommander.simulateItemEnd(mediaItem3) @@ -175,9 +175,9 @@ class TestCurrentMediaItemTracker { @Test fun testMediaItemTransitionWithAsyncItem() = runTest { val expectedStates = listOf(EventState.IDLE, EventState.START, EventState.END, EventState.START, EventState.END) - val mediaItem = createMediaItem("M1") + val mediaItem = createMediaItemWithMediaId("M1") val mediaItem2 = MediaItem.Builder().setMediaId("M2").build() - val mediaItem2Loaded = createMediaItem("M2") + val mediaItem2Loaded = createMediaItemWithMediaId("M2") analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionSeek(mediaItem, mediaItem2) Assert.assertEquals(listOf(EventState.IDLE, EventState.START, EventState.END), tracker.stateList) @@ -203,15 +203,15 @@ class TestCurrentMediaItemTracker { @Test fun testMediaTransitionSameItemAuto() = runTest { val expectedStates = listOf(EventState.IDLE, EventState.START, EventState.EOF, EventState.START, EventState.EOF) - val mediaItem = createMediaItem("M1") - val mediaItem2 = createMediaItem("M2") + val mediaItem = createMediaItemWithMediaId("M1") + val mediaItem2 = createMediaItemWithMediaId("M2") analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionAuto(mediaItem, mediaItem2) analyticsCommander.simulateItemEnd(mediaItem2) Assert.assertEquals("Different Item", expectedStates, tracker.stateList) tracker.clear() - val mediaItem3 = createMediaItem("M1") + val mediaItem3 = createMediaItemWithMediaId("M1") analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionAuto(mediaItem, mediaItem3) analyticsCommander.simulateItemEnd(mediaItem3) @@ -221,7 +221,7 @@ class TestCurrentMediaItemTracker { @Test fun testMediaTransitionRepeat() = runTest { val expectedStates = listOf(EventState.IDLE, EventState.START, EventState.EOF, EventState.START) - val mediaItem = createMediaItem("M1") + val mediaItem = createMediaItemWithMediaId("M1") analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionRepeat(mediaItem) @@ -231,7 +231,7 @@ class TestCurrentMediaItemTracker { @Test fun testMultipleStop() = runTest { - val mediaItem = createMediaItem("M1") + val mediaItem = createMediaItemWithMediaId("M1") analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemEnd(mediaItem) analyticsCommander.simulateRelease(mediaItem) @@ -242,7 +242,7 @@ class TestCurrentMediaItemTracker { @Test fun testStartEndDisableAtStartAnalytics() = runTest { - val mediaItem = createMediaItem("M1") + val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE) currentItemTracker.enabled = false analyticsCommander.simulateItemStart(mediaItem) @@ -252,7 +252,7 @@ class TestCurrentMediaItemTracker { @Test fun testStartEndToggleAnalytics() = runTest { - val mediaItem = createMediaItem("M1") + val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.END, EventState.START, EventState.EOF) currentItemTracker.enabled = true analyticsCommander.simulateItemStart(mediaItem) @@ -266,7 +266,7 @@ class TestCurrentMediaItemTracker { @Test fun testStartAsyncLoadEndToggleAnalytics() = runTest { val mediaItemEmpty = MediaItem.Builder().setMediaId("M1").build() - val mediaItemLoaded = createMediaItem("M1") + val mediaItemLoaded = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.END, EventState.START, EventState.EOF) currentItemTracker.enabled = true analyticsCommander.simulateItemStart(mediaItemEmpty) @@ -281,7 +281,7 @@ class TestCurrentMediaItemTracker { @Test fun testStartAsyncLoadEndDisableAtEnd() = runTest { val mediaItemEmpty = MediaItem.Builder().setMediaId("M1").build() - val mediaItemLoaded = createMediaItem("M1") + val mediaItemLoaded = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.EOF) currentItemTracker.enabled = true analyticsCommander.simulateItemStart(mediaItemEmpty) @@ -293,7 +293,7 @@ class TestCurrentMediaItemTracker { @Test fun testStartRemoveItem() = runTest { - val mediaItem = createMediaItem("M1") + val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.END) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemRemoved(mediaItem) @@ -303,9 +303,9 @@ class TestCurrentMediaItemTracker { companion object { private val uri: Uri = mockk(relaxed = true) - fun createMediaItem(mediaId: String): MediaItem { + fun createMediaItemWithMediaId(mediaId: String): MediaItem { every { uri.toString() } returns "https://host/media.mp4" - every { uri.equals(Any()) } returns true + every { uri == Any() } returns true return MediaItem.Builder() .setUri(uri) .setMediaId(mediaId) From efd5d1c39d4f26afe36d8f9e552741d247b79736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 24 Jan 2024 09:10:30 +0100 Subject: [PATCH 2/8] Simplify dummy classes creation --- .../player/TestMediaItemTrackerList.kt | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerList.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerList.kt index 8a7c79d04..93011f508 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerList.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerList.kt @@ -89,30 +89,21 @@ class TestMediaItemTrackerList { Assert.assertNull(trackerC) } - private class ItemTrackerA : MediaItemTracker { + private open class EmptyItemTracker : MediaItemTracker { override fun start(player: ExoPlayer, initialData: Any?) { + // Nothing } override fun stop(player: ExoPlayer, reason: MediaItemTracker.StopReason, positionMs: Long) { + // Nothing } - } - private class ItemTrackerB : MediaItemTracker { - override fun start(player: ExoPlayer, initialData: Any?) { - } - - override fun stop(player: ExoPlayer, reason: MediaItemTracker.StopReason, positionMs: Long) { - } - } + private class ItemTrackerA : EmptyItemTracker() - private open class ItemTrackerC : MediaItemTracker { - override fun start(player: ExoPlayer, initialData: Any?) { - } + private class ItemTrackerB : EmptyItemTracker() - override fun stop(player: ExoPlayer, reason: MediaItemTracker.StopReason, positionMs: Long) { - } - } + private open class ItemTrackerC : EmptyItemTracker() private class ItemTrackerD : ItemTrackerC() } From 5d3360f8b09032f886720b5286070f4d553521b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 24 Jan 2024 12:16:02 +0100 Subject: [PATCH 3/8] Change currentPosition behaviors. --- .../srgssr/pillarbox/player/PlayerCallbackFlow.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PlayerCallbackFlow.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PlayerCallbackFlow.kt index 530e726c3..7ead8c522 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PlayerCallbackFlow.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PlayerCallbackFlow.kt @@ -24,6 +24,9 @@ import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.transformLatest @@ -149,6 +152,7 @@ fun Player.mediaItemCountAsFlow(): Flow = callbackFlow { /** * Ticker emits event every [interval] when [Player.isPlaying] is true. + * Emit a value once at least once. */ @OptIn(ExperimentalCoroutinesApi::class) fun Player.tickerWhilePlayingAsFlow( @@ -162,9 +166,11 @@ fun Player.tickerWhilePlayingAsFlow( /** * Current position of the player update every [updateInterval] when it is playing. + * Send current position once if not playing. */ fun Player.currentPositionAsFlow(updateInterval: Duration = DefaultUpdateInterval): Flow = merge( + flowOf(currentPosition).filter { !isPlaying }, tickerWhilePlayingAsFlow(updateInterval).map { currentPosition }, @@ -178,14 +184,11 @@ private fun Player.positionChangedFlow(): Flow = callbackFlow { newPosition: Player.PositionInfo, reason: Int ) { - if (reason == Player.DISCONTINUITY_REASON_SEEK) { - trySend(currentPosition) - } + trySend(newPosition.positionMs) } } - trySend(currentPosition) addPlayerListener(player = this@positionChangedFlow, listener) -} +}.distinctUntilChanged() /** * Current buffered percentage as flow [Player.getBufferedPercentage] From 011aa2d669b1934781b906b8d0efb2b3fbe91e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 24 Jan 2024 12:16:47 +0100 Subject: [PATCH 4/8] PlayerCallbackFlow test with Turbine --- gradle/libs.versions.toml | 2 + pillarbox-player/build.gradle.kts | 1 + .../player/TestPlayerCallbackFlow.kt | 380 +++++++++--------- 3 files changed, 186 insertions(+), 197 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d8e726c02..556a32a4c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,6 +31,7 @@ okhttp = "4.12.0" srg-data-provider = "0.8.0" tag-commander-core = "5.4.1" tag-commander-server-side = "5.5.1" +turbine = "1.0.0" [libraries] accompanist-navigation-material = { module = "com.google.accompanist:accompanist-navigation-material", version.ref = "accompanist" } @@ -115,6 +116,7 @@ androidx-compose-runtime-saveable = { module = "androidx.compose.runtime:runtime leanback = { group = "androidx.leanback", name = "leanback", version.ref = "androidx-leanback" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } guava = { module = "com.google.guava:guava", version.ref = "guava" } +turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } [plugins] android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } diff --git a/pillarbox-player/build.gradle.kts b/pillarbox-player/build.gradle.kts index ca0be5b0f..96fad64d1 100644 --- a/pillarbox-player/build.gradle.kts +++ b/pillarbox-player/build.gradle.kts @@ -93,6 +93,7 @@ dependencies { androidTestImplementation(libs.mockk) androidTestImplementation(libs.mockk.android) androidTestImplementation(libs.mockk.dsl) + testImplementation(libs.turbine) } publishing { diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt index 2682afc72..bd588f01b 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt @@ -12,20 +12,16 @@ import androidx.media3.common.Player import androidx.media3.common.Player.Commands import androidx.media3.common.Timeline import androidx.media3.common.VideoSize +import app.cash.turbine.test import ch.srgssr.pillarbox.player.test.utils.PlayerListenerCommander +import ch.srgssr.pillarbox.player.utils.StringUtil import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.take -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withTimeout import org.junit.After import org.junit.Assert import org.junit.Before @@ -49,161 +45,150 @@ class TestPlayerCallbackFlow { @Test fun testCurrentPositionWhilePlaying() = runTest { - val positions = listOf(C.TIME_UNSET, 0L, 1000L, 2000L, 3000L, 4000L, 5000L) - every { player.currentPosition } returnsMany positions + every { player.currentPosition } returns C.TIME_UNSET every { player.isPlaying } returns true - val currentPositionFlow = player.currentPositionAsFlow() - val actualPositions = currentPositionFlow.take(positions.size).toList() - Assert.assertEquals(positions, actualPositions) + currentPositionFlow.test { + Assert.assertEquals(C.TIME_UNSET, awaitItem()) + every { player.currentPosition } returns 1000L + Assert.assertEquals(1000L, awaitItem()) + every { player.currentPosition } returns 10_000L + Assert.assertEquals(10_000L, awaitItem()) + cancelAndIgnoreRemainingEvents() + } } /** * Test current position while not playing * We expected a Timeout as the flow doesn't start */ - @Test(expected = TimeoutCancellationException::class) + @Test fun testCurrentPositionWhileNotPlaying() = runTest { - val positions = listOf(C.TIME_UNSET, 0L, 1000L, 2000L, 3000L, 4000L, 5000L) - every { player.currentPosition } returnsMany positions every { player.isPlaying } returns false - - val currentPositionFlow = player.currentPositionAsFlow() - val firstPosition = currentPositionFlow.first() - Assert.assertEquals(positions[0], firstPosition) - - withTimeout(3_000L) { - val actualPositions = currentPositionFlow.take(positions.size).toList() - Assert.assertEquals(positions, actualPositions) + every { player.currentPosition } returns 1000L + player.currentPositionAsFlow().test { + Assert.assertEquals(1000L, awaitItem()) + ensureAllEventsConsumed() } } @Test fun testCurrentBufferedPercentage() = runTest { - val bufferedPercentages = listOf(0, 5, 15, 20, 50, 60, 75, 100) - every { player.bufferedPercentage } returnsMany bufferedPercentages every { player.isPlaying } returns true - - val currentBufferedPercentageFlow = player.currentBufferedPercentageAsFlow() - val actualBufferedPositions = currentBufferedPercentageFlow.take(bufferedPercentages.size).toList() - Assert.assertEquals(bufferedPercentages.map { it / 100f }, actualBufferedPositions) + player.currentBufferedPercentageAsFlow().test { + every { player.bufferedPercentage } returns 0 + Assert.assertEquals(0.0f, awaitItem()) + every { player.bufferedPercentage } returns 75 + Assert.assertEquals(0.75f, awaitItem()) + cancelAndIgnoreRemainingEvents() + } } - @Test(timeout = 5_000) - fun testUpdateCurrentPositionAfterSeek() = runTest { - val positions = listOf(0L, 1000L, 2000L) - every { player.isPlaying } returns false // TO disable periodic update - every { player.currentPosition } returnsMany positions + @Test + fun testUpdateCurrentPositionAfterPositionDiscontinuity() = runTest { + every { player.isPlaying } returns false // disable periodic update + every { player.currentPosition } returns 0L val fakePlayer = PlayerListenerCommander(player) - val playbackSpeedFlow = fakePlayer.currentPositionAsFlow() - val actualPositions = ArrayList() - val job = launch(dispatcher) { - playbackSpeedFlow.take(positions.size).toList(actualPositions) - } - - fakePlayer.onPositionDiscontinuity( - mockk(), - mockk(), - Player.DISCONTINUITY_REASON_SEEK + val discontinuityTests = listOf( + Pair(Player.DISCONTINUITY_REASON_SEEK, 1000L), + Pair(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT, 2000L), + Pair(Player.DISCONTINUITY_REASON_INTERNAL, 3000L), + Pair(Player.DISCONTINUITY_REASON_AUTO_TRANSITION, 4000L), + Pair(Player.DISCONTINUITY_REASON_REMOVE, 0L), + Pair(Player.DISCONTINUITY_REASON_SKIP, C.TIME_UNSET), ) - - fakePlayer.onPositionDiscontinuity( - mockk(), - mockk(), - Player.DISCONTINUITY_REASON_SEEK - ) - - Assert.assertEquals(positions, actualPositions) - job.cancel() + fakePlayer.currentPositionAsFlow().test { + Assert.assertEquals(0L, awaitItem()) + for (case in discontinuityTests) { + fakePlayer.onPositionDiscontinuity( + mockk(), + Player.PositionInfo(null, 0, null, null, 0, case.second, 0, 0, 0), + case.first + ) + Assert.assertEquals(StringUtil.discontinuityReasonString(case.first), case.second, awaitItem()) + } + ensureAllEventsConsumed() + } } - @Test(timeout = 10_000L) + @Test fun testDuration() = runTest { - val durations = listOf(1_000L, 5_000L, 10_000L, 20_000L) - every { player.duration } returnsMany durations - + every { player.duration } returns C.TIME_UNSET val fakePlayer = PlayerListenerCommander(player) val durationFlow = fakePlayer.durationAsFlow() - - val actualDuration = ArrayList() - val job = launch(dispatcher) { - durationFlow.take(durations.size).toList(actualDuration) + durationFlow.test { + every { player.duration } returns 20_000L + fakePlayer.onPlaybackStateChanged(Player.STATE_BUFFERING) + fakePlayer.onPlaybackStateChanged(Player.STATE_READY) + every { player.duration } returns 30_000L + fakePlayer.onTimelineChanged(Timeline.EMPTY, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) + every { player.duration } returns 40_000L + fakePlayer.onTimelineChanged(Timeline.EMPTY, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) + + Assert.assertEquals("initial duration", C.TIME_UNSET, awaitItem()) + Assert.assertEquals("State ready", 20_000L, awaitItem()) + Assert.assertEquals("TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED", 30_000L, awaitItem()) + Assert.assertEquals("TIMELINE_CHANGE_REASON_SOURCE_UPDATE", 40_000L, awaitItem()) } - fakePlayer.onPlaybackStateChanged(Player.STATE_READY) - fakePlayer.onTimelineChanged(Timeline.EMPTY, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) - fakePlayer.onTimelineChanged(Timeline.EMPTY, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) - Assert.assertEquals(durations, actualDuration) - job.cancel() } - @Test(timeout = 2_000) + @Test fun testIsPlaying() = runTest { every { player.isPlaying } returns false val fakePlayer = PlayerListenerCommander(player) - val isPlayFlow = fakePlayer.isPlayingAsFlow() - - val isPlaying = listOf(false, false, true, false, true) - val actualIsPlaying = ArrayList() - val job = launch(dispatcher) { - isPlayFlow.take(isPlaying.size).toList(actualIsPlaying) + val isPlayingFlow = fakePlayer.isPlayingAsFlow() + isPlayingFlow.test { + fakePlayer.onIsPlayingChanged(true) + fakePlayer.onIsPlayingChanged(true) + fakePlayer.onIsPlayingChanged(false) + + Assert.assertEquals("initial isPlaying", false, awaitItem()) + Assert.assertEquals("isPlaying", true, awaitItem()) + Assert.assertEquals("isPlaying", true, awaitItem()) + Assert.assertEquals("isPlaying", false, awaitItem()) } - for (playing in listOf(false, true, false, true)) { - fakePlayer.onIsPlayingChanged(playing) - } - Assert.assertEquals(isPlaying, actualIsPlaying) - job.cancel() } - @Test(timeout = 5_000) + @Test fun testPlaybackState() = runTest { every { player.playbackState } returns Player.STATE_IDLE val fakePlayer = PlayerListenerCommander(player) val playbackStateFlow = fakePlayer.playbackStateAsFlow() - val actualPlaybackStates = ArrayList() val playbackStates = listOf( - Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED ) - val job = launch(dispatcher) { - playbackStateFlow.take(playbackStates.size).toList(actualPlaybackStates) + playbackStateFlow.test { + for (state in playbackStates) { + fakePlayer.onPlaybackStateChanged(state) + } + + Assert.assertEquals("Initial state", Player.STATE_IDLE, awaitItem()) + for (state in playbackStates) { + Assert.assertEquals(StringUtil.playerStateString(state), state, awaitItem()) + } } - - fakePlayer.onPlaybackStateChanged(Player.STATE_BUFFERING) - fakePlayer.onPlaybackStateChanged(Player.STATE_READY) - fakePlayer.onPlaybackStateChanged(Player.STATE_BUFFERING) - fakePlayer.onPlaybackStateChanged(Player.STATE_READY) - fakePlayer.onPlaybackStateChanged(Player.STATE_ENDED) - - Assert.assertEquals(playbackStates, actualPlaybackStates) - job.cancel() } - @Test(timeout = 2_000) - fun testError() = runTest { + @Test + fun testPlaybackError() = runTest { val error = mockk() val noError: PlaybackException? = null every { player.playerError } returns null val fakePlayer = PlayerListenerCommander(player) - val errorFlow = fakePlayer.playerErrorAsFlow() - val actualErrors = ArrayList() - val errors = - listOf( - noError, error, noError - ) - val job = launch(dispatcher) { - errorFlow.take(errors.size).toList(actualErrors) - } - fakePlayer.onPlayerErrorChanged(error) - fakePlayer.onPlayerErrorChanged(noError) + fakePlayer.playerErrorAsFlow().test { + fakePlayer.onPlayerErrorChanged(error) + fakePlayer.onPlayerErrorChanged(noError) - Assert.assertEquals(errors, actualErrors) - job.cancel() + Assert.assertEquals("Initial error", null, awaitItem()) + Assert.assertEquals("error", error, awaitItem()) + Assert.assertEquals("error removed", noError, awaitItem()) + } } @Test(timeout = 2_000) @@ -212,98 +197,108 @@ class TestPlayerCallbackFlow { val command2 = mockk() every { player.availableCommands } returns command1 val fakePlayer = PlayerListenerCommander(player) - val commandsFlow = fakePlayer.availableCommandsAsFlow() - val actualErrors = ArrayList() - val commands = listOf(command1, command2) - val job = launch(dispatcher) { - commandsFlow.take(commands.size).toList(actualErrors) + fakePlayer.availableCommandsAsFlow().test { + fakePlayer.onAvailableCommandsChanged(command2) + Assert.assertEquals(command1, awaitItem()) + Assert.assertEquals(command2, awaitItem()) } - - fakePlayer.onAvailableCommandsChanged(command2) - - Assert.assertEquals(commands, actualErrors) - job.cancel() } @Test fun testShuffleModeEnabled() = runTest { every { player.shuffleModeEnabled } returns false - val listener = PlayerListenerCommander(player) - - val shuffleModeEnabledFlow = listener.shuffleModeEnabledAsFlow() - val shuffleModeEnabled = listOf(false, false, true, false, true) - val actualShuffleModeEnabled = mutableListOf() - - val job = launch(dispatcher) { - shuffleModeEnabledFlow.take(shuffleModeEnabled.size).toList(actualShuffleModeEnabled) + val fakePlayer = PlayerListenerCommander(player) + fakePlayer.shuffleModeEnabledAsFlow().test { + fakePlayer.onShuffleModeEnabledChanged(false) + fakePlayer.onShuffleModeEnabledChanged(false) + fakePlayer.onShuffleModeEnabledChanged(true) + fakePlayer.onShuffleModeEnabledChanged(false) + + Assert.assertEquals("initial state", false, awaitItem()) + Assert.assertEquals(false, awaitItem()) + Assert.assertEquals(false, awaitItem()) + Assert.assertEquals(true, awaitItem()) + Assert.assertEquals(false, awaitItem()) } - - listOf(false, true, false, true) - .forEach(listener::onShuffleModeEnabledChanged) - - Assert.assertEquals(shuffleModeEnabled, actualShuffleModeEnabled) - - job.cancel() } - @Test(timeout = 2_000) + @Test fun testPlaybackSpeed() = runTest { val initialPlaybackRate = 1.5f val initialParameters: PlaybackParameters = PlaybackParameters.DEFAULT.withSpeed(initialPlaybackRate) every { player.playbackParameters } returns initialParameters val fakePlayer = PlayerListenerCommander(player) - val playbackSpeedFlow = fakePlayer.getPlaybackSpeedAsFlow() - val actualSpeeds = ArrayList() - val speeds = listOf(initialPlaybackRate, 2.0f) - val job = launch(dispatcher) { - playbackSpeedFlow.take(speeds.size).toList(actualSpeeds) - } + fakePlayer.getPlaybackSpeedAsFlow().test { + fakePlayer.onPlaybackParametersChanged(initialParameters.withSpeed(2.0f)) + fakePlayer.onPlaybackParametersChanged(initialParameters.withSpeed(0.5f)) - fakePlayer.onPlaybackParametersChanged(initialParameters.withSpeed(2.0f)) - - Assert.assertEquals(speeds, actualSpeeds) - job.cancel() + Assert.assertEquals("Initial playback speed", initialPlaybackRate, awaitItem()) + Assert.assertEquals(2.0f, awaitItem()) + Assert.assertEquals(0.5f, awaitItem()) + } } - @Test(timeout = 5_000) + @Test fun testCurrentMediaIndex() = runTest { - val indices = listOf(1, 1, 2) - every { player.currentMediaItemIndex } returnsMany indices + every { player.currentMediaItemIndex } returns 0 val fakePlayer = PlayerListenerCommander(player) - val currentIndexFlow = fakePlayer.getCurrentMediaItemIndexAsFlow() - val actualIndices = ArrayList() - val job = launch(dispatcher) { - currentIndexFlow.take(indices.size).toList(actualIndices) + val transitionReasonCases = listOf( + Pair(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK, 10), + Pair(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO, 11), + Pair(Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT, 12), + Pair(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED, 13), + ) + fakePlayer.getCurrentMediaItemIndexAsFlow().test { + every { player.currentMediaItemIndex } returns 78 + fakePlayer.onTimelineChanged(mockk(), Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) + every { player.currentMediaItemIndex } returns 2 + fakePlayer.onTimelineChanged(mockk(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) + + for ((reason, index) in transitionReasonCases) { + every { player.currentMediaItemIndex } returns index + fakePlayer.onMediaItemTransition(mockk(), reason) + } + + Assert.assertEquals("Initial index", 0, awaitItem()) + Assert.assertEquals("TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED", 2, awaitItem()) + for ((reason, index) in transitionReasonCases) { + Assert.assertEquals(StringUtil.mediaItemTransitionReasonString(reason), index, awaitItem()) + } + ensureAllEventsConsumed() } - - fakePlayer.onMediaItemTransition(mockk(), Player.MEDIA_ITEM_TRANSITION_REASON_SEEK) - fakePlayer.onTimelineChanged(mockk(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) - - Assert.assertEquals(indices, actualIndices) - job.cancel() } - @Test(timeout = 5_000) + @Test fun testCurrentMediaItem() = runTest { - val mediaItem1: MediaItem? = null - val mediaItem2: MediaItem = mockk() - val mediaItem3: MediaItem = mockk() - val mediaItems = listOf(mediaItem1, mediaItem2, mediaItem3) - every { player.currentMediaItem } returnsMany listOf(mediaItem1, mediaItem3) + every { player.currentMediaItem } returns null val fakePlayer = PlayerListenerCommander(player) - val currentMediaItemFlow = fakePlayer.currentMediaItemAsFlow() - val actualMediaItems = ArrayList() - val job = launch(dispatcher) { - currentMediaItemFlow.take(mediaItems.size).toList(actualMediaItems) + val transitionReasonCases = listOf>( + Pair(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK, mockk()), + Pair(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO, mockk()), + Pair(Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT, mockk()), + Pair(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED, mockk()), + ) + val mediaItemTimeLinePlaylistChanged: MediaItem = mockk() + val mediaItemTimeLineSourceUpdate: MediaItem = mockk() + fakePlayer.currentMediaItemAsFlow().test { + every { player.currentMediaItem } returns mediaItemTimeLinePlaylistChanged + fakePlayer.onTimelineChanged(mockk(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) + every { player.currentMediaItem } returns mediaItemTimeLineSourceUpdate + fakePlayer.onTimelineChanged(mockk(), Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) + + for ((reason, mediaItem) in transitionReasonCases) { + every { player.currentMediaItem } returns mediaItem + fakePlayer.onMediaItemTransition(mediaItem, reason) + } + + Assert.assertNull("Initial", awaitItem()) + Assert.assertEquals("TIMELINE_CHANGE_REASON_SOURCE_UPDATE", mediaItemTimeLineSourceUpdate, awaitItem()) + for ((reason, mediaItem) in transitionReasonCases) { + Assert.assertEquals(StringUtil.mediaItemTransitionReasonString(reason), mediaItem, awaitItem()) + } + ensureAllEventsConsumed() } - - fakePlayer.onMediaItemTransition(mediaItem2, Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) - fakePlayer.onTimelineChanged(mockk(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) - fakePlayer.onTimelineChanged(mockk(), Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) - - Assert.assertEquals(mediaItems, actualMediaItems) - job.cancel() } @Test(timeout = 5_000) @@ -314,46 +309,37 @@ class TestPlayerCallbackFlow { val list1 = listOf(item1, item2) val list2 = listOf(item1, item2, item3) - every { player.mediaItemCount } returnsMany listOf(2, 2, 3, 3) // read twice in getCurrentMediaItems! + + every { player.mediaItemCount } returns 2 every { player.getMediaItemAt(0) } returns item1 every { player.getMediaItemAt(1) } returns item2 every { player.getMediaItemAt(2) } returns item3 val fakePlayer = PlayerListenerCommander(player) - Assert.assertEquals(item1, fakePlayer.getMediaItemAt(0)) - Assert.assertEquals(item2, fakePlayer.getMediaItemAt(1)) - Assert.assertEquals(item3, fakePlayer.getMediaItemAt(2)) + fakePlayer.getCurrentMediaItemsAsFlow().test { + every { player.mediaItemCount } returns 3 + fakePlayer.onTimelineChanged(mockk(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) + every { player.mediaItemCount } returns 1 + fakePlayer.onTimelineChanged(mockk(), Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) - val currentMediaItemFlow = fakePlayer.getCurrentMediaItemsAsFlow() - val actualMediaItems = ArrayList>() - val job = launch(dispatcher) { - currentMediaItemFlow.take(2).toList(actualMediaItems) + Assert.assertEquals("Initial list", list1, awaitItem()) + Assert.assertEquals("TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED", list2, awaitItem()) + Assert.assertEquals("TIMELINE_CHANGE_REASON_SOURCE_UPDATE list", listOf(item1), awaitItem()) } - fakePlayer.onTimelineChanged(mockk(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) - - Assert.assertEquals(2, actualMediaItems.size) - Assert.assertEquals(list1, actualMediaItems[0]) - Assert.assertEquals(list2, actualMediaItems[1]) - job.cancel() } - @Test(timeout = 2_000) + @Test fun testVideoSize() = runTest { val initialSize = VideoSize.UNKNOWN val newSize = VideoSize(1200, 1000) every { player.videoSize } returns initialSize val fakePlayer = PlayerListenerCommander(player) - val videoSize = fakePlayer.videoSizeAsFlow() - val actualVideoSize = ArrayList() - val videoSizes = listOf(initialSize, newSize) - val job = launch(dispatcher) { - videoSize.take(videoSizes.size).toList(actualVideoSize) - } - - fakePlayer.onVideoSizeChanged(newSize) + fakePlayer.videoSizeAsFlow().test { + fakePlayer.onVideoSizeChanged(newSize) - Assert.assertEquals(videoSizes, actualVideoSize) - job.cancel() + Assert.assertEquals("Initial video size", initialSize, awaitItem()) + Assert.assertEquals("Updated video size", newSize, awaitItem()) + } } } From 45f182987e74ddbf9e2961a8c1e20187779017a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 24 Jan 2024 12:17:01 +0100 Subject: [PATCH 5/8] formatter --- .../srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt index 0cfc32e4d..5c9a749ae 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt @@ -42,7 +42,7 @@ class TestMediaItemTrackerRepository { } } - override fun start(player: ExoPlayer, initialData: Any?) { + override fun start(player: ExoPlayer, initialData: Any?){ } From da0242504d7b4365c2b00f57b704349af9a606f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Thu, 25 Jan 2024 17:32:21 +0100 Subject: [PATCH 6/8] Asserts at the end --- .../srgssr/pillarbox/player/TestPlayerCallbackFlow.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt index bd588f01b..c5473854b 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt @@ -99,13 +99,16 @@ class TestPlayerCallbackFlow { ) fakePlayer.currentPositionAsFlow().test { Assert.assertEquals(0L, awaitItem()) - for (case in discontinuityTests) { + for ((reason, position) in discontinuityTests) { fakePlayer.onPositionDiscontinuity( mockk(), - Player.PositionInfo(null, 0, null, null, 0, case.second, 0, 0, 0), - case.first + Player.PositionInfo(null, 0, null, null, 0, position, 0, 0, 0), + reason ) - Assert.assertEquals(StringUtil.discontinuityReasonString(case.first), case.second, awaitItem()) + } + + for ((reason, position) in discontinuityTests) { + Assert.assertEquals(StringUtil.discontinuityReasonString(reason), position, awaitItem()) } ensureAllEventsConsumed() } From 84abd07123225614ae07465d288b46afdefd669d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Thu, 25 Jan 2024 19:51:37 +0100 Subject: [PATCH 7/8] Order dependencies --- pillarbox-player/build.gradle.kts | 2 +- .../srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pillarbox-player/build.gradle.kts b/pillarbox-player/build.gradle.kts index 96fad64d1..a768568d1 100644 --- a/pillarbox-player/build.gradle.kts +++ b/pillarbox-player/build.gradle.kts @@ -82,6 +82,7 @@ dependencies { testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.mockk) testImplementation(libs.mockk.dsl) + testImplementation(libs.turbine) androidTestImplementation(project(":pillarbox-player-testutils")) @@ -93,7 +94,6 @@ dependencies { androidTestImplementation(libs.mockk) androidTestImplementation(libs.mockk.android) androidTestImplementation(libs.mockk.dsl) - testImplementation(libs.turbine) } publishing { diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt index 5c9a749ae..0cfc32e4d 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt @@ -42,7 +42,7 @@ class TestMediaItemTrackerRepository { } } - override fun start(player: ExoPlayer, initialData: Any?){ + override fun start(player: ExoPlayer, initialData: Any?) { } From 99b6a60c99d72c50d060d25f1f6818d1c10435f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Fri, 26 Jan 2024 08:51:56 +0100 Subject: [PATCH 8/8] Avoid `Flow` creation in `currentPositionAsFlow` and add missing `ensureAllEventsConsumed` in tests --- .../pillarbox/player/PlayerCallbackFlow.kt | 4 +- .../player/TestPlayerCallbackFlow.kt | 51 +++++++++++-------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PlayerCallbackFlow.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PlayerCallbackFlow.kt index 7ead8c522..49bd84c79 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PlayerCallbackFlow.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PlayerCallbackFlow.kt @@ -25,7 +25,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -170,7 +170,7 @@ fun Player.tickerWhilePlayingAsFlow( */ fun Player.currentPositionAsFlow(updateInterval: Duration = DefaultUpdateInterval): Flow = merge( - flowOf(currentPosition).filter { !isPlaying }, + if (isPlaying) emptyFlow() else flowOf(currentPosition), tickerWhilePlayingAsFlow(updateInterval).map { currentPosition }, diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt index c5473854b..76d059dff 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestPlayerCallbackFlow.kt @@ -54,7 +54,7 @@ class TestPlayerCallbackFlow { Assert.assertEquals(1000L, awaitItem()) every { player.currentPosition } returns 10_000L Assert.assertEquals(10_000L, awaitItem()) - cancelAndIgnoreRemainingEvents() + ensureAllEventsConsumed() } } @@ -80,7 +80,7 @@ class TestPlayerCallbackFlow { Assert.assertEquals(0.0f, awaitItem()) every { player.bufferedPercentage } returns 75 Assert.assertEquals(0.75f, awaitItem()) - cancelAndIgnoreRemainingEvents() + ensureAllEventsConsumed() } } @@ -89,13 +89,13 @@ class TestPlayerCallbackFlow { every { player.isPlaying } returns false // disable periodic update every { player.currentPosition } returns 0L val fakePlayer = PlayerListenerCommander(player) - val discontinuityTests = listOf( - Pair(Player.DISCONTINUITY_REASON_SEEK, 1000L), - Pair(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT, 2000L), - Pair(Player.DISCONTINUITY_REASON_INTERNAL, 3000L), - Pair(Player.DISCONTINUITY_REASON_AUTO_TRANSITION, 4000L), - Pair(Player.DISCONTINUITY_REASON_REMOVE, 0L), - Pair(Player.DISCONTINUITY_REASON_SKIP, C.TIME_UNSET), + val discontinuityTests = mapOf( + Player.DISCONTINUITY_REASON_SEEK to 1000L, + Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT to 2000L, + Player.DISCONTINUITY_REASON_INTERNAL to 3000L, + Player.DISCONTINUITY_REASON_AUTO_TRANSITION to 4000L, + Player.DISCONTINUITY_REASON_REMOVE to 0L, + Player.DISCONTINUITY_REASON_SKIP to C.TIME_UNSET, ) fakePlayer.currentPositionAsFlow().test { Assert.assertEquals(0L, awaitItem()) @@ -132,6 +132,7 @@ class TestPlayerCallbackFlow { Assert.assertEquals("State ready", 20_000L, awaitItem()) Assert.assertEquals("TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED", 30_000L, awaitItem()) Assert.assertEquals("TIMELINE_CHANGE_REASON_SOURCE_UPDATE", 40_000L, awaitItem()) + ensureAllEventsConsumed() } } @@ -149,6 +150,7 @@ class TestPlayerCallbackFlow { Assert.assertEquals("isPlaying", true, awaitItem()) Assert.assertEquals("isPlaying", true, awaitItem()) Assert.assertEquals("isPlaying", false, awaitItem()) + ensureAllEventsConsumed() } } @@ -174,6 +176,7 @@ class TestPlayerCallbackFlow { for (state in playbackStates) { Assert.assertEquals(StringUtil.playerStateString(state), state, awaitItem()) } + ensureAllEventsConsumed() } } @@ -191,10 +194,11 @@ class TestPlayerCallbackFlow { Assert.assertEquals("Initial error", null, awaitItem()) Assert.assertEquals("error", error, awaitItem()) Assert.assertEquals("error removed", noError, awaitItem()) + ensureAllEventsConsumed() } } - @Test(timeout = 2_000) + @Test fun testAvailableCommands() = runTest { val command1 = mockk() val command2 = mockk() @@ -204,6 +208,7 @@ class TestPlayerCallbackFlow { fakePlayer.onAvailableCommandsChanged(command2) Assert.assertEquals(command1, awaitItem()) Assert.assertEquals(command2, awaitItem()) + ensureAllEventsConsumed() } } @@ -223,6 +228,7 @@ class TestPlayerCallbackFlow { Assert.assertEquals(false, awaitItem()) Assert.assertEquals(true, awaitItem()) Assert.assertEquals(false, awaitItem()) + ensureAllEventsConsumed() } } @@ -239,6 +245,7 @@ class TestPlayerCallbackFlow { Assert.assertEquals("Initial playback speed", initialPlaybackRate, awaitItem()) Assert.assertEquals(2.0f, awaitItem()) Assert.assertEquals(0.5f, awaitItem()) + ensureAllEventsConsumed() } } @@ -246,11 +253,11 @@ class TestPlayerCallbackFlow { fun testCurrentMediaIndex() = runTest { every { player.currentMediaItemIndex } returns 0 val fakePlayer = PlayerListenerCommander(player) - val transitionReasonCases = listOf( - Pair(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK, 10), - Pair(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO, 11), - Pair(Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT, 12), - Pair(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED, 13), + val transitionReasonCases = mapOf( + Player.MEDIA_ITEM_TRANSITION_REASON_SEEK to 10, + Player.MEDIA_ITEM_TRANSITION_REASON_AUTO to 11, + Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT to 12, + Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED to 13, ) fakePlayer.getCurrentMediaItemIndexAsFlow().test { every { player.currentMediaItemIndex } returns 78 @@ -276,11 +283,11 @@ class TestPlayerCallbackFlow { fun testCurrentMediaItem() = runTest { every { player.currentMediaItem } returns null val fakePlayer = PlayerListenerCommander(player) - val transitionReasonCases = listOf>( - Pair(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK, mockk()), - Pair(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO, mockk()), - Pair(Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT, mockk()), - Pair(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED, mockk()), + val transitionReasonCases = mapOf( + Player.MEDIA_ITEM_TRANSITION_REASON_SEEK to mockk(), + Player.MEDIA_ITEM_TRANSITION_REASON_AUTO to mockk(), + Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT to mockk(), + Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED to mockk(), ) val mediaItemTimeLinePlaylistChanged: MediaItem = mockk() val mediaItemTimeLineSourceUpdate: MediaItem = mockk() @@ -304,7 +311,7 @@ class TestPlayerCallbackFlow { } } - @Test(timeout = 5_000) + @Test fun testCurrentMediaItems() = runTest { val item1 = mockk() val item2 = mockk() @@ -329,6 +336,7 @@ class TestPlayerCallbackFlow { Assert.assertEquals("Initial list", list1, awaitItem()) Assert.assertEquals("TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED", list2, awaitItem()) Assert.assertEquals("TIMELINE_CHANGE_REASON_SOURCE_UPDATE list", listOf(item1), awaitItem()) + ensureAllEventsConsumed() } } @@ -343,6 +351,7 @@ class TestPlayerCallbackFlow { Assert.assertEquals("Initial video size", initialSize, awaitItem()) Assert.assertEquals("Updated video size", newSize, awaitItem()) + ensureAllEventsConsumed() } } }