# Advertising in the Video Center player

Source: https://dev.arcxp.com/video-center/developer-docs/advertising-and-the-video-center-player/

Advertising and the Video Center player

The Video Center player supports pre-roll ads through Google Ad Manager. You can tailor ad logic for each video using a JavaScript function. The system makes an ad request per video right before the video begins playing.

## The ad tag URL

By default, the video player looks for a value located at `window.PoWaSettings.advertising.adTag`. The value can be either a string or a function. If the value is a string and it begins with `window`, the player then looks at that location and restarts the same process (for example, is `window.other.location.adTag` a string or a function?). If the ultimate value is a string, the system assumes that string is an ad tag URL. If the ultimate value is a function, the player provides an object (`{powa, videoData}`) containing the `powa` object (for example, the player) and the JSON object of the current video (`videoData`).

The value returned by `adTag` must be either of the following:

- An ad tag URL (string).
- A `falsey` value (also written as `falsy`). For example, an empty string or null.
- A Promise that resolves to either of those values or rejects.

If the value returned by `adTag` is `falsey`, then the system doesn’t make an ad request for the current video. If the system returns a string, it passes the string to the Google IMA plugin to make the actual ad request.

## Ad timeouts

The player also sets ad timeouts, which you can customize. You can choose from three standard timeouts:

- `vastLoad`. This timeout runs for each ad wrapper and defaults to `5` seconds. For example, if an ad has three wrappers, the maximum allowed load time is 15 seconds.
- `videoLoad`. This timeout runs on the fetch for the ad video and defaults to `8` seconds.
- `adStart`. This timeout overrides the other timeouts and runs at the start of the ad request. Defaults to `5` seconds.

> **Tip:**
> 
> You can customize ad timeouts by providing the timeout in seconds for any or all of the timeouts. If you require customization per player (for example, more than one player on a page), you can set each timeout as a function that receives the `powa` object that pertains to each player and then returns the desired ad timeout in seconds.

## Additional settings

You can configure the location with the `locale` setting (for example, `es`, `fr`, `it`). You can set the maximum number of ad wrappers with the `maxAdWrappers` setting (default: `4`). By default, the player supports VPAID ads, but you can disable this setting with the `vpaidEnabled` property.

> **Important:**
> 
> Defining the ad tag function at `window.PoWaSettings.advertising.adTag` works for development, but you must provide the `adTag` (string or function) to the Video Center Player team for them to include in the organization’s `<org>.js` configuration file.

The following is an example of additional settings for advertising with the Video Center player:

```
    window.PoWaSettings.advertising = {
    vpaidEnabled: true, // default
    maxAdWrappers: 4,   // default
    locale: '',         // default, '' defaults to 'en', e.g. 'es', 'fr', etc.
};
```

## Examples

This section provides examples of values for `window.PoWaSettings.advertising.adTag`.

### Simple static string

```
    window.PoWaSettings = window.PoWaSettings || {};
window.PoWaSettings.advertising = window.PoWaSettings.advertising || {}
window.PoWaSettings.advertising.adTag =  'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=';
```

### Simple string function

```
    window.PoWaSettings = window.PoWaSettings || {};
window.PoWaSettings.advertising = window.PoWaSettings.advertising || {}
window.PoWaSettings.advertising.adTag = () => 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=';
```

### Function with Logic

```
    window.PoWaSettings = window.PoWaSettings || {};
window.PoWaSettings.advertising = window.PoWaSettings.advertising || {};
window.PoWaSettings.advertising.adTag = () => {
    let everyOtherVideo = 0;

    return ({powa, videoData}) => {
        const videoAdZone = videoData.additional_properties.advertising.videoAdZone;
        const commercialAdNode = videoData.additional_properties.advertising.commercialAdNode;
        const adTag = `https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/${ commercialAdNode }${ videoAdZone }&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=`;

        return everyOtherVideo++ % 2 === 0 ? adTag : null;
    }
}();
```

### Asynchronous function

```
    window.PoWaSettings.advertising.adTag = () => {
    return new Promise((resolve, reject) => {
        const adTagURL = 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=';

        setTimeout(() => {
            resolve(adTagURL);
        }, 100);
    });
};
```

> **Note:**
>
> Mobile Safari may disallow the ad from playing if the `async` function takes too long to resolve. Approximately one second seems to be the threshold.

### Changing location

```
    window.PoWaSettings.advertising.adTag = 'window.other.location.adTag';
window.other.location.adTag = 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=';
```

### Ad timeouts

```
    window.PoWaSettings.advertising.timeouts = {
    standard: {
        vastLoad: 5,   // default
        videoLoad: 8,  // default
        adStart: 5,    // default
    }
};
```

### Custom ad timeouts

```
    window.PoWaSettings.advertising.timeouts = {
    standard: {
        // give the first player on a page short ad timeouts
        vastLoad: powa => powa.getDataset().powaIndex === 0 ? 3 : 5,
        videoLoad: powa => powa.getDataset().powaIndex === 0 ? 3 : 8,
        adStart: powa => powa.getDataset().powaIndex === 0 ? 5 : 25,
    }
};
```

## Inserting ads server-side

Instead of running an ad before a video plays, you can stitch ads into streams server-side. This technology has many names: Server-Side Ad Insertion (SSAI), Dynamic Ad Insertion (DAI), and ad stitching. The benefit to ad stitching is that this process inserts ads into a live stream similar to a commercial break on broadcast television.

Ad stitching tailors ads to the individual watching the stream by setting the ad tag request parameters on the client side.

> **Note:**
>
> If you are interested in using server-side ad insertion, you must add the MediaTailor plugin to your player configuration. See more details in [Setting up dynamic ad insertion in Video Center](https://docs.arcxp.com/en/products/video-center/getting-started-with-live-video-in-video-center/setting-up-dynamic-ad-insertion-in-video-center.html).

Arc XP leverages AWS MediaTailor for ad stitching with client-side reporting of ad events. You can insert ads into live streams as long as that live stream contains cue points to indicate a window when ads can be inserted. You can also insert ads into video-on-demand (VOD) streams as pre-roll. VOD does not require cue points. If the initial request to MediaTailor fails, the player resolves to run client-side ads.

The client-side ad insertion process starts very early in the player lifecycle and consists of the following steps:

1. The player checks if ad insertion is enabled for the video in question. If ad insertion is enabled, the system then obtains a unique video stream for the current end user.
2. The player obtains the ad parameters the system sends to MediaTailor on the initial request.
The player leverages the `window.PoWaSettings.advertising.adTag` function to obtain these parameters. The `adTag` function returns an ad tag request URL string (for example, `"https://adserver.com/Ads?Key1=Val1&Key2=Val2"`).
3. The player takes the query string parameters off of the ad tag URL (for example, `"key1=val1&key2=val2"`) and converts them to a JSON object (for example, `{"key1": "val1", "key2": "val2"}`).
4. The player provides the `adTag` function with a Boolean `adInsertion` property on the object that is passed in to it (for example, `adTag({adInsertion, powa, videoData})`) to facilitate the customization of the ad tag request URL for ad insertion.
5. The player makes a request to MediaTailor.
On the server, MediaTailor takes the ad parameters that we send and parameters that are available server-side (like client IP) and constructs a new ad tag request URL. The template the system uses to create this server-side ad request URL is the Ad Decision Server URL field in Video Center. This new server-side ad tag request URL is what the system uses to request all of the ads that are inserted into the stream.
6. MediaTailor responds with a unique video stream URL and a separate tracking URL from which we obtain information about the ads that have been inserted into this stream.
7. After the user starts the stream and is receiving video, the player makes a call to the tracking URL to obtain information about any available ads.
VOD streams require just one call to the tracking URL for any potential pre-roll ads. Live streams require additional calls as the stream progresses. The `hls.js` file provides visibility to the cue points that were added to the stream and the player sends additional requests to the tracking URL when it encounters a cue point. Running HLS natively (like mobile Safari), the player does not have visibility to the stream’s cue points, so the player sends additional requests to the tracking URL on an interval.
8. The player receives information from the tracking URL. The response details when the ad starts, the ad’s duration, and any tracking events that the player is responsible for.
9. The player waits for any stream events (like ad start, 25% complete, and more) or user events (like click through, mute, and pause) and sends the related tracking beacons.

The player supports two request timeouts for our MediaTailor implementation: a two-second timeout for the initial session request and a five-second timeout for the tracking requests. You can customize these timeout requests with `window.PoWaSettings.advertising.mediaTailor.session` and `window.PoWaSettings.advertising.mediaTailor.tracking`.

### VPAID ads

The player supports Video Player-Ad Interface Definition (VPAID) ads through MediaTailor. MediaTailor can stitch in some VPAID ads, in which case the player treats the ad the same as any other stitched ad. When a VPAID ad is stitched in, the player flags the `adMeta` object with `stitchedVPAID: true`. If MediaTailor cannot stitch the ad into the stream, the player runs VPAID ad client-side. A VPAID is denoted by the `VPAID` property in the ad’s `adMeta` object.

**Example**

```
    window.PoWaSettings = window.PoWaSettings || {};
window.PoWaSettings.advertising = window.PoWaSettings.advertising || {};
window.PoWaSettings.advertising.adTag = ({adInsertion, powa, videoData}) => {
    if (adInsertion) {
        return 'https://example.com/ad?key1=val1&key2=val2&ad_insertion=true';
    }
    else {
        return 'https://example.com/ad?key1=val1&key2=val2&ad_insertion=false';
    }
};
window.PoWaSettings.advertising.timeouts = window.PoWaSettings.advertising.timeouts || {};
window.PoWaSettings.advertising.timeouts.mediaTailor = {
    session: 3 * 1000, // three seconds
    tracking: 10 * 1000, // ten seconds
};

window.addEventListener('powaReady', event => {
    new MediaTailor({
        powa: event.detail.powa,

        // how long to store ad data locally
        // e.g. for DVR functionality during a live stream
        adMemoryDuration: 60 * 60, // in seconds, default: one hour

        // how often to request ad data when using native HLS
        // our live streams are usually 6-second chunks
        // and we usually buffer three chunks
        // 6 * 3 = 18
        trackingInterval: 18, // in seconds, default: 18 seconds
    });
})
```

## Ad bar

If enabled, the ad bar appears during ads and offers countdowns and controls. You can enable the ad bar with the default configuration by setting `window.PoWaSettings.advertising.adBar = true` or with a configuration through `window.PoWaSettings.advertising.adBar = {...}`.

By default, the ad bar provides controls to play or pause, mute or unmute, and skip the ad. The ad bar also displays an ad countdown for the duration of the ad. The ad bar defaults to a 15-second skip offset to enable the skip control. The ad bar also displays a skip countdown to count down until the skip option is available. A two-second threshold is factored into the skip calculation so that ads slightly longer than the skip offset do not trigger the skip control.

**Example**

```
    window.PoWaSettings = window.PoWaSettings || {};
PoWaSettings.advertising = PoWaSettings.advertising || {};
PoWaSettings.advertising.adBar = true;
```

### Customization

You can customize the ad bar by defining style and template properties in the `PoWaSettings.advertising.adBar` object. This object also accepts Boolean values to enable or disable play or pause controls, mute or unmute controls, the ad countdown, and the skip control. This object is also where you can adjust the length of your `skipOffset` and `skipThreshold`. You can customize the language that the ad bar displays with the `adSkipText`, `adCountdownTemplate`, and `adSkipCountdownTemplate` properties.

The `checkSizing` function is run on every `PoWa.EVENTS.TIME` update to the ad bar and is employed to hide the ad countdown and skip countdown elements if those elements overlap the controls on the ad bar. In other words, this function checks if the player is too small to display all of the ad bar’s elements and conditionally hides one or both countdowns to ensure that the more important elements are visible.

The ad bar resizes the ad so that the ad bar does not overlay the ad. You can disable this function, and the default style of the ad bar has a level of transparency such that the ad is not completely obscured. Some advertisers take exception to the ad being obscured at all, so the default is to resize. To disable the setting, set `window.PoWaSettings.advertising.adBar.shrinkAd = false`. Otherwise, `shrinkAd` must be a function that returns an object with values needed to resize the ad. The `shrinkAdCSS` function accepts the object created by `shrinkAd` and returns a JavaScript CSS object to be applied to the ad to resize it.

The following is an example of ad bar customization using several properties:

```
// defaults
PoWaSettings.advertising.adBar = {
    // enable play/pause control
    playControl: true,

    // enable mute/unmute control
    muteControl: true,

    // enable ad countdown
    adCountdown: true,

    // enable skip control
    skipControl: true,

    // allow user to skip after N seconds
    skipOffset: 15,

    // fudge factor to prevent showing 'skip' for ads slightly longer than skipOffset
    skipThreshold: 2,

    // text displayed on 'skip control'
    adSkipText: 'Skip',

    // receives {duration, elapsed, remaining, skipOffset, skipThreshold}
    adCountdownTemplate: timing => `This ad will end in ${ Math.ceil(timing.remaining) } seconds`,

    // receives {duration, elapsed, remaining, skipOffset, skipThreshold}
    adSkipCountdownTemplate: timing => `You can skip this ad in ${ Math.ceil(timing.skipOffset - (timing.duration - timing.remaining)) } seconds`,

    // ensure countdowns aren't too long, else, hide them
    // receives object of all ad bar elements wrapped in PoWaPack objects
    checkSizing: adBarElements => {
        const $controls      = adBarElements.$controls;
        const $countdown     = adBarElements.$countdown;
        const $skipContainer = adBarElements.$skipContainer;
        const $skipCountdown = adBarElements.$skipCountdown;

        const adControlsRight = $controls.position().left + $controls.width();
        const adCountdownRight = $countdown.position().left + $countdown.width();
        const adSkipContainerLeft = $skipContainer.position().left;

        if (adCountdownRight > adSkipContainerLeft) {
            $countdown.addClass(classes.hidden);
        }

        if (adControlsRight > adSkipContainerLeft) {
            $skipCountdown.addClass(classes.hidden);
        }
    },

    // receives adBar = {height, width}, player = {fullscreen, height, width}
    shrinkAd: (adBar, player) => {
        const sides = (adBar.height * (player.width/player.height)) / 2;

        return {
            top: adBar.height,
            bottom: 0,
            left: sides,
            right: sides,

            height: player.height - adBar.height,
            width: player.width - (2 * sides),
        };
    },

    // receives {top, bottom, left, right, height, width} (from shrinkAd)
    shrinkAdCSS: shrinkBy => {
        return {
            top: `${ shrinkBy.top }px`,
            left: `${ shrinkBy.left }px`,
            width: `${ shrinkBy.width }px`,
            height: `${ shrinkBy.height }px`
        };
    },

    style: config => `
         .powa-sell-bar {
              position: absolute;
              top: 0px;
              left: 0px;

              width: 100%;
              height: 22px;

              display: flex;
              align-items: center;
              justify-content: flex-start;

              pointer-events: auto;
              font-family: Helvetica;

              color: rgb(240, 248, 255);
              background-color: rgba(0, 0, 0, 0.7);

              z-index: 2;
         }

         .powa-sell-bar-controls {
              display: flex;
              align-items: center;
              justify-content: flex-start;

              margin: 0;
              padding: 0;
         }

         .powa-sell-bar-control {
              display: flex;
              align-items: center;
              justify-content: center;

              width: 20px;
         }

         .powa-sell-bar-control:active {
              color: rgb(153, 50, 204);
         }

         .powa-sell-bar-countdown {
              font-size: 14px;
              font-weight: 400;
              line-height: normal;
              margin: 0 3px 0 3px;
              padding: 0;
         }

         .powa-sell-bar-skip-container {
              position: absolute;
              right: 0px;

              display: flex;
              justify-content: flex-end;

              padding: 0 3px 0 3px;
         }

         .powa-sell-bar-skip-countdown {
              font-size: 14px;
              font-weight: 400;
              line-height: normal;
              margin: 0;
              padding: 0;
         }

         .powa-sell-bar-skip {
              font-size: 14px;
              font-weight: 400;
              cursor: pointer;
              line-height: normal;
              margin: 0;
              padding: 0;
         }

         .powa-sell-bar-skip:hover {
              text-decoration: underline;
         }

         .powa-sell-bar-skip:active {
              color: rgb(153, 50, 204);
         }

         .powa-sell-bar-hidden {
              display: none;
         }
    `,

    template: config => `
         <div class="powa-sell-bar">
              <span class="powa-sell-bar-controls">
                    <span class="powa-sell-bar-control powa-sell-bar-play-pause powa-click-play-pause-click">
                         <span class="powa-sell-bar-play powa-sell-bar-hidden" aria-label="Play"><i class="fa fa-play" aria-hidden="true"></i></span>
                         <span class="powa-sell-bar-pause" aria-label="Pause"><i class="fa fa-pause" aria-hidden="true"></i></span>
                    </span>
                    <span class="powa-sell-bar-control powa-sell-bar-mute-unmute powa-click-mute-unmute-click">
                         <span class="powa-sell-bar-mute" aria-label="Mute"><i class="fa fa-volume-up" aria-hidden="true"></i></span>
                         <span class="powa-sell-bar-unmute powa-sell-bar-hidden" aria-label="Unmute"><i class="fa fa-volume-off" aria-hidden="true"></i></span>
                    </span>
              </span>
              <span class="powa-sell-bar-countdown powa-sell-bar-hidden"></span>
              <span class="powa-sell-bar-skip-container">
                    <span class="powa-sell-bar-skip-countdown powa-sell-bar-hidden"></span>
                    <span class="powa-sell-bar-skip powa-click-skip-click powa-sell-bar-hidden">Skip</span>
              </span>
         </div>
    `,
};
```

The following is an example of an Ad bar in Spanish without the play or pause control:

```
    window.PoWaSettings.advertising.adBar = {
  playControl: false,

  adSkipText: 'Omitir anuncio',

  adCountdownTemplate: timing => {
    return `Anuncio: ${ Math.ceil(timing.remaining) } segundos`;
  },

  adSkipCountdownTemplate: timing => {
    return `Podrá saltar el anuncio en ${ Math.ceil(timing.skipOffset - (timing.duration - timing.remaining)) } segundos`;
  },
};
```
