Skip to content

Commit

Permalink
Revisit media item tracker (#717)
Browse files Browse the repository at this point in the history
Co-authored-by: Gaëtan Muller <[email protected]>
Co-authored-by: Gaëtan Muller <[email protected]>
  • Loading branch information
3 people authored Sep 27, 2024
1 parent 5d0b010 commit 1dc6530
Show file tree
Hide file tree
Showing 43 changed files with 857 additions and 1,667 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import ch.srgssr.pillarbox.analytics.commandersact.CommandersAct
import ch.srgssr.pillarbox.analytics.commandersact.CommandersActEvent
import ch.srgssr.pillarbox.analytics.commandersact.CommandersActPageView
import ch.srgssr.pillarbox.analytics.commandersact.CommandersActSrg
import ch.srgssr.pillarbox.analytics.commandersact.NoOpCommandersAct
import ch.srgssr.pillarbox.analytics.comscore.ComScore
import ch.srgssr.pillarbox.analytics.comscore.ComScorePageView
import ch.srgssr.pillarbox.analytics.comscore.ComScoreSrg
import ch.srgssr.pillarbox.analytics.comscore.NoOpComScore

/**
* Analytics for SRG SSR
Expand Down Expand Up @@ -44,18 +46,18 @@ object SRGAnalytics {
* SRG CommandersAct analytics, do not use it unless you don't have any other choice!
* Meant to be used internally inside Pillarbox
*/
val commandersAct: CommandersAct?
val commandersAct: CommandersAct
get() {
return instance?.commandersAct
return instance?.commandersAct ?: NoOpCommandersAct
}

/**
* SRG ComScore analytics, do not use it unless you don't have any other choice!
* Meant to be used internally inside Pillarbox
*/
val comScore: ComScore?
val comScore: ComScore
get() {
return instance?.comScore
return instance?.comScore ?: NoOpComScore
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,20 @@ interface CommandersAct {
*/
fun setConsentServices(consentServices: List<String>)
}

internal object NoOpCommandersAct : CommandersAct {

override fun sendPageView(pageView: CommandersActPageView) = Unit

override fun sendEvent(event: CommandersActEvent) = Unit

override fun sendTcMediaEvent(event: TCMediaEvent) = Unit

override fun putPermanentData(labels: Map<String, String>) = Unit

override fun removePermanentData(label: String) = Unit

override fun getPermanentDataLabel(label: String): String? = null

override fun setConsentServices(consentServices: List<String>) = Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,16 @@ interface ComScore {
*/
fun setUserConsent(userConsent: ComScoreUserConsent)
}

internal object NoOpComScore : ComScore {

override fun sendPageView(pageView: ComScorePageView) = Unit

override fun putPersistentLabels(labels: Map<String, String>) = Unit

override fun removePersistentLabel(label: String) = Unit

override fun getPersistentLabel(label: String): String? = null

override fun setUserConsent(userConsent: ComScoreUserConsent) = Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ import androidx.media3.exoplayer.LoadControl
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.PillarboxExoPlayer
import ch.srgssr.pillarbox.player.PillarboxExoPlayer.Companion.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION
import ch.srgssr.pillarbox.player.PillarboxLoadControl
import ch.srgssr.pillarbox.player.SeekIncrement
import ch.srgssr.pillarbox.player.source.PillarboxMediaSourceFactory
import ch.srgssr.pillarbox.player.tracker.MediaItemTrackerProvider
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration
Expand All @@ -37,7 +35,6 @@ object DefaultPillarbox {
* @param context The context.
* @param seekIncrement The seek increment.
* @param maxSeekToPreviousPosition The [Player.getMaxSeekToPreviousPosition] value.
* @param mediaItemTrackerRepository The provider of MediaItemTracker, by default [DefaultMediaItemTrackerRepository].
* @param mediaCompositionService The [MediaCompositionService] to use, by default [HttpMediaCompositionService].
* @param loadControl The load control, by default [PillarboxLoadControl].
* @return [PillarboxExoPlayer] suited for SRG.
Expand All @@ -46,15 +43,13 @@ object DefaultPillarbox {
context: Context,
seekIncrement: SeekIncrement = defaultSeekIncrement,
maxSeekToPreviousPosition: Duration = DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION,
mediaItemTrackerRepository: MediaItemTrackerProvider = DefaultMediaItemTrackerRepository(),
mediaCompositionService: MediaCompositionService = HttpMediaCompositionService(),
loadControl: LoadControl = PillarboxLoadControl(),
): PillarboxExoPlayer {
return DefaultPillarbox(
context = context,
seekIncrement = seekIncrement,
maxSeekToPreviousPosition = maxSeekToPreviousPosition,
mediaItemTrackerRepository = mediaItemTrackerRepository,
mediaCompositionService = mediaCompositionService,
loadControl = loadControl,
clock = Clock.DEFAULT,
Expand All @@ -68,7 +63,6 @@ object DefaultPillarbox {
* @param context The context.
* @param seekIncrement The seek increment.
* @param maxSeekToPreviousPosition The [Player.getMaxSeekToPreviousPosition] value.
* @param mediaItemTrackerRepository The provider of MediaItemTracker, by default [DefaultMediaItemTrackerRepository].
* @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.
Expand All @@ -80,7 +74,6 @@ object DefaultPillarbox {
context: Context,
seekIncrement: SeekIncrement = defaultSeekIncrement,
maxSeekToPreviousPosition: Duration = DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION,
mediaItemTrackerRepository: MediaItemTrackerProvider = DefaultMediaItemTrackerRepository(),
loadControl: LoadControl = DefaultLoadControl(),
mediaCompositionService: MediaCompositionService = HttpMediaCompositionService(),
clock: Clock,
Expand All @@ -93,7 +86,6 @@ object DefaultPillarbox {
mediaSourceFactory = PillarboxMediaSourceFactory(context).apply {
addAssetLoader(SRGAssetLoader(context, mediaCompositionService))
},
mediaItemTrackerProvider = mediaItemTrackerRepository,
loadControl = loadControl,
clock = clock,
coroutineContext = coroutineContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import androidx.media3.common.MediaMetadata
import androidx.media3.common.MimeTypes
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import ch.srgssr.pillarbox.analytics.SRGAnalytics
import ch.srgssr.pillarbox.analytics.commandersact.CommandersAct
import ch.srgssr.pillarbox.core.business.HttpResultException
import ch.srgssr.pillarbox.core.business.akamai.AkamaiTokenDataSource
import ch.srgssr.pillarbox.core.business.akamai.AkamaiTokenProvider
Expand All @@ -31,10 +33,13 @@ import ch.srgssr.pillarbox.core.business.tracker.commandersact.CommandersActTrac
import ch.srgssr.pillarbox.core.business.tracker.comscore.ComScoreTracker
import ch.srgssr.pillarbox.player.asset.Asset
import ch.srgssr.pillarbox.player.asset.AssetLoader
import ch.srgssr.pillarbox.player.tracker.MediaItemTrackerData
import ch.srgssr.pillarbox.player.tracker.FactoryData
import ch.srgssr.pillarbox.player.tracker.MutableMediaItemTrackerData
import io.ktor.client.plugins.ClientRequestException
import kotlinx.coroutines.Dispatchers
import kotlinx.serialization.SerializationException
import java.io.IOException
import kotlin.coroutines.CoroutineContext

/**
* Mime Type for representing SRG SSR content
Expand All @@ -46,13 +51,18 @@ const val MimeTypeSrg = "${MimeTypes.BASE_TYPE_APPLICATION}/srg-ssr"
*
* @param context The context.
* @param mediaCompositionService The service to load a [MediaComposition].
* @param commandersAct The CommandersAct implementation to use with [CommandersActTracker].
* @param coroutineContext The [CoroutineContext] to use with [CommandersActTracker]
*/
class SRGAssetLoader(
class SRGAssetLoader internal constructor(
context: Context,
private val mediaCompositionService: MediaCompositionService = HttpMediaCompositionService()
private val mediaCompositionService: MediaCompositionService,
private val commandersAct: CommandersAct,
private val coroutineContext: CoroutineContext,
) : AssetLoader(
mediaSourceFactory = DefaultMediaSourceFactory(AkamaiTokenDataSource.Factory(AkamaiTokenProvider(), DefaultDataSource.Factory(context)))
) {

/**
* An interface to customize how [SRGAssetLoader] should fill [MediaMetadata].
*/
Expand Down Expand Up @@ -80,13 +90,13 @@ class SRGAssetLoader(
/**
* Provide Tracker Data to the [Asset]. The official SRG trackers are always setup by [SRGAssetLoader].
*
* @param trackerDataBuilder The [MediaItemTrackerData.Builder] to add trackers data.
* @param trackerDataBuilder The [MutableMediaItemTrackerData] to add tracker data.
* @param resource The [Resource] the player will play.
* @param chapter The main [Chapter] from the mediaComposition.
* @param mediaComposition The [MediaComposition] loaded from [MediaCompositionService].
*/
fun provide(
trackerDataBuilder: MediaItemTrackerData.Builder,
trackerDataBuilder: MutableMediaItemTrackerData,
resource: Resource,
chapter: Chapter,
mediaComposition: MediaComposition
Expand All @@ -105,6 +115,11 @@ class SRGAssetLoader(
*/
var trackerDataProvider: TrackerDataProvider? = null

constructor(
context: Context,
mediaCompositionService: MediaCompositionService = HttpMediaCompositionService(),
) : this(context, mediaCompositionService, SRGAnalytics.commandersAct, Dispatchers.Default)

override fun canLoadAsset(mediaItem: MediaItem): Boolean {
val localConfiguration = mediaItem.localConfiguration ?: return false

Expand Down Expand Up @@ -139,24 +154,23 @@ class SRGAssetLoader(
if (resource.tokenType == Resource.TokenType.AKAMAI) {
uri = AkamaiTokenDataSource.appendTokenQueryToUri(uri)
}
val trackerData = MediaItemTrackerData.Builder().apply {
trackerDataProvider?.provide(this, resource, chapter, result)
putData(SRGEventLoggerTracker::class.java)
getComScoreData(result, chapter, resource)?.let {
putData(ComScoreTracker::class.java, it)
}
getCommandersActData(result, chapter, resource)?.let {
putData(CommandersActTracker::class.java, it)
}
}.build()
val trackerData = MutableMediaItemTrackerData()
trackerDataProvider?.provide(trackerData, resource, chapter, result)
trackerData[SRGEventLoggerTracker::class.java] = FactoryData(SRGEventLoggerTracker.Factory(), Unit)
getComScoreData(result, chapter, resource)?.let {
trackerData[ComScoreTracker::class.java] = FactoryData(ComScoreTracker.Factory(), it)
}
getCommandersActData(result, chapter, resource)?.let {
trackerData[CommandersActTracker::class.java] = FactoryData(CommandersActTracker.Factory(commandersAct, coroutineContext), it)
}

val loadingMediaItem = MediaItem.Builder()
.setDrmConfiguration(fillDrmConfiguration(resource))
.setUri(uri)
.build()
return Asset(
mediaSource = mediaSourceFactory.createMediaSource(loadingMediaItem),
trackersData = trackerData,
trackersData = trackerData.toMediaItemTrackerData(),
mediaMetadata = mediaItem.mediaMetadata.buildUpon().apply {
mediaMetadataProvider.provide(
this,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,30 @@
*/
package ch.srgssr.pillarbox.core.business.tracker

import android.util.Log
import androidx.media3.exoplayer.ExoPlayer
import ch.srgssr.pillarbox.player.tracker.MediaItemTracker
import ch.srgssr.pillarbox.player.utils.PillarboxEventLogger
import kotlin.time.Duration.Companion.milliseconds

/**
* Enable/Disable EventLogger when item is currently active.
*/
class SRGEventLoggerTracker : MediaItemTracker {
class SRGEventLoggerTracker : MediaItemTracker<Unit> {
private val eventLogger = PillarboxEventLogger(TAG)

override fun start(player: ExoPlayer, initialData: Any?) {
Log.w(TAG, "---- Start")
override fun start(player: ExoPlayer, data: Unit) {
player.addAnalyticsListener(eventLogger)
}

override fun stop(player: ExoPlayer, reason: MediaItemTracker.StopReason, positionMs: Long) {
Log.w(TAG, "---- Stop because $reason at ${positionMs.milliseconds}")
override fun stop(player: ExoPlayer) {
player.removeAnalyticsListener(eventLogger)
}

/**
* Factory for a [SRGEventLoggerTracker]
*/
class Factory : MediaItemTracker.Factory {
class Factory : MediaItemTracker.Factory<Unit> {

override fun create(): MediaItemTracker {
override fun create(): MediaItemTracker<Unit> {
return SRGEventLoggerTracker()
}
}
Expand Down
Loading

0 comments on commit 1dc6530

Please sign in to comment.