Skip to content

Commit

Permalink
test: Add UI for configuring CSAI Example (#101)
Browse files Browse the repository at this point in the history
* Custom layout for IMA client and server ads activities

* Add some param views (but not the right ones)

* layoutage

* there we go

* fix build

* Add IMA example tags from the google developer site

* Ok here we go

* work on more of this

* Done

* up

* cool heres a spinner adapter

* almost there

* Here we go

* View is more clunky than I remember

* now we are almost ready to try it

* ok

* there we go

* added dryness

* now for cleanup

* renaming things

* OK

* nice

* done

* fix colors

* now here we go

* One more fix

* Add Data Key entry

* commit env key

* Ok set some timers

* works

* Oops the label

* cleanup

* app version name now usable

* messing with it

* fix

* Add ad tag title

* IMA example should not autoplay

* Fix the layout

* Add scrolling

* Ok that should fix scrolling

* max lines

* now its better

* chore: add short video to ad tag example and set as default

* Cleanup in the DAI example

* remove extraneous typos

* Maybe try this way of versioning the test app

* Revert "Maybe try this way of versioning the test app"

This reverts commit ff8c7e1.

---------

Co-authored-by: AJ Lauer Barinov <[email protected]>
  • Loading branch information
daytime-em and andrewjl-mux authored Nov 13, 2024
1 parent 8d90cc8 commit 612b60f
Show file tree
Hide file tree
Showing 18 changed files with 810 additions and 36 deletions.
4 changes: 3 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ android {
targetSdk 35
minSdk 21
versionCode 1
versionName "1.0"
def commit = "git rev-parse --short HEAD".execute().inputReader().readLines().join()
def branch = "git branch --show-current".execute().inputReader().readLines().join()
versionName "$branch-$commit"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
android:theme="@style/Theme.MuxDataSDKForMedia3" />
<activity
android:name=".examples.ima.ImaClientAdsActivity"
android:screenOrientation="portrait"
android:exported="false" />
<activity
android:name=".examples.ima.ImaServerAdsActivity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ object Constants {
const val VOD_TEST_URL_STEVE = "http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/sl.m3u8"
const val VOD_TEST_URL_DRAGON_WARRIOR_LADY =
"https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8"
const val VOD_TEST_URL_TEARS_OF_STEEL = "https://stream.mux.com/u02xH9SB1ZZNNjPiQp4l6mhzBKJ101uExYx4LU02J5Xm88.m3u8"
const val VOD_TEST_URL_BIG_BUCK_BUNNY = "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
const val VOD_TEST_SHORT_VIDEO = "https://stream.mux.com/a4nOgmxGWg6gULfcBbAa00gXyfcwPnAFldF8RdsNyk8M.m3u8"
const val AD_TAG_SIMPLE_W_MID_ROLL = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostoptimizedpod&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator="
const val AD_TAG_COMPLEX = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostlongpod&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator="
const val AD_TAG_BROKEN_REDIRECT = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_ad_samples&sz=640x480&cust_params=sample_ct%3Dredirecterror&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator="
const val SSAI_ASSET_TAG_BUCK = "c-rArva4ShKVIAkNfy6HUQ"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package com.mux.stats.muxdatasdkformedia3.examples

import android.net.Uri
import android.os.Bundle
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaItem.AdsConfiguration
import com.mux.stats.muxdatasdkformedia3.Constants

/**
* Helper class for the example Activities that handles managing playback parameters, etc for
* IMA client-side ads
*/
class ImaClientAdsParamHelper {
// todo: set data from Intent (ie, deep linking)

var sourceUrl: String? = null
var adTagUrl: String? = null
var title: String? = null
var envKey: String? = null

fun createMediaItemBuilder(): MediaItem.Builder {
return MediaItem.Builder()
.setUri(Uri.parse(sourceUrlOrDefault()))
.setAdsConfiguration(
AdsConfiguration.Builder(Uri.parse(adTagUrlOrDefault())).build()
)
}

fun adTagUrlOrDefault(): String {
return adTagUrl?.ifEmpty { DEFAULT_AD_TAG_URL } ?: DEFAULT_AD_TAG_URL
}

fun sourceUrlOrDefault(): String {
return sourceUrl?.ifEmpty { DEFAULT_SOURCE_URL } ?: DEFAULT_SOURCE_URL
}

fun envKeyOrDefault(): String {
return envKey?.ifEmpty { Constants.MUX_DATA_ENV_KEY } ?: Constants.MUX_DATA_ENV_KEY
}

fun createMediaItem(): MediaItem {
return createMediaItemBuilder().build()
}

fun saveInstanceState(state: Bundle) {
state.putString("source url", sourceUrl)
state.putString("ad tag url", adTagUrl)
state.putString("env key", envKey)
}

fun restoreInstanceState(state: Bundle) {
sourceUrl = state.getString("source url", null)
adTagUrl = state.getString("ad tag url", null)
envKey = state.getString("env key", null)
}

companion object {
const val DEFAULT_SOURCE_URL = Constants.VOD_TEST_SHORT_VIDEO
const val DEFAULT_AD_TAG_URL = Constants.AD_TAG_COMPLEX
}
}

data class ImaAdTagExample(
val title: String,
val adTagUrl: String,
)

object ImaAdTags {

val googleTags: List<ImaAdTagExample> = listOf(
ImaAdTagExample(
title = "Single Inline Linear",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_ad_samples&sz=640x480&cust_params=sample_ct%3Dlinear&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "Single Skippable Inline",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_preroll_skippable&sz=640x480&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "Single Redirect Linear",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_ad_samples&sz=640x480&cust_params=sample_ct%3Dredirectlinear&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "Single Redirect Error",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_ad_samples&sz=640x480&cust_params=sample_ct%3Dredirecterror&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "Single Redirect Broken (Fallback)",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_ad_samples&sz=640x480&cust_params=sample_ct%3Dredirecterror&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&nofb=1&correlator="
),
ImaAdTagExample(
title = "Single VPAID 2.0 Linear",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_ad_samples&sz=640x480&cust_params=sample_ct%3Dlinearvpaid2js&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "Single VPAID 2.0 Non-Linear",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_ad_samples&sz=640x480&cust_params=sample_ct%3Dnonlinearvpaid2js&ciu_szs=728x90%2C300x250&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "Single Non-linear Inline",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/nonlinear_ad_samples&sz=480x70&cust_params=sample_ct%3Dnonlinear&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "Single Vertical Inline Linear",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_vertical_ad_samples&sz=360x640&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "VMAP Session Ad Rule Pre-roll",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sar%3Da0f2&ciu_szs=300x250&ad_rule=1&gdfp_req=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "VMAP Pre-roll",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpreonly&ciu_szs=300x250%2C728x90&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "VMAP Pre-roll + Bumper",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpreonlybumper&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "VMAP Post-roll",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpostonly&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "VMAP Post-roll + Bumper",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpostonlybumper&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "VMAP Pre-, Mid-, and Post-rolls, Single Ads",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpost&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator="
),
ImaAdTagExample(
title = "VMAP - Pre-roll Single Ad, Mid-roll Standard Pod with 3 ads, Post-roll Single Ad",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostpod&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator="
),
ImaAdTagExample(
title = "VMAP - Pre-roll Single Ad, Mid-roll Optimized Pod with 3 Ads, Post-roll Single Ad",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostoptimizedpod&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator="
),
ImaAdTagExample(
title = "VMAP - Pre-roll Single Ad, Mid-roll Standard Pod with 3 Ads, Post-roll Single Ad (bumpers around all ad breaks)",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostpodbumper&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator="
),
ImaAdTagExample(
title = "VMAP - Pre-roll Single Ad, Mid-roll Optimized Pod with 3 Ads, Post-roll Single Ad (bumpers around all ad breaks)",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostoptimizedpodbumper&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator="
),
ImaAdTagExample(
title = "VMAP - Pre-roll Single Ad, Mid-roll Standard Pods with 5 Ads Every 10 Seconds, Post-roll Single Ad",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostlongpod&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator="
),
ImaAdTagExample(
title = "SIMID Survey Pre-roll",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/simid&description_url=https%3A%2F%2Fdevelopers.google.com%2Finteractive-media-ads&sz=640x480&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator="
),
ImaAdTagExample(
title = "OM SDK Sample Pre-roll",
adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/omid_ad_samples&env=vp&gdfp_req=1&output=vast&sz=640x480&description_url=http%3A%2F%2Ftest_site.com%2Fhomepage&vpmute=0&vpa=0&vad_format=linear&url=http%3A%2F%2Ftest_site.com&vpos=preroll&unviewed_position_start=1&correlator="
),
)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package com.mux.stats.muxdatasdkformedia3.examples.ima

import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaItem.AdsConfiguration
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
Expand All @@ -18,7 +15,11 @@ import androidx.media3.exoplayer.ima.ImaAdsLoader
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.ui.PlayerView
import com.mux.stats.muxdatasdkformedia3.Constants
import com.mux.stats.muxdatasdkformedia3.databinding.ActivityPlayerBinding
import com.mux.stats.muxdatasdkformedia3.databinding.ActivityImaClientAdsBinding
import com.mux.stats.muxdatasdkformedia3.examples.ImaAdTags
import com.mux.stats.muxdatasdkformedia3.examples.ImaClientAdsParamHelper
import com.mux.stats.muxdatasdkformedia3.view.SpinnerParamEntryView
import com.mux.stats.sdk.core.model.CustomData
import com.mux.stats.sdk.core.model.CustomerData
import com.mux.stats.sdk.core.model.CustomerPlayerData
import com.mux.stats.sdk.core.model.CustomerVideoData
Expand All @@ -29,35 +30,113 @@ import com.mux.stats.sdk.muxstats.monitorWithMuxData

class ImaClientAdsActivity : AppCompatActivity() {

private lateinit var view: ActivityPlayerBinding
private lateinit var view: ActivityImaClientAdsBinding
private var player: Player? = null
private var muxStats: MuxStatsSdkMedia3<ExoPlayer>? = null
private var adsLoader: ImaAdsLoader? = null

private val paramHelper = ImaClientAdsParamHelper()

@OptIn(UnstableApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
view = ActivityPlayerBinding.inflate(layoutInflater)
view = ActivityImaClientAdsBinding.inflate(layoutInflater)
setContentView(view.root)

savedInstanceState?.let { paramHelper.restoreInstanceState(it) }

view.imaClientAdsSrcUrl.onClear = {
paramHelper.sourceUrl = null
}
view.imaClientAdsSpinner.onSelected = {
// update the ad tag whenever a new spinner item is selected
val (title, adTagUrl) = view.imaClientAdsSpinner.entry
// ... unless you need to enter text
if (!adTagUrl.isNullOrBlank()) {
paramHelper.adTagUrl = adTagUrl
paramHelper.title = title
val customerData = createCustomerData(title, adTagUrl)
muxStats?.updateCustomerData(customerData)
initPlayer(play = player?.isPlaying == true)
}
}
view.imaClientAdsUpdateMediaItem.setOnClickListener {
// update everything when the 'update' button is clicked
paramHelper.sourceUrl = view.imaClientAdsSrcUrl.entry
paramHelper.envKey = view.imaClientAdsDataKey.entry

val (title, adTagUrl) = view.imaClientAdsSpinner.entry
paramHelper.adTagUrl = adTagUrl
val customerData = createCustomerData(title, adTagUrl)
muxStats?.updateCustomerData(customerData)

initPlayer(play = player?.isPlaying == true)
}
view.imaClientAdsSpinner.adapter = createAdTagAdapter()

view.imaClientAdsSrcUrl.hint = ImaClientAdsParamHelper.DEFAULT_SOURCE_URL
view.imaClientAdsSrcUrl.entry = paramHelper.sourceUrl
view.imaClientAdsSrcUrl.onClear = { paramHelper.sourceUrl = null }

view.imaClientAdsDataKey.hint = Constants.MUX_DATA_ENV_KEY
view.imaClientAdsDataKey.entry = paramHelper.envKey
view.imaClientAdsDataKey.onClear = { paramHelper.envKey = null }

view.playerView.apply {
setShowBuffering(PlayerView.SHOW_BUFFERING_WHEN_PLAYING)
controllerAutoShow = true
}

window.addFlags(View.KEEP_SCREEN_ON)
}

override fun onResume() {
super.onResume()
startPlaying(Constants.VOD_TEST_URL_DRAGON_WARRIOR_LADY, Constants.AD_TAG_COMPLEX)
}

override fun onPause() {
stopPlaying()
super.onPause()
}

private fun startPlaying(mediaUrl: String, adTagUri: String) {
override fun onSaveInstanceState(outState: Bundle) {
paramHelper.saveInstanceState(outState)
super.onSaveInstanceState(outState)
}

private fun createCustomerData(title: String?, adTagUrl: String?): CustomerData {
return CustomerData(
CustomerPlayerData().apply { },
CustomerVideoData().apply {
videoTitle = "Media3 - IMA Ads: $title"
},
CustomerViewData().apply { },
CustomData().apply {
customData1 = adTagUrl
customData2 = title
}
)
}

private fun createAdTagAdapter(): SpinnerParamEntryView.Adapter {
val googleTags = ImaAdTags.googleTags.map { tag ->
SpinnerParamEntryView.Item(
customAllowed = false,
title = tag.title,
text = tag.adTagUrl
)
}
val customTag = SpinnerParamEntryView.Item(
customAllowed = true,
title = "Custom Ad Tag",
text = null,
)
val allTags = listOf(customTag) + googleTags

return view.imaClientAdsSpinner.Adapter(this, allTags)
}

private fun initPlayer(play: Boolean = true) {
stopPlaying()

player = createPlayer().also { newPlayer ->
Expand All @@ -72,14 +151,9 @@ class ImaClientAdsActivity : AppCompatActivity() {
.apply { setPlayer(newPlayer) }

view.playerView.player = newPlayer
newPlayer.setMediaItem(
MediaItem.Builder()
.setUri(Uri.parse(mediaUrl))
.setAdsConfiguration(AdsConfiguration.Builder(Uri.parse(adTagUri)).build())
.build()
)
newPlayer.prepare()
newPlayer.playWhenReady = true
newPlayer.setMediaItem(paramHelper.createMediaItem())
if (play) { newPlayer.prepare() }
newPlayer.playWhenReady = play
}
}

Expand All @@ -96,17 +170,11 @@ class ImaClientAdsActivity : AppCompatActivity() {

private fun monitorPlayer(player: ExoPlayer): MuxStatsSdkMedia3<ExoPlayer> {
// You can add your own data to a View, which will override any data we collect
val customerData = CustomerData(
CustomerPlayerData().apply { },
CustomerVideoData().apply {
videoTitle = "Mux Data for Media3 - CSAI Ads"
},
CustomerViewData().apply { }
)
val customerData = createCustomerData(paramHelper.title, paramHelper.adTagUrl)

return player.monitorWithMuxData(
context = this,
envKey = Constants.MUX_DATA_ENV_KEY,
envKey = paramHelper.envKeyOrDefault(),
customerData = customerData,
playerView = view.playerView
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ class ImaServerAdsActivity : AppCompatActivity() {
.apply { state?.let { adsLoaderState = it } }
.monitorWith(
{ muxStats }, // This is safe, will only be called while playing
{
{ adEvent ->
/*your ad event handling here*/
when (it.type) {
AdEvent.AdEventType.AD_PERIOD_STARTED -> view.skipAd.visibility = View.VISIBLE
AdEvent.AdEventType.AD_PERIOD_ENDED -> view.skipAd.visibility = View.GONE
else -> { /* ignore */ }
}
when (adEvent.type) {
AdEvent.AdEventType.AD_PERIOD_STARTED -> view.skipAd.visibility = View.VISIBLE
AdEvent.AdEventType.AD_PERIOD_ENDED -> view.skipAd.visibility = View.GONE
else -> { /* ignore */ }
}
},
{ /*your ad error handling here*/ },
)
Expand Down
Loading

0 comments on commit 612b60f

Please sign in to comment.