Skip to content

Commit

Permalink
Update some documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
MGaetan89 committed Mar 19, 2024
1 parent a877097 commit b3e982f
Show file tree
Hide file tree
Showing 20 changed files with 75 additions and 106 deletions.
1 change: 0 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ kotlinx-kover-gradle = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", v
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-http = { module = "io.ktor:ktor-http", version.ref = "ktor" }
Expand Down
1 change: 0 additions & 1 deletion pillarbox-core-business/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ dependencies {
testImplementation(libs.junit)
testImplementation(libs.kotlin.test)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.ktor.client.mock)
testImplementation(libs.mockk)
testImplementation(libs.mockk.dsl)
testRuntimeOnly(libs.robolectric)
Expand Down
14 changes: 8 additions & 6 deletions pillarbox-core-business/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,17 @@ In order to play an urn content with PillarboxPlayer, you have to create it like
```kotlin
val player = PillarboxPlayer(
context = context,
mediaSourceFactory = SRGMediaSource.Factory(context),
mediaSourceFactory = PillarboxMediaSourceFactory(context).apply {
addAssetLoader(SRGAssetLoader(context))
},
mediaItemTrackerProvider = DefaultMediaItemTrackerRepository()
)
```

### Create MediaItem with URN

In order to tell PillarboxPlayer to load a specific MediaItem with SRGMediaSource.Factory.
The MediaItem have to be created by `SRGMediaItemBuilder`.
In order to tell `PillarboxPlayer` to load a specific `MediaItem` with `PillarboxMediaSourceFactory`, the `MediaItem` has to be created with
`SRGMediaItemBuilder` :

```kotlin
val urnToPlay = "urn:rts:video:12345"
Expand Down Expand Up @@ -78,12 +80,12 @@ All exceptions thrown by `MediaCompositionMediaItemSource` are caught by the pla

## Going further

SRGMediaSource factory can be created with a MediaCompositionService, this MediaCompositionService can be used to retrieve a MediaComposition.
You can create you own MediaCompositionService for loading the MediaComposition.
`PillarboxMediaSource` factory can be created with a `MediaCompositionService`, which can be used to retrieve a `MediaComposition`. You can create
you own `MediaCompositionService` to load the `MediaComposition` :

```kotlin
class MediaCompositionMapDataSource : MediaCompositionService {
private val mediaCompositionMap = HashMap<Uri, MediaComposition>()
private val mediaCompositionMap = mutableMapOf<Uri, MediaComposition>()

override suspend fun fetchMediaComposition(uri: Uri): Result<MediaComposition> {
return mediaCompositionMap[uri]?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import ch.srgssr.pillarbox.core.business.source.MimeTypeSrg
import java.net.URL

/**
* Create a [MediaItem] that can be parsed by SRGMediaSource
* Create a [MediaItem] that can be parsed by [PillarboxMediaSource][ch.srgssr.pillarbox.player.source.PillarboxMediaSource].
*
* @param mediaItem Build a new [SRGMediaItemBuilder] from an existing [MediaItem].
*/
Expand Down Expand Up @@ -44,7 +44,7 @@ class SRGMediaItemBuilder(mediaItem: MediaItem) {
/**
* Set media metadata
*
* @param mediaMetadata The [MediaMetadata] to use set to [MediaItem].
* @param mediaMetadata The [MediaMetadata] to set to [MediaItem].
* @return this for convenience
*/
fun setMediaMetadata(mediaMetadata: MediaMetadata): SRGMediaItemBuilder {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal class ResourceSelector {
return try {
chapter.listResource?.first {
(it.type == Resource.Type.DASH || it.type == Resource.Type.HLS || it.type == Resource.Type.PROGRESSIVE) &&
(it.drmList == null || it.drmList.find { drm -> drm.type == Drm.Type.WIDEVINE } != null)
(it.drmList == null || it.drmList.any { drm -> drm.type == Drm.Type.WIDEVINE })
}
} catch (e: NoSuchElementException) {
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface MediaCompositionService {
/**
* Fetch media composition
*
* @param uri Uri to get MediaComposition.
* @param uri The uri of the [MediaComposition] to fetch.
* @return Result
*/
suspend fun fetchMediaComposition(uri: Uri): Result<MediaComposition>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ class SRGAssetLoader(
*/
fun interface MediaMetadataProvider {
/**
* Provide
* Feed the available information from the [resource], [chapter], and [mediaComposition] into the provided [mediaMetadataBuilder].
*
* @param mediaMetadataBuilder The [MediaMetadata.Builder] used to build MediaMetadata.
* @param mediaMetadataBuilder The [MediaMetadata.Builder] used to build the [MediaMetadata].
* @param resource The [Resource] the player will play.
* @param chapter The main [Chapter] from the mediaComposition
* @param chapter The main [Chapter] from the mediaComposition.
* @param mediaComposition The [MediaComposition] loaded from [MediaCompositionService].
*/
fun provide(
Expand All @@ -83,7 +83,7 @@ class SRGAssetLoader(
*
* @param trackerDataBuilder The [MediaItemTrackerData.Builder] to add trackers data.
* @param resource The [Resource] the player will play.
* @param chapter The main [Chapter] from the mediaComposition
* @param chapter The main [Chapter] from the mediaComposition.
* @param mediaComposition The [MediaComposition] loaded from [MediaCompositionService].
*/
fun provide(
Expand All @@ -107,7 +107,9 @@ class SRGAssetLoader(
var trackerDataProvider: TrackerDataProvider? = null

override fun canLoadAsset(mediaItem: MediaItem): Boolean {
return mediaItem.localConfiguration?.mimeType == MimeTypeSrg || mediaItem.localConfiguration?.uri?.lastPathSegment.isValidMediaUrn()
val localConfiguration = mediaItem.localConfiguration ?: return false

return localConfiguration.mimeType == MimeTypeSrg || localConfiguration.uri.lastPathSegment.isValidMediaUrn()
}

override suspend fun loadAsset(mediaItem: MediaItem): Asset {
Expand Down Expand Up @@ -171,7 +173,7 @@ class SRGAssetLoader(
}

private fun fillDrmConfiguration(resource: Resource): MediaItem.DrmConfiguration? {
val drm = resource.drmList.orEmpty().find { it.type == Drm.Type.WIDEVINE }
val drm = resource.drmList?.find { it.type == Drm.Type.WIDEVINE }
return drm?.let {
MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri(it.licenseUrl)
Expand All @@ -180,15 +182,15 @@ class SRGAssetLoader(
}

/**
* ComScore (MediaPulse) don't want to track audio. Integration layer doesn't fill analytics labels for audio content,
* ComScore (MediaPulse) doesn't want to track audio. Integration layer doesn't fill analytics labels for audio content,
* but only in [chapter] and [resource]. MediaComposition will still have analytics content.
*/
private fun getComScoreData(
mediaComposition: MediaComposition,
chapter: Chapter,
resource: Resource
): ComScoreTracker.Data? {
val comScoreData = HashMap<String, String>().apply {
val comScoreData = mutableMapOf<String, String>().apply {
chapter.comScoreAnalyticsLabels?.let {
mediaComposition.comScoreAnalyticsLabels?.let { mediaComposition -> putAll(mediaComposition) }
putAll(it)
Expand All @@ -203,15 +205,15 @@ class SRGAssetLoader(
}

/**
* ComScore (MediaPulse) don't want to track audio. Integration layer doesn't fill analytics labels for audio content,
* CommandersAct doesn't want to track audio. Integration layer doesn't fill analytics labels for audio content,
* but only in [chapter] and [resource]. MediaComposition will still have analytics content.
*/
private fun getCommandersActData(
mediaComposition: MediaComposition,
chapter: Chapter,
resource: Resource
): CommandersActTracker.Data? {
val commandersActData = HashMap<String, String>().apply {
val commandersActData = mutableMapOf<String, String>().apply {
mediaComposition.analyticsLabels?.let { mediaComposition -> putAll(mediaComposition) }
chapter.analyticsLabels?.let { putAll(it) }
resource.analyticsLabels?.let { putAll(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,42 +31,42 @@ import kotlin.test.Test
class SRGAssetLoaderTest {

private val mediaCompositionService = DummyMediaCompositionProvider()
private lateinit var mediaSourceFactory: SRGAssetLoader
private lateinit var assetLoader: SRGAssetLoader

@BeforeTest
fun init() {
val context: Context = ApplicationProvider.getApplicationContext()
mediaSourceFactory = SRGAssetLoader(context, mediaCompositionService)
assetLoader = SRGAssetLoader(context, mediaCompositionService)
}

@Test(expected = IllegalStateException::class)
fun testNoMediaId() = runTest {
mediaSourceFactory.loadAsset(MediaItem.Builder().build())
assetLoader.loadAsset(MediaItem.Builder().build())
}

@Test(expected = IllegalArgumentException::class)
fun testInvalidMediaId() = runTest {
mediaSourceFactory.loadAsset(SRGMediaItemBuilder("urn:rts:show:radio:1234").build())
assetLoader.loadAsset(SRGMediaItemBuilder("urn:rts:show:radio:1234").build())
}

@Test(expected = ResourceNotFoundException::class)
fun testNoResource() = runTest {
mediaSourceFactory.loadAsset(SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_NO_RESOURCES).build())
assetLoader.loadAsset(SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_NO_RESOURCES).build())
}

@Test(expected = ResourceNotFoundException::class)
fun testNoCompatibleResource() = runTest {
mediaSourceFactory.loadAsset(SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_INCOMPATIBLE_RESOURCE).build())
assetLoader.loadAsset(SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_INCOMPATIBLE_RESOURCE).build())
}

@Test
fun testCompatibleResource() = runTest {
mediaSourceFactory.loadAsset(SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_HLS_RESOURCE).build())
assetLoader.loadAsset(SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_HLS_RESOURCE).build())
}

@Test
fun testMetadata() = runTest {
val asset = mediaSourceFactory.loadAsset(SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_METADATA).build())
val asset = assetLoader.loadAsset(SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_METADATA).build())
val metadata = asset.mediaMetadata
val expected =
MediaMetadata.Builder()
Expand All @@ -86,7 +86,7 @@ class SRGAssetLoaderTest {
.setDescription("CustomDescription")
.build()

val asset = mediaSourceFactory.loadAsset(
val asset = assetLoader.loadAsset(
SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_METADATA)
.setMediaMetadata(input)
.build()
Expand All @@ -104,7 +104,7 @@ class SRGAssetLoaderTest {
val input = MediaMetadata.Builder()
.setTitle("CustomTitle")
.build()
val asset = mediaSourceFactory.loadAsset(
val asset = assetLoader.loadAsset(
SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_METADATA).setMediaMetadata(input).build()
)
val metadata = asset.mediaMetadata
Expand All @@ -119,11 +119,11 @@ class SRGAssetLoaderTest {

@Test
fun testCustomMetadataProvider() = runTest {
mediaSourceFactory.mediaMetadataProvider = SRGAssetLoader.MediaMetadataProvider { mediaMetadataBuilder, _, _, _ ->
assetLoader.mediaMetadataProvider = SRGAssetLoader.MediaMetadataProvider { mediaMetadataBuilder, _, _, _ ->
mediaMetadataBuilder.setTitle("My custom title")
mediaMetadataBuilder.setSubtitle("My custom subtitle")
}
val asset = mediaSourceFactory.loadAsset(SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_METADATA).build())
val asset = assetLoader.loadAsset(SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_METADATA).build())
val expected = MediaMetadata.Builder()
.setTitle("My custom title")
.setSubtitle("My custom subtitle")
Expand All @@ -133,14 +133,14 @@ class SRGAssetLoaderTest {

@Test(expected = BlockReasonException::class)
fun testBlockReason() = runTest {
mediaSourceFactory.loadAsset(
assetLoader.loadAsset(
SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_BLOCK_REASON).build()
)
}

@Test(expected = BlockReasonException::class)
fun testBlockedSegment() = runTest {
mediaSourceFactory.loadAsset(
assetLoader.loadAsset(
SRGMediaItemBuilder(DummyMediaCompositionProvider.URN_SEGMENT_BLOCK_REASON).build()
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ class CommandersActTrackerIntegrationTest {
mockk<ComScoreTracker>(relaxed = true)
}

val mediaCompositionWithFallbackDataSource = LocalMediaCompositionWithFallbackDataSource(context)
val mediaCompositionWithFallbackService = LocalMediaCompositionWithFallbackService(context)

player = DefaultPillarbox(
context = context,
mediaItemTrackerRepository = mediaItemTrackerRepository,
mediaCompositionService = mediaCompositionWithFallbackDataSource,
mediaCompositionService = mediaCompositionWithFallbackService,
clock = clock,
)
}
Expand Down Expand Up @@ -801,9 +801,9 @@ class CommandersActTrackerIntegrationTest {
assertTrue(tcMediaEvents.all { it.sourceId == null })
}

private class LocalMediaCompositionWithFallbackDataSource(
private class LocalMediaCompositionWithFallbackService(
context: Context,
private val fallbackDataSource: MediaCompositionService = HttpMediaCompositionService(),
private val fallbackService: MediaCompositionService = HttpMediaCompositionService(),
) : MediaCompositionService {
private var mediaComposition: MediaComposition? = null

Expand All @@ -820,7 +820,7 @@ class CommandersActTrackerIntegrationTest {
requireNotNull(mediaComposition)
}
} else {
fallbackDataSource.fetchMediaComposition(uri)
fallbackService.fetchMediaComposition(uri)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ data class DemoItem(
) : Serializable {
/**
* Convert to a [MediaItem]
* When [uri] is a Urn MediaItem is created with [SRGMediaItemBuilder].
* When [uri] is an URN, the [MediaItem] is created with [SRGMediaItemBuilder].
*/
fun toMediaItem(ilHost: URL = IlHost.PROD): MediaItem {
return if (uri.startsWith("urn:")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,26 @@ class CustomAssetLoader(context: Context) : AssetLoader(DefaultMediaSourceFactor
mediaMetadata = MediaMetadata.Builder()
.setTitle("${mediaItem.mediaMetadata.title}:NotSeekable")
.build(),
mediaSource = NoneSeekableMediaSource(mediaSource)
mediaSource = NotSeekableMediaSource(mediaSource)
)
}
}

/**
* A [MediaSource] that cannot be seekable.
* A [MediaSource] that cannot be seek.
*/
private class NoneSeekableMediaSource(mediaSource: MediaSource) :
private class NotSeekableMediaSource(mediaSource: MediaSource) :
WrappingMediaSource(mediaSource) {

override fun onChildSourceInfoRefreshed(newTimeline: Timeline) {
super.onChildSourceInfoRefreshed(TimelineWithUpdatedMediaItem(NoneSeekableContent(newTimeline), mediaItem))
super.onChildSourceInfoRefreshed(TimelineWithUpdatedMediaItem(NotSeekableContent(newTimeline), mediaItem))
}

/**
* Let's say the business required that the [NoneSeekableMediaSource] cannot be seek at any time.
* Let's say the business required that the [NotSeekableMediaSource] cannot be seek at any time.
* @param timeline The [Timeline] to forward.
*/
private class NoneSeekableContent(timeline: Timeline) : ForwardingTimeline(timeline) {
private class NotSeekableContent(timeline: Timeline) : ForwardingTimeline(timeline) {
override fun getWindow(windowIndex: Int, window: Window, defaultPositionProjectionUs: Long): Window {
val internalWindow = timeline.getWindow(windowIndex, window, defaultPositionProjectionUs)
internalWindow.isSeekable = false
Expand Down
1 change: 0 additions & 1 deletion pillarbox-player/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ dependencies {
testImplementation(libs.robolectric.shadows.framework)
testImplementation(libs.turbine)

androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.monitor)
androidTestRuntimeOnly(libs.androidx.test.runner)
androidTestImplementation(libs.junit)
Expand Down
Loading

0 comments on commit b3e982f

Please sign in to comment.