Source: lib/ads/interstitial_tracker.js

/*! @license
 * Shaka Player
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */


goog.provide('shaka.ads.InterstitialTracker');

goog.require('shaka.ads.InterstitialAdSession');
goog.require('shaka.ads.Utils');


/**
 * Sends the tracking beacons of an interstitial. Event types follow the VAST
 * tracking event names, which other formats (e.g. SVTA2053) reuse.
 *
 * @implements {shaka.ads.InterstitialAdSession.Tracker}
 */
shaka.ads.InterstitialTracker = class {
  /**
   * @param {function():!Array<shaka.ads.InterstitialTracker.Event>} getEvents
   *   Returns the current events, which may change once deferred data is
   *   resolved.
   * @param {function(string)} sendBeacon
   * @param {!Set<shaka.ads.InterstitialTracker.Event>=} fired Events already
   *   sent, which may be shared with other trackers (e.g. the slots of a pod).
   */
  constructor(getEvents, sendBeacon, fired = new Set()) {
    /** @private {function():!Array<shaka.ads.InterstitialTracker.Event>} */
    this.getEvents_ = getEvents;
    /** @private {function(string)} */
    this.sendBeacon_ = sendBeacon;
    /** @private {!Set<shaka.ads.InterstitialTracker.Event>} */
    this.fired_ = fired;
    /** @private {boolean} */
    this.released_ = false;
  }

  /** @override */
  release() {
    this.released_ = true;
  }

  /** @override */
  resolve() {
    return Promise.resolve();
  }

  /** @override */
  onEvent(type) {
    const name = shaka.ads.InterstitialTracker.getName(type);
    if (name) {
      this.fire(name);
    }
  }

  /** @override */
  onProgress(previous, position) {
    if (this.released_) {
      return;
    }
    for (const event of this.getEvents_()) {
      if (event.offset == null || this.fired_.has(event) ||
          event.offset < previous || event.offset > position) {
        continue;
      }
      this.fired_.add(event);
      for (const url of event.urls) {
        this.sendBeacon_(url);
      }
    }
  }

  /**
   * Sends the beacons of the given event type. Events that describe a state
   * are sent once; events that describe an action are sent every time.
   *
   * @param {string} name
   */
  fire(name) {
    if (this.released_) {
      return;
    }
    const repeated = shaka.ads.InterstitialTracker.isRepeated(name);
    for (const event of this.getEvents_()) {
      if (event.type != name || event.offset != null ||
          (!repeated && this.fired_.has(event))) {
        continue;
      }
      if (!repeated) {
        this.fired_.add(event);
      }
      for (const url of event.urls) {
        this.sendBeacon_(url);
      }
    }
  }

  /**
   * @param {string} type An ad event type, or a VAST tracking event name.
   * @return {?string} The VAST tracking event name.
   */
  static getName(type) {
    const InterstitialTracker = shaka.ads.InterstitialTracker;
    return InterstitialTracker.NAMES_.get(type) ||
        (InterstitialTracker.REPEATED_.has(type) ? type : null);
  }

  /**
   * Whether the event describes an action, which is reported every time it
   * happens, rather than a state, which is reported once.
   *
   * @param {string} name
   * @return {boolean}
   */
  static isRepeated(name) {
    return shaka.ads.InterstitialTracker.REPEATED_.has(name);
  }

  /**
   * @param {?shaka.extern.AdTrackingEvent} tracking
   * @return {!Array<shaka.ads.InterstitialTracker.Event>}
   */
  static fromTracking(tracking) {
    const events = [];
    if (tracking) {
      for (const type of Object.keys(tracking)) {
        const urls = tracking[type];
        if (urls && urls.length) {
          events.push({type, urls});
        }
      }
    }
    return events;
  }
};


/**
 * @typedef {{
 *   type: string,
 *   offset: (number|undefined),
 *   urls: !Array<string>,
 * }}
 *
 * @property {string} type
 *   The VAST tracking event name.
 * @property {(number|undefined)} offset
 *   The position, in seconds, of a progress event.
 * @property {!Array<string>} urls
 *   The beacon URLs, which may contain VAST macros.
 */
shaka.ads.InterstitialTracker.Event;


/**
 * Maps ad events to tracking event names.
 *
 * @private @const {!Map<string, string>}
 */
shaka.ads.InterstitialTracker.NAMES_ = new Map([
  [shaka.ads.Utils.AD_IMPRESSION, 'impression'],
  [shaka.ads.Utils.AD_STARTED, 'start'],
  [shaka.ads.Utils.AD_LOADED, 'loaded'],
  [shaka.ads.Utils.AD_FIRST_QUARTILE, 'firstQuartile'],
  [shaka.ads.Utils.AD_MIDPOINT, 'midpoint'],
  [shaka.ads.Utils.AD_THIRD_QUARTILE, 'thirdQuartile'],
  [shaka.ads.Utils.AD_COMPLETE, 'complete'],
  [shaka.ads.Utils.AD_SKIPPED, 'skip'],
  [shaka.ads.Utils.AD_ERROR, 'error'],
  [shaka.ads.Utils.AD_CLICKED, 'clickTracking'],
  [shaka.ads.Utils.AD_PAUSED, 'pause'],
  [shaka.ads.Utils.AD_RESUMED, 'resume'],
  [shaka.ads.Utils.AD_MUTED, 'mute'],
  [shaka.ads.Utils.AD_VOLUME_CHANGED, 'unmute'],
]);


/**
 * Tracking events that describe an action, so they are sent every time.
 *
 * @private @const {!Set<string>}
 */
shaka.ads.InterstitialTracker.REPEATED_ = new Set([
  'pause', 'resume', 'mute', 'unmute', 'clickTracking', 'error',
  'playerExpand', 'playerCollapse',
]);