Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update models for QoS #654

Merged
merged 18 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ import ch.srgssr.pillarbox.player.analytics.PillarboxAnalyticsListener
import ch.srgssr.pillarbox.player.analytics.PlaybackSessionManager
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.runOnApplicationLooper
import ch.srgssr.pillarbox.player.utils.BitrateUtil.toByteRate
import ch.srgssr.pillarbox.player.utils.DebugLogger
Expand Down Expand Up @@ -63,11 +70,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,
)
)
}
Expand Down Expand Up @@ -126,8 +133,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(),
)
}
Expand Down Expand Up @@ -166,6 +175,7 @@ internal class QoSCoordinator(
throwable = it,
playerPosition = player.currentPosition,
severity = QoSError.Severity.FATAL,
url = url,
),
)
}
Expand All @@ -180,16 +190,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,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package ch.srgssr.pillarbox.player.qos

import android.util.Log
import ch.srgssr.pillarbox.player.qos.models.QoSMessage

/**
* QoS message handler
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) SRG SSR. All rights reserved.
* License information is available from the LICENSE file.
*/
package ch.srgssr.pillarbox.player.qos.models

/**
* Represents a [Player][androidx.media3.common.Player] error to send to a QoS server.
*
* @property log The log associated with the error.
* @property message The error message.
* @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,
val message: String,
val name: String,
val playerPosition: Long?,
val severity: Severity,
val url: String,
) {
/**
* Represents a [Player][androidx.media3.common.Player] error severity.
*/
enum class Severity {
FATAL,
WARNING,
}

constructor(
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,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright (c) SRG SSR. All rights reserved.
* License information is available from the LICENSE file.
*/
package ch.srgssr.pillarbox.player.qos.models

/**
* Represents a generic event, which contains metrics about the current media stream.
*
* @property bandwidth The device-measured network bandwidth, in bytes per second.
* @property bitrate The bitrate of the current stream, in bytes per second.
* @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 stall The information about stalls.
* @property url The URL of the stream.
*/
data class QoSEvent(
val bandwidth: Long,
val bitrate: Int,
val bufferDuration: Long,
val playbackDuration: Long,
val playerPosition: Long,
val stall: QoSStall,
val url: String,
)
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -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

/**
* Represents a QoS message.
*
* @property data The data associated with the message.
* @property eventName The name of the event.
* @property sessionId The session id.
* @property timestamp The current timestamp.
*/
data class QoSMessage(
val data: Any,
val eventName: String,
val sessionId: String,
val timestamp: Long = System.currentTimeMillis(),
)
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) SRG SSR. All rights reserved.
* License information is available from the LICENSE file.
*/
package ch.srgssr.pillarbox.player.qos.models

import android.content.Context
import android.content.res.Configuration
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 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 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,
) {
constructor(
context: Context,
media: QoSMedia,
timeMetrics: QoSSessionTimings,
) : this(
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 {
private val OPERATING_SYSTEM_VERSION = Build.VERSION.RELEASE
private const val PHONE_TABLET_WIDTH_THRESHOLD = 600
private const val PLATFORM_NAME = "android"
private const val PLAYER_NAME = "pillarbox"
private const val PLAYER_VERSION = BuildConfig.VERSION_NAME

@Suppress("FunctionOnlyReturningConstant")
private fun getDeviceId(): String {
// TODO Define this somehow (maybe use TCPredefinedVariables.getInstance().uniqueIdentifier)
return ""
}

private fun getDeviceModel(): String {
return Build.MANUFACTURER + " " + Build.MODEL
}

private fun Context.getDeviceType(): DeviceType {
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
StaehliJ marked this conversation as resolved.
Show resolved Hide resolved
Configuration.UI_MODE_TYPE_NORMAL -> {
val smallestWidthDp = configuration.smallestScreenWidthDp

if (smallestWidthDp >= PHONE_TABLET_WIDTH_THRESHOLD) {
DeviceType.TABLET
} else {
DeviceType.PHONE
}
}

Configuration.UI_MODE_TYPE_TELEVISION -> DeviceType.TV
else -> DeviceType.UNKNOWN
}
}

private fun Context.getWindowBounds(): Rect {
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager

return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Rect().also {
@Suppress("DEPRECATION")
windowManager.defaultDisplay.getRectSize(it)
}
} else {
windowManager.maximumWindowMetrics.bounds
}
}
}
}
Loading
Loading