From 44a02134bd1b4c563b04afc3cb7e128dc64a5a23 Mon Sep 17 00:00:00 2001 From: OlegRyz Date: Wed, 28 Aug 2024 15:59:40 +0200 Subject: [PATCH 1/5] Implement Ads API --- .../connector/uplynk/UplynkConnector.kt | 39 +++++++++++++++++- .../connector/uplynk/UplynkListener.kt | 38 ++++++++++++++++++ .../uplynk/internal/UplynkAdIntegration.kt | 3 +- .../uplynk/internal/UplynkAdScheduler.kt | 40 +++++++++++++------ .../uplynk/internal/UplynkEventDispatcher.kt | 23 +++++++++++ 5 files changed, 128 insertions(+), 15 deletions(-) diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt index c6d9b920..94516e1c 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt @@ -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" @@ -23,7 +26,41 @@ class UplynkConnector( private val theoplayerView: THEOplayerView, ) { private lateinit var integration: UplynkAdIntegration - private val eventDispatcher = UplynkEventDispatcher() + + var adBreaks: List? = null + private set + var currentAdBreak: UplynkAdBreak? = null + private set + var currentAd: UplynkAd? = null + private set + + private val eventDispatcher = UplynkEventDispatcher().also { + it.addListener(object : UplynkListener { + override fun onAdBreaksUpdated(ads: UplynkAds) { + this@UplynkConnector.adBreaks = ads.breaks + } + + override fun onAdBegin(ad: UplynkAd) { + check(this@UplynkConnector.currentAd == null) { "Begin ad that before ending previous currentAd = ${this@UplynkConnector.currentAd} beginAd = $ad" } + this@UplynkConnector.currentAd = ad + } + + override fun onAdEnd(ad: UplynkAd) { + check(this@UplynkConnector.currentAd == ad) { "Trying to end ad that is not current. currentAd = ${this@UplynkConnector.currentAd} endedAd = $ad" } + this@UplynkConnector.currentAd = null + } + + override fun onAdBreakBegin(adBreak: UplynkAdBreak) { + check(this@UplynkConnector.currentAdBreak == null) { "Begin adbreak before ending previous currentAdBreak = ${this@UplynkConnector.currentAdBreak} beginAdBreak = $adBreak" } + this@UplynkConnector.currentAdBreak = adBreak + } + + override fun onAdBreakEnd(adBreak: UplynkAdBreak) { + check(this@UplynkConnector.currentAdBreak == adBreak) { "Trying to end adbreak that is not current. currentAdBreak = ${this@UplynkConnector.currentAdBreak} endedAdBreak = $adBreak" } + this@UplynkConnector.currentAdBreak = null + } + }) + } init { theoplayerView.player.ads.registerServerSideIntegration(INTEGRATION_ID, this::setupIntegration) diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkListener.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkListener.kt index 3346ec66..4c352446 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkListener.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkListener.kt @@ -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 @@ -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) {} } diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdIntegration.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdIntegration.kt index edfa1f0d..a71a78c8 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdIntegration.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdIntegration.kt @@ -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) diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdScheduler.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdScheduler.kt index 81374593..b0afce92 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdScheduler.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkAdScheduler.kt @@ -27,9 +27,10 @@ enum class AdBreakState { FINISHED, } -class UplynkAdScheduler( +internal class UplynkAdScheduler( uplynkAdBreaks: List, - private val adHandler: AdHandler + private val adHandler: AdHandler, + private val eventDispatcher: UplynkEventDispatcher ) { private val adBreaks = uplynkAdBreaks.map { adHandler.createAdBreak(it) @@ -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) + } } } @@ -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 } diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkEventDispatcher.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkEventDispatcher.kt index 4cb50e48..6534a9ad 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkEventDispatcher.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkEventDispatcher.kt @@ -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 { @@ -28,6 +31,26 @@ internal class UplynkEventDispatcher { listeners.forEach { it.onPreplayFailure(e) } } + fun dispatchAdBreaksUpdatedEvents(ads: UplynkAds) { + 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) From eb141d9c9981b49be9818ca9db56e1b40576f843 Mon Sep 17 00:00:00 2001 From: OlegRyz Date: Wed, 28 Aug 2024 17:34:40 +0200 Subject: [PATCH 2/5] Add javadocs --- .../android/connector/uplynk/UplynkConnector.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt index 94516e1c..3a444470 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt @@ -27,10 +27,21 @@ class UplynkConnector( ) { private lateinit var integration: UplynkAdIntegration + /** + * Scheduled ad breaks + */ var adBreaks: List? = 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 @@ -78,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 { From 5eef75ac34634d65a9d598a86075113ca6d3512a Mon Sep 17 00:00:00 2001 From: OlegRyz Date: Thu, 29 Aug 2024 09:21:48 +0200 Subject: [PATCH 3/5] Make AdHandler internal --- .../theoplayer/android/connector/uplynk/internal/AdHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/AdHandler.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/AdHandler.kt index a1d3e2e7..7bb03df6 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/AdHandler.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/AdHandler.kt @@ -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() fun createAdBreak(adBreak: UplynkAdBreak) { From 2b2bd4f430c66811ceaf2297c9b77daf5cca53db Mon Sep 17 00:00:00 2001 From: OlegRyz Date: Thu, 29 Aug 2024 13:58:06 +0200 Subject: [PATCH 4/5] Remove accessor --- .../connector/uplynk/UplynkConnector.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt index 3a444470..b1a28797 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt @@ -48,27 +48,27 @@ class UplynkConnector( private val eventDispatcher = UplynkEventDispatcher().also { it.addListener(object : UplynkListener { override fun onAdBreaksUpdated(ads: UplynkAds) { - this@UplynkConnector.adBreaks = ads.breaks + adBreaks = ads.breaks } override fun onAdBegin(ad: UplynkAd) { - check(this@UplynkConnector.currentAd == null) { "Begin ad that before ending previous currentAd = ${this@UplynkConnector.currentAd} beginAd = $ad" } - this@UplynkConnector.currentAd = ad + check(currentAd == null) { "Begin ad that before ending previous currentAd = ${currentAd} beginAd = $ad" } + currentAd = ad } override fun onAdEnd(ad: UplynkAd) { - check(this@UplynkConnector.currentAd == ad) { "Trying to end ad that is not current. currentAd = ${this@UplynkConnector.currentAd} endedAd = $ad" } - this@UplynkConnector.currentAd = null + check(currentAd == ad) { "Trying to end ad that is not current. currentAd = ${currentAd} endedAd = $ad" } + currentAd = null } override fun onAdBreakBegin(adBreak: UplynkAdBreak) { - check(this@UplynkConnector.currentAdBreak == null) { "Begin adbreak before ending previous currentAdBreak = ${this@UplynkConnector.currentAdBreak} beginAdBreak = $adBreak" } - this@UplynkConnector.currentAdBreak = adBreak + check(currentAdBreak == null) { "Begin adbreak before ending previous currentAdBreak = ${currentAdBreak} beginAdBreak = $adBreak" } + currentAdBreak = adBreak } override fun onAdBreakEnd(adBreak: UplynkAdBreak) { - check(this@UplynkConnector.currentAdBreak == adBreak) { "Trying to end adbreak that is not current. currentAdBreak = ${this@UplynkConnector.currentAdBreak} endedAdBreak = $adBreak" } - this@UplynkConnector.currentAdBreak = null + check(currentAdBreak == adBreak) { "Trying to end adbreak that is not current. currentAdBreak = ${currentAdBreak} endedAdBreak = $adBreak" } + currentAdBreak = null } }) } From cf5c77f9b8bfa7228838ca3b69b0b8b902316e05 Mon Sep 17 00:00:00 2001 From: OlegRyz Date: Thu, 29 Aug 2024 13:59:39 +0200 Subject: [PATCH 5/5] Call all event listeners on the main thread --- .../connector/uplynk/internal/UplynkEventDispatcher.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkEventDispatcher.kt b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkEventDispatcher.kt index 6534a9ad..e46b5c7e 100644 --- a/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkEventDispatcher.kt +++ b/connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/internal/UplynkEventDispatcher.kt @@ -31,23 +31,23 @@ internal class UplynkEventDispatcher { listeners.forEach { it.onPreplayFailure(e) } } - fun dispatchAdBreaksUpdatedEvents(ads: UplynkAds) { + fun dispatchAdBreaksUpdatedEvents(ads: UplynkAds) = handler.post { listeners.forEach { it.onAdBreaksUpdated(ads) } } - fun dispatchAdBeginEvent(currentAd: UplynkAd) { + fun dispatchAdBeginEvent(currentAd: UplynkAd) = handler.post { listeners.forEach { it.onAdBegin(currentAd) } } - fun dispatchAdEndEvent(currentAd: UplynkAd) { + fun dispatchAdEndEvent(currentAd: UplynkAd) = handler.post { listeners.forEach { it.onAdEnd(currentAd) } } - fun dispatchAdBreakBeginEvent(currentAdBreak: UplynkAdBreak) { + fun dispatchAdBreakBeginEvent(currentAdBreak: UplynkAdBreak) = handler.post { listeners.forEach { it.onAdBreakBegin(currentAdBreak) } } - fun dispatchAdBreakEndEvent(currentAdBreak: UplynkAdBreak) { + fun dispatchAdBreakEndEvent(currentAdBreak: UplynkAdBreak) = handler.post { listeners.forEach { it.onAdBreakEnd(currentAdBreak) } }