Skip to content

Commit 556ca23

Browse files
Uplynk ad skipping (#47)
* WIP * List based integration * Refactor to get ad break info from UplynkAdScheduler as the list might get updated after a ping response * play none config and seek to an ad * implement skip * basic skip implementation * handle backward seek and logic for getLastUnWatchedAdBreakOffset * Update logic * Formatting * Remove log * Remove TAG * Formatting * Add config * Fix test * failing test * Log ad events * update comment * update layout * Media3 integration * refactor and fixes based on testing * remove duplicate code * Comment edits * New line at EOF * Add button for changing backend * Remove duplicate ad events * Rename Uplynk Media to Uplynk * Address review comments * Rename method * Rename method * fix last unwatched break logic * playback URL parameters * Update description * provide default value * cleanup * Update Readme * Don't rely of seek time in SEEKING event * show use of UplynkConfiguration in sample app * revert some changes * check for userId when externalIds are provided * use require instead of check * get the seeking time the right way * ping on seeking * fix ping bug --------- Co-authored-by: Mattias Buelens <[email protected]>
1 parent 67d8ed3 commit 556ca23

File tree

17 files changed

+451
-80
lines changed

17 files changed

+451
-80
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ dependencies {
4141

4242
implementation "com.theoplayer.theoplayer-sdk-android:core:$sdkVersion"
4343
implementation "com.theoplayer.theoplayer-sdk-android:integration-ads-ima:$sdkVersion"
44+
implementation "com.theoplayer.theoplayer-sdk-android:integration-media3:$sdkVersion"
4445

4546
implementation project(":connectors:analytics:conviva")
4647
implementation libs.conviva

app/src/main/java/com/theoplayer/android/connector/MainActivity.kt

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.util.Log
55
import android.view.View
66
import android.view.ViewGroup
77
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
8+
import android.widget.Button
89
import android.widget.FrameLayout
910
import androidx.appcompat.app.AlertDialog
1011
import androidx.appcompat.app.AppCompatActivity
@@ -15,14 +16,17 @@ import com.theoplayer.android.api.ads.ima.GoogleImaIntegrationFactory
1516
import com.theoplayer.android.api.event.ads.AdBreakEvent
1617
import com.theoplayer.android.api.event.ads.AdsEventTypes
1718
import com.theoplayer.android.api.event.ads.SingleAdEvent
18-
import com.theoplayer.android.api.event.player.PlayerEventTypes
19+
import com.theoplayer.android.api.media3.Media3PlayerIntegration
20+
import com.theoplayer.android.api.media3.Media3PlayerIntegrationFactory.createMedia3PlayerIntegration
1921
import com.theoplayer.android.connector.analytics.comscore.ComscoreConfiguration
2022
import com.theoplayer.android.connector.analytics.comscore.ComscoreConnector
2123
import com.theoplayer.android.connector.analytics.comscore.ComscoreMediaType
2224
import com.theoplayer.android.connector.analytics.comscore.ComscoreMetaData
2325
import com.theoplayer.android.connector.analytics.conviva.ConvivaConfiguration
2426
import com.theoplayer.android.connector.analytics.conviva.ConvivaConnector
2527
import com.theoplayer.android.connector.analytics.nielsen.NielsenConnector
28+
import com.theoplayer.android.connector.uplynk.SkippedAdStrategy
29+
import com.theoplayer.android.connector.uplynk.UplynkConfiguration
2630
import com.theoplayer.android.connector.uplynk.UplynkConnector
2731
import com.theoplayer.android.connector.uplynk.UplynkListener
2832
import com.theoplayer.android.connector.uplynk.network.AssetInfoResponse
@@ -41,12 +45,17 @@ class MainActivity : AppCompatActivity() {
4145
private lateinit var comscoreConnector: ComscoreConnector
4246
private lateinit var yospaceConnector: YospaceConnector
4347
private lateinit var uplynkConnector: UplynkConnector
48+
private var media3PlayerIntegration: Media3PlayerIntegration? = null
4449
private var selectedSource: Source = sources.first()
50+
private var useMedia3 = false
51+
private var btn_backend: Button? = null
4552

4653
override fun onCreate(savedInstanceState: Bundle?) {
4754
super.onCreate(savedInstanceState)
4855
setContentView(R.layout.activity_main)
4956

57+
btn_backend = findViewById(R.id.button_backend)
58+
5059
setupTHEOplayer()
5160
setupGoogleImaIntegration()
5261
setupConviva()
@@ -152,7 +161,7 @@ class MainActivity : AppCompatActivity() {
152161
}
153162

154163
private fun setupUplynk() {
155-
uplynkConnector = UplynkConnector(theoplayerView)
164+
uplynkConnector = UplynkConnector(theoplayerView, UplynkConfiguration(defaultSkipOffset = 5, SkippedAdStrategy.PLAY_LAST))
156165
uplynkConnector.addListener(object: UplynkListener {
157166
override fun onPreplayVodResponse(response: PreplayVodResponse) {
158167
Log.d("UplynkConnectorEvents", "PREPLAY_VOD_RESPONSE $response")
@@ -178,14 +187,6 @@ class MainActivity : AppCompatActivity() {
178187
Log.d("UplynkConnectorEvents", "PING_RESPONSE $pingResponse")
179188
}
180189
})
181-
182-
theoplayerView.player.ads.addEventListener(AdsEventTypes.AD_ERROR) {
183-
Log.d("UplynkConnectorEvents", "AD_ERROR " + it.error)
184-
}
185-
186-
theoplayerView.player.addEventListener(PlayerEventTypes.ERROR) {
187-
Log.d("UplynkConnectorEvents", "ERROR " + it.errorObject)
188-
}
189190
}
190191

191192
private fun setupAdListeners() {
@@ -239,11 +240,38 @@ class MainActivity : AppCompatActivity() {
239240
}
240241

241242
private fun setSource(source: Source) {
243+
if (useMedia3) {
244+
if (media3PlayerIntegration == null) {
245+
media3PlayerIntegration = createMedia3PlayerIntegration()
246+
theoplayerView.player.addIntegration(media3PlayerIntegration)
247+
}
248+
} else {
249+
media3PlayerIntegration?.let {
250+
theoplayerView.player.removeIntegration(it)
251+
media3PlayerIntegration = null
252+
}
253+
}
254+
242255
selectedSource = source
243256
theoplayerView.player.source = source.sourceDescription
244257
nielsenConnector.updateMetadata(source.nielsenMetadata)
245258
}
246259

260+
fun selectBackend(view: View) {
261+
val backendList = backend.map { it }.toTypedArray()
262+
val currentBackend = if (useMedia3) 1 else 0
263+
AlertDialog.Builder(this)
264+
.setTitle("Select backend")
265+
.setSingleChoiceItems(backendList, currentBackend) { dialog, position ->
266+
useMedia3 = position == 1
267+
btn_backend?.text = "B/E: " + backendList[position]
268+
setSource(selectedSource)
269+
dialog.dismiss()
270+
}
271+
.create()
272+
.show()
273+
}
274+
247275
fun playPause(view: View) {
248276
if (theoplayerView.player.isPaused) {
249277
theoplayerView.player.play()

app/src/main/java/com/theoplayer/android/connector/Sources.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ data class Source(
1717
val nielsenMetadata: HashMap<String, Any> = hashMapOf()
1818
)
1919

20+
val backend: List<String> by lazy {
21+
listOf("Default", "Media3")
22+
}
23+
2024
val sources: List<Source> by lazy {
2125
listOf(
2226
Source(

app/src/main/res/layout/activity_main.xml

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,43 +22,68 @@
2222
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title" />
2323

2424
<LinearLayout
25-
android:id="@+id/layout_buttons"
25+
android:id="@+id/layout_options"
2626
android:layout_width="match_parent"
2727
android:layout_height="wrap_content"
28-
android:layout_marginStart="8dp"
29-
android:layout_marginEnd="8dp"
30-
android:gravity="center_vertical"
31-
android:orientation="horizontal">
28+
android:orientation="vertical">
3229

33-
<Button
34-
android:layout_width="wrap_content"
30+
<LinearLayout
31+
android:id="@+id/layout_buttons"
32+
android:layout_width="match_parent"
3533
android:layout_height="wrap_content"
34+
android:layout_marginStart="8dp"
3635
android:layout_marginEnd="8dp"
37-
android:minWidth="0dp"
38-
android:onClick="seekBackward"
39-
android:text="-10" />
36+
android:gravity="center_vertical"
37+
android:orientation="horizontal">
4038

41-
<Button
42-
android:layout_width="wrap_content"
43-
android:layout_height="wrap_content"
44-
android:layout_marginEnd="8dp"
45-
android:onClick="selectSource"
46-
android:text="set source" />
39+
<Button
40+
android:layout_width="wrap_content"
41+
android:layout_height="wrap_content"
42+
android:layout_marginEnd="8dp"
43+
android:onClick="selectSource"
44+
android:text="source" />
45+
46+
<Button
47+
android:id="@+id/button_backend"
48+
android:layout_width="wrap_content"
49+
android:layout_height="wrap_content"
50+
android:layout_marginEnd="8dp"
51+
android:onClick="selectBackend"
52+
android:text="backend" />
53+
</LinearLayout>
4754

48-
<Button
49-
android:layout_width="wrap_content"
55+
<LinearLayout
56+
android:id="@+id/layout_action_buttons"
57+
android:layout_width="match_parent"
5058
android:layout_height="wrap_content"
59+
android:layout_marginStart="8dp"
5160
android:layout_marginEnd="8dp"
52-
android:onClick="playPause"
53-
android:text="play/pause" />
61+
android:gravity="center_vertical"
62+
android:orientation="horizontal">
5463

55-
<Button
56-
android:layout_width="wrap_content"
57-
android:layout_height="wrap_content"
58-
android:minWidth="0dp"
59-
android:onClick="seekForward"
60-
android:text="+10" />
64+
<Button
65+
android:layout_width="wrap_content"
66+
android:layout_height="wrap_content"
67+
android:layout_marginEnd="8dp"
68+
android:minWidth="0dp"
69+
android:onClick="seekBackward"
70+
android:text="-10" />
71+
72+
<Button
73+
android:layout_width="wrap_content"
74+
android:layout_height="wrap_content"
75+
android:layout_marginEnd="8dp"
76+
android:onClick="playPause"
77+
android:text="play/pause" />
6178

79+
<Button
80+
android:layout_width="wrap_content"
81+
android:layout_height="wrap_content"
82+
android:layout_marginEnd="8dp"
83+
android:minWidth="0dp"
84+
android:onClick="seekForward"
85+
android:text="+10" />
86+
</LinearLayout>
6287
</LinearLayout>
6388

6489
<FrameLayout
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.theoplayer.android.connector.uplynk
2+
3+
/**
4+
* The action to take when the user seeks over one or more unplayed ads.
5+
*/
6+
enum class SkippedAdStrategy {
7+
/**
8+
* Plays none of the ad breaks skipped due to a seek.
9+
*/
10+
PLAY_NONE,
11+
12+
/**
13+
* Plays all the ad breaks skipped due to a seek.
14+
*/
15+
PLAY_ALL,
16+
17+
/**
18+
* Plays the last ad break skipped due to a seek.
19+
*/
20+
PLAY_LAST
21+
}
22+
23+
/**
24+
* Describes the configuration of the Uplynk integration.
25+
*/
26+
data class UplynkConfiguration(
27+
/**
28+
* The offset after which an ad break may be skipped, in seconds.
29+
*
30+
* @remarks
31+
* If the offset is -1, the ad is unskippable.
32+
* If the offset is 0, the ad is immediately skippable.
33+
* Otherwise it must be a positive number indicating the offset.
34+
*
35+
* @defaultValue `-1`
36+
*/
37+
val defaultSkipOffset: Int = -1,
38+
39+
/**
40+
* The ad skip strategy which is used when seeking over ads.
41+
*
42+
* @defaultValue `PLAY_NONE`.
43+
*/
44+
val onSeekOverAd: SkippedAdStrategy = SkippedAdStrategy.PLAY_NONE
45+
)

connectors/uplynk/src/main/java/com/theoplayer/android/connector/uplynk/UplynkConnector.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@ import com.theoplayer.android.connector.uplynk.internal.network.UplynkApi
1313
internal const val TAG = "UplynkConnector"
1414

1515
/**
16-
* A connector for the Uplynk Media Platform.
16+
* A connector for the Uplynk Platform.
1717
*
1818
* @param theoplayerView
1919
* The THEOplayer view, which will be connected to the created connector.
2020
*
21-
* @see [Uplynk Media Platform](https://docs.edgecast.com/video/index.html)
21+
* @see [Uplynk Platform](https://docs.edgecast.com/video/index.html)
2222
*/
2323
class UplynkConnector(
2424
private val theoplayerView: THEOplayerView,
25+
private val uplynkConfiguration: UplynkConfiguration = UplynkConfiguration()
2526
) {
2627
private lateinit var integration: UplynkAdIntegration
2728
private val eventDispatcher = UplynkEventDispatcher()
@@ -36,7 +37,8 @@ class UplynkConnector(
3637
controller,
3738
eventDispatcher,
3839
UplynkSsaiDescriptionConverter(),
39-
UplynkApi()
40+
UplynkApi(),
41+
uplynkConfiguration
4042
)
4143
this.integration = integration
4244
return integration

0 commit comments

Comments
 (0)