Advertising

PRESTOplay for web includes a built-in ads API plus ready-to-use plugins for popular ad providers, so you can insert client-side and server-side ads without building ad tracking from scratch.

PRESTOplay includes integrations for Google Interactive Media Ads (IMA), Yospace, and FreeWheel AdManager, plus support for HLS interstitials.

Ads API

Ads manager

Call getAdsManager() on a Player instance to access IAdsManager. Call this method after the open() promise resolves. From that point, you can control the currently playing ad.

Ad events

When ads are active, the player emits ad-related events. Attach ad event listeners to the player instance:

player.on(clpp.events.ADS_TIMELINE_CHANGED, event => {
  console.log('==> ad-timeline-changed', event.adsTimeline.getCuePoints());
});

player.on(clpp.events.AD_LOADED, event => {
  console.log('==> ad-loaded', event.ad);
});

Useful ad events include:

  • clpp.events.ADS_TIMELINE_CHANGED

  • clpp.events.AD_LOADED

  • clpp.events.AD_BREAK_STARTED

  • clpp.events.AD_STARTED

  • clpp.events.AD_BUFFERING

  • clpp.events.AD_PROGRESS

  • clpp.events.AD_FIRST_QUARTILE

  • clpp.events.AD_MIDPOINT

  • clpp.events.AD_THIRD_QUARTILE

  • clpp.events.AD_PAUSED

  • clpp.events.AD_RESUMED

  • clpp.events.AD_SKIPPED

  • clpp.events.AD_CLICKED

  • clpp.events.AD_IMPRESSION

  • clpp.events.AD_COMPLETED

  • clpp.events.AD_STOPPED

  • clpp.events.AD_BREAK_STOPPED

See Events for general event-handling patterns.

Google IMA plugin

The SDK includes a Google Interactive Media Ads (IMA) plugin for client-side ads.

To get started, add the IMA library and player styles, and import the IMA plugin:

import "@castlabs/prestoplay/cl.ima";
<link rel="stylesheet" href="clpp.styles.css">
<script src="https://imasdk.googleapis.com/js/sdkloader/ima3.js"></script>

Pass a Video Multiple Ad Playlist (VMAP) ad tag to the player:

player.open({
  // ...
  ima: {
    adTagUrl: '//pubads.g.doubleclick.net/...'
  }
});

Pass a Video Ad Serving Template (VAST) ad tag to the player:

player.open({
  // ...
  ima: {
    adTagUrl: '//pubads.g.doubleclick.net/...',
    isVast: true
  }
});

See ImaConfiguration for all available configuration options.

The Google IMA sample plays pre-roll, mid-roll, and post-roll ads from a VMAP ad tag and logs ad events, including the ad details available through IAd, on the page.

IMA SDK on smart TVs

For smart TV-specific limitations, including skippable ads, see Smart TV.

IMA SDK on iOS

The IMA plugin is supported on iOS in experimental mode, for the same reasons as on smart TVs.

Custom UI controls give a better user experience than built-in video controls on iOS.

See ImaConfiguration and its disableCustomPlayback option to disable ads on iOS devices if you run into issues.

Yospace plugin

To get started with Yospace, download the Yospace ad management SDK from the Yospace developer site. The currently supported version is 3.9.0. Other similar versions may also work, but compatibility is not guaranteed.

Import the Yospace SDK:

<script src="./lib/yospace/admanagement-sdk-3.9.0.min.js"></script>

Import the Yospace plugin:

import { clpp } from "@castlabs/prestoplay";
import "@castlabs/prestoplay/cl.yospace";

Enable the Yospace plugin when calling open() with a Yospace Central Session Manager (CSM) uniform resource identifier (URI). This automatically starts a Yospace session. source.type must be specified:

await player.open({
  source: {
    // Yospace CSM URI
    url: 'https://csm...',
    type: clpp.Type.HLS,
  },
  yospace: {
    enabled: true,
    streamType: clpp.yospace.StreamType.VOD
  }
});

Set up listeners for Yospace-related events:

player.on(clpp.events.YOSPACE_SESSION_STARTED, (e) => {
  // e.detail contains the YospaceAdManagement.Session object
});

player.on(clpp.events.YOSPACE_SESSION_ENDED, (e) => {
  // e.detail contains the YospaceAdManagement.Session object
});

await player.open({
  source: '<manifest_url>',
});

Once the video starts playing, call getSession() on the plugin instance to get the YospaceAdManagement.Session:

const plugin = player.getPlugin(clpp.yospace.YospacePlugin.Id);
const session = plugin.getSession();

FreeWheel plugin (beta)

To configure the FreeWheel plugin:

Add the FreeWheel AdManager SDK and player styles, and import the FreeWheel plugin:

import "@castlabs/prestoplay/cl.freewheel";
<link rel="stylesheet" href="clpp.styles.css">
<script src="//adm.stg.fwmrm.net/libs/adm/6.37.0/AdManager.js"></script>

Add the FreeWheel customer data to the player configuration:

const playerConfig = {
  // ...
  freewheel: {
    networkId: 42015,
    serverUrl: '//demo.v.fwmrm.net/ad/g/1',
    profileId: '42015:js_allinone_profile',
    videoAssetId: 'js_allinone_demo_video',
    pods: [
      { id: 'Preroll_1', position: 0, type: clpp.ads.PodType.PREROLL },
      { id: 'Midroll_1', position: 3, type: clpp.ads.PodType.MIDROLL },
      { id: 'Midroll_2', position: 30, type: clpp.ads.PodType.MIDROLL },
      { id: 'Postroll_1', position: 60, type: clpp.ads.PodType.POSTROLL }
    ]
  }
};

See FreeWheelConfiguration for all available configuration options.

The whole code can look like this:

<link rel="stylesheet" href="clpp.styles.css">
<script src="//adm.stg.fwmrm.net/libs/adm/6.37.0/AdManager.js"></script>
<video id="video" controls></video>
<button id="playButton">Play</button>
<script type="module">
  import { clpp } from "@castlabs/prestoplay";
  import "@castlabs/prestoplay/cl.mse";
  import "@castlabs/prestoplay/cl.freewheel";

  const player = new clpp.Player('video', ...);

  player.open({
    source: '//demo.cf.castlabs.com/media/prestomp4/presto.mp4',
    freewheel: {
      networkId: 42015,
      serverUrl: '//demo.v.fwmrm.net/ad/g/1',
      profileId: '42015:js_allinone_profile',
      videoAssetId: 'js_allinone_demo_video',
      pods: [
        { id: 'Preroll_1', position: 0, type: clpp.ads.PodType.PREROLL },
        { id: 'Midroll_1', position: 3, type: clpp.ads.PodType.MIDROLL },
        { id: 'Midroll_2', position: 30, type: clpp.ads.PodType.MIDROLL },
        { id: 'Postroll_1', position: 60, type: clpp.ads.PodType.POSTROLL }
      ]
    }
  });

  // Listen to ad events the same way as in the IMA example.

  document.getElementById('playButton').addEventListener('click', () => {
    player.play();
  });
</script>

Broadpeak SSAI ads

The Broadpeak plugin also supports server-side ad insertion (SSAI) tracking. See Broadpeak SSAI ads on the Analytics page.

HLS interstitials

Support for HLS interstitials is limited to modern browsers. To enable this feature on Safari, set preferNativeHlsOnSafari to false:

const player = new clpp.Player('video', {
  streaming: {
    preferNativeHlsOnSafari: false,
  }
});

You can disable this feature entirely:

const player = new clpp.Player('video', {
  ads: {
    hls: {
      enable: false,
    },
  },
});

See HlsAdsConfiguration for other configuration options.

Playback session ID

To give decisioning servers useful context, you can enable playback session identification:

const player = new clpp.Player('video', {
  mse: {
    sendPlaybackSessionIdHeader: true,
  }
});

With this in place, the player sends the X-Playback-Session-Id HTTP header with every HTTP request related to primary playback, and the _HLS_primary_id query parameter containing the same value with every interstitial request.

Note

For this to work, your primary content server must allow the HTTP header X-Playback-Session-Id in Access-Control-Allow-Headers.

Rendering UI

To render UI related to ads, listen to ad events and use the event payload (IAd) to get information about the currently playing ad.

player.on(clpp.events.AD_STARTED, event => {
  const position = event.ad.getPositionInSequence();
  const count = event.ad.getSequenceLength();
  adLabel = `Ad ${position} / ${count}`;
});

player.on(clpp.events.AD_BREAK_STOPPED, () => {
  adLabel = null;
});

To render a play/pause button, listen to play and pause events as well as ad paused, resumed, and started. In all these cases the return value of isPaused() may change.

function renderPlayButton() {
  if (player.isPaused()) {
    playButton.textContent = 'Play';
  } else {
    playButton.textContent = 'Pause';
  }
}

player.on('play', () => {
  renderPlayButton();
});
player.on('pause', () => {
  renderPlayButton();
});
player.on(clpp.events.AD_STARTED, (e) => {
  renderPlayButton();
});
player.on(clpp.events.AD_PAUSED, () => {
  renderPlayButton();
});
player.on(clpp.events.AD_RESUMED, () => {
  renderPlayButton();
});

Seek UI during ads

Some ad formats do not allow seeking. Use canSeek() on the current ad (IAd#canSeek) to determine whether the seek bar and any other seek or playback rate controls should be enabled.

function renderSeekBar(ad) {
  const seekEnabled = ad ? ad.canSeek() : true;
  seekBar.disabled = !seekEnabled;
  playbackRateSelect.disabled = !seekEnabled;
  // Hide or disable any other seek or playback rate controls accordingly.
}

player.on(clpp.events.AD_STARTED, event => {
  // Check and possibly disable seeking when the ad starts.
  renderSeekBar(event.ad);
});

player.on(clpp.events.AD_BREAK_STOPPED, () => {
  // Re-enable seek when the ad break ends.
  renderSeekBar(null);
});

Skippable ads

Use getSkipTimeOffset() to determine whether an ad is skippable. It returns the number of seconds of playback before the ad can be skipped, or -1 if the ad is not skippable.

To skip the current ad, call skip() on the ads manager.

Here’s an example of rendering a skip button in the UI:

let currentAd = null;

player.on(clpp.events.AD_STARTED, event => {
  currentAd = event.ad;
  renderSkipButton();
});

player.on(clpp.events.AD_PROGRESS, () => {
  renderSkipButton();
});

player.on(clpp.events.AD_BREAK_STOPPED, () => {
  currentAd = null;
  skipButton.style.display = 'none';
});

function renderSkipButton() {
  if (!currentAd) return;
  const skipOffset = currentAd.getSkipTimeOffset();
  if (skipOffset === -1) {
    // Ad is not skippable.
    skipButton.style.display = 'none';
    return;
  }
  const remaining = skipOffset - currentAd.getPosition();
  if (remaining <= 0) {
    skipButton.textContent = 'Skip Ad';
    skipButton.disabled = false;
  } else {
    skipButton.textContent = `Skip in ${Math.ceil(remaining)}s`;
    skipButton.disabled = true;
  }
  skipButton.style.display = 'block';
}

skipButton.addEventListener('click', () => {
  player.getAdsManager().skip();
});
Next topic: Analytics
Previous topic: Plugins