Ad Breaks

The CAF Receiver SDK features native support for ad breaks and companion ads within a given media stream. Before starting development on ad breaks, make sure to become familiar and set up a Basic CAF Receiver. CAF's new Break APIs ensure that users have a consistent experience across all Cast-enabled devices when ads are part of the playback.

In this guide, a break refers to an interval for playback containing one or more ads or bumpers and each ad or bumper is referred to as a break clip.

This diagram shows two ad breaks, each with two ads:

Events

The following table describes the events that are triggered when breaks are encountered during playback.

Break Event Description
BREAK_STARTED Fired when the first break clip in a break starts loading. Event is cast.framework.events.BreaksEvent.
BREAK_ENDED Fired when the last break clip in a break ends. Event is cast.framework.events.BreaksEvent.
Note: This event fires even if a user skips the last break clip in a break to end the break.
BREAK_CLIP_LOADING Fired when a break clip starts loading. Event is cast.framework.events.BreaksEvent.
BREAK_CLIP_STARTED Fired when a break clip starts. Event is cast.framework.events.BreaksEvent.
BREAK_CLIP_ENDED Fired when a break clip ends. Event is cast.framework.events.BreaksEvent.
Note: This event fires even if a user skips a break clip. Please check the endedReason property of BreaksEvent. To figure out amount of time the break clip was watched please check getBreakClipCurrentTimeSec and getBreakClipDurationSec of PlayerManager.

These events can be used for analytics and ad playback tracking. When VMAP (Video Multiple Ad Playlist) and VAST (Video Ad Serving Template) are used, any standard tracking events provided in the responses are automatically dispatched by the SDK.

While a break is playing, CAF Receiver SDK broadcasts MediaStatus with breakStatus to all connected senders. Senders can use this information to update UIs and suppress any seek operations.

CAF Receiver SDK provides two ways to incorporate ad breaks to the receiver: client-side and server-side stitching.

Client-side ad stitching

For client-side ad stitching, also referred to as non-embedded, the necessary ad information should be specified with Break and BreakClip objects when the media is being loaded. The following snippet is an example of a function adding pre-roll, mid-roll, and post-roll breaks, where the references to third_party and its methods are examples of helper functions a developer might have in their app. The snippet also shows how tracking events can be fired when a user watches a break clip to completion by listening for the BREAK_CLIP_ENDED event and checking the value of endedReason.

/**
 * @param {!cast.framework.messages.MediaInformation} mediaInformation
 */
function addBreakToMedia(mediaInformation) {
  mediaInformation.breakClips = [
  {
    id: 'bc1',
    title: third_party.getBreakClipTitle('bc1'),
    contentId: third_party.getBreakClipUrl('bc1'),
    contentType: third_party.getBreakClipContentType('bc1'),
    posterUrl: third_party.getBreakClipPosterUrl('bc1')
  },
  {
    id: 'bc2'
    ...
  },
  {
    id: 'bc3'
    ...
  },
  {
    id: 'bc4'
    ...
  }];
  mediaInformation.breaks = [
  {
    id: 'b1',
    breakClipIds: ['bc1', 'bc2'],
    position: 0  //pre-roll position
  },
  {
    id: 'b2',
    breakClipIds: ['bc3'],
    position: 10*60 //ten minutes
  },{
    id: 'b3',
    breakClipIds: ['bc4'],
    position: -1  //post-roll position (-1 is only valid client stitching; for server-side ad stitching, exact position is required)
  }];
}

const context = cast.framework.CastReceiverContext.getInstance();
const playerManager = context.getPlayerManager();

playerManager.addEventListener(cast.framework.events.EventType.BREAK_CLIP_ENDED, function(event){
  if(event.endedReason === cast.framework.events.EndedReason.END_OF_STREAM){
    //call your ad tracking code here for break clip watched to completion
  }
});


playerManager.setMessageInterceptor(
    cast.framework.messages.MessageType.LOAD,
    loadRequestData => {
      addBreakToMedia(loadRequestData.media);
      return loadRequestData;
    });
context.start();

VAST Ads

The CAF Receiver SDK supports IAB standard VAST ads. To include VAST ads in your media, create a VastAdsRequest object and assign it to the vastAdsRequest property of a BreakClip. The VastAdsRequest object must have the adsResponse property or adTagUrl property defined.

/**
 * @param {!cast.framework.messages.MediaInformation} mediaInformation
 */
function addBreakToMedia(mediaInformation) {
  mediaInformation.breakClips = [
  {
    id: 'bc1',
    vastAdsRequest:{
      adTagUrl: 'https://castsample.com/vast?rand=' + Math.floor(Math.random()* 10000)
    }
  }];
}

The CAF Receiver SDK sends a request to the specified adTagUrl and parses the XML response to generate a BreakClip object. Below is an example of a BreakClip object generated by our SDK.

{
  "id": "GENERATED:0",
  "contentId": "https://file.mp4",
  "contentType": "video/mp4",
  "title": "Preroll",
  "duration": 10,
  "clickThroughUrl": "https://click"
}

VMAP ads

The CAF Receiver SDK also supports the IAB VMAP standard. When a VMAP is provided, CAF will parse the VMAP response and generate the Break objects for the given <AdBreak> entries in the response. It will also generate the appropriate BreakClips with a VastAdsRequest object for each <AdSource> entry provided in the VMAP. To use VMAP to insert ads into your content, create a VastAdsRequest object and assign it to the vmapAdsRequest property of the MediaInformation object as part of your load request.

/**
 * @param {!cast.framework.messages.MediaInformation} mediaInformation
 */
function addBreakToMedia(mediaInformation) {
  mediaInformation.vmapAdsRequest = {
    adTagUrl: 'https://castsample.com/vmap?rand=' + Math.floor(Math.random()* 10000)
  };
}

Server-side ad stitching

For server-side stitching, also referred to as embedded ads, the server is expected to provide a single stream that contains both the primary media and ads. In this case, the developer is expected to provide the duration and contentType of the BreakClip. The contentUrl is omitted. In addition, the isEmbedded must be set to true.

/**
 * @param {!cast.framework.messages.MediaInformation} mediaInformation
 */
function addBreakToMedia(mediaInformation) {
  mediaInformation.breakClips = [
  {
    id: 'bc1',
    title: third_party.getBreakClipTitle('bc1'),
    posterUrl: third_party.getBreakClipPosterUrl('bc1'),
    duration: third_party.getBreakClipDuration('bc1')
  },
  {
    id: 'bc2'
    ...
  },
  {
    id: 'bc3'
    ...
  },
  {
    id: 'bc4'
    ...
  }];
  media.breaks = [
  {
    id: 'b1',
    breakClipIds: ['bc1', 'bc2'],
    position: 0,
    isEmbedded: true
  },
  {
    id: 'b2',
    breakClipIds: ['bc3', 'bc4'],
    position: 10*60,
    isEmbedded: true
  }];
}

const context = cast.framework.CastReceiverContext.getInstance();
const playerManager = context.getPlayerManager();
playerManager.setMessageInterceptor(
    cast.framework.messages.MessageType.LOAD,
    loadRequestData => {
      addBreakToMedia(loadRequestData.media);
      return loadRequestData;
    });
context.start();

Behavior of breaks

Default break behavior

  1. Once a break is started, it will be marked as isWatched. If a user then scrubs to a point earlier than the break, content begins playback as normal, and the break will subsequently be skipped as having been previously watched.
  2. If a user scrubs past a break(s) without watching the break, then the last unplayed break between seekFrom and seekTo will play before content playback begins.

Custom break behavior

The default behavior for breaks and break clips can be modified using the setBreakClipLoadInterceptor and setBreakSeekInterceptor methods on the BreakManager object.

setBreakClipLoadInterceptor

  • The setBreakClipLoadInterceptor is called before a break is encountered. This function is invoked once per breakClip. It passes a BreakClip object into the callback function.
    For example, if there is a pre-roll break, the setBreakClipLoadInterceptor is called for the pre-roll break as soon as playback starts. Immediately after the pre-roll finishes playing, the setBreakClipLoadInterceptor for the next break is called.
  • If null or nothing is returned in the callback function to setBreakClipLoadInterceptor, the breakClip is skipped over. All the interceptors for a break are called immediately.
  • Using setBreakClipLoadInterceptor, a BreakClip object can be modified before its playback begins.

setBreakSeekInterceptor

The setBreakSeekInterceptor is triggered after a seek operation and passes a BreakSeekData object to the callback function. The BreakSeekData object contains an array of Breaks whose positions are defined between the current playhead time and seek destination time (i.e. seekFrom and seekTo). After a forward seek operation, the default behavior is to play the last unplayed break prior to the seekTo time. Once the ads in that break have played, content playback resumes from the seekTo value.

This interceptor allows the BreakClip objects in the respective Breaks to be modified.

If you want to customize the behavior, you can implement setBreakSeekInterceptor to override the default behavior. If setBreakSeekInterceptor is implemented, you must be explicit about which breaks to play.

  • If a value of null or nothing is returned from setBreakSeekInterceptor, the break is skipped over.
  • If same object that is passed into the callback function is returned, then the default behavior is overriden and all the breaks are played. In addition, if a user scrubs to an earlier point then they will see the breaks even if they had previously watched those breaks. If setBreakSeekInterceptor is not implemented, then breaks that have already been watched are skipped.

Making break clips skippable

To make a break clip skippable, specify after how many seconds pass before the break clip is skippable in the BreakClip object.

/**
 * @param {!cast.framework.messages.MediaInformation} mediaInformation
 */
function addBreakToMedia(media) {
  media.breakClips = [
  {
    id: 'bc1',
    ...
    whenSkippable: 10 //to allow user to skip break clip from sender after 10 seconds
  }];
}

Auto-skip ads

Ads can be skipped automatically without any user interaction. Two ways to skip ads are:

  1. Setting the isWatched property of a Break to true will automatically skip the break.
    playerManager.addEventListener(cast.framework.events.category.CORE, function(event){
      if(event.type === cast.framework.events.EventType.PLAYER_LOADING){
        let breaks = playerManager.getBreaks();
        for(let i in breaks){
          breaks[i].isWatched = true;
        }
      }
    });
  2. A BreakClip can be skipped by returning a null value in the setBreakClipLoadInterceptor callBack.
    playerManager.getBreakManager().setBreakClipLoadInterceptor(breakClip => {
      return null;
    });