Skip to content

Commit

Permalink
POC for testing a player
Browse files Browse the repository at this point in the history
  • Loading branch information
MGaetan89 committed Jan 5, 2024
1 parent 04e2875 commit b063c9f
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 0 deletions.
6 changes: 6 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ androidx-media = "1.7.0"
androidx-media3 = "1.2.0"
androidx-navigation = "2.7.6"
androidx-paging = "3.2.1"
androidx-test = "1.5.0"
androidx-test-espresso = "3.5.1"
androidx-test-ext-junit = "1.1.5"
androidx-test-monitor = "1.6.1"
androidx-test-runner = "1.5.2"
Expand Down Expand Up @@ -86,6 +88,7 @@ androidx-media3-ui-leanback = { group = "androidx.media3", name = "media3-ui-lea
androidx-media3-dash = { group = "androidx.media3", name = "media3-exoplayer-dash", version.ref = "androidx-media3" }
androidx-media3-hls = { group = "androidx.media3", name = "media3-exoplayer-hls", version.ref = "androidx-media3" }
androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "androidx-media3" }
androidx-media3-test-utils = { module = "androidx.media3:media3-test-utils", version.ref = "androidx-media3" }
androidx-media = { group = "androidx.media", name = "media", version.ref = "androidx-media" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
Expand All @@ -112,6 +115,9 @@ androidx-compose-material-icons-extended = { module = "androidx.compose.material
androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" }
androidx-compose-runtime-saveable = { module = "androidx.compose.runtime:runtime-saveable" }
leanback = { group = "androidx.leanback", name = "leanback", version.ref = "androidx-leanback" }
androidx-test = { module = "androidx.test:core", version.ref = "androidx-test" }
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" }
androidx-test-espresso-idling-resource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "androidx-test-espresso" }
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" }

Expand Down
10 changes: 10 additions & 0 deletions pillarbox-ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ dependencies {
implementation(libs.kotlinx.coroutines.core)

debugImplementation(libs.androidx.compose.ui.tooling)

androidTestImplementation(libs.androidx.media3.exoplayer)
androidTestImplementation(libs.androidx.media3.test.utils)
androidTestImplementation(libs.androidx.test)
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.test.espresso.idling.resource)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestRuntimeOnly(libs.androidx.test.runner)
androidTestImplementation(libs.junit)
androidTestImplementation(libs.kotlinx.coroutines.test)
}

publishing {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright (c) SRG SSR. All rights reserved.
* License information is available from the LICENSE file.
*/
package ch.srgssr.pillarbox.ui

import android.os.Looper
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.Player.Listener
import androidx.media3.test.utils.FakeClock
import androidx.media3.test.utils.TestExoPlayerBuilder
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExternalResource
import org.junit.runner.RunWith
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

// TODO Move this in "pillarbox-player-testutils"
private class PlayerIdlingResource(
@Player.State private val expectedPlaybackState: Int
) : IdlingResource {
private var callback: IdlingResource.ResourceCallback? = null

@Player.State
var playbackState: Int = Player.STATE_IDLE
set(value) {
field = value

if (playbackState == expectedPlaybackState) {
callback?.onTransitionToIdle()
}
}

override fun getName(): String {
return "PlayerIdlingResource"
}

override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
this.callback = callback
}

override fun isIdleNow(): Boolean {
return playbackState == expectedPlaybackState
}
}

// TODO Move this in "pillarbox-player-testutils"
class ExoPlayerRule(
private val mediaUri: String,
@Player.State private val waitForPlaybackState: Int
) : ExternalResource() {
private val playerIdlingResource = PlayerIdlingResource(waitForPlaybackState)

lateinit var clock: FakeClock
private set

lateinit var player: Player
private set

override fun before() {
Looper.prepare()
IdlingRegistry.getInstance().register(playerIdlingResource)

setupClock()
setupPlayer()

Espresso.onIdle()
}

override fun after() {
player.release()

IdlingRegistry.getInstance().unregister(playerIdlingResource)
Looper.myLooper()?.quit()
}

private fun setupClock() {
clock = FakeClock(true)
}

private fun setupPlayer() {
player = TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setClock(clock)
.build()
player.addListener(
object : Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
playerIdlingResource.playbackState = playbackState
}
}
)
player.setMediaItem(MediaItem.fromUri(mediaUri))
player.prepare()
player.play()
}
}

@RunWith(AndroidJUnit4::class)
class TestSimpleProgressTrackerState {
@get:Rule
val playerRule = ExoPlayerRule(
mediaUri = "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd",
waitForPlaybackState = Player.STATE_READY
)

@Test
fun progressWithoutManualChanges() = runTest {
val progressTrackerState = SimpleProgressTrackerState(playerRule.player, this)

val playerPositions = (0L..50L step 5L).toList()
playerPositions.forEach { playerPosition ->
playerRule.clock.advanceTime(playerPosition)
}

val actualProgress = mutableListOf<Duration>()
launch(coroutineContext) {
progressTrackerState.progress
.toList(actualProgress)
}.join()

assertEquals(playerPositions.map { it.milliseconds }, actualProgress)
}
}

0 comments on commit b063c9f

Please sign in to comment.