From 7b0b04df2b876acf7d0ebc1a4fc779d44e77f927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Thu, 12 Dec 2024 11:28:50 +0100 Subject: [PATCH 01/15] Force using known IntegrationLayer urls. --- .../pillarbox/core/business/SRGMediaItem.kt | 62 ++++--------- .../integrationlayer/ImageScalingService.kt | 6 +- .../integrationlayer/service/IlHost.kt | 93 +++++++++++++++++-- .../core/business/source/SRGAssetLoader.kt | 7 +- .../core/business/SRGMediaItemBuilderTest.kt | 5 +- .../business/integrationlayer/IlUrnTest.kt | 23 +++++ .../ImageScalingServiceTest.kt | 18 ++-- .../pillarbox/demo/shared/data/DemoItem.kt | 2 +- .../pillarbox/demo/shared/di/PlayerModule.kt | 5 +- .../srgssr/pillarbox/demo/MainNavigation.kt | 9 +- .../pillarbox/demo/ui/lists/ListsHome.kt | 4 +- 11 files changed, 150 insertions(+), 84 deletions(-) create mode 100644 pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrnTest.kt diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt index 78d0105b6..53d5a62a3 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt @@ -4,17 +4,16 @@ */ package ch.srgssr.pillarbox.core.business -import android.net.Uri import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata -import ch.srgssr.pillarbox.core.business.integrationlayer.data.isValidMediaUrn +import ch.srgssr.pillarbox.core.business.integrationlayer.service.ILUrl +import ch.srgssr.pillarbox.core.business.integrationlayer.service.ILUrl.Companion.toIlUrl import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlLocation import ch.srgssr.pillarbox.core.business.integrationlayer.service.Vector import ch.srgssr.pillarbox.core.business.source.MimeTypeSrg import ch.srgssr.pillarbox.player.PillarboxDsl import ch.srgssr.pillarbox.player.source.PillarboxMediaSource -import java.net.URL /** * Creates a [MediaItem] suited for SRG SSR content identified by a URN. @@ -29,7 +28,7 @@ import java.net.URL * * ```kotlin * val mediaItem: MediaItem = SRGMediaItem("urn:rts:audio:3262363") { - * host(IlHost.Default) + * host(IlUrl.Default) * vector(Vector.TV) * } * ``` @@ -60,7 +59,7 @@ fun SRGMediaItem(urn: String, block: SRGMediaItemBuilder.() -> Unit = {}): Media * **Usage example** * ```kotlin * val mediaItem: MediaItem = sourceItem.buildUpon { - * host(IlHost.Stage) + * host(IlUrl.Stage) * } * ``` * @@ -78,24 +77,20 @@ fun MediaItem.buildUpon(block: SRGMediaItemBuilder.() -> Unit): MediaItem { class SRGMediaItemBuilder internal constructor(mediaItem: MediaItem) { private val mediaItemBuilder = mediaItem.buildUpon() private var urn: String = mediaItem.mediaId - private var host: URL = IlHost.DEFAULT + private var host: IlHost = IlHost.PROD private var forceSAM: Boolean = false private var ilLocation: IlLocation? = null private var vector: Vector = Vector.MOBILE init { - urn = mediaItem.mediaId mediaItem.localConfiguration?.let { localConfiguration -> - val uri = localConfiguration.uri - val urn = uri.lastPathSegment - if (uri.toString().contains(PATH) && urn.isValidMediaUrn()) { - uri.host?.let { hostname -> host = URL(Uri.Builder().scheme(host.protocol).authority(hostname).build().toString()) } - this.urn = urn!! - this.forceSAM = uri.getQueryParameter(PARAM_FORCE_SAM)?.toBooleanStrictOrNull() == true - this.ilLocation = uri.getQueryParameter(PARAM_FORCE_LOCATION)?.let { IlLocation.fromName(it) } - uri.getQueryParameter(PARAM_VECTOR) - ?.let { Vector.fromLabel(it) } - ?.let { vector = it } + runCatching { + val ilUrl = localConfiguration.uri.toIlUrl() + host = ilUrl.host + urn = ilUrl.urn + forceSAM = ilUrl.forceSAM + ilLocation = ilUrl.ilLocation + vector = ilUrl.vector } } } @@ -132,7 +127,7 @@ class SRGMediaItemBuilder internal constructor(mediaItem: MediaItem) { * * @param host The URL of the integration layer server. */ - fun host(host: URL) { + fun host(host: IlHost) { this.host = host } @@ -172,35 +167,10 @@ class SRGMediaItemBuilder internal constructor(mediaItem: MediaItem) { * @return A new [MediaItem] ready for playback. */ fun build(): MediaItem { - require(urn.isValidMediaUrn()) { "Not a valid Urn!" } - mediaItemBuilder.setMediaId(urn) + val ilUrl = ILUrl(host = host, urn = urn, vector = vector, forceSAM = forceSAM, ilLocation = ilLocation) + mediaItemBuilder.setUri(ilUrl.uri) + mediaItemBuilder.setMediaId(ilUrl.urn) mediaItemBuilder.setMimeType(MimeTypeSrg) - val uri = Uri.Builder().apply { - scheme(host.protocol) - authority(host.host) - if (forceSAM) { - appendEncodedPath("sam") - } - appendEncodedPath(PATH) - appendEncodedPath(urn) - if (forceSAM) { - appendQueryParameter(PARAM_FORCE_SAM, true.toString()) - } - ilLocation?.let { - appendQueryParameter(PARAM_FORCE_LOCATION, it.toString()) - } - appendQueryParameter(PARAM_VECTOR, vector.toString()) - appendQueryParameter(PARAM_ONLY_CHAPTERS, true.toString()) - }.build() - mediaItemBuilder.setUri(uri) return mediaItemBuilder.build() } - - private companion object { - private const val PATH = "integrationlayer/2.1/mediaComposition/byUrn/" - private const val PARAM_ONLY_CHAPTERS = "onlyChapters" - private const val PARAM_FORCE_SAM = "forceSAM" - private const val PARAM_FORCE_LOCATION = "forceLocation" - private const val PARAM_VECTOR = "vector" - } } diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingService.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingService.kt index 9bf0bb867..ddd97fe60 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingService.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingService.kt @@ -11,10 +11,10 @@ import java.net.URLEncoder /** * Service used to get a scaled image URL. This only works for SRG images. * - * @param baseUrl Base URL of the service. + * @param ilHost Base URL of the service. */ internal class ImageScalingService( - private val baseUrl: URL = IlHost.DEFAULT + private val ilHost: IlHost = IlHost.PROD ) { fun getScaledImageUrl( @@ -22,6 +22,6 @@ internal class ImageScalingService( ): String { val encodedImageUrl = URLEncoder.encode(imageUrl, Charsets.UTF_8.name()) - return "${baseUrl}images/?imageUrl=$encodedImageUrl&format=webp&width=480" + return "${ilHost.baseHostUrl}/images/?imageUrl=$encodedImageUrl&format=webp&width=480" } } diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt index d22bfcf7d..8ffddd501 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt @@ -4,29 +4,104 @@ */ package ch.srgssr.pillarbox.core.business.integrationlayer.service -import java.net.URL +import android.net.Uri +import ch.srgssr.pillarbox.core.business.integrationlayer.data.isValidMediaUrn + +/** + * @property host the [IlHost] to use. + * @property urn the URN of the media to request. + * @property vector The [Vector] to use. + * @property forceSAM Force SAM usage. + * @property ilLocation the [IlLocation] of the request. + */ +data class ILUrl( + val host: IlHost, + val urn: String, + val vector: Vector, + val forceSAM: Boolean = false, + val ilLocation: IlLocation? = null, +) { + + /** + * Uri + */ + val uri: Uri = Uri.parse(host.baseHostUrl).buildUpon().apply { + appendEncodedPath(PATH) + if (forceSAM) { + appendEncodedPath("sam") + appendQueryParameter(PARAM_FORCE_SAM, true.toString()) + } + appendEncodedPath(urn) + ilLocation?.let { + appendQueryParameter(PARAM_FORCE_LOCATION, it.toString()) + } + appendQueryParameter(PARAM_VECTOR, vector.toString()) + appendQueryParameter(PARAM_ONLY_CHAPTERS, true.toString()) + if (forceSAM) appendEncodedPath("sam") + }.build() + + @Suppress("UndocumentedPublicClass") + companion object { + private const val PARAM_ONLY_CHAPTERS = "onlyChapters" + private const val PARAM_FORCE_SAM = "forceSAM" + private const val PARAM_FORCE_LOCATION = "forceLocation" + private const val PARAM_VECTOR = "vector" + + /** + * To il url + * + * @return + */ + fun Uri.toIlUrl(): ILUrl { + val urn = lastPathSegment + val host = IlHost.parse(toString()) + check(urn.isValidMediaUrn()) { "Invalid urn $urn" } + checkNotNull(host) { "Invalid url $this" } + val forceSAM = getQueryParameter(PARAM_FORCE_SAM)?.toBooleanStrictOrNull() == true + val ilLocation = getQueryParameter(PARAM_FORCE_LOCATION)?.let { IlLocation.fromName(it) } + val vector = getQueryParameter(PARAM_VECTOR)?.let { Vector.fromLabel(it) } ?: Vector.MOBILE + + return ILUrl(host = host, urn = checkNotNull(urn), vector, ilLocation = ilLocation, forceSAM = forceSAM) + } + } +} /** * Object containing the different host URLs for the integration layer service. + * @property baseHostUrl The base hostname url. */ -object IlHost { +enum class IlHost(val baseHostUrl: String) { + /** * The base URL for the production environment. */ - val PROD = URL("https://il.srgssr.ch/") + PROD(baseHostUrl = "https://il.srgssr.ch)"), /** * The base URL for the test environment. */ - val TEST = URL("https://il-test.srgssr.ch/") + TEST(baseHostUrl = "https://il-test.srgssr.ch"), /** * The base URL for the stage environment. */ - val STAGE = URL("https://il-stage.srgssr.ch/") + STAGE(baseHostUrl = "https://il-stage.srgssr.ch"), - /** - * The default host used by the library. - */ - val DEFAULT = PROD + ; + + @Suppress("UndocumentedPublicClass") + companion object { + + /** + * Parses the given [url] and returns the corresponding [IlHost]. + * + * @param url The url to parse + * @return null if the [url] does not match any [IlHost]. + */ + fun parse(url: String): IlHost? { + return entries.find { url.contains(it.baseHostUrl) } + } + } } + +private const val PATH = "integrationlayer/2.1/mediaComposition/byUrn/" diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt index 4e4cc47f9..051c6df1f 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt @@ -23,7 +23,7 @@ import ch.srgssr.pillarbox.core.business.integrationlayer.data.Chapter import ch.srgssr.pillarbox.core.business.integrationlayer.data.Drm import ch.srgssr.pillarbox.core.business.integrationlayer.data.MediaComposition import ch.srgssr.pillarbox.core.business.integrationlayer.data.Resource -import ch.srgssr.pillarbox.core.business.integrationlayer.data.isValidMediaUrn +import ch.srgssr.pillarbox.core.business.integrationlayer.service.ILUrl.Companion.toIlUrl import ch.srgssr.pillarbox.core.business.integrationlayer.service.MediaCompositionService import ch.srgssr.pillarbox.core.business.tracker.SRGEventLoggerTracker import ch.srgssr.pillarbox.core.business.tracker.commandersact.CommandersActTracker @@ -112,8 +112,9 @@ class SRGAssetLoader internal constructor( override fun canLoadAsset(mediaItem: MediaItem): Boolean { val localConfiguration = mediaItem.localConfiguration ?: return false - - return localConfiguration.mimeType == MimeTypeSrg || localConfiguration.uri.lastPathSegment.isValidMediaUrn() + return localConfiguration.mimeType == MimeTypeSrg && kotlin.runCatching { + localConfiguration.uri.toIlUrl() + }.isSuccess } override suspend fun loadAsset(mediaItem: MediaItem): Asset { diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGMediaItemBuilderTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGMediaItemBuilderTest.kt index cb3cac6db..5338040f3 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGMediaItemBuilderTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGMediaItemBuilderTest.kt @@ -14,7 +14,6 @@ import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlLocation import ch.srgssr.pillarbox.core.business.integrationlayer.service.Vector import ch.srgssr.pillarbox.core.business.source.MimeTypeSrg import org.junit.runner.RunWith -import java.net.URL import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -199,7 +198,7 @@ class SRGMediaItemBuilderTest { companion object { fun String.toIlUri( - host: URL = IlHost.DEFAULT, + host: IlHost = IlHost.PROD, vector: Vector = Vector.MOBILE, forceSAM: Boolean = false, ilLocation: IlLocation? = null, @@ -214,7 +213,7 @@ class SRGMediaItemBuilderTest { "$name=$value" } - return "${host}${samPath}integrationlayer/2.1/mediaComposition/byUrn/$this?$queryParameters".toUri() + return "${host.baseHostUrl}${samPath}integrationlayer/2.1/mediaComposition/byUrn/$this?$queryParameters".toUri() } } } diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrnTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrnTest.kt new file mode 100644 index 000000000..35b2fceb2 --- /dev/null +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrnTest.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.core.business.integrationlayer + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import ch.srgssr.pillarbox.core.business.integrationlayer.service.ILUrl +import ch.srgssr.pillarbox.core.business.integrationlayer.service.ILUrl.Companion.toIlUrl +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost +import ch.srgssr.pillarbox.core.business.integrationlayer.service.Vector +import org.junit.runner.RunWith +import kotlin.test.Test +import kotlin.test.assertEquals + +@RunWith(AndroidJUnit4::class) +class IlUrnTest { + @Test + fun testInOut() { + val ilUrn = ILUrl(host = IlHost.PROD, urn = "urn:rts:video:12345", vector = Vector.MOBILE) + assertEquals(ilUrn, ilUrn.uri.toIlUrl()) + } +} diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingServiceTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingServiceTest.kt index ca2210834..a582a1303 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingServiceTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingServiceTest.kt @@ -12,37 +12,37 @@ import kotlin.test.assertEquals class ImageScalingServiceTest { @Test fun getScaledImageUrlProd() { - val baseUrl = IlHost.PROD + val host = IlHost.PROD val imageUrl = "https://www.rts.ch/2020/05/18/14/20/11333286.image/16x9" - val imageScalingService = ImageScalingService(baseUrl) + val imageScalingService = ImageScalingService(host) val scaledImageUrl = imageScalingService.getScaledImageUrl(imageUrl) val encodedImageUrl = URLEncoder.encode(imageUrl, Charsets.UTF_8) - assertEquals("${baseUrl}images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) + assertEquals("${host}images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) } @Test fun getScaledImageUrlTest() { - val baseUrl = IlHost.TEST + val host = IlHost.TEST val imageUrl = "https://www.rts.ch/2021/08/05/18/12/12396566.image/2x3" - val imageScalingService = ImageScalingService(baseUrl) + val imageScalingService = ImageScalingService(host) val scaledImageUrl = imageScalingService.getScaledImageUrl(imageUrl) val encodedImageUrl = URLEncoder.encode(imageUrl, Charsets.UTF_8) - assertEquals("${baseUrl}images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) + assertEquals("${host}images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) } @Test fun getScaledImageUrlStage() { - val baseUrl = IlHost.STAGE + val host = IlHost.STAGE val imageUrl = "https://www.rts.ch/2022/10/06/17/32/13444418.image/4x5" - val imageScalingService = ImageScalingService(baseUrl) + val imageScalingService = ImageScalingService(host) val scaledImageUrl = imageScalingService.getScaledImageUrl(imageUrl) val encodedImageUrl = URLEncoder.encode(imageUrl, Charsets.UTF_8) - assertEquals("${baseUrl}images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) + assertEquals("${host}images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) } } diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/DemoItem.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/DemoItem.kt index a7c1593f2..4bd79a6ae 100644 --- a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/DemoItem.kt +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/DemoItem.kt @@ -89,7 +89,7 @@ sealed class DemoItem( override val description: String? = null, override val imageUri: String? = null, override val languageTag: String? = null, - val host: java.net.URL = IlHost.PROD, + val host: IlHost = IlHost.PROD, val forceSAM: Boolean = false, val ilLocation: IlLocation? = null, ) : DemoItem(urn, title, description, imageUri, languageTag) { diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/di/PlayerModule.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/di/PlayerModule.kt index 18bd2e10c..91e97de8b 100644 --- a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/di/PlayerModule.kt +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/di/PlayerModule.kt @@ -18,7 +18,6 @@ import ch.srgssr.pillarbox.player.PillarboxExoPlayer import ch.srgssr.pillarbox.player.PreloadConfiguration import okhttp3.Interceptor import okhttp3.Response -import java.net.URL import kotlin.time.Duration.Companion.seconds import ch.srg.dataProvider.integrationlayer.request.IlHost as DataProviderIlHost @@ -42,7 +41,7 @@ object PlayerModule { */ fun createIlRepository( context: Context, - ilHost: URL = IlHost.DEFAULT, + ilHost: IlHost = IlHost.PROD, forceSAM: Boolean = false, ilLocation: IlLocation? = null, ): ILRepository { @@ -55,7 +54,7 @@ object PlayerModule { return ILRepository(dataProviderPaging = DataProviderPaging(ilService), ilService = ilService) } - private fun URL.toDataProviderIlHost(forceSAM: Boolean): DataProviderIlHost { + private fun IlHost.toDataProviderIlHost(forceSAM: Boolean): DataProviderIlHost { return when (this) { IlHost.PROD -> if (forceSAM) DataProviderIlHost.PROD_SAM else DataProviderIlHost.PROD IlHost.STAGE -> if (forceSAM) DataProviderIlHost.STAGE_SAM else DataProviderIlHost.STAGE diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/MainNavigation.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/MainNavigation.kt index 274b6cb75..658fbb8a7 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/MainNavigation.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/MainNavigation.kt @@ -76,7 +76,6 @@ import ch.srgssr.pillarbox.demo.ui.settings.AppSettingsView import ch.srgssr.pillarbox.demo.ui.showcases.showcasesNavGraph import ch.srgssr.pillarbox.demo.ui.theme.PillarboxTheme import ch.srgssr.pillarbox.demo.ui.theme.paddings -import java.net.URL private val bottomNavItems = listOf(HomeDestination.Examples, HomeDestination.Showcases, HomeDestination.Lists, HomeDestination.Search, HomeDestination.Settings) @@ -99,7 +98,7 @@ fun MainNavigation() { val navBackStackEntry by navController.currentBackStackEntryAsState() val currentDestination = navBackStackEntry?.destination - var ilHost by remember { mutableStateOf(IlHost.DEFAULT) } + var ilHost by remember { mutableStateOf(IlHost.PROD) } var forceSAM by remember { mutableStateOf(false) } var ilLocation by remember { mutableStateOf(null) } @@ -192,10 +191,10 @@ fun MainNavigation() { @Composable private fun ListsMenu( - currentServer: URL, + currentServer: IlHost, currentForceSAM: Boolean, currentLocation: IlLocation?, - onServerSelected: (server: URL, forceSAM: Boolean, location: IlLocation?) -> Unit + onServerSelected: (server: IlHost, forceSAM: Boolean, location: IlLocation?) -> Unit ) { val context = LocalContext.current val servers = remember { getServers(context).groupBy { it.serverName }.values } @@ -323,7 +322,7 @@ internal fun getServers(context: Context): List { internal data class EnvironmentConfig( val name: String, val serverName: String, - val host: URL, + val host: IlHost, val forceSAM: Boolean = false, val location: IlLocation? = null, ) { diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/lists/ListsHome.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/lists/ListsHome.kt index 7ffd42416..6cc06f27a 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/lists/ListsHome.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/lists/ListsHome.kt @@ -25,6 +25,7 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.toRoute import androidx.paging.compose.collectAsLazyPagingItems +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlLocation import ch.srgssr.pillarbox.demo.DemoPageView import ch.srgssr.pillarbox.demo.composable @@ -41,7 +42,6 @@ import ch.srgssr.pillarbox.demo.ui.components.DemoListSectionView import ch.srgssr.pillarbox.demo.ui.player.SimplePlayerActivity import ch.srgssr.pillarbox.demo.ui.theme.PillarboxTheme import ch.srgssr.pillarbox.demo.ui.theme.paddings -import java.net.URL private val defaultListsLevels = listOf("app", "pillarbox", "lists") @@ -51,7 +51,7 @@ private val defaultListsLevels = listOf("app", "pillarbox", "lists") fun NavGraphBuilder.listsNavGraph( navController: NavController, ilRepository: ILRepository, - ilHost: URL, + ilHost: IlHost, forceSAM: Boolean, ilLocation: IlLocation?, ) { From c1523042cc9117bbaacb69ffbe4227a569d23d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Mon, 16 Dec 2024 08:36:33 +0100 Subject: [PATCH 02/15] Refactor IlUrl --- .../pillarbox/core/business/SRGMediaItem.kt | 6 +- .../integrationlayer/service/IlHost.kt | 64 ----------------- .../integrationlayer/service/IlUrl.kt | 68 +++++++++++++++++++ .../core/business/source/SRGAssetLoader.kt | 2 +- .../business/integrationlayer/IlUrnTest.kt | 6 +- 5 files changed, 75 insertions(+), 71 deletions(-) create mode 100644 pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt index 53d5a62a3..9fdcecdb5 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt @@ -6,10 +6,10 @@ package ch.srgssr.pillarbox.core.business import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata -import ch.srgssr.pillarbox.core.business.integrationlayer.service.ILUrl -import ch.srgssr.pillarbox.core.business.integrationlayer.service.ILUrl.Companion.toIlUrl import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlLocation +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlUrl +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlUrl.Companion.toIlUrl import ch.srgssr.pillarbox.core.business.integrationlayer.service.Vector import ch.srgssr.pillarbox.core.business.source.MimeTypeSrg import ch.srgssr.pillarbox.player.PillarboxDsl @@ -167,7 +167,7 @@ class SRGMediaItemBuilder internal constructor(mediaItem: MediaItem) { * @return A new [MediaItem] ready for playback. */ fun build(): MediaItem { - val ilUrl = ILUrl(host = host, urn = urn, vector = vector, forceSAM = forceSAM, ilLocation = ilLocation) + val ilUrl = IlUrl(host = host, urn = urn, vector = vector, forceSAM = forceSAM, ilLocation = ilLocation) mediaItemBuilder.setUri(ilUrl.uri) mediaItemBuilder.setMediaId(ilUrl.urn) mediaItemBuilder.setMimeType(MimeTypeSrg) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt index 8ffddd501..18a32ad1c 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt @@ -4,68 +4,6 @@ */ package ch.srgssr.pillarbox.core.business.integrationlayer.service -import android.net.Uri -import ch.srgssr.pillarbox.core.business.integrationlayer.data.isValidMediaUrn - -/** - * @property host the [IlHost] to use. - * @property urn the URN of the media to request. - * @property vector The [Vector] to use. - * @property forceSAM Force SAM usage. - * @property ilLocation the [IlLocation] of the request. - */ -data class ILUrl( - val host: IlHost, - val urn: String, - val vector: Vector, - val forceSAM: Boolean = false, - val ilLocation: IlLocation? = null, -) { - - /** - * Uri - */ - val uri: Uri = Uri.parse(host.baseHostUrl).buildUpon().apply { - appendEncodedPath(PATH) - if (forceSAM) { - appendEncodedPath("sam") - appendQueryParameter(PARAM_FORCE_SAM, true.toString()) - } - appendEncodedPath(urn) - ilLocation?.let { - appendQueryParameter(PARAM_FORCE_LOCATION, it.toString()) - } - appendQueryParameter(PARAM_VECTOR, vector.toString()) - appendQueryParameter(PARAM_ONLY_CHAPTERS, true.toString()) - if (forceSAM) appendEncodedPath("sam") - }.build() - - @Suppress("UndocumentedPublicClass") - companion object { - private const val PARAM_ONLY_CHAPTERS = "onlyChapters" - private const val PARAM_FORCE_SAM = "forceSAM" - private const val PARAM_FORCE_LOCATION = "forceLocation" - private const val PARAM_VECTOR = "vector" - - /** - * To il url - * - * @return - */ - fun Uri.toIlUrl(): ILUrl { - val urn = lastPathSegment - val host = IlHost.parse(toString()) - check(urn.isValidMediaUrn()) { "Invalid urn $urn" } - checkNotNull(host) { "Invalid url $this" } - val forceSAM = getQueryParameter(PARAM_FORCE_SAM)?.toBooleanStrictOrNull() == true - val ilLocation = getQueryParameter(PARAM_FORCE_LOCATION)?.let { IlLocation.fromName(it) } - val vector = getQueryParameter(PARAM_VECTOR)?.let { Vector.fromLabel(it) } ?: Vector.MOBILE - - return ILUrl(host = host, urn = checkNotNull(urn), vector, ilLocation = ilLocation, forceSAM = forceSAM) - } - } -} - /** * Object containing the different host URLs for the integration layer service. * @property baseHostUrl The base hostname url. @@ -103,5 +41,3 @@ enum class IlHost(val baseHostUrl: String) { } } } - -private const val PATH = "integrationlayer/2.1/mediaComposition/byUrn/" diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt new file mode 100644 index 000000000..44264d898 --- /dev/null +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.core.business.integrationlayer.service + +import android.net.Uri +import ch.srgssr.pillarbox.core.business.integrationlayer.data.isValidMediaUrn + +/** + * @property host the [IlHost] to use. + * @property urn the URN of the media to request. + * @property vector The [Vector] to use. + * @property forceSAM Force SAM usage. + * @property ilLocation the [IlLocation] of the request. + */ +data class IlUrl( + val host: IlHost, + val urn: String, + val vector: Vector, + val forceSAM: Boolean = false, + val ilLocation: IlLocation? = null, +) { + + /** + * Uri + */ + val uri: Uri = Uri.parse(host.baseHostUrl).buildUpon().apply { + appendEncodedPath(PATH) + if (forceSAM) { + appendEncodedPath("sam") + appendQueryParameter(PARAM_FORCE_SAM, true.toString()) + } + appendEncodedPath(urn) + ilLocation?.let { + appendQueryParameter(PARAM_FORCE_LOCATION, it.toString()) + } + appendQueryParameter(PARAM_VECTOR, vector.toString()) + appendQueryParameter(PARAM_ONLY_CHAPTERS, true.toString()) + if (forceSAM) appendEncodedPath("sam") + }.build() + + @Suppress("UndocumentedPublicClass") + companion object { + private const val PARAM_ONLY_CHAPTERS = "onlyChapters" + private const val PARAM_FORCE_SAM = "forceSAM" + private const val PARAM_FORCE_LOCATION = "forceLocation" + private const val PARAM_VECTOR = "vector" + private const val PATH = "integrationlayer/2.1/mediaComposition/byUrn/" + + /** + * Convert an [Uri] into a valid [IlUrl]. + * + * @return a [IlUrl] or throw an [IllegalStateException] if the Uri can't be parse. + */ + fun Uri.toIlUrl(): IlUrl { + val urn = lastPathSegment + val host = IlHost.parse(toString()) + check(urn.isValidMediaUrn()) { "Invalid urn $urn" } + checkNotNull(host) { "Invalid url $this" } + val forceSAM = getQueryParameter(PARAM_FORCE_SAM)?.toBooleanStrictOrNull() == true + val ilLocation = getQueryParameter(PARAM_FORCE_LOCATION)?.let { IlLocation.fromName(it) } + val vector = getQueryParameter(PARAM_VECTOR)?.let { Vector.fromLabel(it) } ?: Vector.MOBILE + + return IlUrl(host = host, urn = checkNotNull(urn), vector, ilLocation = ilLocation, forceSAM = forceSAM) + } + } +} diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt index 051c6df1f..05be0d7d5 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt @@ -23,7 +23,7 @@ import ch.srgssr.pillarbox.core.business.integrationlayer.data.Chapter import ch.srgssr.pillarbox.core.business.integrationlayer.data.Drm import ch.srgssr.pillarbox.core.business.integrationlayer.data.MediaComposition import ch.srgssr.pillarbox.core.business.integrationlayer.data.Resource -import ch.srgssr.pillarbox.core.business.integrationlayer.service.ILUrl.Companion.toIlUrl +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlUrl.Companion.toIlUrl import ch.srgssr.pillarbox.core.business.integrationlayer.service.MediaCompositionService import ch.srgssr.pillarbox.core.business.tracker.SRGEventLoggerTracker import ch.srgssr.pillarbox.core.business.tracker.commandersact.CommandersActTracker diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrnTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrnTest.kt index 35b2fceb2..72937cf0a 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrnTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrnTest.kt @@ -5,9 +5,9 @@ package ch.srgssr.pillarbox.core.business.integrationlayer import androidx.test.ext.junit.runners.AndroidJUnit4 -import ch.srgssr.pillarbox.core.business.integrationlayer.service.ILUrl -import ch.srgssr.pillarbox.core.business.integrationlayer.service.ILUrl.Companion.toIlUrl import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlUrl +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlUrl.Companion.toIlUrl import ch.srgssr.pillarbox.core.business.integrationlayer.service.Vector import org.junit.runner.RunWith import kotlin.test.Test @@ -17,7 +17,7 @@ import kotlin.test.assertEquals class IlUrnTest { @Test fun testInOut() { - val ilUrn = ILUrl(host = IlHost.PROD, urn = "urn:rts:video:12345", vector = Vector.MOBILE) + val ilUrn = IlUrl(host = IlHost.PROD, urn = "urn:rts:video:12345", vector = Vector.MOBILE) assertEquals(ilUrn, ilUrn.uri.toIlUrl()) } } From 47b8e175182392a3808474d1be9a13d0fdc8b7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Mon, 16 Dec 2024 08:38:30 +0100 Subject: [PATCH 03/15] Remove duplicate sam path --- .../pillarbox/core/business/integrationlayer/service/IlUrl.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt index 44264d898..7ff30afed 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt @@ -37,7 +37,6 @@ data class IlUrl( } appendQueryParameter(PARAM_VECTOR, vector.toString()) appendQueryParameter(PARAM_ONLY_CHAPTERS, true.toString()) - if (forceSAM) appendEncodedPath("sam") }.build() @Suppress("UndocumentedPublicClass") From 1c11e3ce5a1ad689d00f0748a1eda22d03bd767a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Mon, 16 Dec 2024 08:56:25 +0100 Subject: [PATCH 04/15] Same api behaviors as before and fix tests --- .../core/business/integrationlayer/service/IlUrl.kt | 8 ++++---- .../pillarbox/core/business/SRGMediaItemBuilderTest.kt | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt index 7ff30afed..8052cf27c 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt @@ -26,11 +26,11 @@ data class IlUrl( * Uri */ val uri: Uri = Uri.parse(host.baseHostUrl).buildUpon().apply { - appendEncodedPath(PATH) if (forceSAM) { appendEncodedPath("sam") appendQueryParameter(PARAM_FORCE_SAM, true.toString()) } + appendEncodedPath(PATH) appendEncodedPath(urn) ilLocation?.let { appendQueryParameter(PARAM_FORCE_LOCATION, it.toString()) @@ -50,13 +50,13 @@ data class IlUrl( /** * Convert an [Uri] into a valid [IlUrl]. * - * @return a [IlUrl] or throw an [IllegalStateException] if the Uri can't be parse. + * @return a [IlUrl] or throw an [IllegalArgumentException] if the Uri can't be parse. */ fun Uri.toIlUrl(): IlUrl { val urn = lastPathSegment val host = IlHost.parse(toString()) - check(urn.isValidMediaUrn()) { "Invalid urn $urn" } - checkNotNull(host) { "Invalid url $this" } + require(urn.isValidMediaUrn()) { "Invalid urn $urn found in $this" } + requireNotNull(host) { "Invalid url $this" } val forceSAM = getQueryParameter(PARAM_FORCE_SAM)?.toBooleanStrictOrNull() == true val ilLocation = getQueryParameter(PARAM_FORCE_LOCATION)?.let { IlLocation.fromName(it) } val vector = getQueryParameter(PARAM_VECTOR)?.let { Vector.fromLabel(it) } ?: Vector.MOBILE diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGMediaItemBuilderTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGMediaItemBuilderTest.kt index 5338040f3..56e1708bc 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGMediaItemBuilderTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGMediaItemBuilderTest.kt @@ -104,15 +104,14 @@ class SRGMediaItemBuilderTest { @Test fun `Check uri from existing MediaItem`() { val urn = "urn:rts:audio:3262363" - val ilHost = IlHost.STAGE val inputMediaItem = MediaItem.Builder() - .setUri("${ilHost}integrationlayer/2.1/mediaComposition/byUrn/$urn?vector=${Vector.TV}") + .setUri("https://il-stage.srgssr.ch/integrationlayer/2.1/mediaComposition/byUrn/$urn?vector=${Vector.TV}") .build() val mediaItem = SRGMediaItemBuilder(inputMediaItem).build() val localConfiguration = mediaItem.localConfiguration assertNotNull(localConfiguration) - assertEquals(urn.toIlUri(ilHost, vector = Vector.TV), localConfiguration.uri) + assertEquals(urn.toIlUri(IlHost.STAGE, vector = Vector.TV), localConfiguration.uri) assertEquals(MimeTypeSrg, localConfiguration.mimeType) assertEquals(urn, mediaItem.mediaId) assertEquals(MediaMetadata.EMPTY, mediaItem.mediaMetadata) @@ -163,7 +162,7 @@ class SRGMediaItemBuilderTest { val ilHost = IlHost.STAGE val forceSAM = true val inputMediaItem = MediaItem.Builder() - .setUri("${IlHost.PROD}sam/integrationlayer/2.1/mediaComposition/byUrn/$urn?forceSAM=true") + .setUri("https://il-stage.srgssr.ch/sam/integrationlayer/2.1/mediaComposition/byUrn/$urn?forceSAM=true") .build() val mediaItem = SRGMediaItemBuilder(inputMediaItem).apply { host(ilHost) @@ -213,7 +212,7 @@ class SRGMediaItemBuilderTest { "$name=$value" } - return "${host.baseHostUrl}${samPath}integrationlayer/2.1/mediaComposition/byUrn/$this?$queryParameters".toUri() + return "${host.baseHostUrl}/${samPath}integrationlayer/2.1/mediaComposition/byUrn/$this?$queryParameters".toUri() } } } From e0b34ea0a5ccdfd8376accf222ad00b69e9f3388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Mon, 16 Dec 2024 09:06:18 +0100 Subject: [PATCH 05/15] Test ilHost --- .../integrationlayer/service/IlHost.kt | 2 +- .../business/integrationlayer/IlHostTests.kt | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlHostTests.kt diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt index 18a32ad1c..1fef951d0 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt @@ -13,7 +13,7 @@ enum class IlHost(val baseHostUrl: String) { /** * The base URL for the production environment. */ - PROD(baseHostUrl = "https://il.srgssr.ch)"), + PROD(baseHostUrl = "https://il.srgssr.ch"), /** * The base URL for the test environment. diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlHostTests.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlHostTests.kt new file mode 100644 index 000000000..b565a1fa5 --- /dev/null +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlHostTests.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.core.business.integrationlayer + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost +import org.junit.runner.RunWith +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +@RunWith(AndroidJUnit4::class) +class IlHostTests { + + @Test + fun `Check parsing IlHost from String`() { + var host = IlHost.parse("https://il.srgssr.ch/somePath/andPath/?withParameters=45") + assertEquals(IlHost.PROD, host) + + host = IlHost.parse("https://il-test.srgssr.ch/somePath/andPath/?withParameters=45") + assertEquals(IlHost.TEST, host) + + host = IlHost.parse("https://il-stage.srgssr.ch/somePath/andPath/?withParameters=45") + assertEquals(IlHost.STAGE, host) + } + + @Test + fun `Check null if invalid hostname`() { + assertNull(IlHost.parse("https://il-foo.srgssr.ch/somePath/andPath/?withParameters=45")) + assertNull(IlHost.parse("https://www.google.com/search?q=45")) + } +} From cd0e662036ebe9679d0c192af9798135bdc1acdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Mon, 16 Dec 2024 09:18:54 +0100 Subject: [PATCH 06/15] Throw exception when building a SRGMediaItem from an existing item --- .../pillarbox/core/business/SRGMediaItem.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt index 9fdcecdb5..dc00bdce8 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt @@ -6,6 +6,7 @@ package ch.srgssr.pillarbox.core.business import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata +import ch.srgssr.pillarbox.core.business.integrationlayer.data.isValidMediaUrn import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlLocation import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlUrl @@ -49,6 +50,7 @@ import ch.srgssr.pillarbox.player.source.PillarboxMediaSource @PillarboxDsl @Suppress("FunctionName") fun SRGMediaItem(urn: String, block: SRGMediaItemBuilder.() -> Unit = {}): MediaItem { + require(urn.isValidMediaUrn()) { "URN is not valid." } return SRGMediaItemBuilder(MediaItem.Builder().setMediaId(urn).build()).apply(block).build() } @@ -84,14 +86,12 @@ class SRGMediaItemBuilder internal constructor(mediaItem: MediaItem) { init { mediaItem.localConfiguration?.let { localConfiguration -> - runCatching { - val ilUrl = localConfiguration.uri.toIlUrl() - host = ilUrl.host - urn = ilUrl.urn - forceSAM = ilUrl.forceSAM - ilLocation = ilUrl.ilLocation - vector = ilUrl.vector - } + val ilUrl = localConfiguration.uri.toIlUrl() + host = ilUrl.host + urn = ilUrl.urn + forceSAM = ilUrl.forceSAM + ilLocation = ilUrl.ilLocation + vector = ilUrl.vector } } From 6d62de9acd1082883bf5c3be6407010afee49bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Mon, 16 Dec 2024 09:19:09 +0100 Subject: [PATCH 07/15] Throw exception if urn is not valid --- .../pillarbox/core/business/integrationlayer/service/IlUrl.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt index 8052cf27c..c24ab8104 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt @@ -22,6 +22,10 @@ data class IlUrl( val ilLocation: IlLocation? = null, ) { + init { + require(urn.isValidMediaUrn()) + } + /** * Uri */ From 45374ef25721bbcab0d66e14dc3a7e69cbaa1fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Mon, 16 Dec 2024 09:19:15 +0100 Subject: [PATCH 08/15] fix test --- .../business/integrationlayer/ImageScalingServiceTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingServiceTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingServiceTest.kt index a582a1303..5adf07bab 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingServiceTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingServiceTest.kt @@ -19,7 +19,7 @@ class ImageScalingServiceTest { val scaledImageUrl = imageScalingService.getScaledImageUrl(imageUrl) val encodedImageUrl = URLEncoder.encode(imageUrl, Charsets.UTF_8) - assertEquals("${host}images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) + assertEquals("${host.baseHostUrl}/images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) } @Test @@ -31,7 +31,7 @@ class ImageScalingServiceTest { val scaledImageUrl = imageScalingService.getScaledImageUrl(imageUrl) val encodedImageUrl = URLEncoder.encode(imageUrl, Charsets.UTF_8) - assertEquals("${host}images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) + assertEquals("${host.baseHostUrl}/images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) } @Test @@ -43,6 +43,6 @@ class ImageScalingServiceTest { val scaledImageUrl = imageScalingService.getScaledImageUrl(imageUrl) val encodedImageUrl = URLEncoder.encode(imageUrl, Charsets.UTF_8) - assertEquals("${host}images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) + assertEquals("${host.baseHostUrl}/images/?imageUrl=$encodedImageUrl&format=webp&width=480", scaledImageUrl) } } From 0f41ada60fdd0b4d083bb61968030b561c46176e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Mon, 16 Dec 2024 09:43:22 +0100 Subject: [PATCH 09/15] Add IlUrl tests --- .../integrationlayer/service/IlUrl.kt | 2 +- .../business/integrationlayer/IlUrlTest.kt | 79 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrlTest.kt diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt index c24ab8104..0c7e51cd1 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt @@ -61,7 +61,7 @@ data class IlUrl( val host = IlHost.parse(toString()) require(urn.isValidMediaUrn()) { "Invalid urn $urn found in $this" } requireNotNull(host) { "Invalid url $this" } - val forceSAM = getQueryParameter(PARAM_FORCE_SAM)?.toBooleanStrictOrNull() == true + val forceSAM = getQueryParameter(PARAM_FORCE_SAM)?.toBooleanStrictOrNull() == true || pathSegments.contains("sam") val ilLocation = getQueryParameter(PARAM_FORCE_LOCATION)?.let { IlLocation.fromName(it) } val vector = getQueryParameter(PARAM_VECTOR)?.let { Vector.fromLabel(it) } ?: Vector.MOBILE diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrlTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrlTest.kt new file mode 100644 index 000000000..4a6941f10 --- /dev/null +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrlTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.core.business.integrationlayer + +import android.net.Uri +import androidx.test.ext.junit.runners.AndroidJUnit4 +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlLocation +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlUrl +import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlUrl.Companion.toIlUrl +import ch.srgssr.pillarbox.core.business.integrationlayer.service.Vector +import org.junit.runner.RunWith +import kotlin.test.Test +import kotlin.test.assertEquals + +@RunWith(AndroidJUnit4::class) +class IlUrlTest { + + @Test(expected = IllegalArgumentException::class) + fun `toIlUrl throw error when not an url`() { + Uri.parse("yolo").toIlUrl() + } + + @Test(expected = IllegalArgumentException::class) + fun `toIlUrl throw error with invalid il host name`() { + Uri.parse("https://il-foo.srg.ch/media/ByUrn/1234").toIlUrl() + } + + @Test(expected = IllegalArgumentException::class) + fun `toIlUrl throw error with invalid urn`() { + Uri.parse("${IlHost.PROD.baseHostUrl}/integrationlayer/2.1/mediaComposition/byUrn/").toIlUrl() + } + + @Test + fun `toIlUrl correctly filled`() { + val host = IlHost.PROD + val urn = "urn:rts:video:1234" + val uri = Uri.parse("${host.baseHostUrl}/sam/integrationlayer/2.1/mediaComposition/byUrn/$urn?vector=TVPLAY&forceLocation=WW") + val expected = IlUrl(host = host, urn = urn, vector = Vector.TV, forceSAM = true, ilLocation = IlLocation.WW) + assertEquals(expected, uri.toIlUrl()) + } + + @Test(expected = IllegalArgumentException::class) + fun `create IlUrl with invalid urn`() { + IlUrl(host = IlHost.PROD, urn = "urn:invalid:1234", vector = Vector.MOBILE) + } + + @Test + fun `ILUrl create correct uri with default parameters`() { + val uri = Uri.parse( + "${IlHost.PROD.baseHostUrl}/integrationlayer/2.1/mediaComposition/byUrn/" + + "urn:rts:video:1234?vector=${Vector.MOBILE}&onlyChapters=true" + ) + val ilUrl = IlUrl(host = IlHost.PROD, urn = "urn:rts:video:1234", vector = Vector.MOBILE) + assertEquals(uri, ilUrl.uri) + } + + @Test + fun `ILUrl create correct uri with forceSAM`() { + val uri = Uri.parse( + "${IlHost.PROD.baseHostUrl}/sam/integrationlayer/2.1/mediaComposition/byUrn/" + + "urn:rts:video:1234?forceSAM=true&vector=${Vector.MOBILE}&onlyChapters=true" + ) + val ilUrl = IlUrl(host = IlHost.PROD, urn = "urn:rts:video:1234", vector = Vector.MOBILE, forceSAM = true) + assertEquals(uri, ilUrl.uri) + } + + @Test + fun `ILUrl create correct uri with ilLocation`() { + val uri = Uri.parse( + "${IlHost.PROD.baseHostUrl}/integrationlayer/2.1/mediaComposition/byUrn/" + + "urn:rts:video:1234?forceLocation=WW&vector=${Vector.MOBILE}&onlyChapters=true" + ) + val ilUrl = IlUrl(host = IlHost.PROD, urn = "urn:rts:video:1234", vector = Vector.MOBILE, ilLocation = IlLocation.WW) + assertEquals(uri, ilUrl.uri) + } +} From 551e56bdf09b77dcc19741c45c889ec69eb36d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Mon, 16 Dec 2024 10:36:10 +0100 Subject: [PATCH 10/15] Add SRGAssetLoader canLoadAsset tests --- .../core/business/source/SRGAssetLoader.kt | 2 +- .../pillarbox/core/business/SRGAssetLoaderTest.kt | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt index 05be0d7d5..6121d3ba9 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/source/SRGAssetLoader.kt @@ -112,7 +112,7 @@ class SRGAssetLoader internal constructor( override fun canLoadAsset(mediaItem: MediaItem): Boolean { val localConfiguration = mediaItem.localConfiguration ?: return false - return localConfiguration.mimeType == MimeTypeSrg && kotlin.runCatching { + return localConfiguration.mimeType == MimeTypeSrg && runCatching { localConfiguration.uri.toIlUrl() }.isSuccess } diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGAssetLoaderTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGAssetLoaderTest.kt index 995ff0501..fc6da43f1 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGAssetLoaderTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGAssetLoaderTest.kt @@ -23,6 +23,7 @@ import ch.srgssr.pillarbox.core.business.integrationlayer.data.Segment import ch.srgssr.pillarbox.core.business.integrationlayer.data.TimeInterval import ch.srgssr.pillarbox.core.business.integrationlayer.data.TimeIntervalType import ch.srgssr.pillarbox.core.business.integrationlayer.service.MediaCompositionService +import ch.srgssr.pillarbox.core.business.source.MimeTypeSrg import ch.srgssr.pillarbox.core.business.source.SRGAssetLoader import ch.srgssr.pillarbox.core.business.source.SegmentAdapter import ch.srgssr.pillarbox.core.business.source.TimeIntervalAdapter @@ -51,6 +52,20 @@ class SRGAssetLoaderTest { } } + @Test + fun testCanLoadMediaItem() { + assertFalse(assetLoader.canLoadAsset(MediaItem.EMPTY)) + assertFalse( + assetLoader.canLoadAsset( + MediaItem.Builder() + .setMimeType(MimeTypeSrg) + .setUri("https://fake.il/mediaComposition/urn:rts:video:123/path") + .build() + ) + ) + assertTrue(assetLoader.canLoadAsset(SRGMediaItem("urn:rts:video:123"))) + } + @Test(expected = IllegalStateException::class) fun testNoMediaId() = runTest { assetLoader.loadAsset(MediaItem.Builder().build()) From 451180962518fa442729c0ccc1e2f7edec90b6ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Mon, 16 Dec 2024 17:30:42 +0100 Subject: [PATCH 11/15] Fix detekt no unusedImports --- .../core/business/integrationlayer/ImageScalingService.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingService.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingService.kt index ddd97fe60..1af38fd0d 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingService.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/ImageScalingService.kt @@ -5,7 +5,6 @@ package ch.srgssr.pillarbox.core.business.integrationlayer import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost -import java.net.URL import java.net.URLEncoder /** From ad28c128ebb445e21912d12f26a9e5cfb44d71c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Tue, 17 Dec 2024 10:17:39 +0100 Subject: [PATCH 12/15] Documentation update --- .../pillarbox/core/business/SRGMediaItem.kt | 16 +++---- .../integrationlayer/service/IlHost.kt | 10 +++-- .../integrationlayer/service/IlUrl.kt | 18 ++++---- .../core/business/SRGAssetLoaderTest.kt | 12 +++++- .../{IlHostTests.kt => IlHostTest.kt} | 16 ++++--- .../business/integrationlayer/IlUrlTest.kt | 43 +++++++++++-------- 6 files changed, 68 insertions(+), 47 deletions(-) rename pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/{IlHostTests.kt => IlHostTest.kt} (67%) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt index dc00bdce8..e26d766e2 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/SRGMediaItem.kt @@ -29,7 +29,7 @@ import ch.srgssr.pillarbox.player.source.PillarboxMediaSource * * ```kotlin * val mediaItem: MediaItem = SRGMediaItem("urn:rts:audio:3262363") { - * host(IlUrl.Default) + * host(IlHost.PROD) * vector(Vector.TV) * } * ``` @@ -61,7 +61,7 @@ fun SRGMediaItem(urn: String, block: SRGMediaItemBuilder.() -> Unit = {}): Media * **Usage example** * ```kotlin * val mediaItem: MediaItem = sourceItem.buildUpon { - * host(IlUrl.Stage) + * host(IlHost.STAGE) * } * ``` * @@ -123,9 +123,9 @@ class SRGMediaItemBuilder internal constructor(mediaItem: MediaItem) { } /** - * Sets the host URL to the integration layer. + * Sets the host base URL to the integration layer. * - * @param host The URL of the integration layer server. + * @param host The base URL of the integration layer server. */ fun host(host: IlHost) { this.host = host @@ -168,9 +168,9 @@ class SRGMediaItemBuilder internal constructor(mediaItem: MediaItem) { */ fun build(): MediaItem { val ilUrl = IlUrl(host = host, urn = urn, vector = vector, forceSAM = forceSAM, ilLocation = ilLocation) - mediaItemBuilder.setUri(ilUrl.uri) - mediaItemBuilder.setMediaId(ilUrl.urn) - mediaItemBuilder.setMimeType(MimeTypeSrg) - return mediaItemBuilder.build() + return mediaItemBuilder.setUri(ilUrl.uri) + .setMediaId(ilUrl.urn) + .setMimeType(MimeTypeSrg) + .build() } } diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt index 1fef951d0..d2b0ab610 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt @@ -5,8 +5,9 @@ package ch.srgssr.pillarbox.core.business.integrationlayer.service /** - * Object containing the different host URLs for the integration layer service. - * @property baseHostUrl The base hostname url. + * Represents the different host URLs for the integration layer service. + * + * @property baseHostUrl The base URL of the environment. */ enum class IlHost(val baseHostUrl: String) { @@ -33,8 +34,9 @@ enum class IlHost(val baseHostUrl: String) { /** * Parses the given [url] and returns the corresponding [IlHost]. * - * @param url The url to parse - * @return null if the [url] does not match any [IlHost]. + * @param url The URL to parse. + * + * @return The matching [IlHost] or `null` if none was found. */ fun parse(url: String): IlHost? { return entries.find { url.contains(it.baseHostUrl) } diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt index 0c7e51cd1..ff9e15e1b 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt @@ -8,11 +8,11 @@ import android.net.Uri import ch.srgssr.pillarbox.core.business.integrationlayer.data.isValidMediaUrn /** - * @property host the [IlHost] to use. - * @property urn the URN of the media to request. + * @property host The [IlHost] to use. + * @property urn The URN of the media to request. * @property vector The [Vector] to use. * @property forceSAM Force SAM usage. - * @property ilLocation the [IlLocation] of the request. + * @property ilLocation The [IlLocation] of the request. */ data class IlUrl( val host: IlHost, @@ -27,7 +27,7 @@ data class IlUrl( } /** - * Uri + * [Uri] representation of this [IlUrl]. */ val uri: Uri = Uri.parse(host.baseHostUrl).buildUpon().apply { if (forceSAM) { @@ -52,20 +52,20 @@ data class IlUrl( private const val PATH = "integrationlayer/2.1/mediaComposition/byUrn/" /** - * Convert an [Uri] into a valid [IlUrl]. + * Converts an [Uri] into a valid [IlUrl]. * - * @return a [IlUrl] or throw an [IllegalArgumentException] if the Uri can't be parse. + * @return An [IlUrl] or throws an [IllegalArgumentException] if the [Uri] can't be parsed. */ fun Uri.toIlUrl(): IlUrl { val urn = lastPathSegment + require(urn.isValidMediaUrn()) { "Invalid URN $urn found in $this" } val host = IlHost.parse(toString()) - require(urn.isValidMediaUrn()) { "Invalid urn $urn found in $this" } - requireNotNull(host) { "Invalid url $this" } + requireNotNull(host) { "Invalid URL $this" } val forceSAM = getQueryParameter(PARAM_FORCE_SAM)?.toBooleanStrictOrNull() == true || pathSegments.contains("sam") val ilLocation = getQueryParameter(PARAM_FORCE_LOCATION)?.let { IlLocation.fromName(it) } val vector = getQueryParameter(PARAM_VECTOR)?.let { Vector.fromLabel(it) } ?: Vector.MOBILE - return IlUrl(host = host, urn = checkNotNull(urn), vector, ilLocation = ilLocation, forceSAM = forceSAM) + return IlUrl(host = host, urn = checkNotNull(urn), vector = vector, ilLocation = ilLocation, forceSAM = forceSAM) } } } diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGAssetLoaderTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGAssetLoaderTest.kt index fc6da43f1..63a9c505a 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGAssetLoaderTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGAssetLoaderTest.kt @@ -53,8 +53,17 @@ class SRGAssetLoaderTest { } @Test - fun testCanLoadMediaItem() { + fun testCanLoadAsset() { + assertTrue(assetLoader.canLoadAsset(SRGMediaItem("urn:rts:video:123"))) + } + + @Test + fun testCanLoadAsset_emptyMediaItem() { assertFalse(assetLoader.canLoadAsset(MediaItem.EMPTY)) + } + + @Test + fun testCanLoadAsset_invalidUri() { assertFalse( assetLoader.canLoadAsset( MediaItem.Builder() @@ -63,7 +72,6 @@ class SRGAssetLoaderTest { .build() ) ) - assertTrue(assetLoader.canLoadAsset(SRGMediaItem("urn:rts:video:123"))) } @Test(expected = IllegalStateException::class) diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlHostTests.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlHostTest.kt similarity index 67% rename from pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlHostTests.kt rename to pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlHostTest.kt index b565a1fa5..6bb3dcccb 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlHostTests.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlHostTest.kt @@ -12,17 +12,23 @@ import kotlin.test.assertEquals import kotlin.test.assertNull @RunWith(AndroidJUnit4::class) -class IlHostTests { +class IlHostTest { @Test - fun `Check parsing IlHost from String`() { - var host = IlHost.parse("https://il.srgssr.ch/somePath/andPath/?withParameters=45") + fun `parse prod IlHost`() { + val host = IlHost.parse("https://il.srgssr.ch/somePath/andPath/?withParameters=45") assertEquals(IlHost.PROD, host) + } - host = IlHost.parse("https://il-test.srgssr.ch/somePath/andPath/?withParameters=45") + @Test + fun `parse test IlHost`() { + val host = IlHost.parse("https://il-test.srgssr.ch/somePath/andPath/?withParameters=45") assertEquals(IlHost.TEST, host) + } - host = IlHost.parse("https://il-stage.srgssr.ch/somePath/andPath/?withParameters=45") + @Test + fun `parse stage IlHost`() { + val host = IlHost.parse("https://il-stage.srgssr.ch/somePath/andPath/?withParameters=45") assertEquals(IlHost.STAGE, host) } diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrlTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrlTest.kt index 4a6941f10..d63ca0726 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrlTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/integrationlayer/IlUrlTest.kt @@ -19,17 +19,17 @@ import kotlin.test.assertEquals class IlUrlTest { @Test(expected = IllegalArgumentException::class) - fun `toIlUrl throw error when not an url`() { + fun `toIlUrl throws an error when not an url`() { Uri.parse("yolo").toIlUrl() } @Test(expected = IllegalArgumentException::class) - fun `toIlUrl throw error with invalid il host name`() { - Uri.parse("https://il-foo.srg.ch/media/ByUrn/1234").toIlUrl() + fun `toIlUrl throws an error with invalid il host name`() { + Uri.parse("https://il-foo.srg.ch/media/ByUrn/urn:rts:video:123").toIlUrl() } @Test(expected = IllegalArgumentException::class) - fun `toIlUrl throw error with invalid urn`() { + fun `toIlUrl throws an error with invalid urn`() { Uri.parse("${IlHost.PROD.baseHostUrl}/integrationlayer/2.1/mediaComposition/byUrn/").toIlUrl() } @@ -37,8 +37,10 @@ class IlUrlTest { fun `toIlUrl correctly filled`() { val host = IlHost.PROD val urn = "urn:rts:video:1234" - val uri = Uri.parse("${host.baseHostUrl}/sam/integrationlayer/2.1/mediaComposition/byUrn/$urn?vector=TVPLAY&forceLocation=WW") - val expected = IlUrl(host = host, urn = urn, vector = Vector.TV, forceSAM = true, ilLocation = IlLocation.WW) + val vector = Vector.TV + val ilLocation = IlLocation.WW + val uri = Uri.parse("${host.baseHostUrl}/sam/integrationlayer/2.1/mediaComposition/byUrn/$urn?vector=$vector&forceLocation=$ilLocation") + val expected = IlUrl(host = host, urn = urn, vector = vector, forceSAM = true, ilLocation = ilLocation) assertEquals(expected, uri.toIlUrl()) } @@ -49,31 +51,34 @@ class IlUrlTest { @Test fun `ILUrl create correct uri with default parameters`() { - val uri = Uri.parse( - "${IlHost.PROD.baseHostUrl}/integrationlayer/2.1/mediaComposition/byUrn/" + - "urn:rts:video:1234?vector=${Vector.MOBILE}&onlyChapters=true" - ) - val ilUrl = IlUrl(host = IlHost.PROD, urn = "urn:rts:video:1234", vector = Vector.MOBILE) + val host = IlHost.PROD + val urn = "urn:rts:video:1234" + val vector = Vector.MOBILE + val uri = Uri.parse("${host.baseHostUrl}/integrationlayer/2.1/mediaComposition/byUrn/$urn?vector=$vector&onlyChapters=true") + val ilUrl = IlUrl(host = host, urn = urn, vector = vector) assertEquals(uri, ilUrl.uri) } @Test fun `ILUrl create correct uri with forceSAM`() { - val uri = Uri.parse( - "${IlHost.PROD.baseHostUrl}/sam/integrationlayer/2.1/mediaComposition/byUrn/" + - "urn:rts:video:1234?forceSAM=true&vector=${Vector.MOBILE}&onlyChapters=true" - ) - val ilUrl = IlUrl(host = IlHost.PROD, urn = "urn:rts:video:1234", vector = Vector.MOBILE, forceSAM = true) + val host = IlHost.PROD + val urn = "urn:rts:video:1234" + val vector = Vector.MOBILE + val uri = Uri.parse("${host.baseHostUrl}/sam/integrationlayer/2.1/mediaComposition/byUrn/$urn?forceSAM=true&vector=$vector&onlyChapters=true") + val ilUrl = IlUrl(host = host, urn = urn, vector = vector, forceSAM = true) assertEquals(uri, ilUrl.uri) } @Test fun `ILUrl create correct uri with ilLocation`() { + val host = IlHost.PROD + val urn = "urn:rts:video:1234" + val vector = Vector.MOBILE + val ilLocation = IlLocation.WW val uri = Uri.parse( - "${IlHost.PROD.baseHostUrl}/integrationlayer/2.1/mediaComposition/byUrn/" + - "urn:rts:video:1234?forceLocation=WW&vector=${Vector.MOBILE}&onlyChapters=true" + "${host.baseHostUrl}/integrationlayer/2.1/mediaComposition/byUrn/$urn?forceLocation=$ilLocation&vector=$vector&onlyChapters=true" ) - val ilUrl = IlUrl(host = IlHost.PROD, urn = "urn:rts:video:1234", vector = Vector.MOBILE, ilLocation = IlLocation.WW) + val ilUrl = IlUrl(host = host, urn = urn, vector = vector, ilLocation = ilLocation) assertEquals(uri, ilUrl.uri) } } From be9349707c6503d12774a629c70303714d749702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 18 Dec 2024 08:52:06 +0100 Subject: [PATCH 13/15] StartWith instead of contains --- .../pillarbox/core/business/integrationlayer/service/IlHost.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt index d2b0ab610..795b7fbb3 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlHost.kt @@ -39,7 +39,7 @@ enum class IlHost(val baseHostUrl: String) { * @return The matching [IlHost] or `null` if none was found. */ fun parse(url: String): IlHost? { - return entries.find { url.contains(it.baseHostUrl) } + return entries.find { url.startsWith(it.baseHostUrl) } } } } From 441fb0f2baa37cd987bd860a2c211523b7b82a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 18 Dec 2024 08:53:10 +0100 Subject: [PATCH 14/15] Make Uri.toIlUrl internal --- .../pillarbox/core/business/integrationlayer/service/IlUrl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt index ff9e15e1b..247ed3563 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt @@ -56,7 +56,7 @@ data class IlUrl( * * @return An [IlUrl] or throws an [IllegalArgumentException] if the [Uri] can't be parsed. */ - fun Uri.toIlUrl(): IlUrl { + internal fun Uri.toIlUrl(): IlUrl { val urn = lastPathSegment require(urn.isValidMediaUrn()) { "Invalid URN $urn found in $this" } val host = IlHost.parse(toString()) From e7f6e754202fbaa6327b02a5aab8bd6e1aca7cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Thu, 19 Dec 2024 08:28:12 +0100 Subject: [PATCH 15/15] Make `IlUrl.companion` `internal` --- .../pillarbox/core/business/integrationlayer/service/IlUrl.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt index 247ed3563..83e067697 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/integrationlayer/service/IlUrl.kt @@ -43,8 +43,7 @@ data class IlUrl( appendQueryParameter(PARAM_ONLY_CHAPTERS, true.toString()) }.build() - @Suppress("UndocumentedPublicClass") - companion object { + internal companion object { private const val PARAM_ONLY_CHAPTERS = "onlyChapters" private const val PARAM_FORCE_SAM = "forceSAM" private const val PARAM_FORCE_LOCATION = "forceLocation"