Skip to content

Android

Integration for phone and tablet apps. The shared flow is described under WebView: shared flow; this page has ready-to-use Android code.

What you'll need

  • A WebView with JavaScript enabled.
  • A video player — the example uses Media3 ExoPlayer.
  • The SDK page address for the android platform: https://cdn.adsdk.ru/android/v3/.

Screen layout

xml
<!-- res/layout/activity_ad.xml -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">

    <androidx.media3.ui.PlayerView
        android:id="@+id/adPlayer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:use_controller="false" />

    <!-- On top: receives taps on the SDK buttons -->
    <WebView
        android:id="@+id/adWebView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

Full example

Set up the WebView and open the SDK page

kotlin
class AdActivity : AppCompatActivity() {

    private lateinit var webView: WebView
    private lateinit var player: ExoPlayer
    private val main = Handler(Looper.getMainLooper())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_ad)

        player = ExoPlayer.Builder(this).build()
        findViewById<PlayerView>(R.id.adPlayer).player = player
        player.addListener(playerListener)

        webView = findViewById(R.id.adWebView)
        webView.setBackgroundColor(Color.TRANSPARENT)
        webView.settings.javaScriptEnabled = true
        webView.settings.mediaPlaybackRequiresUserGesture = false
        webView.addJavascriptInterface(AdEvents(), "Android")
        webView.loadUrl(adPageUrl(vastUrl = intent.getStringExtra(EXTRA_VAST_URL)!!))
    }

    private fun adPageUrl(vastUrl: String): String =
        Uri.parse("https://cdn.adsdk.ru/android/v3/").buildUpon()
            .appendQueryParameter("url", vastUrl)
            .appendQueryParameter("lang", "ru")
            .appendQueryParameter("controls", """["ad-skip-btn","ad-click-btn","ad-more-menu"]""")
            .build()
            .toString()

    /** Executes an SDK method. evaluateJavascript can only be called from the main thread. */
    private fun sdk(call: String) = main.post {
        webView.evaluateJavascript("window.myAdController.$call", null)
    }
}

Receive events

The SDK calls methods on the Android object. Declare the ones you need — the SDK skips the rest.

Threads

@JavascriptInterface methods are called on the WebView's background thread. Move anything that touches the player or the UI to the main thread — in the example, that's main.post { }.

kotlin
private inner class AdEvents {

    private var isVpaid = false

    // Arrives before AdLoaded: the creative type tells you who will play it
    @JavascriptInterface
    fun AdCreativeLoaded(
        srcUrl: String, type: String, bannerId: String, isNoBanner: Boolean,
        isPod: Boolean, podLength: Int, isLast: Boolean, erid: String, durationMsec: Int,
    ) {
        isVpaid = type == "application/javascript"
    }

    @JavascriptInterface
    fun AdLoaded(mediaFileUrl: String) = main.post {
        if (isVpaid) {
            sdk("playAd()")                // VPAID plays inside the WebView
        } else {
            player.setMediaItem(MediaItem.fromUri(mediaFileUrl))
            player.prepare()
            player.playWhenReady = true
        }
    }

    @JavascriptInterface
    fun AdSkipped(adNumber: Int) = main.post { player.stop() }

    @JavascriptInterface
    fun AdClick(url: String, adNumber: Int) = main.post {
        startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
    }

    @JavascriptInterface
    fun AdError(code: String, message: String, type: String,
                isNoBanner: Boolean, isLast: Boolean, adNumber: Int) = main.post { close() }

    @JavascriptInterface
    fun AdDestroyed(isLast: Boolean) = main.post {
        player.stop()
        if (isLast) close()
    }
}

Report playback to the SDK

kotlin
private val progress = object : Runnable {
    override fun run() {
        if (player.duration > 0) {
            sdk("timeupdateAd(${player.currentPosition / 1000.0}, ${player.duration / 1000.0})")
        }
        main.postDelayed(this, PROGRESS_INTERVAL_MS)
    }
}

private val playerListener = object : Player.Listener {

    override fun onIsPlayingChanged(isPlaying: Boolean) {
        if (isPlaying) {
            sdk("playAd()")                // both the first start and resuming after a pause
            main.post(progress)
        } else {
            main.removeCallbacks(progress)
            if (!player.playWhenReady) sdk("pauseAd()")   // user-initiated pause, not buffering
        }
    }

    override fun onPlaybackStateChanged(state: Int) {
        if (state == Player.STATE_ENDED) {
            val durationSec = player.duration / 1000.0
            sdk("timeupdateAd($durationSec, $durationSec)")   // the SDK will send AdComplete
        }
    }
}

private companion object {
    const val PROGRESS_INTERVAL_MS = 250L
    const val EXTRA_VAST_URL = "vast_url"
}

Finish the ad

kotlin
private fun close() {
    main.removeCallbacks(progress)
    finish()
}

override fun onPause() {
    super.onPause()
    player.pause()                         // the listener will send pauseAd()
}

override fun onDestroy() {
    webView.removeJavascriptInterface("Android")
    webView.destroy()
    player.release()
    super.onDestroy()
}

Event signatures

The WebView looks up an interface method by name and argument count. A method with a different argument count won't be called. See the full list under Events → Android.

EventMethod
Ad loadedAdCreativeLoaded(9 arguments), then AdLoaded(mediaFileUrl)
Playback startedAdStarted(adNumber)
QuartilesAdFirstQuartile(adNumber), AdMidpoint(adNumber), AdThirdQuartile(adNumber)
Became skippableAdSkippableStateChanged(status)
Ad watched to the endAdComplete(adNumber)
Playback finishedAdDestroyed(isLast)
ErrorAdError(code, message, type, isNoBanner, isLast, adNumber)

Common issues

SymptomCause
SDK buttons are visible, no adThe WebView background isn't transparent, or the WebView sits below the player.
No AdStartedplayAd() wasn't called after the player started.
No quartiles or AdCompletetimeupdateAd() isn't being called.
An event doesn't arriveThe method's argument count doesn't match the signature.
The app crashes in the event handlerThe player was touched off the main thread.

More in Troubleshooting.