diff --git a/connectors/uplynk/build.gradle.kts b/connectors/uplynk/build.gradle.kts index 219a3dbc..ab2248d6 100644 --- a/connectors/uplynk/build.gradle.kts +++ b/connectors/uplynk/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { testImplementation(libs.mockito.inline) testImplementation(libs.mockito.kotlin) testImplementation(libs.kotlin.test.junit) + testImplementation(libs.robolectric) androidTestImplementation(libs.androidx.test.ext.junit) androidTestImplementation(libs.androidx.test.espresso.core) } diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/PingScheduler.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/PingScheduler.kt index adfccaac..b3c195b3 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/PingScheduler.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/PingScheduler.kt @@ -6,8 +6,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlin.time.Duration -import kotlin.time.DurationUnit -import kotlin.time.toDuration +import kotlin.time.Duration.Companion.seconds internal class PingScheduler( private val uplynkApi: UplynkApi, @@ -17,7 +16,7 @@ internal class PingScheduler( private val eventDispatcher: UplynkEventDispatcher, private val adScheduler: UplynkAdScheduler ) { - private val NEGATIVE_TIME = (-1).toDuration(DurationUnit.SECONDS) + private val NEGATIVE_TIME = (-1).seconds private var nextRequestTime: Duration = NEGATIVE_TIME private var seekStart: Duration = NEGATIVE_TIME @@ -27,12 +26,14 @@ internal class PingScheduler( fun onTimeUpdate(time: Duration) { if (nextRequestTime.isPositive() && time > nextRequestTime) { nextRequestTime = NEGATIVE_TIME - performPing(uplynkDescriptionConverter.buildPingUrl(prefix, sessionId, time)) + performPing(uplynkDescriptionConverter.buildPingUrl(prefix, sessionId, time).toString()) } } fun onStart(time: Duration) = - performPing(uplynkDescriptionConverter.buildStartPingUrl(prefix, sessionId, time)) + performPing( + uplynkDescriptionConverter.buildStartPingUrl(prefix, sessionId, time).toString() + ) fun onSeeking(time: Duration) { @@ -48,7 +49,7 @@ internal class PingScheduler( sessionId, time, seekStart - ) + ).toString() ) seekStart = NEGATIVE_TIME } diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdIntegration.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdIntegration.kt index 46891bdd..34ce188a 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdIntegration.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdIntegration.kt @@ -226,7 +226,7 @@ internal class UplynkAdIntegration( } val playUrl = uplynkDescriptionConverter.buildPlaybackUrl(minimalResponse.playURL, ssaiDescription) - var newUplynkSource = uplynkSource.copy(src = playUrl) + var newUplynkSource = uplynkSource.copy(src = playUrl.toString()) minimalResponse.drm?.let { drm -> if (drm.required) { @@ -259,7 +259,7 @@ internal class UplynkAdIntegration( .buildAssetInfoUrls(ssaiDescription, minimalResponse.sid, minimalResponse.prefix) .mapNotNull { try { - uplynkApi.assetInfo(it) + uplynkApi.assetInfo(it.toString()) } catch (e: Exception) { eventDispatcher.dispatchAssetInfoFailure(e) controller.error(e) @@ -275,7 +275,7 @@ internal class UplynkAdIntegration( private suspend fun requestLive(ssaiDescription: UplynkSsaiDescription): PreplayInternalLiveResponse { return uplynkDescriptionConverter .buildPreplayLiveUrl(ssaiDescription) - .let { uplynkApi.preplayLive(it) } + .let { uplynkApi.preplayLive(it.toString()) } .also { try { val response = it.parseExternalResponse() @@ -291,7 +291,7 @@ internal class UplynkAdIntegration( private suspend fun requestVod(ssaiDescription: UplynkSsaiDescription): PreplayInternalVodResponse { return uplynkDescriptionConverter .buildPreplayVodUrl(ssaiDescription) - .let { uplynkApi.preplayVod(it) } + .let { uplynkApi.preplayVod(it.toString()) } .also { try { val response = it.parseExternalResponse() diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionConverter.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionConverter.kt index 41592672..192207bb 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionConverter.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionConverter.kt @@ -1,39 +1,54 @@ package com.theoplayer.android.connector.uplynk.internal +import android.net.Uri +import androidx.core.net.toUri import com.theoplayer.android.connector.uplynk.UplynkSsaiDescription import kotlin.time.Duration internal class UplynkSsaiDescriptionConverter { private val DEFAULT_PREFIX = "https://content.uplynk.com" - fun buildPreplayVodUrl(ssaiDescription: UplynkSsaiDescription): String = with(ssaiDescription) { - val prefix = prefix ?: DEFAULT_PREFIX - - return "$prefix/preplay/$urlAssetId?v=2$drmParameters$pingParameters$urlParameters" - } - - fun buildPreplayLiveUrl(ssaiDescription: UplynkSsaiDescription): String = with(ssaiDescription) { - val prefix = prefix ?: DEFAULT_PREFIX + fun buildPreplayVodUrl(ssaiDescription: UplynkSsaiDescription): Uri = + with(ssaiDescription) { + return (prefix ?: DEFAULT_PREFIX).toUri().buildUpon().apply { + appendEncodedPath("preplay/$urlAssetId") + appendQueryParameter("v", "2") + drmParameters.forEach { (key, value) -> appendQueryParameter(key, value) } + pingParameters.forEach { (key, value) -> appendQueryParameter(key, value) } + preplayParameters.forEach { (key, value) -> appendQueryParameter(key, value) } + }.build() + } - return "$prefix/preplay/$urlAssetType/$urlAssetId?v=2$drmParameters$pingParameters$urlParameters" - } + fun buildPreplayLiveUrl(ssaiDescription: UplynkSsaiDescription): Uri = + with(ssaiDescription) { + return (prefix ?: DEFAULT_PREFIX).toUri().buildUpon().apply { + appendEncodedPath("preplay/$urlAssetType/$urlAssetId") + appendQueryParameter("v", "2") + drmParameters.forEach { (key, value) -> appendQueryParameter(key, value) } + pingParameters.forEach { (key, value) -> appendQueryParameter(key, value) } + preplayParameters.forEach { (key, value) -> appendQueryParameter(key, value) } + }.build() + } - fun buildPlaybackUrl(playUrl: String, ssaiDescription: UplynkSsaiDescription): String = with(ssaiDescription) { - return "$playUrl$playUrlParameters" - } + fun buildPlaybackUrl(playUrl: String, ssaiDescription: UplynkSsaiDescription): Uri = + with(ssaiDescription) { + return playUrl.toUri().buildUpon().apply { + playbackUrlParameters.forEach { (key, value) -> appendQueryParameter(key, value) } + }.build() + } fun buildAssetInfoUrls( ssaiDescription: UplynkSsaiDescription, sessionId: String, prefix: String - ): List = with(ssaiDescription) { + ): List = with(ssaiDescription) { val urlList = when { assetIds.isNotEmpty() -> assetIds.map { - "$prefix/player/assetinfo/$it.json" + "$prefix/player/assetinfo/$it.json".toUri() } externalIds.isNotEmpty() -> externalIds.map { - "$prefix/player/assetinfo/ext/$userId/$it.json" + "$prefix/player/assetinfo/ext/$userId/$it.json".toUri() } else -> emptyList() @@ -41,19 +56,39 @@ internal class UplynkSsaiDescriptionConverter { return if (sessionId.isBlank()) { urlList } else { - urlList.map { "$it?pbs=$sessionId" } + urlList.map { url -> + url.buildUpon().apply { + appendQueryParameter("pbs", sessionId) + }.build() + } } } fun buildSeekedPingUrl( - prefix: String, sessionId: String, currentTime: Duration, seekStartTime: Duration - ) = buildPingUrl(prefix, sessionId, currentTime) + "&ev=seek&ft=${seekStartTime.inWholeSeconds}" + prefix: String, + sessionId: String, + currentTime: Duration, + seekStartTime: Duration + ): Uri = buildPingUrl(prefix, sessionId, currentTime).buildUpon().apply { + appendQueryParameter("ev", "seek") + appendQueryParameter("ft", seekStartTime.inWholeSeconds.toString()) + }.build() fun buildStartPingUrl( - prefix: String, sessionId: String, currentTime: Duration - ) = buildPingUrl(prefix, sessionId, currentTime) + "&ev=start" + prefix: String, + sessionId: String, + currentTime: Duration + ): Uri = buildPingUrl(prefix, sessionId, currentTime).buildUpon().apply { + appendQueryParameter("ev", "start") + }.build() fun buildPingUrl( - prefix: String, sessionId: String, currentTime: Duration - ) = "$prefix/session/ping/$sessionId.json?v=3&pt=${currentTime.inWholeSeconds}" + prefix: String, + sessionId: String, + currentTime: Duration + ): Uri = prefix.toUri().buildUpon().apply { + appendEncodedPath("session/ping/$sessionId.json") + appendQueryParameter("v", "3") + appendQueryParameter("pt", currentTime.inWholeSeconds.toString()) + }.build() } diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionUrlExtensions.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionUrlExtensions.kt index 9d2b1a99..ce2fc847 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionUrlExtensions.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionUrlExtensions.kt @@ -3,34 +3,28 @@ package com.theoplayer.android.connector.uplynk.internal import com.theoplayer.android.connector.uplynk.UplynkAssetType import com.theoplayer.android.connector.uplynk.UplynkSsaiDescription -internal val UplynkSsaiDescription.drmParameters: String - get() = if (contentProtected) { - "&manifest=mpd&rmt=wv" - } else { - "" - } +internal typealias QueryParameter = Pair -internal val UplynkSsaiDescription.urlParameters - get() = if (preplayParameters.isNotEmpty()) { - preplayParameters.map { "${it.key}=${it.value}" }.joinToString("&", prefix = "&") - } else { - "" - } - -internal val UplynkSsaiDescription.playUrlParameters - get() = if (playbackUrlParameters.isNotEmpty()) { - playbackUrlParameters.map { "${it.key}=${it.value}" }.joinToString("&", prefix = "?") +internal val UplynkSsaiDescription.drmParameters: List + get() = if (contentProtected) { + listOf( + "manifest" to "mpd", + "rmt" to "wv", + ) } else { - "" + listOf() } -internal val UplynkSsaiDescription.pingParameters: String +internal val UplynkSsaiDescription.pingParameters: List get() { val feature = UplynkPingFeatures.from(this) return if (feature == UplynkPingFeatures.NO_PING) { - "" + listOf() } else { - "&ad.cping=1&ad.pingf=${feature.pingfValue}" + listOf( + "ad.cping" to "1", + "ad.pingf" to feature.pingfValue.toString() + ) } } diff --git a/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/AdHandlerTest.kt b/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/AdHandlerTest.kt index 6cf37ddf..472d2a61 100644 --- a/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/AdHandlerTest.kt +++ b/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/AdHandlerTest.kt @@ -19,8 +19,7 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import kotlin.time.Duration -import kotlin.time.DurationUnit -import kotlin.time.toDuration +import kotlin.time.Duration.Companion.seconds class AdHandlerTest { @@ -49,8 +48,8 @@ class AdHandlerTest { listOf(), "", "", - 100.toDuration(DurationUnit.SECONDS), - 200.toDuration(DurationUnit.SECONDS) + 100.seconds, + 200.seconds ) adHandler.createAdBreak(adBreak) @@ -72,8 +71,8 @@ class AdHandlerTest { listOf(), "", "", - 100.toDuration(DurationUnit.SECONDS), - 200.toDuration(DurationUnit.SECONDS) + 100.seconds, + 200.seconds ) adHandler.createAdBreak(adBreak) @@ -93,7 +92,7 @@ class AdHandlerTest { mapOf(), 1f, 2f, - 100.toDuration(DurationUnit.SECONDS) + 100.seconds ), UplynkAd( null, @@ -103,7 +102,7 @@ class AdHandlerTest { mapOf(), 1f, 2f, - 200.toDuration(DurationUnit.SECONDS) + 200.seconds ), UplynkAd( null, @@ -113,9 +112,9 @@ class AdHandlerTest { mapOf(), 1f, 2f, - 300.toDuration(DurationUnit.SECONDS) + 300.seconds ), - ), "", "", 400.toDuration(DurationUnit.SECONDS), 500.toDuration(DurationUnit.SECONDS) + ), "", "", 400.seconds, 500.seconds ) adHandler.createAdBreak(adBreak) @@ -156,7 +155,7 @@ class AdHandlerTest { @Test fun onAdBegin_forUnknownAd_throwsAnException() { val ad = - UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.toDuration(DurationUnit.SECONDS)) + UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.seconds) assertThrows(java.lang.IllegalStateException::class.java) { adHandler.onAdBegin(ad) @@ -166,13 +165,13 @@ class AdHandlerTest { @Test fun onAdBegin_forCreatedAdBreak_callsBeginAd() { val uplynkAd = - UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.toDuration(DurationUnit.SECONDS)) + UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.seconds) val adBreak = UplynkAdBreak( listOf(uplynkAd), "", "", - 400.toDuration(DurationUnit.SECONDS), - 500.toDuration(DurationUnit.SECONDS) + 400.seconds, + 500.seconds ) adHandler.createAdBreak(adBreak) @@ -184,7 +183,7 @@ class AdHandlerTest { @Test fun onAdEnd_forUnknownAd_throwsAnException() { val ad = - UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.toDuration(DurationUnit.SECONDS)) + UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.seconds) assertThrows(java.lang.IllegalStateException::class.java) { adHandler.onAdEnd(ad) @@ -194,13 +193,13 @@ class AdHandlerTest { @Test fun onAdEnd_forCreatedAdBreak_callsEndAd() { val uplynkAd = - UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.toDuration(DurationUnit.SECONDS)) + UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.seconds) val adBreak = UplynkAdBreak( listOf(uplynkAd), "", "", - 400.toDuration(DurationUnit.SECONDS), - 500.toDuration(DurationUnit.SECONDS) + 400.seconds, + 500.seconds ) adHandler.createAdBreak(adBreak) @@ -212,20 +211,20 @@ class AdHandlerTest { @Test fun onAdProgressUpdate_forCreatedAdBreak_callsUpdateAdProgress() { val uplynkAd = - UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.toDuration(DurationUnit.SECONDS)) + UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.seconds) val adBreak = UplynkAdBreak( listOf(uplynkAd), "", "", - 400.toDuration(DurationUnit.SECONDS), - 500.toDuration(DurationUnit.SECONDS) + 400.seconds, + 500.seconds ) adHandler.createAdBreak(adBreak) adHandler.onAdProgressUpdate( UplynkAdState(uplynkAd, AdState.STARTED), adBreak, - 450.toDuration(DurationUnit.SECONDS) + 450.seconds ) verify(controller).updateAdProgress(eq(mockAd), eq(0.5)) @@ -234,13 +233,13 @@ class AdHandlerTest { @Test fun onAdProgressUpdate_forUnknownAd_throwsAnException() { val ad = - UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.toDuration(DurationUnit.SECONDS)) + UplynkAd(null, listOf(), "", "", mapOf(), 1f, 2f, 100.seconds) val adBreak = UplynkAdBreak( listOf(ad), "", "", - 400.toDuration(DurationUnit.SECONDS), - 500.toDuration(DurationUnit.SECONDS) + 400.seconds, + 500.seconds ) diff --git a/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionConverterTest.kt b/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionConverterTest.kt index 01dddc44..de8a8661 100644 --- a/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionConverterTest.kt +++ b/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionConverterTest.kt @@ -1,15 +1,17 @@ package com.theoplayer.android.connector.uplynk.internal +import androidx.core.net.toUri import com.theoplayer.android.connector.uplynk.UplynkSsaiDescription import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.MockitoAnnotations -import kotlin.test.assertContains -import kotlin.time.DurationUnit -import kotlin.time.toDuration +import org.robolectric.RobolectricTestRunner +import kotlin.time.Duration.Companion.seconds +@RunWith(RobolectricTestRunner::class) class UplynkSsaiDescriptionConverterTest { private lateinit var ssaiDescription: UplynkSsaiDescription @@ -21,14 +23,14 @@ class UplynkSsaiDescriptionConverterTest { ssaiDescription = UplynkSsaiDescription( prefix = "preplayprefix", assetIds = listOf("asset1", "asset2", "asset3"), - preplayParameters = LinkedHashMap(mapOf("p1" to "v1", "p2" to "v2", "p3" to "v3")) + preplayParameters = linkedMapOf("p1" to "v1", "p2" to "v2", "p3" to "v3") ) converter = UplynkSsaiDescriptionConverter() } @Test fun buildPreplayVodUrl_whenPrefixIsNotNull_startsUrlFromPrefix() { - val result = converter.buildPreplayVodUrl(ssaiDescription) + val result = converter.buildPreplayVodUrl(ssaiDescription).toString() assertTrue(result.startsWith("preplayprefix")) } @@ -37,14 +39,14 @@ class UplynkSsaiDescriptionConverterTest { fun buildPreplayVodUrl_whenPrefixIsNull_startsUrlFromPrefix() { ssaiDescription = ssaiDescription.copy(prefix = null) - val result = converter.buildPreplayVodUrl(ssaiDescription) + val result = converter.buildPreplayVodUrl(ssaiDescription).toString() assertTrue(result.startsWith("https://content.uplynk.com")) } @Test fun buildPreplayVodUrl_whenAssetIdHasMultipleValues_addsThemAsCommaSeparatedList() { - val result = converter.buildPreplayVodUrl(ssaiDescription) + val result = converter.buildPreplayVodUrl(ssaiDescription).toString() assertTrue(result.contains("/asset1,asset2,asset3/")) } @@ -53,7 +55,7 @@ class UplynkSsaiDescriptionConverterTest { fun buildPreplayVodUrl_whenAssetIdHasSingleValue_usesItAsJsonFilename() { ssaiDescription = ssaiDescription.copy(assetIds = listOf("singleasset")) - val result = converter.buildPreplayVodUrl(ssaiDescription) + val result = converter.buildPreplayVodUrl(ssaiDescription).toString() assertTrue(result.contains("/singleasset.json")) } @@ -64,7 +66,7 @@ class UplynkSsaiDescriptionConverterTest { assetIds = listOf(), externalIds = listOf("extId1", "extId2"), userId = "userId" ) - val result = converter.buildPreplayVodUrl(ssaiDescription) + val result = converter.buildPreplayVodUrl(ssaiDescription).toString() assertTrue(result.contains("userId")) assertTrue(result.contains("extId1,extId2/multiple.json")) @@ -76,7 +78,7 @@ class UplynkSsaiDescriptionConverterTest { assetIds = listOf(), externalIds = listOf("extId1"), userId = "userId" ) - val result = converter.buildPreplayVodUrl(ssaiDescription) + val result = converter.buildPreplayVodUrl(ssaiDescription).toString() assertTrue(result.contains("userId")) assertTrue(result.contains("extId1.json")) @@ -86,32 +88,37 @@ class UplynkSsaiDescriptionConverterTest { fun buildPreplayVodUrl_always_followsTheTemplate() { val result = converter.buildPreplayVodUrl(ssaiDescription) - val items = result.split("/", "?") - assertEquals("preplayprefix", items[0]) - assertEquals("preplay", items[1]) - assertEquals("asset1,asset2,asset3", items[2]) - assertEquals("multiple.json", items[3]) - assertEquals("v=2&p1=v1&p2=v2&p3=v3", items[4]) + assertEquals( + listOf( + "preplayprefix", + "preplay", + "asset1,asset2,asset3", + "multiple.json" + ), + result.pathSegments + ) + assertEquals("v=2&p1=v1&p2=v2&p3=v3", result.query) } @Test fun buildAssetInfoUrls_withoutSid_doesNotContainPbsParameter() { val result = converter.buildAssetInfoUrls(ssaiDescription, "", "prefix") - assertTrue(result.none { it.contains("pbs=") }) + assertTrue(result.none { it.queryParameterNames.contains("pbs") }) } @Test fun buildAssetInfoUrls_withSid_hasPbsParameter() { val result = converter.buildAssetInfoUrls(ssaiDescription, "sessionId", "prefix") - assertTrue(result.all { it.contains("pbs=sessionId") }) + assertTrue(result.all { it.getQueryParameters("pbs") == listOf("sessionId") }) } @Test fun buildAssetInfoUrls_whenAssetIdIsEmptyAndExternalIdIsEmpty_returnsEmptyUrl() { ssaiDescription = UplynkSsaiDescription( - assetIds = listOf(), externalIds = listOf() + assetIds = listOf(), + externalIds = listOf() ) val result = converter.buildAssetInfoUrls(ssaiDescription, "", "prefix") @@ -124,10 +131,14 @@ class UplynkSsaiDescriptionConverterTest { val result = converter.buildAssetInfoUrls(ssaiDescription, "", "prefix") assertEquals(3, result.size) - - assertContains(result, "prefix/player/assetinfo/asset1.json") - assertContains(result, "prefix/player/assetinfo/asset2.json") - assertContains(result, "prefix/player/assetinfo/asset3.json") + assertEquals( + listOf( + "prefix/player/assetinfo/asset1.json".toUri(), + "prefix/player/assetinfo/asset2.json".toUri(), + "prefix/player/assetinfo/asset3.json".toUri() + ), + result + ) } @Test @@ -141,46 +152,50 @@ class UplynkSsaiDescriptionConverterTest { val result = converter.buildAssetInfoUrls(ssaiDescription, "", "prefix") assertEquals(2, result.size) - - assertContains(result, "prefix/player/assetinfo/ext/userId/extId1.json") - assertContains(result, "prefix/player/assetinfo/ext/userId/extId2.json") + assertEquals( + listOf( + "prefix/player/assetinfo/ext/userId/extId1.json".toUri(), + "prefix/player/assetinfo/ext/userId/extId2.json".toUri() + ), + result + ) } @Test fun buildStartPingUrl_always_hasStartParameter() { - val result = converter.buildStartPingUrl("prefix", "sessionId", 200.toDuration(DurationUnit.SECONDS)) + val result = converter.buildStartPingUrl("prefix", "sessionId", 200.seconds) - assertContains(result, "ev=start") + assertEquals(listOf("start"), result.getQueryParameters("ev")) } @Test fun buildStartPingUrl_always_startsTheSameAsNormalPingRequest() { - val result = converter.buildStartPingUrl("prefix", "sessionId", 200.toDuration(DurationUnit.SECONDS)) - val pingUrl = converter.buildPingUrl("prefix", "sessionId", 200.toDuration(DurationUnit.SECONDS)) + val result = converter.buildStartPingUrl("prefix", "sessionId", 200.seconds).toString() + val pingUrl = converter.buildPingUrl("prefix", "sessionId", 200.seconds).toString() - assertContains(result, pingUrl) + assertTrue(result.startsWith(pingUrl)) } @Test fun buildSeekedPingUrl_always_hasSeekParameters() { - val result = converter.buildSeekedPingUrl("prefix", "sessionId", 200.toDuration(DurationUnit.SECONDS), 180.toDuration(DurationUnit.SECONDS)) + val result = converter.buildSeekedPingUrl("prefix", "sessionId", 200.seconds, 180.seconds) - assertContains(result, "ev=seek") - assertContains(result, "ft=180") + assertEquals(listOf("seek"), result.getQueryParameters("ev")) + assertEquals(listOf("180"), result.getQueryParameters("ft")) } @Test fun buildSeekedPingUrl_always_startsTheSameAsNormalPingRequest() { - val result = converter.buildSeekedPingUrl("prefix", "sessionId", 200.toDuration(DurationUnit.SECONDS), 180.toDuration(DurationUnit.SECONDS)) - val pingUrl = converter.buildPingUrl("prefix", "sessionId", 200.toDuration(DurationUnit.SECONDS)) + val result = converter.buildSeekedPingUrl("prefix", "sessionId", 200.seconds, 180.seconds).toString() + val pingUrl = converter.buildPingUrl("prefix", "sessionId", 200.seconds).toString() - assertContains(result, pingUrl) + assertTrue(result.startsWith(pingUrl)) } @Test fun buildPingUrl_always_followsThePingTemplate() { val currentTime = 200 - val result = converter.buildPingUrl("prefix", "sessionId", currentTime.toDuration(DurationUnit.SECONDS)) + val result = converter.buildPingUrl("prefix", "sessionId", currentTime.seconds).toString() assertEquals(result, "prefix/session/ping/sessionId.json?v=3&pt=200") } diff --git a/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionSerializerTests.kt b/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionSerializerTests.kt index b6c5f6d2..e30544a7 100644 --- a/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionSerializerTests.kt +++ b/connectors/uplynk/src/test/java/com/theoplayer/android/connector/uplynk/internal/UplynkSsaiDescriptionSerializerTests.kt @@ -24,7 +24,7 @@ class UplynkSsaiDescriptionSerializerTests { val ssaiDescription = UplynkSsaiDescription( prefix = "preplayprefix", assetIds = listOf("asset1", "asset2", "asset3"), - preplayParameters = LinkedHashMap(mapOf("p1" to "v1", "p2" to "v2", "p3" to "v3")) + preplayParameters = linkedMapOf("p1" to "v1", "p2" to "v2", "p3" to "v3") ) val jsonString = UplynkSsaiDescriptionDeserializer.toJson(ssaiDescription) val jsonObject = Json.parseToJsonElement(jsonString).jsonObject @@ -43,7 +43,7 @@ class UplynkSsaiDescriptionSerializerTests { val ssaiDescription = UplynkSsaiDescription( prefix = "preplayprefix", assetIds = listOf("asset1", "asset2", "asset3"), - preplayParameters = LinkedHashMap(mapOf("p1" to "v1", "p2" to "v2", "p3" to "v3")) + preplayParameters = linkedMapOf("p1" to "v1", "p2" to "v2", "p3" to "v3") ) val jsonString = UplynkSsaiDescriptionDeserializer.toJson(ssaiDescription) val deserializedSsaiDescription = UplynkSsaiDescriptionDeserializer.fromJson(jsonString) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b653b85f..54b78353 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,6 +23,7 @@ mockitoKotlin = "4.1.0" androidxEspresso = "3.6.1" androidxTestCore = "1.7.0" androidxTestExt = "1.3.0" +robolectric = "4.16.1" conviva = "4.0.43" nielsen = "9.2.0.0" comscore = "6.10.0" @@ -50,6 +51,7 @@ kotlin-test-junit = { group = "org.jetbrains.kotlin", name = "kotlin-test-junit" junit = { group = "junit", name = "junit", version.ref = "junit" } mockito-inline = { group = "org.mockito", name = "mockito-inline", version.ref = "mockito" } mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockitoKotlin" } +robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } theoplayer = { group = "com.theoplayer.theoplayer-sdk-android", name = "core", version.ref = "theoplayer" } theoplayer-integration-dai = { group = "com.theoplayer.theoplayer-sdk-android", name = "integration-ads-dai", version.ref = "theoplayer" } theoplayer-integration-ima = { group = "com.theoplayer.theoplayer-sdk-android", name = "integration-ads-ima", version.ref = "theoplayer" }