diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/QoSCoordinator.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/QoSCoordinator.kt index 8615317e0..55d1a24c3 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/QoSCoordinator.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/QoSCoordinator.kt @@ -17,9 +17,11 @@ import ch.srgssr.pillarbox.player.analytics.metrics.MetricsCollector import ch.srgssr.pillarbox.player.analytics.metrics.PlaybackMetrics import ch.srgssr.pillarbox.player.qos.models.QoSError import ch.srgssr.pillarbox.player.qos.models.QoSEvent +import ch.srgssr.pillarbox.player.qos.models.QoSMedia import ch.srgssr.pillarbox.player.qos.models.QoSMessage import ch.srgssr.pillarbox.player.qos.models.QoSSession import ch.srgssr.pillarbox.player.qos.models.QoSSessionTimings +import ch.srgssr.pillarbox.player.qos.models.QoSStall import ch.srgssr.pillarbox.player.utils.DebugLogger import ch.srgssr.pillarbox.player.utils.Heartbeat import kotlin.coroutines.CoroutineContext @@ -65,11 +67,11 @@ internal class QoSCoordinator( sessionManager.getSessionById(metrics.sessionId)?.let { sendStartEvent( session = it, - timings = QoSSessionTimings( - asset = metrics.loadDuration.asset, - mediaSource = metrics.loadDuration.source, - currentToStart = metrics.loadDuration.timeToReady, - drm = metrics.loadDuration.drm + timeMetrics = QoSSessionTimings( + asset = metrics.loadDuration.source, + drm = metrics.loadDuration.drm, + metadata = metrics.loadDuration.asset, + total = metrics.loadDuration.timeToReady, ) ) } @@ -128,8 +130,10 @@ internal class QoSCoordinator( bufferDuration = player.totalBufferedDuration, playbackDuration = playbackDuration.inWholeMilliseconds, playerPosition = player.currentPosition, - stallCount = stallCount, - stallDuration = stallDuration.inWholeSeconds, + stall = QoSStall( + count = stallCount, + duration = stallDuration.inWholeMilliseconds, + ), url = url.toString(), ) } @@ -179,6 +183,7 @@ internal class QoSCoordinator( throwable = it, playerPosition = player.currentPosition, severity = QoSError.Severity.FATAL, + url = url, ), ) } @@ -193,16 +198,20 @@ internal class QoSCoordinator( private fun sendStartEvent( session: PlaybackSessionManager.Session, - timings: QoSSessionTimings, + timeMetrics: QoSSessionTimings, ) { sendEvent( eventName = "START", session = session, data = QoSSession( context = context, - mediaId = session.mediaItem.mediaId, - mediaSource = session.mediaItem.localConfiguration?.uri.toString(), - timings = timings, + media = QoSMedia( + assetUrl = url, + id = session.mediaItem.mediaId, + metadataUrl = session.mediaItem.localConfiguration?.uri.toString(), + origin = context.packageName, + ), + timeMetrics = timeMetrics, ), ) } diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSDevice.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSDevice.kt new file mode 100644 index 000000000..92a9524a0 --- /dev/null +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSDevice.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.qos.models + +/** + * Information about the device. + * + * @property id The unique identifier of the device. + * @property model The model of the device. + * @property type The type of device. + */ +data class QoSDevice( + val id: String, + val model: String, + val type: DeviceType, +) { + /** + * The type of device. + */ + enum class DeviceType { + CAR, + DESKTOP, + PHONE, + TABLET, + TV, + UNKNOWN, + } +} diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSError.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSError.kt index d353246e9..64bbc9b24 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSError.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSError.kt @@ -12,6 +12,7 @@ package ch.srgssr.pillarbox.player.qos.models * @property name The name of the error. * @property playerPosition The position of the player when the error occurred, in milliseconds, or `null` if not available. * @property severity The severity of the error, either [FATAL][Severity.FATAL] or [WARNING][Severity.WARNING]. + * @property url The last loaded url. */ data class QoSError( val log: String, @@ -19,6 +20,7 @@ data class QoSError( val name: String, val playerPosition: Long?, val severity: Severity, + val url: String, ) { /** * Represents a [Player][androidx.media3.common.Player] error severity. @@ -32,11 +34,13 @@ data class QoSError( throwable: Throwable, playerPosition: Long?, severity: Severity, + url: String, ) : this( log = throwable.stackTraceToString(), message = throwable.message.orEmpty(), name = throwable::class.simpleName.orEmpty(), playerPosition = playerPosition, severity = severity, + url = url, ) } diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSEvent.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSEvent.kt index 82ee72638..2fc58aef3 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSEvent.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSEvent.kt @@ -12,8 +12,7 @@ package ch.srgssr.pillarbox.player.qos.models * @property bufferDuration The forward duration of the buffer, in milliseconds. * @property playbackDuration The duration of the playback, in milliseconds. * @property playerPosition The position of the player, in milliseconds. - * @property stallCount The number of stalls that have occurred, not as a result of a seek. - * @property stallDuration The total duration of the stalls, in milliseconds. + * @property stall The information about stalls. * @property url The URL of the stream. */ data class QoSEvent( @@ -22,7 +21,6 @@ data class QoSEvent( val bufferDuration: Long, val playbackDuration: Long, val playerPosition: Long, - val stallCount: Int, - val stallDuration: Long, + val stall: QoSStall, val url: String, ) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSMedia.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSMedia.kt new file mode 100644 index 000000000..d399839ae --- /dev/null +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSMedia.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.qos.models + +/** + * Information about the media being played. + * + * @property assetUrl The URL of the asset. + * @property id The id of the media. + * @property metadataUrl The URL of the metadata. + * @property origin The origin of the media. + */ +data class QoSMedia( + val assetUrl: String, + val id: String, + val metadataUrl: String, + val origin: String, +) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSOS.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSOS.kt new file mode 100644 index 000000000..0347f30eb --- /dev/null +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSOS.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.qos.models + +/** + * Information about the operating system. + * + * @property name The name of the operating system. + * @property version The version of the operating system. + */ +data class QoSOS( + val name: String, + val version: String, +) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSPlayer.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSPlayer.kt new file mode 100644 index 000000000..8a174ed43 --- /dev/null +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSPlayer.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.qos.models + +/** + * Information about the player. + * + * @property name The name of the player. + * @property platform The platform of the player. + * @property version The version of the player. + */ +data class QoSPlayer( + val name: String, + val platform: String, + val version: String, +) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSScreen.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSScreen.kt new file mode 100644 index 000000000..be912a787 --- /dev/null +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSScreen.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.qos.models + +/** + * Information about the device screen. + * + * @property height The height of the screen, in pixels. + * @property width The width of the screen, in pixels. + */ +data class QoSScreen( + val height: Int, + val width: Int, +) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSSession.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSSession.kt index ad6d7b34a..b9734a23c 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSSession.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSSession.kt @@ -10,65 +10,51 @@ import android.graphics.Rect import android.os.Build import android.view.WindowManager import ch.srgssr.pillarbox.player.BuildConfig +import ch.srgssr.pillarbox.player.qos.models.QoSDevice.DeviceType /** * Represents a QoS session, which contains information about the device, current media, and player. * - * @property deviceId The unique identifier of the device. - * @property deviceModel The model of the device. - * @property deviceType The type of device. - * @property mediaId The identifier of the media being played. - * @property mediaSource The source URL of the media being played. - * @property operatingSystemName The name of the operating system. - * @property operatingSystemVersion The version of the operating system. - * @property origin The origin of the player. - * @property playerName The name of the player. - * @property playerPlatform The platform of the player. - * @property playerVersion The version of the player. - * @property screenHeight The height of the screen in pixels. - * @property screenWidth The width of the screen in pixels. - * @property timings The timing until the current media started to play. + * @property device The information about the device. + * @property media The information about the media being played. + * @property operatingSystem The information about the operating system. + * @property player The information about the player. + * @property screen The information about the device screen. + * @property timeMetrics The metrics about the time needed to load the various media components. */ data class QoSSession( - val deviceId: String, - val deviceModel: String = getDeviceModel(), - val deviceType: DeviceType, - val mediaId: String, - val mediaSource: String, - val operatingSystemName: String = PLATFORM_NAME, - val operatingSystemVersion: String = OPERATING_SYSTEM_VERSION, - val origin: String, - val playerName: String = PLAYER_NAME, - val playerPlatform: String = PLATFORM_NAME, - val playerVersion: String = PLAYER_VERSION, - val screenHeight: Int, - val screenWidth: Int, - val timings: QoSSessionTimings = QoSSessionTimings.Empty, + val device: QoSDevice, + val media: QoSMedia, + val operatingSystem: QoSOS = QoSOS( + name = PLATFORM_NAME, + version = OPERATING_SYSTEM_VERSION, + ), + val player: QoSPlayer = QoSPlayer( + name = PLAYER_NAME, + platform = PLATFORM_NAME, + version = PLAYER_VERSION, + ), + val screen: QoSScreen, + val timeMetrics: QoSSessionTimings = QoSSessionTimings.Empty, ) { - /** - * The type of device. - */ - enum class DeviceType { - CAR, - PHONE, - TABLET, - TV, - } - constructor( context: Context, - mediaId: String, - mediaSource: String, - timings: QoSSessionTimings + media: QoSMedia, + timeMetrics: QoSSessionTimings, ) : this( - deviceId = getDeviceId(), - deviceType = context.getDeviceType(), - mediaId = mediaId, - mediaSource = mediaSource, - origin = context.packageName, - screenHeight = context.getWindowBounds().height(), - screenWidth = context.getWindowBounds().width(), - timings = timings + device = QoSDevice( + id = getDeviceId(), + model = getDeviceModel(), + type = context.getDeviceType(), + ), + media = media, + screen = context.getWindowBounds().let { windowBounds -> + QoSScreen( + height = windowBounds.height(), + width = windowBounds.width(), + ) + }, + timeMetrics = timeMetrics, ) private companion object { @@ -92,6 +78,7 @@ data class QoSSession( val configuration = resources.configuration return when (configuration.uiMode and Configuration.UI_MODE_TYPE_MASK) { Configuration.UI_MODE_TYPE_CAR -> DeviceType.CAR + Configuration.UI_MODE_TYPE_DESK -> DeviceType.DESKTOP Configuration.UI_MODE_TYPE_NORMAL -> { val smallestWidthDp = configuration.smallestScreenWidthDp @@ -103,7 +90,7 @@ data class QoSSession( } Configuration.UI_MODE_TYPE_TELEVISION -> DeviceType.TV - else -> DeviceType.PHONE // TODO Do we assume PHONE by default? Or do we throw an exception? + else -> DeviceType.UNKNOWN } } diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTimings.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTimings.kt index f32962160..994171547 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTimings.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTimings.kt @@ -10,16 +10,18 @@ import kotlin.time.Duration * Represents the timings until the current media started to play. * * @property asset The time spent to load the asset. - * @property currentToStart The time spent to load from the moment the [MediaItem][androidx.media3.common.MediaItem] became the current item until it - * started to play. * @property drm The time spent to load the DRM. - * @property mediaSource The time spent to load the media source. + * @property metadata The time spent to load the media source. + * @property token The time spent to load the token. + * @property total The time spent to load from the moment the [MediaItem][androidx.media3.common.MediaItem] became the current item until it + * started to play. */ data class QoSSessionTimings( val asset: Duration? = null, - val currentToStart: Duration? = null, val drm: Duration? = null, - val mediaSource: Duration? = null, + val metadata: Duration? = null, + val token: Duration? = null, + val total: Duration? = null, ) { companion object { /** @@ -27,9 +29,10 @@ data class QoSSessionTimings( */ val Empty = QoSSessionTimings( asset = null, - currentToStart = null, drm = null, - mediaSource = null, + metadata = null, + token = null, + total = null, ) } } diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSStall.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSStall.kt new file mode 100644 index 000000000..943e5c52b --- /dev/null +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/qos/models/QoSStall.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.qos.models + +/** + * Information about stalls. + * + * @property count The number of stalls that have occurred, not as a result of a seek. + * @property duration The total duration of the stalls, in milliseconds. + */ +data class QoSStall( + val count: Int, + val duration: Long, +) diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSErrorTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSErrorTest.kt index b94d6f3f2..3871b05b9 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSErrorTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSErrorTest.kt @@ -19,6 +19,7 @@ class QoSErrorTest { throwable = throwable, playerPosition = 5.minutes.inWholeMilliseconds, severity = QoSError.Severity.WARNING, + url = "", ) val logLines = qosError.log.lineSequence() @@ -39,6 +40,7 @@ class QoSErrorTest { throwable = throwable, playerPosition = 30.seconds.inWholeMilliseconds, severity = QoSError.Severity.FATAL, + url = "", ) val logLines = qosError.log.lineSequence() diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTest.kt index 4a8e33d9f..4896ff0b6 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTest.kt @@ -18,18 +18,21 @@ class QoSSessionTest { fun `contextConstructor provides correct default values`() { val qosSession = createQoSSession() - assertEquals("", qosSession.deviceId) - assertEquals("unknown robolectric", qosSession.deviceModel) - assertEquals(QoSSession.DeviceType.PHONE, qosSession.deviceType) - assertEquals("android", qosSession.operatingSystemName) - assertEquals("5.0.2", qosSession.operatingSystemVersion) - assertEquals("ch.srgssr.pillarbox.player.test", qosSession.origin) - assertEquals("pillarbox", qosSession.playerName) - assertEquals("android", qosSession.playerPlatform) - assertEquals("Local", qosSession.playerVersion) - assertEquals(470, qosSession.screenHeight) - assertEquals(320, qosSession.screenWidth) - assertEquals(QoSSessionTimings.Empty, qosSession.timings) + assertEquals("", qosSession.device.id) + assertEquals("unknown robolectric", qosSession.device.model) + assertEquals(QoSDevice.DeviceType.PHONE, qosSession.device.type) + assertEquals(ASSET_URL, qosSession.media.assetUrl) + assertEquals(MEDIA_ID, qosSession.media.id) + assertEquals(METADATA_URL, qosSession.media.metadataUrl) + assertEquals(ORIGIN, qosSession.media.origin) + assertEquals(OPERATING_SYSTEM_NAME, qosSession.operatingSystem.name) + assertEquals("5.0.2", qosSession.operatingSystem.version) + assertEquals(PLAYER_NAME, qosSession.player.name) + assertEquals(PLAYER_PLATFORM, qosSession.player.platform) + assertEquals(PLAYER_VERSION, qosSession.player.version) + assertEquals(SCREEN_HEIGHT, qosSession.screen.height) + assertEquals(SCREEN_WIDTH, qosSession.screen.width) + assertEquals(QoSSessionTimings.Empty, qosSession.timeMetrics) } @Test @@ -37,18 +40,21 @@ class QoSSessionTest { fun `contextConstructor provides correct default values (API 30)`() { val qosSession = createQoSSession() - assertEquals("", qosSession.deviceId) - assertEquals("robolectric robolectric", qosSession.deviceModel) - assertEquals(QoSSession.DeviceType.PHONE, qosSession.deviceType) - assertEquals("android", qosSession.operatingSystemName) - assertEquals("11", qosSession.operatingSystemVersion) - assertEquals("ch.srgssr.pillarbox.player.test", qosSession.origin) - assertEquals("pillarbox", qosSession.playerName) - assertEquals("android", qosSession.playerPlatform) - assertEquals("Local", qosSession.playerVersion) - assertEquals(470, qosSession.screenHeight) - assertEquals(320, qosSession.screenWidth) - assertEquals(QoSSessionTimings.Empty, qosSession.timings) + assertEquals("", qosSession.device.id) + assertEquals("robolectric robolectric", qosSession.device.model) + assertEquals(QoSDevice.DeviceType.PHONE, qosSession.device.type) + assertEquals(ASSET_URL, qosSession.media.assetUrl) + assertEquals(MEDIA_ID, qosSession.media.id) + assertEquals(METADATA_URL, qosSession.media.metadataUrl) + assertEquals(ORIGIN, qosSession.media.origin) + assertEquals(OPERATING_SYSTEM_NAME, qosSession.operatingSystem.name) + assertEquals("11", qosSession.operatingSystem.version) + assertEquals(PLAYER_NAME, qosSession.player.name) + assertEquals(PLAYER_PLATFORM, qosSession.player.platform) + assertEquals(PLAYER_VERSION, qosSession.player.version) + assertEquals(SCREEN_HEIGHT, qosSession.screen.height) + assertEquals(SCREEN_WIDTH, qosSession.screen.width) + assertEquals(QoSSessionTimings.Empty, qosSession.timeMetrics) } @Test @@ -56,18 +62,43 @@ class QoSSessionTest { fun `contextConstructor provides correct default values for car`() { val qosSession = createQoSSession() - assertEquals("", qosSession.deviceId) - assertEquals("unknown robolectric", qosSession.deviceModel) - assertEquals(QoSSession.DeviceType.CAR, qosSession.deviceType) - assertEquals("android", qosSession.operatingSystemName) - assertEquals("5.0.2", qosSession.operatingSystemVersion) - assertEquals("ch.srgssr.pillarbox.player.test", qosSession.origin) - assertEquals("pillarbox", qosSession.playerName) - assertEquals("android", qosSession.playerPlatform) - assertEquals("Local", qosSession.playerVersion) - assertEquals(470, qosSession.screenHeight) - assertEquals(320, qosSession.screenWidth) - assertEquals(QoSSessionTimings.Empty, qosSession.timings) + assertEquals("", qosSession.device.id) + assertEquals("unknown robolectric", qosSession.device.model) + assertEquals(QoSDevice.DeviceType.CAR, qosSession.device.type) + assertEquals(ASSET_URL, qosSession.media.assetUrl) + assertEquals(MEDIA_ID, qosSession.media.id) + assertEquals(METADATA_URL, qosSession.media.metadataUrl) + assertEquals(ORIGIN, qosSession.media.origin) + assertEquals(OPERATING_SYSTEM_NAME, qosSession.operatingSystem.name) + assertEquals("5.0.2", qosSession.operatingSystem.version) + assertEquals(PLAYER_NAME, qosSession.player.name) + assertEquals(PLAYER_PLATFORM, qosSession.player.platform) + assertEquals(PLAYER_VERSION, qosSession.player.version) + assertEquals(SCREEN_HEIGHT, qosSession.screen.height) + assertEquals(SCREEN_WIDTH, qosSession.screen.width) + assertEquals(QoSSessionTimings.Empty, qosSession.timeMetrics) + } + + @Test + @Config(qualifiers = "desk") + fun `contextConstructor provides correct default values for desktop`() { + val qosSession = createQoSSession() + + assertEquals("", qosSession.device.id) + assertEquals("unknown robolectric", qosSession.device.model) + assertEquals(QoSDevice.DeviceType.DESKTOP, qosSession.device.type) + assertEquals(ASSET_URL, qosSession.media.assetUrl) + assertEquals(MEDIA_ID, qosSession.media.id) + assertEquals(METADATA_URL, qosSession.media.metadataUrl) + assertEquals(ORIGIN, qosSession.media.origin) + assertEquals(OPERATING_SYSTEM_NAME, qosSession.operatingSystem.name) + assertEquals("5.0.2", qosSession.operatingSystem.version) + assertEquals(PLAYER_NAME, qosSession.player.name) + assertEquals(PLAYER_PLATFORM, qosSession.player.platform) + assertEquals(PLAYER_VERSION, qosSession.player.version) + assertEquals(SCREEN_HEIGHT, qosSession.screen.height) + assertEquals(SCREEN_WIDTH, qosSession.screen.width) + assertEquals(QoSSessionTimings.Empty, qosSession.timeMetrics) } @Test @@ -75,18 +106,21 @@ class QoSSessionTest { fun `contextConstructor provides correct default values for phone (sw320dp)`() { val qosSession = createQoSSession() - assertEquals("", qosSession.deviceId) - assertEquals("unknown robolectric", qosSession.deviceModel) - assertEquals(QoSSession.DeviceType.PHONE, qosSession.deviceType) - assertEquals("android", qosSession.operatingSystemName) - assertEquals("5.0.2", qosSession.operatingSystemVersion) - assertEquals("ch.srgssr.pillarbox.player.test", qosSession.origin) - assertEquals("pillarbox", qosSession.playerName) - assertEquals("android", qosSession.playerPlatform) - assertEquals("Local", qosSession.playerVersion) - assertEquals(470, qosSession.screenHeight) - assertEquals(320, qosSession.screenWidth) - assertEquals(QoSSessionTimings.Empty, qosSession.timings) + assertEquals("", qosSession.device.id) + assertEquals("unknown robolectric", qosSession.device.model) + assertEquals(QoSDevice.DeviceType.PHONE, qosSession.device.type) + assertEquals(ASSET_URL, qosSession.media.assetUrl) + assertEquals(MEDIA_ID, qosSession.media.id) + assertEquals(METADATA_URL, qosSession.media.metadataUrl) + assertEquals(ORIGIN, qosSession.media.origin) + assertEquals(OPERATING_SYSTEM_NAME, qosSession.operatingSystem.name) + assertEquals("5.0.2", qosSession.operatingSystem.version) + assertEquals(PLAYER_NAME, qosSession.player.name) + assertEquals(PLAYER_PLATFORM, qosSession.player.platform) + assertEquals(PLAYER_VERSION, qosSession.player.version) + assertEquals(SCREEN_HEIGHT, qosSession.screen.height) + assertEquals(SCREEN_WIDTH, qosSession.screen.width) + assertEquals(QoSSessionTimings.Empty, qosSession.timeMetrics) } @Test @@ -94,18 +128,21 @@ class QoSSessionTest { fun `contextConstructor provides correct default values for tablet (sw600dp)`() { val qosSession = createQoSSession() - assertEquals("", qosSession.deviceId) - assertEquals("unknown robolectric", qosSession.deviceModel) - assertEquals(QoSSession.DeviceType.TABLET, qosSession.deviceType) - assertEquals("android", qosSession.operatingSystemName) - assertEquals("5.0.2", qosSession.operatingSystemVersion) - assertEquals("ch.srgssr.pillarbox.player.test", qosSession.origin) - assertEquals("pillarbox", qosSession.playerName) - assertEquals("android", qosSession.playerPlatform) - assertEquals("Local", qosSession.playerVersion) - assertEquals(470, qosSession.screenHeight) - assertEquals(320, qosSession.screenWidth) - assertEquals(QoSSessionTimings.Empty, qosSession.timings) + assertEquals("", qosSession.device.id) + assertEquals("unknown robolectric", qosSession.device.model) + assertEquals(QoSDevice.DeviceType.TABLET, qosSession.device.type) + assertEquals(ASSET_URL, qosSession.media.assetUrl) + assertEquals(MEDIA_ID, qosSession.media.id) + assertEquals(METADATA_URL, qosSession.media.metadataUrl) + assertEquals(ORIGIN, qosSession.media.origin) + assertEquals(OPERATING_SYSTEM_NAME, qosSession.operatingSystem.name) + assertEquals("5.0.2", qosSession.operatingSystem.version) + assertEquals(PLAYER_NAME, qosSession.player.name) + assertEquals(PLAYER_PLATFORM, qosSession.player.platform) + assertEquals(PLAYER_VERSION, qosSession.player.version) + assertEquals(SCREEN_HEIGHT, qosSession.screen.height) + assertEquals(SCREEN_WIDTH, qosSession.screen.width) + assertEquals(QoSSessionTimings.Empty, qosSession.timeMetrics) } @Test @@ -113,18 +150,21 @@ class QoSSessionTest { fun `contextConstructor provides correct default values for tablet (sw720dp)`() { val qosSession = createQoSSession() - assertEquals("", qosSession.deviceId) - assertEquals("unknown robolectric", qosSession.deviceModel) - assertEquals(QoSSession.DeviceType.TABLET, qosSession.deviceType) - assertEquals("android", qosSession.operatingSystemName) - assertEquals("5.0.2", qosSession.operatingSystemVersion) - assertEquals("ch.srgssr.pillarbox.player.test", qosSession.origin) - assertEquals("pillarbox", qosSession.playerName) - assertEquals("android", qosSession.playerPlatform) - assertEquals("Local", qosSession.playerVersion) - assertEquals(470, qosSession.screenHeight) - assertEquals(320, qosSession.screenWidth) - assertEquals(QoSSessionTimings.Empty, qosSession.timings) + assertEquals("", qosSession.device.id) + assertEquals("unknown robolectric", qosSession.device.model) + assertEquals(QoSDevice.DeviceType.TABLET, qosSession.device.type) + assertEquals(ASSET_URL, qosSession.media.assetUrl) + assertEquals(MEDIA_ID, qosSession.media.id) + assertEquals(METADATA_URL, qosSession.media.metadataUrl) + assertEquals(ORIGIN, qosSession.media.origin) + assertEquals(OPERATING_SYSTEM_NAME, qosSession.operatingSystem.name) + assertEquals("5.0.2", qosSession.operatingSystem.version) + assertEquals(PLAYER_NAME, qosSession.player.name) + assertEquals(PLAYER_PLATFORM, qosSession.player.platform) + assertEquals(PLAYER_VERSION, qosSession.player.version) + assertEquals(SCREEN_HEIGHT, qosSession.screen.height) + assertEquals(SCREEN_WIDTH, qosSession.screen.width) + assertEquals(QoSSessionTimings.Empty, qosSession.timeMetrics) } @Test @@ -132,18 +172,21 @@ class QoSSessionTest { fun `contextConstructor provides correct default values for TV`() { val qosSession = createQoSSession() - assertEquals("", qosSession.deviceId) - assertEquals("unknown robolectric", qosSession.deviceModel) - assertEquals(QoSSession.DeviceType.TV, qosSession.deviceType) - assertEquals("android", qosSession.operatingSystemName) - assertEquals("5.0.2", qosSession.operatingSystemVersion) - assertEquals("ch.srgssr.pillarbox.player.test", qosSession.origin) - assertEquals("pillarbox", qosSession.playerName) - assertEquals("android", qosSession.playerPlatform) - assertEquals("Local", qosSession.playerVersion) - assertEquals(470, qosSession.screenHeight) - assertEquals(320, qosSession.screenWidth) - assertEquals(QoSSessionTimings.Empty, qosSession.timings) + assertEquals("", qosSession.device.id) + assertEquals("unknown robolectric", qosSession.device.model) + assertEquals(QoSDevice.DeviceType.TV, qosSession.device.type) + assertEquals(ASSET_URL, qosSession.media.assetUrl) + assertEquals(MEDIA_ID, qosSession.media.id) + assertEquals(METADATA_URL, qosSession.media.metadataUrl) + assertEquals(ORIGIN, qosSession.media.origin) + assertEquals(OPERATING_SYSTEM_NAME, qosSession.operatingSystem.name) + assertEquals("5.0.2", qosSession.operatingSystem.version) + assertEquals(PLAYER_NAME, qosSession.player.name) + assertEquals(PLAYER_PLATFORM, qosSession.player.platform) + assertEquals(PLAYER_VERSION, qosSession.player.version) + assertEquals(SCREEN_HEIGHT, qosSession.screen.height) + assertEquals(SCREEN_WIDTH, qosSession.screen.width) + assertEquals(QoSSessionTimings.Empty, qosSession.timeMetrics) } @Test @@ -151,18 +194,21 @@ class QoSSessionTest { fun `contextConstructor provides correct default values for watch`() { val qosSession = createQoSSession() - assertEquals("", qosSession.deviceId) - assertEquals("unknown robolectric", qosSession.deviceModel) - assertEquals(QoSSession.DeviceType.PHONE, qosSession.deviceType) - assertEquals("android", qosSession.operatingSystemName) - assertEquals("5.0.2", qosSession.operatingSystemVersion) - assertEquals("ch.srgssr.pillarbox.player.test", qosSession.origin) - assertEquals("pillarbox", qosSession.playerName) - assertEquals("android", qosSession.playerPlatform) - assertEquals("Local", qosSession.playerVersion) - assertEquals(470, qosSession.screenHeight) - assertEquals(320, qosSession.screenWidth) - assertEquals(QoSSessionTimings.Empty, qosSession.timings) + assertEquals("", qosSession.device.id) + assertEquals("unknown robolectric", qosSession.device.model) + assertEquals(QoSDevice.DeviceType.UNKNOWN, qosSession.device.type) + assertEquals(ASSET_URL, qosSession.media.assetUrl) + assertEquals(MEDIA_ID, qosSession.media.id) + assertEquals(METADATA_URL, qosSession.media.metadataUrl) + assertEquals(ORIGIN, qosSession.media.origin) + assertEquals(OPERATING_SYSTEM_NAME, qosSession.operatingSystem.name) + assertEquals("5.0.2", qosSession.operatingSystem.version) + assertEquals(PLAYER_NAME, qosSession.player.name) + assertEquals(PLAYER_PLATFORM, qosSession.player.platform) + assertEquals(PLAYER_VERSION, qosSession.player.version) + assertEquals(SCREEN_HEIGHT, qosSession.screen.height) + assertEquals(SCREEN_WIDTH, qosSession.screen.width) + assertEquals(QoSSessionTimings.Empty, qosSession.timeMetrics) } private fun createQoSSession(): QoSSession { @@ -170,9 +216,26 @@ class QoSSessionTest { return QoSSession( context = context, - mediaId = "urn:rts:video:12345", - mediaSource = "https://il-stage.srgssr.ch/integrationlayer/2.1/mediaComposition/byUrn/urn:rts:video:12345?vector=APPPLAY", - timings = QoSSessionTimings.Empty, + media = QoSMedia( + assetUrl = ASSET_URL, + id = MEDIA_ID, + metadataUrl = METADATA_URL, + origin = ORIGIN, + ), + timeMetrics = QoSSessionTimings.Empty, ) } + + private companion object { + private const val ASSET_URL = "https://rts-vod-amd.akamaized.net/ww/12345/3037738d-fe91-32e3-93f2-4dbb62a0f9bd/master.m3u8" + private const val MEDIA_ID = "urn:rts:video:12345" + private const val METADATA_URL = "https://il-stage.srgssr.ch/integrationlayer/2.1/mediaComposition/byUrn/urn:rts:video:12345?vector=APPPLAY" + private const val OPERATING_SYSTEM_NAME = "android" + private const val ORIGIN = "ch.srgssr.pillarbox.player.test" + private const val PLAYER_NAME = "pillarbox" + private const val PLAYER_PLATFORM = "android" + private const val PLAYER_VERSION = "Local" + private const val SCREEN_HEIGHT = 470 + private const val SCREEN_WIDTH = 320 + } } diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTimingsTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTimingsTest.kt index 13436f531..9fdb6ce4e 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTimingsTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/qos/models/QoSSessionTimingsTest.kt @@ -9,12 +9,13 @@ import kotlin.test.assertNull class QoSSessionTimingsTest { @Test - fun `zero timings`() { + fun `empty timings`() { val timings = QoSSessionTimings.Empty assertNull(timings.asset) - assertNull(timings.currentToStart) assertNull(timings.drm) - assertNull(timings.mediaSource) + assertNull(timings.metadata) + assertNull(timings.token) + assertNull(timings.total) } }