Skip to content

Commit

Permalink
Refactor media loading (#475)
Browse files Browse the repository at this point in the history
Co-authored-by: Gaëtan Muller <[email protected]>
  • Loading branch information
StaehliJ and MGaetan89 authored Mar 19, 2024
1 parent cf1577a commit 21b35c7
Show file tree
Hide file tree
Showing 70 changed files with 1,640 additions and 1,723 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
82 changes: 18 additions & 64 deletions pillarbox-core-business/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,16 @@

# Pillarbox Core Business module

Provides SRG SSR media URN `MediaItemSource` to Pillarbox. It basically converts an integration layer `MediaComposition` to a
playable `MediaItem`.
Provides SRG SSR media URN `MediaSource` to Pillarbox. It basically converts an integration layer `MediaComposition` to a
playable `MediaSource`.

Supported contents are :

- Video and Audio on demand
- Live streams (with and without DVR)
- Token protected
- DRM protected

Unsupported contents are :

- 360° content
- 360° content (Need to used the correct view)

## Integration

Expand All @@ -34,27 +31,23 @@ More information can be found on the [top level README](../docs/README.md)
In order to play an urn content with PillarboxPlayer, you have to create it like this :

```kotlin
val player = PillarboxPlayer(
val player = PillarboxPlayer(
context = context,
mediaItemSource = MediaCompositionMediaItemSource(DefaultMediaCompositionDataSource(baseUrl = IlHost.PROD)),
/**
* Can be skipped if you never play token-protected content.
*/
dataSourceFactory = AkamaiTokenDataSource.Factory(),
/**
* Required Default SRG MediaItem trackers
*/
mediaSourceFactory = PillarboxMediaSourceFactory(context).apply {
addAssetLoader(SRGAssetLoader(context))
},
mediaItemTrackerProvider = DefaultMediaItemTrackerRepository()
)
```

`MediaCompositionDataSourceImpl` retrieves a `MediaComposition` from the integration layer web service.

### Create MediaItem with URN

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"
val itemToPlay = MediaItem.Builder().setMediaId(urnToPlay).build()
val itemToPlay = SRGMediaItemBuilder(urnToPlay).build()

player.setMediaItem(itemToPlay)
```
Expand Down Expand Up @@ -85,58 +78,19 @@ All exceptions thrown by `MediaCompositionMediaItemSource` are caught by the pla
})
```

## Add custom trackers

`MediaItemTracker` can be added to the player. The data related to tracker have to be added during `MediaItem` creation inside
`MediaCompositionMediaItemSource`. `TrackerDataProvider` allow to add data for specific tracker.

### Create custom MediaItemTracker

```kotlin
class CustomTracker : MediaItemTracker {

data class Data(val mediaComposition: MediaComposition)

// implements here functions
}
```

### Create and add required custom tracker data

```kotlin
val mediaItemSource = MediaCompositionMediaItemSource(
mediaCompositionDataSource = mediaCompositionDataSource,
trackerDataProvider = object : TrackerDataProvider {
override fun update(trackerData: MediaItemTrackerData, resource: Resource, chapter: Chapter, mediaComposition: MediaComposition) {
trackerData.putData(CustomTracker::class.java, CustomTracker.Data(mediaComposition))
}
})
```

### Inject custom tracker to the player

```kotlin
val player = PillarboxPlayer(context = context,
mediaItemSource = mediaItemSource,
mediaItemTrackerProvider = DefaultMediaItemTrackerRepository().apply {
registerFactory(CustomTracker::class.java, CustomTracker.Factory())
}
)
```

## Going further

As you see, the `MediaCompositionMediaItemSource` is created from an interface, so you can load custom MediaComposition easily into Pillarbox by
implementing your own `MediaCompositionDataSource`.
`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 : MediaCompositionDataSource {
private val mediaCompositionMap = HashMap<String, MediaComposition>()
class MediaCompositionMapDataSource : MediaCompositionService {
private val mediaCompositionMap = mutableMapOf<Uri, MediaComposition>()

override suspend fun getMediaCompositionByUrn(urn: String): Result<MediaComposition> {
return mediaCompositionMap[urn]?.let {
override suspend fun fetchMediaComposition(uri: Uri): Result<MediaComposition> {
return mediaCompositionMap[uri]?.let {
Result.success(it)
} ?: Result.failure(IOException("$urn not found"))
} ?: Result.failure(IOException("$uri not found"))
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ package ch.srgssr.pillarbox.core.business
import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.media3.common.util.Clock
import androidx.media3.datasource.DataSource
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.LoadControl
import ch.srgssr.pillarbox.core.business.akamai.AkamaiTokenDataSource
import ch.srgssr.pillarbox.core.business.integrationlayer.service.DefaultMediaCompositionDataSource
import ch.srgssr.pillarbox.core.business.integrationlayer.service.HttpMediaCompositionService
import ch.srgssr.pillarbox.core.business.integrationlayer.service.MediaCompositionService
import ch.srgssr.pillarbox.core.business.source.SRGAssetLoader
import ch.srgssr.pillarbox.core.business.tracker.DefaultMediaItemTrackerRepository
import ch.srgssr.pillarbox.player.PillarboxLoadControl
import ch.srgssr.pillarbox.player.PillarboxPlayer
import ch.srgssr.pillarbox.player.SeekIncrement
import ch.srgssr.pillarbox.player.data.MediaItemSource
import ch.srgssr.pillarbox.player.source.PillarboxMediaSourceFactory
import ch.srgssr.pillarbox.player.tracker.MediaItemTrackerProvider
import kotlin.time.Duration.Companion.seconds

Expand All @@ -32,27 +32,22 @@ object DefaultPillarbox {
* @param context The context.
* @param seekIncrement The seek increment.
* @param mediaItemTrackerRepository The provider of MediaItemTracker, by default [DefaultMediaItemTrackerRepository].
* @param mediaItemSource The MediaItem source by default [MediaCompositionMediaItemSource].
* @param dataSourceFactory The Http exoplayer data source factory, by default [AkamaiTokenDataSource.Factory].
* @param loadControl The load control, by default [DefaultLoadControl].
* @param mediaCompositionService The [MediaCompositionService] to use, by default [HttpMediaCompositionService].
* @param loadControl The load control, by default [PillarboxLoadControl].
* @return [PillarboxPlayer] suited for SRG.
*/
operator fun invoke(
context: Context,
seekIncrement: SeekIncrement = defaultSeekIncrement,
mediaItemTrackerRepository: MediaItemTrackerProvider = DefaultMediaItemTrackerRepository(),
mediaItemSource: MediaItemSource = MediaCompositionMediaItemSource(
mediaCompositionDataSource = DefaultMediaCompositionDataSource(),
),
dataSourceFactory: DataSource.Factory = AkamaiTokenDataSource.Factory(),
mediaCompositionService: MediaCompositionService = HttpMediaCompositionService(),
loadControl: LoadControl = PillarboxLoadControl(),
): PillarboxPlayer {
return DefaultPillarbox(
context = context,
seekIncrement = seekIncrement,
mediaItemTrackerRepository = mediaItemTrackerRepository,
mediaItemSource = mediaItemSource,
dataSourceFactory = dataSourceFactory,
mediaCompositionService = mediaCompositionService,
loadControl = loadControl,
clock = Clock.DEFAULT,
)
Expand All @@ -64,9 +59,8 @@ object DefaultPillarbox {
* @param context The context.
* @param seekIncrement The seek increment.
* @param mediaItemTrackerRepository The provider of MediaItemTracker, by default [DefaultMediaItemTrackerRepository].
* @param mediaItemSource The MediaItem source by default [MediaCompositionMediaItemSource].
* @param dataSourceFactory The Http exoplayer data source factory, by default [AkamaiTokenDataSource.Factory].
* @param loadControl The load control, by default [DefaultLoadControl].
* @param mediaCompositionService The [MediaCompositionService] to use, by default [HttpMediaCompositionService].
* @param clock The internal clock used by the player.
* @return [PillarboxPlayer] suited for SRG.
*/
Expand All @@ -75,18 +69,16 @@ object DefaultPillarbox {
context: Context,
seekIncrement: SeekIncrement = defaultSeekIncrement,
mediaItemTrackerRepository: MediaItemTrackerProvider = DefaultMediaItemTrackerRepository(),
mediaItemSource: MediaItemSource = MediaCompositionMediaItemSource(
mediaCompositionDataSource = DefaultMediaCompositionDataSource(),
),
dataSourceFactory: DataSource.Factory = AkamaiTokenDataSource.Factory(),
loadControl: LoadControl = PillarboxLoadControl(),
loadControl: LoadControl = DefaultLoadControl(),
mediaCompositionService: MediaCompositionService = HttpMediaCompositionService(),
clock: Clock,
): PillarboxPlayer {
return PillarboxPlayer(
context = context,
seekIncrement = seekIncrement,
dataSourceFactory = dataSourceFactory,
mediaItemSource = mediaItemSource,
mediaSourceFactory = PillarboxMediaSourceFactory(context).apply {
addAssetLoader(SRGAssetLoader(context, mediaCompositionService))
},
mediaItemTrackerProvider = mediaItemTrackerRepository,
loadControl = loadControl,
clock = clock,
Expand Down
Loading

0 comments on commit 21b35c7

Please sign in to comment.