A library for sending ad beacons on Android devices. Library is currently integrated with Google PAL & OM SDK. The project also includes a demo app that hosts an ExoPlayer that allows users to test their custom DAI assets with Google PAL & OM SDK signaling.
- Harmonic VOS metadata parsing
- Google Programmatic Access Libraries (PAL)
- Fire ad beacons via Open Measurement SDK (OMSDK)
- Google AdChoices (Why this ad?)
- Android TV ads library (AT&C rendering)
- An overlay showing ad break parameters (ID, duration, fired beacons)
- Flexible integration: supports both full tracking (with views) and beacon-only tracking (headless)
Android 8.0 (API 26) or above
- Min SDK 26
- Target SDK 33
- Compile SDK 34
-
Include this library in your project
Groovy:
dependencies { implementation 'io.github.harmonicinc-com:client-side-ad-tracking-android:0.1.14' }Kotlin:
dependencies { implementation("io.github.harmonicinc-com:client-side-ad-tracking-android:0.1.14") }Change version to the latest available. You may find the latest version here.
-
Include the OMSDK library as dependency
[!NOTE]
As of now (Oct 2023), Google still has no support on bundling local modules into a single AAR (Fat AAR). That blocks us from shipping the OMSDK AAR together with the library.- Create a directory on your app root (we use
libsas an example) - Download the AAR that we've included in this repo: omsdk-android-1.4.5-release.aar
- Place it under
libs - Add the following lines in your dependency block
Kotlin:
dependencies { implementation fileTree(include: ['*.aar'], dir: 'libs') ... }dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar")))) // or just include everything implementation(fileTree("libs")) ... }
- Create a directory on your app root (we use
-
Declare
AD_IDpermission- To enable Google WTA, add the following line to your
AndroidManifest.xml<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
- To enable Google WTA, add the following line to your
-
Initialize parameters & interfaces
- Create an instance of
AdTrackingManagerin activity (preferably, could be somewhere else).class PlayerActivity: FragmentActivity() { private val adTrackingManager = AdTrackingManager(this) }
- Create an instance of
AdTrackingManagerParams. Fill in all the required parameters:val adTrackingParams = AdTrackingManagerParams( descriptionUrl, // String: Description URL of video being played iconSupported, // Boolean: True if WTA (Why this ad?) icon is supported playerType, // String: Name of partner player used to play the ad playerVersion, // String: Version of partner player used ppid, // String: Publisher Provided ID supportedApiFrameworks, // Set<Int>: Supported API frameworks. See appendix for details playerHeight, // Int: Player height playerWidth, // Int: Player width willAdAutoplay, // Boolean: Will ad begin playback automatically? willAdPlayMuted, // Boolean: Will ad being played while muted? continuousPlayback, // Boolean: Will the player continues to play content videos after ad? omidPartnerVersion?, // String?: OMID partner version omidPartnerName?, // String: OMID partner name omidCustomReferenceData?, // String?: OMID custom reference data in JSON string // Optional params: initRequest, // Boolean: Should the session init API be used? cacheRetentionTimeMs // Long: How long should beacon metadata be cached? (defaults to 2 hours: 2 * 60 * 60 * 1000L) )
- Create a class that implements
PlayerAdapter. Override all mandatory methods and return appropriate values from your player. The demo project includes an example ExoPlayerAdapter.kt for your reference.class YourPlayerAdapter(private val player: YourPlayer): PlayerAdapter { override fun getCurrentPositionMs(): Long { // TO BE IMPLEMENTED } override fun getPresentationStartTimeMs(): Long { // TO BE IMPLEMENTED } override fun getPlaybackRate(): Float { // TO BE IMPLEMENTED } override fun getDuration(): Long { // TO BE IMPLEMENTED } override fun getAudioVolume(): Float { // TO BE IMPLEMENTED } override fun isPaused(): Boolean { // TO BE IMPLEMENTED } }
- Fire the events in
PlayerAdapterwhen specific conditions are met. You might need to listen events emitted from your player.val playerAdapter = YourPlayerAdapter(player) // Call when player starts to buffer playerAdapter.onBufferStart() // Call when playback resumes after buffering playerAdapter.onBufferEnd() // Call when user clicks pause (no need to check if ad is playing. The lib will handle it) playerAdapter.onPause() // Call when user clicks play (no need to check if ad is playing. The lib will handle it) playerAdapter.onResume() // Call when the user clicks an ad playerAdapter.onVideoAdClick() // Call when the user clicks somewhere other than an ad (e.g. skip, mute, etc.) playerAdapter.onVideoAdViewTouch() // Call when user changes the audio volume (no need to check if ad is playing. The lib will handle it) playerAdapter.onVolumeChanged()
- Create an instance of
-
Using the library
-
Before loading the asset, call
prepareBeforeLoadto preload the library. Note that it should be called within a coroutine scope.val manifestUrl = "https://www.example.com" // put your URL here CoroutineScope(Dispatchers.Main).launch { adTrackingManager.prepareBeforeLoad(manifestUrl, adTrackingParams) }
-
After preloading, check if the asset supports Harmonic SSAI using
isSSAISupported. Make sure the asset is supported by the library before continuing.val isSSAISupported = adTrackingManager.isSSAISupported()
-
Check if a new URL is obtained by the library. If that is the case, use that URL for your playback.
val updatedManifestUrl = manifestUrl if (adTrackingManager.getObtainedManifestUrl() != null) { updatedManifestUrl = adTrackingManager.getObtainedManifestUrl() }
-
To obtain the generated nonce, call
appendNonceToUrlval manifestUrls = listOf("https://www.example.com") val urlsWithNonce = adTrackingManager.appendNonceToUrl(manifestUrls)
-
Finally, call
onPlayto start the library. There are two usage scenarios:Full Tracking (with views) - Enables OMSDK verification, overlays, and ad choices:
// Must be called from the main thread when providing playerView adTrackingManager.onPlay( playerContext, // context in your player view (typically Activity or Fragment context) playerAdapter, // adapter created above overlayFrameLayout, // Optional. A frame layout for showing overlays (e.g. WTA icon, tracking debug overlay) playerView // Your player view. Required for OMSDK verification and viewability measurement )
Beacon-Only Tracking (without views) - Only PMM beacon tracking, useful for headless scenarios:
adTrackingManager.onPlay( context, // Android context (Activity, Service, or Application context) playerAdapter // adapter created above // overlayFrameLayout and playerView are optional - omit them for beacon-only tracking )
[!IMPORTANT]
Threading Requirements:- When
playerViewis provided: Must be called from the main thread
The library will throw an
IllegalStateExceptionif called from a background thread whenplayerViewis provided.[!NOTE]
WhenplayerViewis not provided, only PMM beacon tracking will be active. OMSDK verification, tracking overlays, and ad choices will be disabled since they require view access for viewability measurement. - When
-
-
Stop the library after playback
- Remember to clean the library after unloading the asset. Otherwise it will keep querying ads metadata and the cached metadata will not be removed.
adTrackingManager.cleanupAfterStop()
- Remember to clean the library after unloading the asset. Otherwise it will keep querying ads metadata and the cached metadata will not be removed.
-
Error Handling (Optional)
- You can optionally set an error listener to receive callbacks when errors occur in the ad tracking system.
adTrackingManager.setErrorListener(object : AdTrackingErrorListener { override fun onError(error: AdTrackingError) { when (error) { is AdTrackingError.SessionInitError -> { // Handle session initialization errors (may be non-recoverable, check error.errorIsRecoverable) Log.e("AdTracking", "Initialization error: ${error.errorMessage}", error.errorCause) } is AdTrackingError.BeaconError -> { // Handle beacon sending errors Log.w("AdTracking", "Beacon error: Event=${error.event}, URL=${error.beaconUrl}, Message=${error.errorMessage}", error.errorCause) } is AdTrackingError.MetadataError -> { // Handle metadata errors Log.w("AdTracking", "Metadata error: ${error.errorMessage}", error.errorCause) } } } })
- Available Error Types:
SessionInitError: Occurs during session initialization when URLs/session ID cannot be constructed properly. (May be non-recoverable)- If the fallback implemented by the library also fails, then
error.errorIsRecoverablewill befalse. Ad tracking will not be enabled.
- If the fallback implemented by the library also fails, then
BeaconError: Occurs when a beacon request fails to be sent. Includes the beacon URL, event type, error message, and underlying cause.MetadataError: Occurs when ad metadata cannot be fetched or parsed. The library will fetch the metadata again after a set delay.
- You can optionally set an error listener to receive callbacks when errors occur in the ad tracking system.
Follow below steps so traffic (especially HTTPS) can be proxied & decrypted by Charles
- Open Charles Proxy, go to Help > SSL Proxying > Save Charles Root Certificate
- Save as
<your_android_app_project_root>/src/main/res/raw/charles_ssl_cert.pem - Create an XML under
<root>/src/main/res/xml/network_security_config.xmlwith the following content:<network-security-config> <debug-overrides> <trust-anchors> <!-- Trust user added CAs while debuggable only --> <certificates src="user" /> <certificates src="@raw/charles_ssl_cert" /> </trust-anchors> </debug-overrides> </network-security-config>
- Reference to the new config in your app's manifest (i.e.
AndroidManifest.xml)<?xml version="1.0" encoding="utf-8"?> <manifest> <application android:networkSecurityConfig="@xml/network_security_config"> </application> </manifest>
- Configure the proxy settings on your Android device/emulator. Please refer to your device documentation for instructions.
- You should now be able to capture SSL traffic in Charles. Look for segments/manifest/metadata requests and see if you can view the response body.
Note
Allowing insecure traffic is not recommended in production environment. Remember to undo the changes before publishing.
Android apps by default blocks plain text traffic. If you would like to play insecure streams (HTTP):
- In your app's manifest (i.e.
AndroidManifest.xml), add an extra flag<?xml version="1.0" encoding="utf-8"?> <manifest> <application android:usesCleartextTraffic="true"> </application> </manifest>
- Create an XML under
<root>/src/main/res/xml/network_security_config.xmlwith the following content:<network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
- Reference to the new config in your app's manifest (i.e.
AndroidManifest.xml)<?xml version="1.0" encoding="utf-8"?> <manifest> <application android:networkSecurityConfig="@xml/network_security_config"> </application> </manifest>
To show/hide the tracking overlay, call showTrackingOverlay
adTrackingManager.showTrackingOverlay(state)Reference: IAB AdCOM v1.0 FINAL
The following table is a list of API frameworks either supported by a placement or required by an ad.
| Value | Definition |
| 1 | VPAID 1.0 |
| 2 | VPAID 2.0 |
| 3 | MRAID 1.0 |
| 4 | ORMMA |
| 5 | MRAID 2.0 |
| 6 | MRAID 3.0 |
| 7 | OMID 1.0 |
| 8 | SIMID 1.0 |
| 9 | SIMID 1.1 |
| 500+ | Vendor-specific codes. |
Note
Applicable when initRequest in AdTrackingManagerParams is true (default is true).
-
The library sends a request to the manifest endpoint with the query param "initSession=true". For e.g., a GET request is sent to:
https://my-host/variant/v1/dash/manifest.mpd?initSession=true -
The ad insertion service (PMM) responds with the URLs. For e.g.,
{ "manifestUrl": "./manifest.mpd?sessid=a700d638-a4e8-49cd-b288-6809bd35a3ed&vosad_inst_id=pmm-0", "trackingUrl": "./metadata?sessid=a700d638-a4e8-49cd-b288-6809bd35a3ed&vosad_inst_id=pmm-0" } -
The library constructs the URLs by combining the host in the original URL and the relative URLs obtained. For e.g.,
Manifest URL: https://my-host/variant/v1/dash/manifest.mpd?sessid=a700d638-a4e8-49cd-b288-6809bd35a3ed&vosad_inst_id=pmm-0 Metadata URL: https://my-host/variant/v1/dash/metadata?sessid=a700d638-a4e8-49cd-b288-6809bd35a3ed&vosad_inst_id=pmm-0
Note
When appendNonceToUrl is called, the resulting URL will be constructed using the URL obtained above.