Warning: The IMA SDK for Cast SDK v2 has been officially deprecated, as of November 17th, 2021. All existing users are urged to migrate to CAF native Ad Breaks.

HTML5 receiver app

This guide shows you how to create a custom receiver app that communicates between the sender app and receiver device and allows you to integrate the IMA SDK into your Cast app.

If you'd rather follow along with a finished IMA SDK receiver, download the sample app or view it on GitHub. You can also register a custom receiver to use our sample on GitHub with your cast device.

Our receiver sample is hosted on GitHub, so you can set up your custom receiver for testing by using the following URL in your receiver: https://googleads.github.io/googleads-ima-cast/client_receiver/player.html

Receiver files

The receiver is made up of three parts: HTML, CSS, and Javascript. Create an HTML file, named player.html, containing the following code:

player.html

<html>
<head>
  <title>IMA Example receiver</title>
  <link rel="stylesheet" type="text/css" href="style.css">
  <script src="//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js"></script>
  <script src="//imasdk.googleapis.com/js/sdkloader/ima3.js"></script>
  <script type="text/javascript" src="player.js"></script>
</head>
<body>
  <div id="adContainer">
    <video id="mediaElement"></video>
    <script>
      var player = new Player(document.getElementById('mediaElement'));
    </script>
  </div>
</body>
</html>

This HTML references the IMA SDK libraries and the Google Cast libraries, in addition to style.css and player.js, which are detailed below. Other than these references, it is creating a single Player variable, which you also define in player.js.

Create a CSS page, named style.css, containing the following code for the receiver:

style.css

video {
  width: 100%;
  height: 100%;
  overflow-y: hidden;
}

#adContainer {
  width: 100%;
  height: 100%;
}

This specifies the presentation of the adContainer DIV, created in the HTML above.

Javascript

Constructor

The main logic for the receiver goes inside the player.js file. Create a new file with this name. Create a constructor for the Player type. Put the following code into the file:

player.js

'use strict';

/**
 * Creates new player for video and ad playback.
 * @param {cast.receiver.MediaManager} mediaElement The video element.
 */
var Player = function(mediaElement) {
  var namespace = 'urn:x-cast:com.google.ads.ima.cast';
  this.mediaElement_ = mediaElement;
  this.mediaManager_ = new cast.receiver.MediaManager(this.mediaElement_);
  this.castReceiverManager_ = cast.receiver.CastReceiverManager.getInstance();
  this.imaMessageBus_ = this.castReceiverManager_.getCastMessageBus(namespace);
  this.castReceiverManager_.start();

  this.originalOnLoad_ = this.mediaManager_.onLoad.bind(this.mediaManager_);
  this.originalOnEnded_ = this.mediaManager_.onEnded.bind(this.mediaManager_);
  this.originalOnSeek_ = this.mediaManager_.onSeek.bind(this.mediaManager_);

  this.setupCallbacks_();
};

This is the constructor function for the Player type. It takes a media element DOM, which it uses to play HTML5 video. Create a cast.receiver.MediaManager, which sends and receives media messages and events. Obtain a reference to the cast.receiver.CastReceiverManager, which allows communication with the platform and sends/receives system messages and events.

To send and receive messages specific to your app, create a channel for your specific namespace, and start up the CastReceiverManager.

There are several handlers that are overriden in MediaManager, so save the original handlers here to call them from the new handlers.

Set up callbacks

The last thing the constructor does is call setupCallbacks_(), so define it here:

player.js

/**
 * Attaches necessary callbacks.
 * @private
 */
Player.prototype.setupCallbacks_ = function() {
  var self = this;

  // Google Cast device is disconnected from sender app.
  this.castReceiverManager_.onSenderDisconnected = function() {
    window.close();
  };

  // Receives messages from sender app. The message is a comma separated string
  // where the first substring indicates the function to be called and the
  // following substrings are the parameters to be passed to the function.
  this.imaMessageBus_.onMessage = function(event) {
    console.log(event.data);
    var message = event.data.split(',');
    var method = message[0];
    switch (method) {
      case 'requestAd':
        var adTag = message[1];
        var currentTime = parseFloat(message[2]);
        self.requestAd_(adTag, currentTime);
        break;
      case 'seek':
        var time = parseFloat(message[1]);
        self.seek_(time);
        break;
      default:
        self.broadcast_('Message not recognized');
        break;
    }
  };

  // Initializes IMA SDK when Media Manager is loaded.
  this.mediaManager_.onLoad = function(event) {
    self.originalOnLoadEvent_ = event;
    self.initIMA_();
    self.originalOnLoad_(self.originalOnLoadEvent_);
  };
};

/**
 * Sends messages to all connected sender apps.
 * @param {!string} message Message to be sent to senders.
 * @private
 */
Player.prototype.broadcast_ = function(message) {
  this.imaMessageBus_.broadcast(message);
};

Handle the CastReceiverManager.onSenderDisconneded message by closing the window. Then, define some message types that you want to respond to from the sender app on the ima namespace bus. All of these messages are passed as a comma-separated string where the first substring is the function name. The sender asks you to request an ad when casting is begun, so you receive a requestAd message in this case, with the ad tag being the second parameter and the start time being the third.

The other sender message is seek, which is called when casting into content (and not ads). Override MediaManager.onLoad() to initialize the IMA SDK before calling the original onLoad(). Also define a broadcast function to broadcast messages to senders.

Initialize IMA

Implement the IMA initialization code that sets up the AdsLoader, which is used to request ads.

player.js

/**
 * Creates new AdsLoader and adds listeners.
 * @private
 */
Player.prototype.initIMA_ = function() {
  this.currentContentTime_ = 0;
  var adDisplayContainer = new google.ima.AdDisplayContainer(
      document.getElementById('adContainer'), this.mediaElement_);
  adDisplayContainer.initialize();
  this.adsLoader_ = new google.ima.AdsLoader(adDisplayContainer);
  this.adsLoader_.addEventListener(
      google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
      this.onAdsManagerLoaded_.bind(this), false);
  this.adsLoader_.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR,
      this.onAdError_.bind(this), false);
  this.adsLoader_.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED,
      this.onAllAdsCompleted_.bind(this), false);
};

/**
 * Once the ads have been retrieved, use AdsManager to play the ads. Sends AdsManager
 * playAdsAfterTime if starting in the middle of content.
 * @param {ima.AdsManagerLoadedEvent} adsManagerLoadedEvent The loaded event.
 * @private
 */
Player.prototype.onAdsManagerLoaded_ = function(adsManagerLoadedEvent) {
  var adsRenderingSettings = new google.ima.AdsRenderingSettings();
  adsRenderingSettings.playAdsAfterTime = this.currentContentTime_;

  console.log(this.mediaElement_);
  // Get the ads manager.
  this.adsManager_ = adsManagerLoadedEvent.getAdsManager(
    this.mediaElement_, adsRenderingSettings);

  // Add listeners to the required events.
  this.adsManager_.addEventListener(
      google.ima.AdErrorEvent.Type.AD_ERROR,
      this.onAdError_.bind(this));
  this.adsManager_.addEventListener(
      google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED,
      this.onContentPauseRequested_.bind(this));
  this.adsManager_.addEventListener(
      google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED,
      this.onContentResumeRequested_.bind(this));

  try {
    this.adsManager_.init(this.mediaElement_.width, this.mediaElement_.height,
        google.ima.ViewMode.FULLSCREEN);
    this.adsManager_.start();
  } catch (adError) {
    // An error may be thrown if there was a problem with the VAST response.
    this.broadcast_('Ads Manager Error: ' + adError.getMessage());
  }
};

You've set up and initialized the AdDisplayContainer using the adContainer DIV and the media element, then created an AdsLoader with that container. You registered event listeners to your AdsLoader to handle ad events. Once the ads are retrieved, AdsManager displays them.

AdsManager events and requesting ads

In the previous section, you added event listeners for AdsManager events and ad errors. This section goes over these in detail.

player.js

/**
 * Handles errors from AdsLoader and AdsManager.
 * @param {ima.AdErrorEvent} adErrorEvent error
 * @private
 */
Player.prototype.onAdError_ = function(adErrorEvent) {
  this.broadcast_('Ad Error: ' + adErrorEvent.getError().toString());
  // Handle the error logging.
  if (this.adsManager_) {
    this.adsManager_.destroy();
  }
  this.mediaElement_.play();
};

/**
 * When content is paused by AdsManager to start playing an ad.
 * @private
 */
Player.prototype.onContentPauseRequested_ = function() {
  this.currentContentTime_ = this.mediaElement_.currentTime;
  this.broadcast_('onContentPauseRequested,' + this.currentContentTime_);
  this.mediaManager_.onEnded = function(event) {};
  this.mediaManager_.onSeek = function(event) {};
};

/**
 * When an ad finishes playing and AdsManager resumes content.
 * @private
 */
Player.prototype.onContentResumeRequested_ = function() {
  this.broadcast_('onContentResumeRequested');
  this.mediaManager_.onEnded = this.originalOnEnded_.bind(this.mediaManager_);
  this.mediaManager_.onSeek = this.originalOnSeek_.bind(this.mediaManager_);

  this.originalOnLoad_(this.originalOnLoadEvent_);
  this.seek_(this.currentContentTime_);
};

/**
 * Destroys AdsManager when all requested ads have finished playing.
 * @private
 */
Player.prototype.onAllAdsCompleted_ = function() {
  if (this.adsManager_) {
    this.adsManager_.destroy();
  }
};

/**
 * Sets time video should seek to when content resumes and requests ad tag.
 * @param {!string} adTag ad tag to be requested.
 * @param {!float} currentTime time of content video to resume from.
 * @private
 */
Player.prototype.requestAd_ = function(adTag, currentTime) {
  if (currentTime != 0) {
    this.currentContentTime_ = currentTime;
  }
  var adsRequest = new google.ima.AdsRequest();
  adsRequest.adTagUrl = adTag;
  adsRequest.linearAdSlotWidth = this.mediaElement_.width;
  adsRequest.linearAdSlotHeight = this.mediaElement_.height;
  adsRequest.nonLinearAdSlotWidth = this.mediaElement_.width;
  adsRequest.nonLinearAdSlotHeight = this.mediaElement_.height / 3;
  this.adsLoader_.requestAds(adsRequest);
};

/**
 * Seeks content video.
 * @param {!float} time time to seek to.
 * @private
 */
Player.prototype.seek_ = function(time) {
  this.currentContentTime_ = time;
  this.mediaElement_.currentTime = time;
  this.mediaElement_.play();
};

The onAdError() function handles errors from AdsLoader and AdsManager. If the receiver encounters any errors, it logs them and then destroy the AdsManager and then play the content media. Next, you have two functions that handle pausing and resuming the content, followed by a function to clean up the AdsManager after ads have completed.

While the receiver is playing ads, the cast functions onEnded and onSeek are overridden to prevent the cast device from assuming that an ad ending means the content video is over.

Finally, there is a function that requests ads from a server using AdsLoader and one that plays the content video.

Next steps

At this point you should have a finished cast custom receiver app. To try it out: