diff --git a/dataprovider-retrofit/build.gradle.kts b/dataprovider-retrofit/build.gradle.kts index f9c1aa4..5b9137b 100644 --- a/dataprovider-retrofit/build.gradle.kts +++ b/dataprovider-retrofit/build.gradle.kts @@ -53,7 +53,6 @@ android { dependencies { api(project(":data")) api(libs.retrofit) - compileOnly(libs.androidx.annotation) api(platform(libs.retrofit.bom)) implementation(libs.retrofit.converter.kotlinx.serialization) implementation(libs.okhttp.logging.interceptor) diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/IlHost.java b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/IlHost.java deleted file mode 100644 index eb62c0f..0000000 --- a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/IlHost.java +++ /dev/null @@ -1,76 +0,0 @@ -package ch.srg.dataProvider.integrationlayer.request; - -import android.net.Uri; - -import androidx.annotation.NonNull; - -import java.io.Serializable; - -/** - * Copyright (c) SRG SSR. All rights reserved. - *

- * License information is available from the LICENSE file. - */ -public final class IlHost implements Serializable { - public static final IlHost PROD = new IlHost("PROD", "il.srgssr.ch"); - public static final IlHost TEST = new IlHost("TEST", "il-test.srgssr.ch"); - public static final IlHost STAGE = new IlHost("STAGE", "il-stage.srgssr.ch"); - public static final IlHost MMF = new IlHost("MMF", "play-mmf.herokuapp.com/android_26CE9E49-9600"); - public static final IlHost MMF_PUBLIC = new IlHost("MMF", "play-mmf.herokuapp.com"); - public static final IlHost PROD_SAM = new IlHost("PROD_SAM", "il.srgssr.ch/sam"); - public static final IlHost TEST_SAM = new IlHost("TEST_SAM", "il-test.srgssr.ch/sam"); - public static final IlHost STAGE_SAM = new IlHost("STAGE_SAM", "il-stage.srgssr.ch/sam"); - - @NonNull - private final String value; - @NonNull - private final String name; - - /** - * @param name designed name - * @param value hostname of the integration layer url - */ - public IlHost(@NonNull final String name, @NonNull final String value) { - this.name = name; - this.value = value; - } - - @NonNull - public String getValue() { - return value; - } - - @NonNull - public String getName() { - return name; - } - - @NonNull - public Uri getHostUri() { - return Uri.parse("https://" + value); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - IlHost ilHost = (IlHost) o; - - if (!value.equals(ilHost.value)) return false; - return name.equals(ilHost.name); - } - - @Override - public int hashCode() { - int result = value.hashCode(); - result = 31 * result + name.hashCode(); - return result; - } - - @NonNull - @Override - public String toString() { - return name; - } -} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/IlHost.kt b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/IlHost.kt new file mode 100644 index 0000000..d9f1e69 --- /dev/null +++ b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/IlHost.kt @@ -0,0 +1,52 @@ +package ch.srg.dataProvider.integrationlayer.request + +import android.net.Uri +import java.io.Serializable + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + * + * @property name designed name + * @property value hostname of the integration layer url + */ +data class IlHost( + val name: String, + val value: String, +) : Serializable { + val hostUri: Uri + get() = Uri.parse("https://$value") + + override fun toString(): String { + return name + } + + companion object { + private const val serialVersionUID = 1L + + @JvmField + val PROD = IlHost("PROD", "il.srgssr.ch") + + @JvmField + val TEST = IlHost("TEST", "il-test.srgssr.ch") + + @JvmField + val STAGE = IlHost("STAGE", "il-stage.srgssr.ch") + + @JvmField + val MMF = IlHost("MMF", "play-mmf.herokuapp.com/android_26CE9E49-9600") + + @JvmField + val MMF_PUBLIC = IlHost("MMF", "play-mmf.herokuapp.com") + + @JvmField + val PROD_SAM = IlHost("PROD_SAM", "il.srgssr.ch/sam") + + @JvmField + val TEST_SAM = IlHost("TEST_SAM", "il-test.srgssr.ch/sam") + + @JvmField + val STAGE_SAM = IlHost("STAGE_SAM", "il-stage.srgssr.ch/sam") + } +} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/IlUrn.java b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/IlUrn.java deleted file mode 100755 index e5393d4..0000000 --- a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/IlUrn.java +++ /dev/null @@ -1,186 +0,0 @@ -package ch.srg.dataProvider.integrationlayer.utils; - -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * This very simple class represents a URN provided by the IL. - */ -public class IlUrn { - private final static Pattern PATTERN_BUMAM = Pattern.compile("^urn:(srf|rts|rsi|rtr|swi|default):(?:[^:]+:)?(video|audio|videoset|show|assetgroup):([^:]+)$", Pattern.CASE_INSENSITIVE); - private final static Pattern PATTERN_SWISSTXT = Pattern.compile("^urn:(swisstxt):(?:[^:]+:)?(video|audio|videoset|show|assetgroup):(srf|rts|rsi|rtr|swi):([^:]+)$", Pattern.CASE_INSENSITIVE); - - public static final String ASSET_VIDEO = "video"; - public static final String ASSET_VIDEO_SET = "videoset"; - public static final String ASSET_AUDIO = "audio"; - public static final String ASSET_SHOW = "show"; - public static final String ASSET_GROUP = "assetgroup"; - - private String underlying; - - private String bu; - private String assetType; - private String id; - - /** - * @param urn urn can be any media identifier, valid or not - * @throws IllegalArgumentException if not a valid urn - */ - public IlUrn(@NonNull String urn) throws IllegalArgumentException { - if (!parseBuMam(urn) - && !parseSwissTxt(urn)) { - throw new IllegalArgumentException(String.format("URN '%s' does not match a valid URN pattern.", urn)); - } - } - - private boolean parseBuMam(@NonNull String urn) { - Matcher matcher; - matcher = PATTERN_BUMAM.matcher(urn); - if (matcher.matches()) { - bu = matcher.group(1).toLowerCase(Locale.US); - assetType = matcher.group(2).toLowerCase(Locale.US); - - if (assetType.equals(ASSET_GROUP)) { - // is a synonym of show (used in search request URN) - assetType = ASSET_SHOW; - } - - id = matcher.group(3); // Do not transform ID since it is case-sensitive. - underlying = "urn:" + bu + ":" + assetType + ":" + id; - return true; - } else { - return false; - } - } - - private boolean parseSwissTxt(@NonNull String urn) { - Matcher matcher = PATTERN_SWISSTXT.matcher(urn); - if (matcher.matches()) { - String swisstxt = matcher.group(1).toLowerCase(Locale.US); - bu = matcher.group(3).toLowerCase(Locale.US); - assetType = matcher.group(2).toLowerCase(Locale.US); - if (assetType.equals(ASSET_GROUP)) { - // is a synonym of show (used in search request URN) - assetType = ASSET_SHOW; - } - id = matcher.group(4); - underlying = "urn:" + swisstxt + ":" + assetType + ":" + bu + ":" + id; - return true; - } else { - return false; - } - } - - public IlUrn(@Nullable String bu, @Nullable String assetType, @Nullable String id) { - this.bu = bu == null ? "default" : bu; - this.assetType = assetType; - this.id = id; - underlying = "urn:" + this.bu + ":" + this.assetType + ":" + this.id; - } - - /** - * @param string potential URN - * @return true iff a valid urn (false if string is null) - */ - public static boolean isUrn(@Nullable String string) { - return string != null && (PATTERN_BUMAM.matcher(string).matches() || PATTERN_SWISSTXT.matcher(string).matches()); - } - - - public static String format(String bu, String assetType, String id) { - return String.format("urn:%s:%s:%s", bu, assetType, id); - } - - /** - * Returns Business Unit. - */ - public String getBu() { - return bu; - } - - /** - * Return Asset Type (either audio or video) - */ - public String getAssetType() { - return assetType; - } - - /** - * Return ID - */ - public String getId() { - return id; - } - - /** - * Returns the underlying string representation. - */ - @NonNull - @Override - public String toString() { - return underlying; - } - - public boolean isAudio() { - return TextUtils.equals(ASSET_AUDIO, assetType); - } - - public boolean isVideo() { - return TextUtils.equals(ASSET_VIDEO, assetType) || TextUtils.equals(ASSET_VIDEO_SET, assetType); - } - - /** - * @param urn urn can be any media identifier, valid or not - * @return true if urn is valid and of video type - */ - public static boolean isAudio(@Nullable String urn) { - return isUrn(urn) && new IlUrn(urn).isAudio(); - } - - /** - * @param urn urn can be any media identifier, valid or not - * @return true if urn is valid and of video type - */ - public static boolean isVideo(@Nullable String urn) { - return isUrn(urn) && new IlUrn(urn).isVideo(); - } - - public boolean isShow() { - return TextUtils.equals(ASSET_SHOW, assetType); - } - - - public boolean equalsToString(@NonNull String o) { - try { - return underlying.equals(new IlUrn(o).toString()); - } catch (IllegalArgumentException invalidUrnException) { - return false; - } - } - - /** - * Get Id for given urn string. - * - * @param urn urn can be any media identifier, valid or not - * @return id or full string if unparseable URN - */ - public static String getId(@Nullable String urn) { - return isUrn(urn) ? new IlUrn(urn).getId() : urn; - } - - /** - * Get Asset Type for given urn string. - * - * @param urn urn can be any media identifier, valid or not - * @return assetType or "tv" if unparseable URN - */ - public static String getAssetType(@Nullable String urn) { - return isUrn(urn) ? new IlUrn(urn).getAssetType() : ASSET_VIDEO; - } -} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/IlUrn.kt b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/IlUrn.kt new file mode 100644 index 0000000..4e9ec12 --- /dev/null +++ b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/IlUrn.kt @@ -0,0 +1,178 @@ +package ch.srg.dataProvider.integrationlayer.utils + +import java.util.regex.Pattern + +/** + * This very simple class represents a URN provided by the IL. + */ +class IlUrn private constructor() { + private var underlying: String? = null + + /** + * Returns Business Unit. + */ + var bu: String? = null + private set + + /** + * Return Asset Type (either audio or video) + */ + var assetType: String? = null + private set + + /** + * Return ID + */ + var id: String? = null + private set + + val isAudio: Boolean + get() = assetType == ASSET_AUDIO + + val isVideo: Boolean + get() = assetType == ASSET_VIDEO || assetType == ASSET_VIDEO_SET + + val isShow: Boolean + get() = assetType == ASSET_SHOW + + /** + * @param urn urn can be any media identifier, valid or not + * @throws IllegalArgumentException if not a valid urn + */ + @Throws(IllegalArgumentException::class) + constructor(urn: String) : this() { + require(parseBuMam(urn) || parseSwissTxt(urn)) { + "URN '$urn' does not match a valid URN pattern." + } + } + + constructor(bu: String?, assetType: String?, id: String?) : this() { + this.bu = bu ?: "default" + this.assetType = assetType + this.id = id + + underlying = "urn:" + this.bu + ":" + this.assetType + ":" + this.id + } + + fun equalsToString(other: String): Boolean { + return try { + underlying == IlUrn(other).toString() + } catch (_: IllegalArgumentException) { + false + } + } + + override fun toString(): String { + return underlying.orEmpty() + } + + @Suppress("MagicNumber") + private fun parseBuMam(urn: String): Boolean { + val matcher = PATTERN_BUMAM.matcher(urn) + if (matcher.matches()) { + bu = matcher.group(1)?.lowercase() + assetType = matcher.group(2)?.lowercase() + id = matcher.group(3) // Do not transform ID since it is case-sensitive. + + if (assetType == ASSET_GROUP) { + // Is a synonym of show (used in search request URN) + assetType = ASSET_SHOW + } + + underlying = "urn:$bu:$assetType:$id" + + return true + } else { + return false + } + } + + @Suppress("MagicNumber") + private fun parseSwissTxt(urn: String): Boolean { + val matcher = PATTERN_SWISSTXT.matcher(urn) + if (matcher.matches()) { + val swissTxt = matcher.group(1)?.lowercase() + bu = matcher.group(3)?.lowercase() + assetType = matcher.group(2)?.lowercase() + id = matcher.group(4) + + if (assetType == ASSET_GROUP) { + // Is a synonym of show (used in search request URN) + assetType = ASSET_SHOW + } + + underlying = "urn:$swissTxt:$assetType:$bu:$id" + + return true + } else { + return false + } + } + + companion object { + const val ASSET_VIDEO = "video" + const val ASSET_VIDEO_SET = "videoset" + const val ASSET_AUDIO = "audio" + const val ASSET_SHOW = "show" + const val ASSET_GROUP = "assetgroup" + + private val PATTERN_BUMAM = + "^urn:(srf|rts|rsi|rtr|swi|default):(?:[^:]+:)?(video|audio|videoset|show|assetgroup):([^:]+)$".toPattern(Pattern.CASE_INSENSITIVE) + private val PATTERN_SWISSTXT = + "^urn:(swisstxt):(?:[^:]+:)?(video|audio|videoset|show|assetgroup):(srf|rts|rsi|rtr|swi):([^:]+)$".toPattern(Pattern.CASE_INSENSITIVE) + + /** + * @param string potential URN + * @return true iff a valid urn (false if string is null) + */ + @JvmStatic + fun isUrn(string: String?): Boolean { + return string != null && (PATTERN_BUMAM.matcher(string).matches() || PATTERN_SWISSTXT.matcher(string).matches()) + } + + @JvmStatic + fun format(bu: String?, assetType: String?, id: String?): String { + return "urn:$bu:$assetType:$id" + } + + /** + * @param urn urn can be any media identifier, valid or not + * @return true if urn is valid and of video type + */ + @JvmStatic + fun isAudio(urn: String?): Boolean { + return urn != null && isUrn(urn) && IlUrn(urn).isAudio + } + + /** + * @param urn urn can be any media identifier, valid or not + * @return true if urn is valid and of video type + */ + @JvmStatic + fun isVideo(urn: String?): Boolean { + return urn != null && isUrn(urn) && IlUrn(urn).isVideo + } + + /** + * Get Id for given urn string. + * + * @param urn urn can be any media identifier, valid or not + * @return id or full string if unparseable URN + */ + @JvmStatic + fun getId(urn: String?): String? { + return if (urn != null && isUrn(urn)) IlUrn(urn).id else urn + } + + /** + * Get Asset Type for given urn string. + * + * @param urn urn can be any media identifier, valid or not + * @return assetType or "tv" if unparseable URN + */ + @JvmStatic + fun getAssetType(urn: String?): String? { + return if (urn != null && isUrn(urn)) IlUrn(urn).assetType else ASSET_VIDEO + } + } +} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparator.java b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparator.java deleted file mode 100644 index 2b6c1a9..0000000 --- a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparator.java +++ /dev/null @@ -1,72 +0,0 @@ -package ch.srg.dataProvider.integrationlayer.utils; - -import androidx.annotation.Nullable; - -import java.util.Comparator; -import java.util.List; - -import ch.srg.dataProvider.integrationlayer.data.remote.Quality; -import ch.srg.dataProvider.integrationlayer.data.remote.Resource; -import ch.srg.dataProvider.integrationlayer.data.remote.StreamingMethod; - -/** - * Copyright (c) SRG SSR. All rights reserved. - *

- * License information is available from the LICENSE file. - */ -public class StreamComparator implements Comparator { - private static final int HANDICAP_STREAMING = 1000; - private static final int HANDICAP_DVR = 10; - private static final int HANDICAP_QUALITY = 1; - - public static final int SCORE_NOT_SUPPORTED = Integer.MAX_VALUE; - - private final List orderedQualities; - private final List orderedStreaming; - @Nullable - private final List supportedDrms; - private final boolean dvrSupported; - - public StreamComparator(List orderedQualities, List orderedStreaming, @Nullable List supportedDrms, boolean dvrSupported) { - this.orderedQualities = orderedQualities; - this.orderedStreaming = orderedStreaming; - this.supportedDrms = supportedDrms; - this.dvrSupported = dvrSupported; - } - - @Override - public int compare(Resource lhs, Resource rhs) { - int l = score(lhs); - int r = score(rhs); - //noinspection UseCompareMethod - return l == r ? 0 : ((l > r) ? 1 : -1); - } - - public int score(Resource r) { - if (r.getStreamingMethod() == null - || (!dvrSupported && r.getDvr()) - || (!isSupportedDrm(r.getDrmList()))) { - return SCORE_NOT_SUPPORTED; - } - int indexOfStreaming = orderedStreaming.indexOf(r.getStreamingMethod()); // will return -1 if not in the list - int indexOfQuality = orderedQualities.indexOf(r.getQuality()); // will return -1 if not in the list - - return (indexOfStreaming < 0 ? orderedStreaming.size() : indexOfStreaming) * HANDICAP_STREAMING - + (indexOfQuality < 0 ? orderedQualities.size() : indexOfQuality) * HANDICAP_QUALITY - + (r.getDvr() ? 0 : HANDICAP_DVR); - } - - private boolean isSupportedDrm(List drmList) { - if (drmList == null) { - return true; - } - if (supportedDrms != null) { - for (Resource.Drm drm : drmList) { - if (supportedDrms.contains(drm.getType())) { - return true; - } - } - } - return false; - } -} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparator.kt b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparator.kt new file mode 100644 index 0000000..3151796 --- /dev/null +++ b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparator.kt @@ -0,0 +1,58 @@ +package ch.srg.dataProvider.integrationlayer.utils + +import ch.srg.dataProvider.integrationlayer.data.remote.Quality +import ch.srg.dataProvider.integrationlayer.data.remote.Resource +import ch.srg.dataProvider.integrationlayer.data.remote.StreamingMethod + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ +class StreamComparator( + private val orderedQualities: List, + private val orderedStreaming: List, + private val supportedDrms: List?, + private val dvrSupported: Boolean, +) : Comparator { + override fun compare(lhs: Resource, rhs: Resource): Int { + val l = score(lhs) + val r = score(rhs) + + return l.compareTo(r) + } + + fun score(resource: Resource): Int { + if ((!dvrSupported && resource.dvr) || (!isSupportedDrm(resource.drmList))) { + return SCORE_NOT_SUPPORTED + } + + val indexOfStreaming = orderedStreaming.indexOf(resource.streamingMethod) // Will return -1 if not in the list + val indexOfQuality = orderedQualities.indexOf(resource.quality) // Will return -1 if not in the list + val scoreStreaming = (if (indexOfStreaming < 0) orderedStreaming.size else indexOfStreaming) * HANDICAP_STREAMING + val scoreQuality = (if (indexOfQuality < 0) orderedQualities.size else indexOfQuality) * HANDICAP_QUALITY + val scoreDvr = if (resource.dvr) 0 else HANDICAP_DVR + + return scoreStreaming + scoreQuality + scoreDvr + } + + private fun isSupportedDrm(drmList: List?): Boolean { + if (drmList == null) { + return true + } + + if (supportedDrms.isNullOrEmpty()) { + return false + } + + return drmList.any { it.type in supportedDrms } + } + + companion object { + const val SCORE_NOT_SUPPORTED = Int.MAX_VALUE + + private const val HANDICAP_STREAMING = 1000 + private const val HANDICAP_DVR = 10 + private const val HANDICAP_QUALITY = 1 + } +} diff --git a/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/MediaFetcherInputs.kt b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/MediaFetcherInputs.kt new file mode 100644 index 0000000..47da78f --- /dev/null +++ b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/MediaFetcherInputs.kt @@ -0,0 +1,42 @@ +package ch.srg.dataProvider.integrationlayer.utils + +import ch.srg.dataProvider.integrationlayer.data.remote.Quality +import ch.srg.dataProvider.integrationlayer.data.remote.Resource +import ch.srg.dataProvider.integrationlayer.data.remote.StreamingMethod + +/** + * Copyright (c) SRG SSR. All rights reserved. + * + * + * License information is available from the LICENSE file. + */ +object MediaFetcherInputs { + @JvmField + @Transient + val STREAMING_ORDER_DEFAULT: List = listOf( + StreamingMethod.DASH, + StreamingMethod.HLS, + StreamingMethod.PROGRESSIVE + ) + + @Transient + val DRM_TYPES: List = listOf( + Resource.Drm.Type.WIDEVINE, Resource.Drm.Type.PLAYREADY + ) + + @JvmField + @Transient + val QUALITY_ORDER: List = listOf( + Quality.SD, + Quality.HQ, + Quality.HD + ) + + @JvmField + @Transient + val QUALITY_ORDER_HD: List = listOf( + Quality.HD, + Quality.HQ, + Quality.SD + ) +} diff --git a/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/ResourceBuilder.kt b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/ResourceBuilder.kt new file mode 100644 index 0000000..e4126a3 --- /dev/null +++ b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/ResourceBuilder.kt @@ -0,0 +1,31 @@ +package ch.srg.dataProvider.integrationlayer.utils + +import ch.srg.dataProvider.integrationlayer.data.remote.Quality +import ch.srg.dataProvider.integrationlayer.data.remote.Resource +import ch.srg.dataProvider.integrationlayer.data.remote.Resource.Drm +import ch.srg.dataProvider.integrationlayer.data.remote.StreamingMethod + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ +object ResourceBuilder { + fun createResourceFrom( + url: String, + quality: Quality, + streaming: StreamingMethod, + live: Boolean, + dvr: Boolean, + vararg drmTypes: Drm.Type + ): Resource { + var drmList: MutableList? = null + if (drmTypes.isNotEmpty()) { + drmList = ArrayList() + for (type in drmTypes) { + drmList.add(Drm(type, "license", null)) + } + } + return Resource(url = url, quality = quality, streamingMethod = streaming, live = live, dvr = dvr, drmList = drmList) + } +} diff --git a/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparatorAllPossibilitiesTest.kt b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparatorAllPossibilitiesTest.kt new file mode 100644 index 0000000..16a4648 --- /dev/null +++ b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparatorAllPossibilitiesTest.kt @@ -0,0 +1,224 @@ +package ch.srg.dataProvider.integrationlayer.utils + +import ch.srg.dataProvider.integrationlayer.data.remote.Quality +import ch.srg.dataProvider.integrationlayer.data.remote.Resource +import ch.srg.dataProvider.integrationlayer.data.remote.StreamingMethod +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import java.util.Collections + +/** + * Copyright (c) SRG SSR. All rights reserved. + * + * + * License information is available from the LICENSE file. + */ +@RunWith(Parameterized::class) +class StreamComparatorAllPossibilitiesTest( + private val qualitiesOrder: List, + private val streamingOrder: List, + private val dvrSupport: Boolean, + private val drmSupported: Boolean, + private val expectedResource: ExpectedResult, +) { + + class ExpectedResult(val quality: Quality, val streaming: StreamingMethod, val drmType: Resource.Drm.Type?, val dvr: Boolean) { + constructor(quality: Quality, streaming: StreamingMethod, dvr: Boolean) : this(quality, streaming, null, dvr) + } + + @Test + fun firstDashHdDrmFirstDvr() { + val comparator = StreamComparator(qualitiesOrder, streamingOrder, if (drmSupported) MediaFetcherInputs.DRM_TYPES else null, dvrSupport) + val listResource = listAllPossibilities() + sortAndPrint(comparator, listResource) + val r = listResource[0] + + checkResource(r, expectedResource) + } + + private fun checkResource(r: Resource, expectedResult: ExpectedResult) { + Assert.assertNotNull(r.streamingMethod) + Assert.assertNotNull(r.quality) + Assert.assertEquals(expectedResult.streaming, r.streamingMethod) + Assert.assertEquals(expectedResult.quality, r.quality) + Assert.assertEquals("hasDrm", expectedResult.drmType != null, r.hasDrm()) + r.drmList?.let { + Assert.assertTrue(it.contains(createDrm(Resource.Drm.Type.WIDEVINE))) + } + + Assert.assertEquals("dvr", expectedResult.dvr, r.dvr) + } + + private fun listAllPossibilities(): List { + val resourceList: MutableList = ArrayList() + // StreamComparator keep order in the resource list regarding to DRM situation. + resourceList.add( + ResourceBuilder.createResourceFrom( + "hd_hls_drm_ios", + Quality.HD, + StreamingMethod.HLS, + true, + true, + Resource.Drm.Type.FAIRPLAY + ) + ) + + resourceList.add( + ResourceBuilder.createResourceFrom( + "sd_dash_drm", + Quality.SD, + StreamingMethod.DASH, + true, + false, + Resource.Drm.Type.PLAYREADY, + Resource.Drm.Type.WIDEVINE + ) + ) + resourceList.add( + ResourceBuilder.createResourceFrom( + "sd_dash_drm_dvr", + Quality.SD, + StreamingMethod.DASH, + true, + true, + Resource.Drm.Type.PLAYREADY, + Resource.Drm.Type.WIDEVINE + ) + ) + resourceList.add( + ResourceBuilder.createResourceFrom( + "hd_dash_drm", + Quality.HD, + StreamingMethod.DASH, + true, + false, + Resource.Drm.Type.PLAYREADY, + Resource.Drm.Type.WIDEVINE + ) + ) + resourceList.add( + ResourceBuilder.createResourceFrom( + "hd_dash_drm_dvr", + Quality.HD, + StreamingMethod.DASH, + true, + true, + Resource.Drm.Type.PLAYREADY, + Resource.Drm.Type.WIDEVINE + ) + ) + resourceList.add(ResourceBuilder.createResourceFrom("sd_progressive", Quality.SD, StreamingMethod.PROGRESSIVE, true, false)) + resourceList.add(ResourceBuilder.createResourceFrom("sd_progressive_dvr", Quality.SD, StreamingMethod.PROGRESSIVE, true, true)) + resourceList.add(ResourceBuilder.createResourceFrom("hd_progressive", Quality.HD, StreamingMethod.PROGRESSIVE, true, false)) + resourceList.add(ResourceBuilder.createResourceFrom("hd_progressive_dvr", Quality.HD, StreamingMethod.PROGRESSIVE, true, true)) + + resourceList.add(ResourceBuilder.createResourceFrom("sd_dash", Quality.SD, StreamingMethod.DASH, true, false)) + resourceList.add(ResourceBuilder.createResourceFrom("sd_dash_dvr", Quality.SD, StreamingMethod.DASH, true, true)) + resourceList.add(ResourceBuilder.createResourceFrom("hd_dash", Quality.HD, StreamingMethod.DASH, true, false)) + resourceList.add(ResourceBuilder.createResourceFrom("hd_dash_dvr", Quality.HD, StreamingMethod.DASH, true, true)) + + resourceList.add(ResourceBuilder.createResourceFrom("sd_hls", Quality.SD, StreamingMethod.HLS, true, false)) + resourceList.add(ResourceBuilder.createResourceFrom("sd_hls_dvr", Quality.SD, StreamingMethod.HLS, true, true)) + resourceList.add(ResourceBuilder.createResourceFrom("hd_hls", Quality.HD, StreamingMethod.HLS, true, false)) + resourceList.add(ResourceBuilder.createResourceFrom("hd_hls_dvr", Quality.HD, StreamingMethod.HLS, true, true)) + + return resourceList + } + + + private fun sortAndPrint(comparator: StreamComparator, resourceList: List) { + Collections.sort(resourceList, comparator) + for (r in resourceList) { + println(r.url + " -> " + comparator.score(r)) + } + } + + companion object { + var WITH_DVR: Boolean = true + var NO_DVR: Boolean = false + var WITH_DRM: Boolean = true + var NO_DRM: Boolean = false + + var HLS_FIRST: List = listOf(StreamingMethod.HLS, StreamingMethod.DASH, StreamingMethod.PROGRESSIVE) + + @JvmStatic + @Parameterized.Parameters + fun parameters(): Iterable> { + return listOf( + arrayOf( + MediaFetcherInputs.QUALITY_ORDER_HD, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, NO_DVR, WITH_DRM, + ExpectedResult(Quality.HD, StreamingMethod.DASH, Resource.Drm.Type.WIDEVINE, NO_DVR) + ), + arrayOf( + MediaFetcherInputs.QUALITY_ORDER_HD, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, WITH_DVR, WITH_DRM, + ExpectedResult(Quality.HD, StreamingMethod.DASH, Resource.Drm.Type.WIDEVINE, WITH_DVR) + ), + arrayOf( + MediaFetcherInputs.QUALITY_ORDER, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, WITH_DVR, WITH_DRM, + ExpectedResult(Quality.SD, StreamingMethod.DASH, Resource.Drm.Type.WIDEVINE, WITH_DVR) + ), + + arrayOf( + MediaFetcherInputs.QUALITY_ORDER_HD, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, NO_DVR, WITH_DRM, + ExpectedResult(Quality.HD, StreamingMethod.DASH, Resource.Drm.Type.WIDEVINE, NO_DVR) + ), + arrayOf( + MediaFetcherInputs.QUALITY_ORDER, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, NO_DVR, WITH_DRM, + ExpectedResult(Quality.SD, StreamingMethod.DASH, Resource.Drm.Type.WIDEVINE, NO_DVR) + ), + + arrayOf( + MediaFetcherInputs.QUALITY_ORDER_HD, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, NO_DVR, NO_DRM, + ExpectedResult(Quality.HD, StreamingMethod.DASH, NO_DVR) + ), + arrayOf( + MediaFetcherInputs.QUALITY_ORDER, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, NO_DVR, NO_DRM, + ExpectedResult(Quality.SD, StreamingMethod.DASH, NO_DVR) + ), + + arrayOf( + MediaFetcherInputs.QUALITY_ORDER_HD, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, WITH_DVR, NO_DRM, + ExpectedResult(Quality.HD, StreamingMethod.DASH, WITH_DVR) + ), + arrayOf( + MediaFetcherInputs.QUALITY_ORDER, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, WITH_DVR, NO_DRM, + ExpectedResult(Quality.SD, StreamingMethod.DASH, WITH_DVR) + ), + + arrayOf( + MediaFetcherInputs.QUALITY_ORDER_HD, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, NO_DVR, WITH_DRM, + ExpectedResult(Quality.HD, StreamingMethod.DASH, Resource.Drm.Type.WIDEVINE, NO_DVR) + ), + arrayOf( + MediaFetcherInputs.QUALITY_ORDER, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, NO_DVR, WITH_DRM, + ExpectedResult(Quality.SD, StreamingMethod.DASH, Resource.Drm.Type.WIDEVINE, NO_DVR) + ), + + arrayOf( + MediaFetcherInputs.QUALITY_ORDER_HD, HLS_FIRST, NO_DVR, NO_DRM, + ExpectedResult(Quality.HD, StreamingMethod.HLS, NO_DVR) + ), + arrayOf( + MediaFetcherInputs.QUALITY_ORDER, HLS_FIRST, NO_DVR, NO_DRM, + ExpectedResult(Quality.SD, StreamingMethod.HLS, NO_DVR) + ), // no drm type because no HLS resources with Widevine + + arrayOf( + MediaFetcherInputs.QUALITY_ORDER_HD, HLS_FIRST, NO_DVR, WITH_DRM, + ExpectedResult(Quality.HD, StreamingMethod.HLS, null, NO_DVR) + ), + arrayOf( + MediaFetcherInputs.QUALITY_ORDER, HLS_FIRST, NO_DVR, WITH_DRM, + ExpectedResult(Quality.SD, StreamingMethod.HLS, null, NO_DVR) + ), + ) + } + + private fun createDrm(type: Resource.Drm.Type): Resource.Drm { + return Resource.Drm(type, "license", null) + } + + } +} diff --git a/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparatorTest.kt b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparatorTest.kt new file mode 100644 index 0000000..7707e22 --- /dev/null +++ b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/utils/StreamComparatorTest.kt @@ -0,0 +1,208 @@ +package ch.srg.dataProvider.integrationlayer.utils + +import ch.srg.dataProvider.integrationlayer.data.remote.Quality +import ch.srg.dataProvider.integrationlayer.data.remote.Resource +import ch.srg.dataProvider.integrationlayer.data.remote.StreamingMethod +import org.junit.Assert +import org.junit.Test +import java.util.Arrays +import java.util.Collections + +/** + * Copyright (c) SRG SSR. All rights reserved. + * + * + * License information is available from the LICENSE file. + */ +class StreamComparatorTest { + @Test + fun streamOrderWithUncompleteStreamingOrderList() { + val listStreamingOrder: List = listOf(StreamingMethod.DASH, StreamingMethod.HLS) + val comparator = StreamComparator(MediaFetcherInputs.QUALITY_ORDER, listStreamingOrder, null, true) + + val resourceList: MutableList = ArrayList() + resourceList.add(ResourceBuilder.createResourceFrom("1", Quality.HD, StreamingMethod.PROGRESSIVE, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("2", Quality.HD, StreamingMethod.HLS, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("3", Quality.HD, StreamingMethod.DASH, false, false)) + sortAndPrint(comparator, resourceList) + Assert.assertEquals(resourceList[0].streamingMethod, StreamingMethod.DASH) + + val resourceList1: MutableList = ArrayList() + resourceList1.add(ResourceBuilder.createResourceFrom("2", Quality.HD, StreamingMethod.HLS, false, false)) + resourceList1.add(ResourceBuilder.createResourceFrom("1", Quality.HD, StreamingMethod.PROGRESSIVE, false, false)) + resourceList1.add(ResourceBuilder.createResourceFrom("3", Quality.HD, StreamingMethod.DASH, false, false)) + sortAndPrint(comparator, resourceList1) + Assert.assertEquals(resourceList1[0].streamingMethod, StreamingMethod.DASH) + + val resourceList2: MutableList = ArrayList() + resourceList2.add(ResourceBuilder.createResourceFrom("3", Quality.HD, StreamingMethod.DASH, false, false)) + resourceList2.add(ResourceBuilder.createResourceFrom("1", Quality.HD, StreamingMethod.PROGRESSIVE, false, false)) + resourceList2.add(ResourceBuilder.createResourceFrom("2", Quality.HD, StreamingMethod.HLS, false, false)) + sortAndPrint(comparator, resourceList2) + Assert.assertEquals(resourceList2[0].streamingMethod, StreamingMethod.DASH) + } + + @Test + fun streamOrderWithUncompleteQualityOrderList() { + val listQualityOrder = Arrays.asList(Quality.HD, Quality.SD) + val comparator = StreamComparator(listQualityOrder, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, null, true) + + val resourceList: MutableList = ArrayList() + resourceList.add(ResourceBuilder.createResourceFrom("1", Quality.HQ, StreamingMethod.DASH, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("2", Quality.SD, StreamingMethod.DASH, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("3", Quality.HD, StreamingMethod.DASH, false, false)) + sortAndPrint(comparator, resourceList) + Assert.assertEquals(resourceList[0].quality, Quality.HD) + + val resourceList1: MutableList = ArrayList() + resourceList1.add(ResourceBuilder.createResourceFrom("2", Quality.SD, StreamingMethod.DASH, false, false)) + resourceList1.add(ResourceBuilder.createResourceFrom("1", Quality.HQ, StreamingMethod.DASH, false, false)) + resourceList1.add(ResourceBuilder.createResourceFrom("3", Quality.HD, StreamingMethod.DASH, false, false)) + sortAndPrint(comparator, resourceList1) + Assert.assertEquals(resourceList1[0].quality, Quality.HD) + + val resourceList2: MutableList = ArrayList() + resourceList2.add(ResourceBuilder.createResourceFrom("3", Quality.HD, StreamingMethod.DASH, false, false)) + resourceList2.add(ResourceBuilder.createResourceFrom("1", Quality.HQ, StreamingMethod.DASH, false, false)) + resourceList2.add(ResourceBuilder.createResourceFrom("2", Quality.SD, StreamingMethod.DASH, false, false)) + sortAndPrint(comparator, resourceList2) + Assert.assertEquals(resourceList2[0].quality, Quality.HD) + Assert.assertEquals(resourceList2[1].quality, Quality.SD) + } + + @Test + fun dashFirst() { + val comparator = StreamComparator(MediaFetcherInputs.QUALITY_ORDER, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, null, false) + + val resourceList: MutableList = ArrayList() + resourceList.add(ResourceBuilder.createResourceFrom("1", Quality.HD, StreamingMethod.PROGRESSIVE, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("2", Quality.HD, StreamingMethod.HLS, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("4", Quality.SD, StreamingMethod.PROGRESSIVE, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("3", Quality.HD, StreamingMethod.DASH, false, false)) + + sortAndPrint(comparator, resourceList) + Assert.assertEquals(resourceList[0].streamingMethod, StreamingMethod.DASH) + } + + @Test + fun hlsFirstWhenNoDash() { + val comparator = StreamComparator( + MediaFetcherInputs.QUALITY_ORDER_HD, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, MediaFetcherInputs + .DRM_TYPES, false + ) + + val resourceList: MutableList = ArrayList() + resourceList.add(ResourceBuilder.createResourceFrom("3", Quality.SD, StreamingMethod.PROGRESSIVE, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("1", Quality.HD, StreamingMethod.PROGRESSIVE, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("2", Quality.HD, StreamingMethod.HLS, false, false)) + + sortAndPrint(comparator, resourceList) + Assert.assertEquals(resourceList[0].streamingMethod, StreamingMethod.HLS) + } + + @Test + fun unsupportedDvr() { + val comparator = StreamComparator(MediaFetcherInputs.QUALITY_ORDER, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, null, false) + + val resourceList: MutableList = ArrayList() + resourceList.add(ResourceBuilder.createResourceFrom("1", Quality.HD, StreamingMethod.DASH, false, true)) + resourceList.add(ResourceBuilder.createResourceFrom("2", Quality.HD, StreamingMethod.HLS, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("3", Quality.HD, StreamingMethod.DASH, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("4", Quality.SD, StreamingMethod.PROGRESSIVE, false, false)) + sortAndPrint(comparator, resourceList) + Assert.assertEquals("3", resourceList[0].url) + + val resourceList2: MutableList = ArrayList() + resourceList2.add(ResourceBuilder.createResourceFrom("1", Quality.HD, StreamingMethod.DASH, false, true)) + resourceList2.add(ResourceBuilder.createResourceFrom("2", Quality.HD, StreamingMethod.HLS, false, false)) + resourceList2.add(ResourceBuilder.createResourceFrom("4", Quality.SD, StreamingMethod.PROGRESSIVE, false, false)) + sortAndPrint(comparator, resourceList2) + Assert.assertEquals("2", resourceList2[0].url) + } + + @Test + fun unsupportedDvrWithDrmFirst() { + val comparator = StreamComparator(MediaFetcherInputs.QUALITY_ORDER, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, null, false) + + val resourceList: MutableList = ArrayList() + resourceList.add(ResourceBuilder.createResourceFrom("1", Quality.HD, StreamingMethod.DASH, false, true)) + resourceList.add(ResourceBuilder.createResourceFrom("2", Quality.HD, StreamingMethod.HLS, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("3", Quality.HD, StreamingMethod.DASH, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("4", Quality.SD, StreamingMethod.PROGRESSIVE, false, false)) + sortAndPrint(comparator, resourceList) + Assert.assertEquals("3", resourceList[0].url) + + val resourceList2: MutableList = ArrayList() + resourceList2.add(ResourceBuilder.createResourceFrom("1", Quality.HD, StreamingMethod.DASH, false, true)) + resourceList2.add(ResourceBuilder.createResourceFrom("2", Quality.HD, StreamingMethod.HLS, false, false)) + resourceList2.add(ResourceBuilder.createResourceFrom("4", Quality.SD, StreamingMethod.PROGRESSIVE, false, false)) + sortAndPrint(comparator, resourceList2) + Assert.assertEquals("2", resourceList2[0].url) + } + + @Test + fun unsupportedDrm() { + val comparator = StreamComparator(MediaFetcherInputs.QUALITY_ORDER, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, null, false) + + val resourceList: MutableList = ArrayList() + resourceList.add(ResourceBuilder.createResourceFrom("1", Quality.HD, StreamingMethod.DASH, false, false, Resource.Drm.Type.FAIRPLAY)) + resourceList.add(ResourceBuilder.createResourceFrom("2", Quality.HD, StreamingMethod.HLS, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("3", Quality.HD, StreamingMethod.DASH, false, false)) + resourceList.add(ResourceBuilder.createResourceFrom("4", Quality.SD, StreamingMethod.PROGRESSIVE, false, false)) + sortAndPrint(comparator, resourceList) + Assert.assertEquals("3", resourceList[0].url) + + val resourceList2: MutableList = ArrayList() + resourceList2.add(ResourceBuilder.createResourceFrom("1", Quality.HD, StreamingMethod.DASH, false, false, Resource.Drm.Type.FAIRPLAY)) + resourceList2.add(ResourceBuilder.createResourceFrom("2", Quality.HD, StreamingMethod.HLS, false, false)) + resourceList2.add(ResourceBuilder.createResourceFrom("4", Quality.SD, StreamingMethod.PROGRESSIVE, false, false)) + sortAndPrint(comparator, resourceList2) + Assert.assertEquals("2", resourceList2[0].url) + } + + @Test + fun nullStreamingType() { + val comparator = StreamComparator(MediaFetcherInputs.QUALITY_ORDER, MediaFetcherInputs.STREAMING_ORDER_DEFAULT, null, true) + + val resourceList = listWithNullStreamingType() + sortAndPrint(comparator, resourceList) + + Assert.assertNotEquals("null", resourceList[0].url) + } + + private fun listWithNullStreamingType(): List { + val resourceList: MutableList = ArrayList() + resourceList.add(ResourceBuilder.createResourceFrom("hsl", Quality.HD, StreamingMethod.HLS, false, true)) + resourceList.add(ResourceBuilder.createResourceFrom("hsl", Quality.HD, StreamingMethod.DASH, false, true)) + resourceList.add(ResourceBuilder.createResourceFrom("drm_fairplay", Quality.HD, StreamingMethod.HLS, false, true, Resource.Drm.Type.FAIRPLAY)) + resourceList.add( + ResourceBuilder.createResourceFrom( + "drm_playready", + Quality.HD, + StreamingMethod.DASH, + false, + true, + Resource.Drm.Type.PLAYREADY + ) + ) + resourceList.add( + ResourceBuilder.createResourceFrom( + "drm_widevine", + Quality.HD, + StreamingMethod.DASH, + false, + true, + Resource.Drm.Type.WIDEVINE + ) + ) + return resourceList + } + + private fun sortAndPrint(comparator: StreamComparator, resourceList: List) { + Collections.sort(resourceList, comparator) + for (r in resourceList) { + println(r.url + " -> " + comparator.score(r)) + } + } + +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 22fc96f..1879561 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,6 @@ [versions] android-gradle-plugin = "8.6.0" androidx-activity = "1.9.1" -androidx-annotation = "1.8.2" androidx-lifecycle = "2.8.4" androidx-paging = "3.3.2" androidx-test-ext-junit = "1.2.1" @@ -15,7 +14,6 @@ robolectric = "4.13" [libraries] androidx-activity = { module = "androidx.activity:activity", version.ref = "androidx-activity" } -androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" } androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "androidx-lifecycle" } androidx-paging-common = { module = "androidx.paging:paging-common", version.ref = "androidx-paging" } androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext-junit" }