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

Implement Uplynk ads api #29

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 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 @@ -8,6 +8,9 @@ import com.theoplayer.android.connector.uplynk.internal.UplynkAdIntegration
import com.theoplayer.android.connector.uplynk.internal.UplynkSsaiDescriptionConverter
import com.theoplayer.android.connector.uplynk.internal.UplynkEventDispatcher
import com.theoplayer.android.connector.uplynk.internal.network.UplynkApi
import com.theoplayer.android.connector.uplynk.network.UplynkAd
import com.theoplayer.android.connector.uplynk.network.UplynkAdBreak
import com.theoplayer.android.connector.uplynk.network.UplynkAds

internal const val TAG = "UplynkConnector"

Expand All @@ -23,7 +26,52 @@ class UplynkConnector(
private val theoplayerView: THEOplayerView,
) {
private lateinit var integration: UplynkAdIntegration
private val eventDispatcher = UplynkEventDispatcher()

/**
* Scheduled ad breaks
*/
var adBreaks: List<UplynkAdBreak>? = null
private set

/**
* The ad break that is currently played or `null` otherwise
*/
var currentAdBreak: UplynkAdBreak? = null
private set

/**
* The ad that is currently played or `null` otherwise
*/
var currentAd: UplynkAd? = null
private set
Comment on lines +30 to +46
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe more of a philosophical question: why do we have both UplynkConnector.currentAds and player.ads.currentAds? Wasn't the whole point of the custom SSAI integrations that we can put everything behind the THEOplayer ads API?

Same thing with the new UplynkListener methods: how are those different from the existing events on player.ads?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only difference between them is the type of Ads object. player.ads.currentAds - has common type of an ad, that is part of THEOplayer project. UplynkConnector.currentAds has specific type for Uplynk ads. The same as for UplynkListener

I think we have to discuss if we even need this PR or if we could find some other solution

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, I think this is a limitation of our custom SSAI API then. We should provide a way to add some integration-specific data to an custom Ad or AdBreak.

I suggest we park this PR for now until we have that new API.


private val eventDispatcher = UplynkEventDispatcher().also {
it.addListener(object : UplynkListener {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: make UplynkConnector implementing UplynkListener

Copy link
Contributor Author

@OlegRyz OlegRyz Aug 29, 2024

Choose a reason for hiding this comment

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

I thought about it, but didn't do it because UplynkConnector is a part of public API and I think it might be confusing for a user of this class. So I decided to do it like this.

Do you think it would be better to make UplynkConnector implementing UplynkListener?

Copy link
Contributor

Choose a reason for hiding this comment

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

Indeed, it would be confusing..
Maybe we move the checks to the internal too?

override fun onAdBreaksUpdated(ads: UplynkAds) {
[email protected] = ads.breaks
MattiasBuelens marked this conversation as resolved.
Show resolved Hide resolved
}

override fun onAdBegin(ad: UplynkAd) {
check([email protected] == null) { "Begin ad that before ending previous currentAd = ${[email protected]} beginAd = $ad" }
[email protected] = ad
}

override fun onAdEnd(ad: UplynkAd) {
check([email protected] == ad) { "Trying to end ad that is not current. currentAd = ${[email protected]} endedAd = $ad" }
[email protected] = null
}

override fun onAdBreakBegin(adBreak: UplynkAdBreak) {
check([email protected] == null) { "Begin adbreak before ending previous currentAdBreak = ${[email protected]} beginAdBreak = $adBreak" }
[email protected] = adBreak
}

override fun onAdBreakEnd(adBreak: UplynkAdBreak) {
check([email protected] == adBreak) { "Trying to end adbreak that is not current. currentAdBreak = ${[email protected]} endedAdBreak = $adBreak" }
[email protected] = null
}
})
}

init {
theoplayerView.player.ads.registerServerSideIntegration(INTEGRATION_ID, this::setupIntegration)
Expand All @@ -41,8 +89,14 @@ class UplynkConnector(
return integration
}

/**
* Add a listener for events
*/
fun addListener(listener: UplynkListener) = eventDispatcher.addListener(listener)

/**
* Remove a listener for events
*/
fun removeListener(listener: UplynkListener) = eventDispatcher.removeListener(listener)

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package com.theoplayer.android.connector.uplynk

import com.theoplayer.android.connector.uplynk.network.AssetInfoResponse
import com.theoplayer.android.connector.uplynk.network.PreplayResponse
import com.theoplayer.android.connector.uplynk.network.UplynkAd
import com.theoplayer.android.connector.uplynk.network.UplynkAdBreak
import com.theoplayer.android.connector.uplynk.network.UplynkAds

/**
* A listener interface for receiving events related to Uplynk
Expand Down Expand Up @@ -40,4 +43,39 @@ interface UplynkListener {
* @param exception the `Exception` occurred during the request
*/
fun onAssetInfoFailure(exception: Exception) {}

/**
* Called when ad break data is updated.
*
* @param ads The updated ad break data.
*/
fun onAdBreaksUpdated(ads: UplynkAds) {}

/**
* Called when an individual ad begins.
*
* @param ad The ad that is starting.
*/
fun onAdBegin(ad: UplynkAd) {}

/**
* Called when an individual ad ends.
*
* @param ad The ad that has finished.
*/
fun onAdEnd(ad: UplynkAd) {}

/**
* Called when an ad break begins.
*
* @param adBreak The ad break that is starting.
*/
fun onAdBreakBegin(adBreak: UplynkAdBreak) {}

/**
* Called when an ad break ends.
*
* @param adBreak The ad break that has ended.
*/
fun onAdBreakEnd(adBreak: UplynkAdBreak) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import kotlin.time.DurationUnit
private val Duration.secToMs: Int
get() = this.toInt(DurationUnit.MILLISECONDS)

class AdHandler(private val controller: ServerSideAdIntegrationController) {
internal class AdHandler(private val controller: ServerSideAdIntegrationController) {
private val scheduledAds = WeakHashMap<UplynkAd, Ad>()

fun createAdBreak(adBreak: UplynkAdBreak) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ internal class UplynkAdIntegration(
try {
val response = it.parseExternalResponse()
eventDispatcher.dispatchPreplayEvents(response)
adScheduler = UplynkAdScheduler(response.ads.breaks, AdHandler(controller))
eventDispatcher.dispatchAdBreaksUpdatedEvents(response.ads)
adScheduler = UplynkAdScheduler(response.ads.breaks, AdHandler(controller), eventDispatcher)
} catch (e: Exception) {
eventDispatcher.dispatchPreplayFailure(e)
controller.error(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ enum class AdBreakState {
FINISHED,
}

class UplynkAdScheduler(
internal class UplynkAdScheduler(
uplynkAdBreaks: List<UplynkAdBreak>,
private val adHandler: AdHandler
private val adHandler: AdHandler,
private val eventDispatcher: UplynkEventDispatcher
) {
private val adBreaks = uplynkAdBreaks.map {
adHandler.createAdBreak(it)
Expand All @@ -42,8 +43,16 @@ class UplynkAdScheduler(
) {
if (currentAdBreak.state == newState) return
currentAdBreak.state = newState
if (currentAdBreak.state == AdBreakState.FINISHED) {
endAllStartedAds(currentAdBreak)
when (currentAdBreak.state) {
AdBreakState.NOT_PLAYED -> {}
AdBreakState.FINISHED -> {
endAllStartedAds(currentAdBreak)
eventDispatcher.dispatchAdBreakEndEvent(currentAdBreak.adBreak)
}

AdBreakState.STARTED -> {
eventDispatcher.dispatchAdBreakBeginEvent(currentAdBreak.adBreak)
}
}
}

Expand Down Expand Up @@ -82,21 +91,26 @@ class UplynkAdScheduler(
currentAd.state = state
when (currentAd.state) {
AdState.NOT_PLAYED -> {}
AdState.STARTED -> adHandler.onAdBegin(currentAd.ad)
AdState.COMPLETED -> adHandler.onAdEnd(currentAd.ad)
AdState.STARTED -> {
adHandler.onAdBegin(currentAd.ad)
eventDispatcher.dispatchAdBeginEvent(currentAd.ad)
}

AdState.COMPLETED -> {
adHandler.onAdEnd(currentAd.ad)
eventDispatcher.dispatchAdEndEvent(currentAd.ad)
}
}
}

private fun endAllStartedAds(
currentAdBreak: UplynkAdBreakState,
currentAd: UplynkAdState? = null
) {
currentAdBreak.ads
.takeWhile { currentAd == null || it != currentAd }
.forEach {
moveAdToState(it, AdState.COMPLETED)
}
}
) = currentAdBreak.ads
.filter { it.state == AdState.STARTED && it != currentAd }
.forEach {
moveAdToState(it, AdState.COMPLETED)
}

private fun endAllAdBreaks() = adBreaks
.filter { it.state == AdBreakState.STARTED }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import android.os.Looper
import com.theoplayer.android.connector.uplynk.UplynkListener
import com.theoplayer.android.connector.uplynk.network.AssetInfoResponse
import com.theoplayer.android.connector.uplynk.network.PreplayResponse
import com.theoplayer.android.connector.uplynk.network.UplynkAd
import com.theoplayer.android.connector.uplynk.network.UplynkAdBreak
import com.theoplayer.android.connector.uplynk.network.UplynkAds
import java.util.concurrent.CopyOnWriteArrayList

internal class UplynkEventDispatcher {
Expand All @@ -28,6 +31,26 @@ internal class UplynkEventDispatcher {
listeners.forEach { it.onPreplayFailure(e) }
}

fun dispatchAdBreaksUpdatedEvents(ads: UplynkAds) {
MattiasBuelens marked this conversation as resolved.
Show resolved Hide resolved
listeners.forEach { it.onAdBreaksUpdated(ads) }
}

fun dispatchAdBeginEvent(currentAd: UplynkAd) {
listeners.forEach { it.onAdBegin(currentAd) }
}

fun dispatchAdEndEvent(currentAd: UplynkAd) {
listeners.forEach { it.onAdEnd(currentAd) }
}

fun dispatchAdBreakBeginEvent(currentAdBreak: UplynkAdBreak) {
listeners.forEach { it.onAdBreakBegin(currentAdBreak) }
}

fun dispatchAdBreakEndEvent(currentAdBreak: UplynkAdBreak) {
listeners.forEach { it.onAdBreakEnd(currentAdBreak) }
}

fun addListener(listener: UplynkListener) = listeners.add(listener)

fun removeListener(listener: UplynkListener) = listeners.remove(listener)
Expand Down