Skip to content

Ads#

Table of Contents#

Introduction#

The Storyteller SDK supports displaying ads that can be created in the Storyteller CMS (First Party Ads), as well as Ads from Google Ad Manager via an SDK extension developed by Storyteller and Ads from other sources via custom implementation provided by the integrator.

Which source of ads is used can be configured on your behalf by a member of the Storyteller Delivery Team.

Choosing an Ad Module#

The Storyteller SDK provides two ad module options for integrating Google ads:

Feature StorytellerGamModule StorytellerAdMobModule
Custom Native Templates Supported Not supported
Key-Value Pair Targeting Supported Supported (via network extras)
Bottom Banner Ads Supported Supported
Banner Priority Mode Not supported Supported
Use Case Publishers with GAM accounts Apps using standard AdMob

Important: Only one ad module should be used at a time. Do not register both StorytellerGamModule and StorytellerAdMobModule simultaneously. The SDK behavior is undefined when both modules are registered.

Storyteller First Party Ads#

If your tenant is configured to use Storyteller First Party Ads, which can be managed in the Storyteller CMS, then no changes to the Storyteller integration code are necessary. The Ads code is managed entirely within the Storyteller SDK.

Storyteller GAM SDK#

To use Ads from Google Ad Manager in Storyteller, first reach out to your Storyteller contact and they will assist you with setting up Google Ad Manager to traffic ads to Storyteller.

You will then need to use the Storyteller Google Ad Manager SDK extension to fetch the ads from Google Ad Manager.

To use this extension, first install it using Gradle. Ensure you have the following Maven repository added to your settings.gradle:

maven {
  url = uri("https://storyteller.mycloudrepo.io/public/repositories/storyteller-sdk")
}

Then add the following reference to your version catalog file:

storyteller-ads = { module = "Storyteller:ads", version.ref = "storyteller" }

And finally reference this in your build.gradle:

implementation(libs.storyteller.ads)

Basic Setup#

Now initialize the extension as follows:

import com.storyteller.modules.ads.StorytellerGamModule
import com.storyteller.domain.ads.entities.CustomNativeTemplateIds

val storytellerGamModule = StorytellerGamModule.getInstance(applicationContext).apply {
  init(
    adUnit = { storytellerAdRequestInfo: StorytellerAdRequestInfo -> "/33813572/storyteller" },
  )
}
fun initializeStoryteller() {
  Storyteller.modules = listOf(storytellerGamModule)
  //initialize code
}

You will need to supply the following parameters:

Parameter Name Description
{ storytellerAdRequestInfo -> adUnitId } The lambda which returns desired adUnitId. This can be used for dynamic adUnitId changes depending on storytellerAdRequestInfo content. If you do not need dynamic adUnit changes simply put the static ad unit as a return value.
bottomBannerAdUnit Optional lambda that returns the Ad Unit ID used specifically for the Clips bottom banner placement. Leave this null if you don't plan to serve Clips bottom banner ads.

Then pass the newly created instance of the extension to the modules property on the Storyteller instance:

Storyteller.modules = listOf(storytellerGamModule)

Showcase examples#

Setup with the dynamic Ad Unit changes#

Example for dynamic Ad Unit changes when we want to use different adUnit for Stories and Clips:

import com.storyteller.modules.ads.StorytellerGamModule
import com.storyteller.domain.ads.entities.CustomNativeTemplateIds

val storytellerGamModule = StorytellerGamModule.getInstance(applicationContext).apply {
  init(
    adUnit = { storytellerAdRequestInfo: StorytellerAdRequestInfo ->
      when (storytellerAdRequestInfo) {
        is StorytellerAdRequestInfo.StoriesAdRequestInfo -> "/33813572/storyteller/stories"
        else -> "/33813572/storyteller/clips"
      }
    },
  )
}

fun initializeStoryteller() {
  Storyteller.modules = listOf(storytellerGamModule)
  //initialize code
}

Setup with the additional parameters#

You can also supply optional parameters templateIds and keyValuePairs if needed:

import com.storyteller.modules.ads.StorytellerGamModule
import com.storyteller.domain.ads.entities.StorytellerCustomNativeTemplateIds

val storytellerGamModule = StorytellerGamModule.getInstance(applicationContext).apply {
  init(
    adUnit = { storytellerAdRequestInfo: StorytellerAdRequestInfo ->
      when (storytellerAdRequestInfo) {
        is StorytellerAdRequestInfo.StoriesAdRequestInfo -> "/33813572/storyteller/stories"
        else -> "/33813572/storyteller/clips"
      }
    },
    templateIds = StorytellerCustomNativeTemplateIds("12102683", "12269089"),
    keyValuePairs = { emptyMap() },
  )
}

fun initializeStoryteller() {
  Storyteller.modules = listOf(storytellerGamModule)
  //initialize code
}
Parameter Name Description
templateIds If you have worked with the Storyteller Delivery team to setup Custom Native Ads, you will need to supply their IDs here. If you are only using Stories (but not Clips) it is only necessary to supply one property of this struct.
keyValuePairs A function that is called each time we request a new ad. The Storyteller GAM SDK passes a default set of KVPs to GAM to allow targeting based on the content of the Stories/Clips the user is viewing. If you have any additional parameters that you need to be able to target by, these should be passed here. By default, Storytelles always includes the following KVPs: stApiKey(current API key), stCollection(the identifier of the clips collection where the ad will be displayed), stClipCategories(list of categories for the current clip associated with the item for ad targeting), stNextClipCategories(list of categories for the next clip associated with the item for ad targeting), stAdIndex(count of the Ad position from the start of the stCollection).

Storyteller AdMob SDK#

The Storyteller AdMob Module provides integration with standard AdMob ads for apps that do not use Google Ad Manager. This module is an alternative to the GAM module and supports native ads with an optional banner fallback strategy.

Important: Only use one ad module at a time. Do not use StorytellerAdMobModule together with StorytellerGamModule. The SDK behavior is undefined when both modules are registered.

To use this extension, first install it using Gradle. Ensure you have the following Maven repository added to your settings.gradle:

maven {
  url = uri("https://storyteller.mycloudrepo.io/public/repositories/storyteller-sdk")
}

Then add the following reference to your version catalog file:

storyteller-ads = { module = "Storyteller:ads", version.ref = "storyteller" }

And finally reference this in your build.gradle:

implementation(libs.storyteller.ads)

AdMob Basic Setup#

Initialize the AdMob module as follows:

import com.storyteller.modules.ads.StorytellerAdMobModule
import com.storyteller.domain.ads.entities.StorytellerAdRequestInfo

val adMobModule = StorytellerAdMobModule.getInstance(applicationContext).apply {
  init(
    nativeAdUnit = { adRequestInfo: StorytellerAdRequestInfo -> "ca-app-pub-xxx/yyy" },
  )
}

fun initializeStoryteller() {
  Storyteller.modules = listOf(adMobModule)
  // initialize code
}

You will need to supply the following parameters:

Parameter Name Description
nativeAdUnit A lambda function that returns the AdMob ad unit ID for native ads. AdMob ad unit IDs follow the format ca-app-pub-xxx/yyy. This is the primary ad type used by the module.
bannerAdUnit Optional lambda that returns the Ad Unit ID for banner ads used as fallback when native ads fail to load (e.g., no fill).
bottomBannerAdUnit Optional lambda that returns the Ad Unit ID used specifically for the Clips bottom banner placement. Leave this null if you don't plan to serve Clips bottom banner ads.
enableBannerAdPriority Optional boolean. When true, banner ads are attempted first with native ads as fallback. When false (default), native ads are attempted first with banner ads as fallback. Only takes effect if bannerAdUnit is configured.
keyValuePairs Optional lambda that returns a Map<String, String> of custom key-value pairs passed to AdMob as network extras for ad targeting. Only sent when Storyteller.eventTrackingOptions.enableAdTracking is enabled.

Then pass the newly created instance of the extension to the modules property on the Storyteller instance:

Storyteller.modules = listOf(adMobModule)

Example for dynamic Ad Unit changes when you want to use different ad units for Stories and Clips:

import com.storyteller.modules.ads.StorytellerAdMobModule
import com.storyteller.domain.ads.entities.StorytellerAdRequestInfo

val adMobModule = StorytellerAdMobModule.getInstance(applicationContext).apply {
  init(
    nativeAdUnit = { adRequestInfo: StorytellerAdRequestInfo ->
      when (adRequestInfo) {
        is StorytellerAdRequestInfo.StoriesAdRequestInfo -> "ca-app-pub-xxx/stories"
        else -> "ca-app-pub-xxx/clips"
      }
    },
  )
}

fun initializeStoryteller() {
  Storyteller.modules = listOf(adMobModule)
  // initialize code
}

AdMob Setup with Banner Fallback#

The AdMob module supports a banner fallback strategy to maximize fill rate. When configured, if a native ad request fails (e.g., due to no fill), the module automatically attempts to load a banner ad as a fallback:

import com.storyteller.modules.ads.StorytellerAdMobModule
import com.storyteller.domain.ads.entities.StorytellerAdRequestInfo

val adMobModule = StorytellerAdMobModule.getInstance(applicationContext).apply {
  init(
    nativeAdUnit = { adRequestInfo: StorytellerAdRequestInfo -> "ca-app-pub-xxx/native" },
    bannerAdUnit = { adRequestInfo: StorytellerAdRequestInfo -> "ca-app-pub-xxx/banner_fallback" },
  )
}

fun initializeStoryteller() {
  Storyteller.modules = listOf(adMobModule)
  // initialize code
}

The fallback flow works as follows:

  1. The module first attempts to load a native ad using nativeAdUnit
  2. If the native ad fails to load, and bannerAdUnit is configured, it attempts to load a banner ad
  3. If both requests fail, the combined error is reported

AdMob Setup with Bottom Banner Ads#

To enable bottom banner ads in Clips, provide the bottomBannerAdUnit parameter:

import com.storyteller.modules.ads.StorytellerAdMobModule
import com.storyteller.domain.ads.entities.StorytellerAdRequestInfo

val adMobModule = StorytellerAdMobModule.getInstance(applicationContext).apply {
  init(
    nativeAdUnit = { adRequestInfo: StorytellerAdRequestInfo -> "ca-app-pub-xxx/native" },
    bottomBannerAdUnit = { adRequestInfo: StorytellerAdRequestInfo -> "ca-app-pub-xxx/bottom_banner" },
  )
}

fun initializeStoryteller() {
  Storyteller.modules = listOf(adMobModule)
  // initialize code
}

AdMob Setup with Key-Value Pairs#

The AdMob module supports custom key-value pairs for ad targeting. These are passed as network extras to AdMob:

import com.storyteller.modules.ads.StorytellerAdMobModule
import com.storyteller.domain.ads.entities.StorytellerAdRequestInfo

val adMobModule = StorytellerAdMobModule.getInstance(applicationContext).apply {
  init(
    nativeAdUnit = { adRequestInfo: StorytellerAdRequestInfo -> "ca-app-pub-xxx/native" },
    keyValuePairs = { mapOf("customKey" to "customValue", "targeting" to "premium") },
  )
}

fun initializeStoryteller() {
  Storyteller.modules = listOf(adMobModule)
  // initialize code
}

Note: Key-value pairs are only sent when Storyteller.eventTrackingOptions.enableAdTracking is enabled. If ad tracking is disabled, the KVPs will not be included in the ad request.

AdMob Setup with Banner Priority#

By default, the AdMob module attempts to load native ads first, with banner ads as a fallback. You can reverse this priority using enableBannerAdPriority:

import com.storyteller.modules.ads.StorytellerAdMobModule
import com.storyteller.domain.ads.entities.StorytellerAdRequestInfo

val adMobModule = StorytellerAdMobModule.getInstance(applicationContext).apply {
  init(
    nativeAdUnit = { adRequestInfo: StorytellerAdRequestInfo -> "ca-app-pub-xxx/native" },
    bannerAdUnit = { adRequestInfo: StorytellerAdRequestInfo -> "ca-app-pub-xxx/banner" },
    enableBannerAdPriority = true, // Try banner first, native as fallback
  )
}

fun initializeStoryteller() {
  Storyteller.modules = listOf(adMobModule)
  // initialize code
}

When enableBannerAdPriority is true:

  1. The module first attempts to load a banner ad using bannerAdUnit
  2. If the banner ad fails to load, it attempts to load a native ad using nativeAdUnit
  3. If both requests fail, the combined error is reported

Note: enableBannerAdPriority only takes effect when bannerAdUnit is also configured. If bannerAdUnit is not provided, native ads will be loaded regardless of this setting.

AdMob Test Ad Unit IDs#

For development and testing, use Google's official test ad unit IDs to avoid generating invalid impressions:

Ad Type Test Ad Unit ID
Native Advanced ca-app-pub-3940256099942544/2247696110
Banner ca-app-pub-3940256099942544/6300978111

Example setup with test ad units:

val adMobModule = StorytellerAdMobModule.getInstance(applicationContext).apply {
  init(
    nativeAdUnit = { _ -> "ca-app-pub-3940256099942544/2247696110" },
    bannerAdUnit = { _ -> "ca-app-pub-3940256099942544/6300978111" },
  )
}

Note: Replace these test ad unit IDs with your production ad unit IDs before releasing your app.

Key Differences from GAM#

When choosing between StorytellerAdMobModule and StorytellerGamModule, consider the following differences:

Feature StorytellerGamModule StorytellerAdMobModule
Custom Native Templates Supported via templateIds parameter Not supported (AdMob limitation)
Key-Value Pair Targeting Supported via keyValuePairs parameter Supported via keyValuePairs (network extras)
Ad Loading Strategy Unified AdLoader supports multiple formats in single request Native ad first (or banner first with enableBannerAdPriority), then fallback
Banner Priority Mode Not supported Supported via enableBannerAdPriority
Bottom Banner Ads Supported Supported
Ad Unit Format GAM format: /network/ad_unit AdMob format: ca-app-pub-xxx/yyy

When to use AdMob Module:

  • Your app uses standard AdMob and does not have a Google Ad Manager account
  • You do not need custom native ad templates
  • You want configurable ad loading priority (banner vs native first)
  • You want a simpler integration with automatic banner fallback

When to use GAM Module:

  • You have a Google Ad Manager account
  • You need custom native ad templates configured with the Storyteller Delivery team
  • You need unified ad loading with multiple formats in a single request
  • You need the default set of Storyteller KVPs automatically included (e.g., stApiKey, stCollection, stClipCategories)

Bottom Banner Ads#

The Clips Player supports bottom banner ads rendered as standard banner views displayed below the video content. Both the GAM module and AdMob module support bottom banner ads. Supply bottomBannerAdUnit in the init() method to fetch inline adaptive banners for the Clips bottom banner placement. Only 300x50 and 320x50 ad sizes are supported.

Example setup with bottom banner ads using GAM:

val storytellerGamModule = StorytellerGamModule.getInstance(applicationContext).apply {
  init(
    adUnit = { storytellerAdRequestInfo: StorytellerAdRequestInfo -> "/your/ad_unit_id" },
    bottomBannerAdUnit = { storytellerAdRequestInfo: StorytellerAdRequestInfo -> "/your/bottom_banner_ad_unit_id" },
  )
}

Example setup with bottom banner ads using AdMob:

val adMobModule = StorytellerAdMobModule.getInstance(applicationContext).apply {
  init(
    nativeAdUnit = { adRequestInfo: StorytellerAdRequestInfo -> "ca-app-pub-xxx/native" },
    bottomBannerAdUnit = { adRequestInfo: StorytellerAdRequestInfo -> "ca-app-pub-xxx/bottom_banner" },
  )
}

StorytellerAdRequestInfo#

The StorytellerAdRequestInfo sealed class has two subclasses:

ClipsAdRequestInfo

Used when an Ad is requested for display in a Clips Player. The properties include:

  • collection - The identifier of the clips collection where the ad will be displayed
  • nextClipCategories - List of categories for the next clip associated with the item for ad targeting
  • adIndex - Count of the Ad position from the start of the Collection
  • itemInfo - Metadata about the item context for ad targeting

StoriesAdRequestInfo

Used when an Ad is requested for display in a Stories Player. The properties include:

  • placement - The identifier of the stories placement where the ad will be displayed
  • categories - List of category identifiers for ad targeting and filtering
  • adIndex - Count of the Ad position from the start of the Collection
  • itemInfo - Metadata about the item context for ad targeting

ItemInfo#

Each request class includes an ItemInfo object that contains:

  • categories - List of categories associated with the item for ad targeting

Non Skippable Ads#

Our Player can enforce a period of time during which ads can't be skipped. When enabled, user interactions that would skip a Story or Clip Ad won't be allowed for that duration. This feature can be configured in the CMS.