From f29591e7995821f321e0b35c63253e763d71e470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 9 Feb 2024 11:14:55 +0100 Subject: [PATCH] MediaItemTrackerData is immutable like MediaItem. --- .../MediaCompositionMediaItemSource.kt | 20 +- .../core/business/TrackerDataProvider.kt | 4 +- .../pillarbox/player/extension/MediaItem.kt | 2 +- .../player/tracker/MediaItemTrackerData.kt | 67 +++++- .../player/extension/MediaItemTest.kt | 5 +- .../CurrentMediaItemTrackerAreEqualTest.kt | 190 ++++++++++++++++++ .../tracker/CurrentMediaItemTrackerTest.kt | 49 +---- .../tracker/MediaItemTrackerDataTest.kt | 66 ++++-- 8 files changed, 317 insertions(+), 86 deletions(-) create mode 100644 pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTrackerAreEqualTest.kt diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/MediaCompositionMediaItemSource.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/MediaCompositionMediaItemSource.kt index eb0890940..3aa923666 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/MediaCompositionMediaItemSource.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/MediaCompositionMediaItemSource.kt @@ -109,15 +109,17 @@ class MediaCompositionMediaItemSource( if (resource.tokenType == Resource.TokenType.AKAMAI) { uri = appendTokenQueryToUri(uri) } - val trackerData = mediaItem.getMediaItemTrackerData() - trackerDataProvider?.update(trackerData, resource, chapter, result) - trackerData.putData(SRGEventLoggerTracker::class.java, null) - getComScoreData(result, chapter, resource)?.let { - trackerData.putData(ComScoreTracker::class.java, it) - } - getCommandersActData(result, chapter, resource)?.let { - trackerData.putData(CommandersActTracker::class.java, it) - } + val trackerData = mediaItem.getMediaItemTrackerData().buildUpon().apply { + trackerDataProvider?.update(this, resource, chapter, result) + putData(SRGEventLoggerTracker::class.java, null) + getComScoreData(result, chapter, resource)?.let { + putData(ComScoreTracker::class.java, it) + } + getCommandersActData(result, chapter, resource)?.let { + putData(CommandersActTracker::class.java, it) + } + }.build() + return mediaItem.buildUpon() .setMediaMetadata(fillMetaData(mediaItem.mediaMetadata, chapter)) .setDrmConfiguration(fillDrmConfiguration(resource)) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/TrackerDataProvider.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/TrackerDataProvider.kt index 854d20e20..806b1bd4c 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/TrackerDataProvider.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/TrackerDataProvider.kt @@ -16,13 +16,13 @@ interface TrackerDataProvider { /** * Update tracker data with given integration layer data. * - * @param trackerData The [MediaItemTrackerData] to update. + * @param trackerData The [MediaItemTrackerData.Builder] to update. * @param resource The selected [Resource]. * @param chapter The selected [Chapter]. * @param mediaComposition The loaded [MediaComposition]. */ fun update( - trackerData: MediaItemTrackerData, + trackerData: MediaItemTrackerData.Builder, resource: Resource, chapter: Chapter, mediaComposition: MediaComposition diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/MediaItem.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/MediaItem.kt index 2ed36d498..83e919240 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/MediaItem.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/MediaItem.kt @@ -25,7 +25,7 @@ fun MediaItem.getMediaItemTrackerDataOrNull(): MediaItemTrackerData? { * @return current [MediaItemTrackerData] or create. */ fun MediaItem.getMediaItemTrackerData(): MediaItemTrackerData { - return getMediaItemTrackerDataOrNull() ?: MediaItemTrackerData() + return getMediaItemTrackerDataOrNull() ?: MediaItemTrackerData.EMPTY } /** diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerData.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerData.kt index 96c9a090c..735cd6fb6 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerData.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerData.kt @@ -5,12 +5,9 @@ package ch.srgssr.pillarbox.player.tracker /** - * MediaItem tracker data. - * - * @constructor Create empty Tracker data + * Immutable MediaItem tracker data. */ -class MediaItemTrackerData { - private val map = HashMap, Any?>() +class MediaItemTrackerData private constructor(private val map: Map, Any?>) { /** * List of tracker class that have data. @@ -43,13 +40,61 @@ class MediaItemTrackerData { } /** - * Put data for trackerClass + * Build upon * - * @param T extends [MediaItemTracker]. - * @param trackerClass The class of the [MediaItemTracker]. - * @param data The data to associated with any instance of trackerClass. + * @return A builder filled with current data. */ - fun putData(trackerClass: Class, data: Any? = null) { - map[trackerClass] = data + fun buildUpon(): Builder = Builder(this) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MediaItemTrackerData + + return map == other.map + } + + override fun hashCode(): Int { + return map.hashCode() + } + + override fun toString(): String { + return "MediaItemTrackerData(map=$map)" + } + + companion object { + /** + * Empty [MediaItemTrackerData]. + */ + val EMPTY = MediaItemTrackerData(emptyMap()) + } + + /** + * Builder + *y + * @param source set this builder with source value. + */ + class Builder(source: MediaItemTrackerData = EMPTY) { + private val map = HashMap, Any?>(source.map) + + /** + * Put data for trackerClass + * + * @param T extends [MediaItemTracker]. + * @param trackerClass The class of the [MediaItemTracker]. + * @param data The data to associated with any instance of trackerClass. + */ + fun putData(trackerClass: Class, data: Any? = null): Builder { + map[trackerClass] = data + return this + } + + /** + * Build + * + * @return a new instance of [MediaItemTrackerData] + */ + fun build(): MediaItemTrackerData = MediaItemTrackerData(map.toMap()) } } diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/MediaItemTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/MediaItemTest.kt index 1fee2cbe6..5616addc6 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/MediaItemTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/MediaItemTest.kt @@ -35,8 +35,9 @@ class MediaItemTest { @Test fun `getMediaItemTrackerData with tag set`() { - val mediaItemTrackerData = MediaItemTrackerData() - mediaItemTrackerData.putData(MediaItemTracker::class.java) + val mediaItemTrackerData = MediaItemTrackerData.Builder().apply { + putData(MediaItemTracker::class.java) + }.build() val mediaItem = MediaItem.Builder() .setUri(mockk()) diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTrackerAreEqualTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTrackerAreEqualTest.kt new file mode 100644 index 000000000..960da7ce6 --- /dev/null +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTrackerAreEqualTest.kt @@ -0,0 +1,190 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.tracker + +import androidx.media3.common.MediaItem +import androidx.media3.common.MediaMetadata +import androidx.media3.exoplayer.ExoPlayer +import androidx.test.ext.junit.runners.AndroidJUnit4 +import ch.srgssr.pillarbox.player.extension.getMediaItemTrackerData +import ch.srgssr.pillarbox.player.extension.setTrackerData +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@RunWith(AndroidJUnit4::class) +class CurrentMediaItemTrackerAreEqualTest { + + @Test + fun `areEqual both mediaItem are null`() { + assertTrue(CurrentMediaItemTracker.areEqual(null, null)) + } + + @Test + fun `areEqual first mediaItem is null`() { + assertFalse(CurrentMediaItemTracker.areEqual(null, MediaItem.EMPTY)) + } + + @Test + fun `areEqual second mediaItem is null`() { + assertFalse(CurrentMediaItemTracker.areEqual(MediaItem.EMPTY, null)) + } + + @Test + fun `areEqual with different media id without tag and url`() { + val mediaItem = createMediaItemWithMediaId("M1") + val mediaItem2 = createMediaItemWithMediaId("M2") + assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual with same media id without tag and url`() { + val mediaItem = createMediaItemWithMediaId("M1") + val mediaItem2 = createMediaItemWithMediaId("M1") + assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual with one default media id`() { + val mediaItem = createMediaItemWithMediaId(MediaItem.DEFAULT_MEDIA_ID) + val mediaItem2 = createMediaItemWithMediaId("M1") + assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual with both default media id`() { + val mediaItem = createMediaItemWithMediaId(MediaItem.DEFAULT_MEDIA_ID) + val mediaItem2 = createMediaItemWithMediaId(MediaItem.DEFAULT_MEDIA_ID) + assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual with same media id same url same tag`() { + val mediaId = "M1" + val url = "https://streaming.com/video.mp4" + val mediaItem = createMediaItemWithMediaId(mediaId = mediaId, url = url, tag = "Tag1") + val mediaItem2 = createMediaItemWithMediaId(mediaId = mediaId, url = url, tag = "Tag1") + assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual with same media id same url without tag`() { + val mediaId = "M1" + val url = "https://streaming.com/video.mp4" + val mediaItem = createMediaItemWithMediaId(mediaId = mediaId, url = url) + val mediaItem2 = createMediaItemWithMediaId(mediaId = mediaId, url = url) + assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual with same media id same url and different tag`() { + val mediaId = "M1" + val url = "https://streaming.com/video.mp4" + val mediaItem = createMediaItemWithMediaId(mediaId = mediaId, url = url, tag = null) + val mediaItem2 = createMediaItemWithMediaId(mediaId = mediaId, url = url, tag = "Tag2") + assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual with same media id different url and same tag`() { + val mediaId = "M1" + val url = "https://streaming.com/video.mp4" + val mediaItem = createMediaItemWithMediaId(mediaId = mediaId, url = url, tag = "Tag1") + val mediaItem2 = createMediaItemWithMediaId(mediaId = mediaId, url = "https://streaming.com/video2.mp4", tag = "Tag1") + assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual no media id same url different tag`() { + val mediaItem = MediaItem.Builder() + .setUri("https://streaming.com/video.mp4") + .setTag("Tag1") + .build() + + val mediaItem2 = mediaItem.buildUpon() + .setTag("Tag2") + .build() + assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual same MediaItemTrackerData content`() { + val mediaItem = MediaItem.Builder() + .setUri("https://streaming.com/video.mp4") + .setTrackerData(MediaItemTrackerData.Builder().apply { putData(Tracker::class.java, "data1") }.build()) + .build() + + val mediaItem2 = MediaItem.Builder() + .setUri("https://streaming.com/video.mp4") + .setTrackerData(MediaItemTrackerData.Builder().apply { putData(Tracker::class.java, "data1") }.build()) + .build() + assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual same MediaItemTrackerData`() { + val mediaItem = MediaItem.Builder() + .setUri("https://streaming.com/video.mp4") + .setTrackerData(MediaItemTrackerData.Builder().apply { putData(Tracker::class.java, "data1") }.build()) + .build() + + val mediaItem2 = mediaItem.buildUpon() + .setTrackerData(mediaItem.getMediaItemTrackerData().buildUpon().apply { putData(Tracker::class.java, "data1") }.build()) + .build() + assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual same MediaItemTrackerData but different MediaMetadata`() { + val mediaItem = MediaItem.Builder() + .setUri("https://streaming.com/video.mp4") + .setTrackerData(MediaItemTrackerData.Builder().apply { putData(Tracker::class.java, "data1") }.build()) + .build() + + val mediaItem2 = mediaItem.buildUpon() + .setTrackerData(mediaItem.getMediaItemTrackerData().buildUpon().apply { putData(Tracker::class.java, "data1") }.build()) + .setMediaMetadata(MediaMetadata.Builder().setTitle("New title").build()) + .build() + assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areNotEqual different data`() { + val mediaItem = MediaItem.Builder() + .setUri("https://streaming.com/video.mp4") + .setTrackerData(MediaItemTrackerData.Builder().apply { putData(Tracker::class.java, "data1") }.build()) + .build() + + val mediaItem2 = mediaItem.buildUpon() + .setTrackerData(mediaItem.getMediaItemTrackerData().buildUpon().apply { putData(Tracker::class.java, "data2") }.build()) + .build() + assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + private class Tracker : MediaItemTracker { + override fun start(player: ExoPlayer, initialData: Any?) { + // Nothing + } + + override fun stop(player: ExoPlayer, reason: MediaItemTracker.StopReason, positionMs: Long) { + // Nothing + } + } + + companion object { + private fun createMediaItemWithMediaId( + mediaId: String, + url: String? = null, + tag: Any? = null, + ): MediaItem { + return MediaItem.Builder() + .setUri(url) + .setMediaId(mediaId) + .setTag(tag) + .build() + } + } +} diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTrackerTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTrackerTest.kt index 8dec305bc..169c13bd2 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTrackerTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTrackerTest.kt @@ -17,8 +17,6 @@ import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue @RunWith(AndroidJUnit4::class) class CurrentMediaItemTrackerTest { @@ -52,51 +50,6 @@ class CurrentMediaItemTrackerTest { clearAllMocks() } - @Test - fun `areEqual both mediaItem are null`() { - assertTrue(CurrentMediaItemTracker.areEqual(null, null)) - } - - @Test - fun `areEqual first mediaItem is null`() { - val mediaItem = createMediaItemWithMediaId("M1") - assertFalse(CurrentMediaItemTracker.areEqual(null, mediaItem)) - } - - @Test - fun `areEqual second mediaItem is null`() { - val mediaItem = createMediaItemWithMediaId("M1") - assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, null)) - } - - @Test - fun `areEqual with different media id`() { - val mediaItem = createMediaItemWithMediaId("M1") - val mediaItem2 = createMediaItemWithMediaId("M2") - assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) - } - - @Test - fun `areEqual with same media id`() { - val mediaItem = createMediaItemWithMediaId("M1") - val mediaItem2 = createMediaItemWithMediaId("M1") - assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) - } - - @Test - fun `areEqual with one default media id`() { - val mediaItem = createMediaItemWithMediaId(MediaItem.DEFAULT_MEDIA_ID) - val mediaItem2 = createMediaItemWithMediaId("M1") - assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) - } - - @Test - fun `areEqual with both default media id`() { - val mediaItem = createMediaItemWithMediaId(MediaItem.DEFAULT_MEDIA_ID) - val mediaItem2 = createMediaItemWithMediaId(MediaItem.DEFAULT_MEDIA_ID) - assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) - } - @Test fun `simple MediaItem without tracker`() { val mediaItem = createMediaItemWithoutTracker("M1") @@ -340,7 +293,7 @@ class CurrentMediaItemTrackerTest { return MediaItem.Builder() .setUri(uri) .setMediaId(mediaId) - .setTag(MediaItemTrackerData().apply { putData(TestTracker::class.java, mediaId) }) + .setTag(MediaItemTrackerData.Builder().apply { putData(TestTracker::class.java, mediaId) }.build()) .build() } diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerDataTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerDataTest.kt index 82d3cc515..68f3f8292 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerDataTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerDataTest.kt @@ -7,30 +7,70 @@ package ch.srgssr.pillarbox.player.tracker import androidx.media3.exoplayer.ExoPlayer import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNotEquals import kotlin.test.assertNull import kotlin.test.assertTrue class MediaItemTrackerDataTest { @Test fun `media item tracker data`() { - val mediaItemTrackerData = MediaItemTrackerData() + val emptyMediaItemTrackerData = MediaItemTrackerData.EMPTY val mediaItemTracker1 = MediaItemTracker1() val mediaItemTracker2 = MediaItemTracker2() - assertTrue(mediaItemTrackerData.trackers.isEmpty()) - assertNull(mediaItemTrackerData.getData(mediaItemTracker1)) - assertNull(mediaItemTrackerData.getDataAs(mediaItemTracker1)) - assertNull(mediaItemTrackerData.getData(mediaItemTracker2)) - assertNull(mediaItemTrackerData.getDataAs(mediaItemTracker2)) + assertTrue(emptyMediaItemTrackerData.trackers.isEmpty()) + assertNull(emptyMediaItemTrackerData.getData(mediaItemTracker1)) + assertNull(emptyMediaItemTrackerData.getDataAs(mediaItemTracker1)) + assertNull(emptyMediaItemTrackerData.getData(mediaItemTracker2)) + assertNull(emptyMediaItemTrackerData.getDataAs(mediaItemTracker2)) - mediaItemTrackerData.putData(mediaItemTracker1::class.java, "Some value") - mediaItemTrackerData.putData(mediaItemTracker2::class.java) + val mediaItemTrackerDataUpdated = emptyMediaItemTrackerData.buildUpon() + .putData(mediaItemTracker1::class.java, "Some value") + .putData(mediaItemTracker2::class.java) + .build() - assertEquals(setOf(mediaItemTracker1::class.java, mediaItemTracker2::class.java), mediaItemTrackerData.trackers) - assertEquals("Some value", mediaItemTrackerData.getData(mediaItemTracker1)) - assertEquals("Some value", mediaItemTrackerData.getDataAs(mediaItemTracker1)) - assertNull(mediaItemTrackerData.getData(mediaItemTracker2)) - assertNull(mediaItemTrackerData.getDataAs(mediaItemTracker2)) + assertEquals(setOf(mediaItemTracker1::class.java, mediaItemTracker2::class.java), mediaItemTrackerDataUpdated.trackers) + assertEquals("Some value", mediaItemTrackerDataUpdated.getData(mediaItemTracker1)) + assertEquals("Some value", mediaItemTrackerDataUpdated.getDataAs(mediaItemTracker1)) + assertNull(mediaItemTrackerDataUpdated.getData(mediaItemTracker2)) + assertNull(mediaItemTrackerDataUpdated.getDataAs(mediaItemTracker2)) + } + + @Test + fun `empty media item tracker data are equals`() { + assertEquals(MediaItemTrackerData.EMPTY, MediaItemTrackerData.Builder().build()) + } + + @Test + fun `media item tracker data are equals`() { + val mediaItemTrackerData1 = MediaItemTrackerData.Builder().apply { + putData(MediaItemTracker1::class.java, "Data1") + putData(MediaItemTracker2::class.java, "Data2") + }.build() + val mediaItemTrackerData2 = MediaItemTrackerData.Builder().apply { + putData(MediaItemTracker1::class.java, "Data1") + putData(MediaItemTracker2::class.java, "Data2") + }.build() + assertEquals(mediaItemTrackerData1, mediaItemTrackerData2) + } + + @Test + fun `media item tracker data are not equals when data changes`() { + val mediaItemTrackerData1 = MediaItemTrackerData.Builder().apply { + putData(MediaItemTracker1::class.java, "Data1") + putData(MediaItemTracker2::class.java, "Data2") + }.build() + val mediaItemTrackerData2 = MediaItemTrackerData.Builder().apply { + putData(MediaItemTracker1::class.java, "Data1") + }.build() + assertNotEquals(mediaItemTrackerData1, mediaItemTrackerData2) + val mediaItemTrackerData3 = MediaItemTrackerData.Builder().apply { + putData(MediaItemTracker1::class.java, "Data1") + } + val mediaItemTrackerData4 = MediaItemTrackerData.Builder().apply { + putData(MediaItemTracker1::class.java, "Data2") + } + assertNotEquals(mediaItemTrackerData3, mediaItemTrackerData4) } private open class EmptyMediaItemTracker : MediaItemTracker {