Skip to content
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
1 change: 1 addition & 0 deletions connectors/uplynk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {
testImplementation(libs.mockito.inline)
testImplementation(libs.mockito.kotlin)
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.robolectric)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.espresso.core)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.toDuration
import kotlin.time.Duration.Companion.seconds

internal class PingScheduler(
private val uplynkApi: UplynkApi,
Expand All @@ -17,7 +16,7 @@ internal class PingScheduler(
private val eventDispatcher: UplynkEventDispatcher,
private val adScheduler: UplynkAdScheduler
) {
private val NEGATIVE_TIME = (-1).toDuration(DurationUnit.SECONDS)
private val NEGATIVE_TIME = (-1).seconds
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


private var nextRequestTime: Duration = NEGATIVE_TIME
private var seekStart: Duration = NEGATIVE_TIME
Expand All @@ -27,12 +26,14 @@ internal class PingScheduler(
fun onTimeUpdate(time: Duration) {
if (nextRequestTime.isPositive() && time > nextRequestTime) {
nextRequestTime = NEGATIVE_TIME
performPing(uplynkDescriptionConverter.buildPingUrl(prefix, sessionId, time))
performPing(uplynkDescriptionConverter.buildPingUrl(prefix, sessionId, time).toString())
}
}

fun onStart(time: Duration) =
performPing(uplynkDescriptionConverter.buildStartPingUrl(prefix, sessionId, time))
performPing(
uplynkDescriptionConverter.buildStartPingUrl(prefix, sessionId, time).toString()
)


fun onSeeking(time: Duration) {
Expand All @@ -48,7 +49,7 @@ internal class PingScheduler(
sessionId,
time,
seekStart
)
).toString()
)
seekStart = NEGATIVE_TIME
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ internal class UplynkAdIntegration(
}

val playUrl = uplynkDescriptionConverter.buildPlaybackUrl(minimalResponse.playURL, ssaiDescription)
var newUplynkSource = uplynkSource.copy(src = playUrl)
var newUplynkSource = uplynkSource.copy(src = playUrl.toString())

minimalResponse.drm?.let { drm ->
if (drm.required) {
Expand Down Expand Up @@ -259,7 +259,7 @@ internal class UplynkAdIntegration(
.buildAssetInfoUrls(ssaiDescription, minimalResponse.sid, minimalResponse.prefix)
.mapNotNull {
try {
uplynkApi.assetInfo(it)
uplynkApi.assetInfo(it.toString())
} catch (e: Exception) {
eventDispatcher.dispatchAssetInfoFailure(e)
controller.error(e)
Expand All @@ -275,7 +275,7 @@ internal class UplynkAdIntegration(
private suspend fun requestLive(ssaiDescription: UplynkSsaiDescription): PreplayInternalLiveResponse {
return uplynkDescriptionConverter
.buildPreplayLiveUrl(ssaiDescription)
.let { uplynkApi.preplayLive(it) }
.let { uplynkApi.preplayLive(it.toString()) }
.also {
try {
val response = it.parseExternalResponse()
Expand All @@ -291,7 +291,7 @@ internal class UplynkAdIntegration(
private suspend fun requestVod(ssaiDescription: UplynkSsaiDescription): PreplayInternalVodResponse {
return uplynkDescriptionConverter
.buildPreplayVodUrl(ssaiDescription)
.let { uplynkApi.preplayVod(it) }
.let { uplynkApi.preplayVod(it.toString()) }
.also {
try {
val response = it.parseExternalResponse()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,59 +1,94 @@
package com.theoplayer.android.connector.uplynk.internal

import android.net.Uri
import androidx.core.net.toUri
import com.theoplayer.android.connector.uplynk.UplynkSsaiDescription
import kotlin.time.Duration

internal class UplynkSsaiDescriptionConverter {
private val DEFAULT_PREFIX = "https://content.uplynk.com"

fun buildPreplayVodUrl(ssaiDescription: UplynkSsaiDescription): String = with(ssaiDescription) {
val prefix = prefix ?: DEFAULT_PREFIX

return "$prefix/preplay/$urlAssetId?v=2$drmParameters$pingParameters$urlParameters"
}

fun buildPreplayLiveUrl(ssaiDescription: UplynkSsaiDescription): String = with(ssaiDescription) {
val prefix = prefix ?: DEFAULT_PREFIX
fun buildPreplayVodUrl(ssaiDescription: UplynkSsaiDescription): Uri =
with(ssaiDescription) {
return (prefix ?: DEFAULT_PREFIX).toUri().buildUpon().apply {
appendEncodedPath("preplay/$urlAssetId")
appendQueryParameter("v", "2")
drmParameters.forEach { (key, value) -> appendQueryParameter(key, value) }
pingParameters.forEach { (key, value) -> appendQueryParameter(key, value) }
preplayParameters.forEach { (key, value) -> appendQueryParameter(key, value) }
}.build()
}

return "$prefix/preplay/$urlAssetType/$urlAssetId?v=2$drmParameters$pingParameters$urlParameters"
}
fun buildPreplayLiveUrl(ssaiDescription: UplynkSsaiDescription): Uri =
with(ssaiDescription) {
return (prefix ?: DEFAULT_PREFIX).toUri().buildUpon().apply {
appendEncodedPath("preplay/$urlAssetType/$urlAssetId")
appendQueryParameter("v", "2")
drmParameters.forEach { (key, value) -> appendQueryParameter(key, value) }
pingParameters.forEach { (key, value) -> appendQueryParameter(key, value) }
preplayParameters.forEach { (key, value) -> appendQueryParameter(key, value) }
}.build()
}

fun buildPlaybackUrl(playUrl: String, ssaiDescription: UplynkSsaiDescription): String = with(ssaiDescription) {
return "$playUrl$playUrlParameters"
}
fun buildPlaybackUrl(playUrl: String, ssaiDescription: UplynkSsaiDescription): Uri =
with(ssaiDescription) {
return playUrl.toUri().buildUpon().apply {
playbackUrlParameters.forEach { (key, value) -> appendQueryParameter(key, value) }
}.build()
}

fun buildAssetInfoUrls(
ssaiDescription: UplynkSsaiDescription,
sessionId: String,
prefix: String
): List<String> = with(ssaiDescription) {
): List<Uri> = with(ssaiDescription) {
val urlList = when {
assetIds.isNotEmpty() -> assetIds.map {
"$prefix/player/assetinfo/$it.json"
"$prefix/player/assetinfo/$it.json".toUri()
}

externalIds.isNotEmpty() -> externalIds.map {
"$prefix/player/assetinfo/ext/$userId/$it.json"
"$prefix/player/assetinfo/ext/$userId/$it.json".toUri()
}

else -> emptyList()
}
return if (sessionId.isBlank()) {
urlList
} else {
urlList.map { "$it?pbs=$sessionId" }
urlList.map { url ->
url.buildUpon().apply {
appendQueryParameter("pbs", sessionId)
}.build()
}
}
}

fun buildSeekedPingUrl(
prefix: String, sessionId: String, currentTime: Duration, seekStartTime: Duration
) = buildPingUrl(prefix, sessionId, currentTime) + "&ev=seek&ft=${seekStartTime.inWholeSeconds}"
prefix: String,
sessionId: String,
currentTime: Duration,
seekStartTime: Duration
): Uri = buildPingUrl(prefix, sessionId, currentTime).buildUpon().apply {
appendQueryParameter("ev", "seek")
appendQueryParameter("ft", seekStartTime.inWholeSeconds.toString())
}.build()

fun buildStartPingUrl(
prefix: String, sessionId: String, currentTime: Duration
) = buildPingUrl(prefix, sessionId, currentTime) + "&ev=start"
prefix: String,
sessionId: String,
currentTime: Duration
): Uri = buildPingUrl(prefix, sessionId, currentTime).buildUpon().apply {
appendQueryParameter("ev", "start")
}.build()

fun buildPingUrl(
prefix: String, sessionId: String, currentTime: Duration
) = "$prefix/session/ping/$sessionId.json?v=3&pt=${currentTime.inWholeSeconds}"
prefix: String,
sessionId: String,
currentTime: Duration
): Uri = prefix.toUri().buildUpon().apply {
appendEncodedPath("session/ping/$sessionId.json")
appendQueryParameter("v", "3")
appendQueryParameter("pt", currentTime.inWholeSeconds.toString())
}.build()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,28 @@ package com.theoplayer.android.connector.uplynk.internal
import com.theoplayer.android.connector.uplynk.UplynkAssetType
import com.theoplayer.android.connector.uplynk.UplynkSsaiDescription

internal val UplynkSsaiDescription.drmParameters: String
get() = if (contentProtected) {
"&manifest=mpd&rmt=wv"
} else {
""
}
internal typealias QueryParameter = Pair<String, String>

internal val UplynkSsaiDescription.urlParameters
get() = if (preplayParameters.isNotEmpty()) {
preplayParameters.map { "${it.key}=${it.value}" }.joinToString("&", prefix = "&")
} else {
""
}

internal val UplynkSsaiDescription.playUrlParameters
get() = if (playbackUrlParameters.isNotEmpty()) {
playbackUrlParameters.map { "${it.key}=${it.value}" }.joinToString("&", prefix = "?")
internal val UplynkSsaiDescription.drmParameters: List<QueryParameter>
get() = if (contentProtected) {
listOf(
"manifest" to "mpd",
"rmt" to "wv",
)
} else {
""
listOf()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe emptyList()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

internal val UplynkSsaiDescription.pingParameters: String
internal val UplynkSsaiDescription.pingParameters: List<QueryParameter>
get() {
val feature = UplynkPingFeatures.from(this)
return if (feature == UplynkPingFeatures.NO_PING) {
""
listOf()
} else {
"&ad.cping=1&ad.pingf=${feature.pingfValue}"
listOf(
"ad.cping" to "1",
"ad.pingf" to feature.pingfValue.toString()
)
}
}

Expand Down
Loading
Loading