Source: lib/ads/ad_utils.js

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


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

goog.require('shaka.net.NetworkingEngine');
goog.require('shaka.util.StringUtils');
goog.require('shaka.util.TXml');


/**
 * A class responsible for ad utils.
 * @export
 */
shaka.ads.Utils = class {
  /**
   * @return {!shaka.extern.AdTrackingEvent}
   */
  static createTracking() {
    return {
      impression: null,
      clickTracking: null,
      start: null,
      firstQuartile: null,
      midpoint: null,
      thirdQuartile: null,
      complete: null,
      skip: null,
      error: null,
      resume: null,
      pause: null,
      mute: null,
      unmute: null,
    };
  }

  /**
   * Creates an interstitial. Only the fields that differ from the defaults
   * need to be given.
   *
   * @param {!Object} fields
   * @return {!shaka.extern.AdInterstitial}
   */
  static createInterstitial(fields) {
    return /** @type {!shaka.extern.AdInterstitial} */ (Object.assign({
      id: null,
      groupId: null,
      startTime: 0,
      endTime: null,
      uri: null,
      mimeType: null,
      isSkippable: false,
      skipOffset: null,
      skipFor: null,
      canJump: true,
      resumeOffset: null,
      playoutLimit: null,
      once: false,
      pre: false,
      post: false,
      timelineRange: false,
      loop: false,
      overlay: null,
      displayOnBackground: false,
      currentVideo: null,
      background: null,
      clickThroughUrl: null,
      tracking: null,
    }, fields));
  }

  /**
   * Creates an interstitial resource that is resolved by fetching a JSON
   * document when the manager schedules it.
   *
   * @param {!Object} fields The fields of the resource, which must include
   *   those of shaka.extern.DeferredInterstitial that describe its timing.
   * @param {!shaka.net.NetworkingEngine} networkingEngine
   * @param {function(number):string} getUri Returns the URI to fetch for a
   *   playback offset.
   * @param {function(*, string, number, !shaka.extern.DeferredInterstitial):
   *     !Array<shaka.extern.AdInterstitial>} parse Parses the document, given
   *   its URI after redirects, the playback offset and the resource.
   * @param {shaka.extern.RequestContext=} context
   * @return {!shaka.extern.DeferredInterstitial}
   */
  static createDeferredInterstitial(fields, networkingEngine, getUri, parse,
      context) {
    /** @type {?shaka.net.NetworkingEngine.PendingRequest} */
    let pending = null;
    const resource = /** @type {!shaka.extern.DeferredInterstitial} */ (
      Object.assign({
        resolving: false,
        resolved: false,
        resolve: async (offset) => {
          const NetworkingEngine = shaka.net.NetworkingEngine;
          const request = NetworkingEngine.makeRequest(
              [getUri(offset)], NetworkingEngine.defaultRetryParameters());
          pending = networkingEngine.request(
              NetworkingEngine.RequestType.ADS, request, context);
          const response = await pending.promise;
          pending = null;
          return parse(
              JSON.parse(shaka.util.StringUtils.fromUTF8(response.data)),
              response.uri, offset, resource);
        },
        abort: () => pending ? pending.abort() : Promise.resolve(),
      }, fields));
    return resource;
  }

  /**
   * @param {!shaka.extern.xml.Node} inline
   * @return {!shaka.extern.AdTrackingEvent}
   */
  static createTrackingFromInline(inline) {
    const TXml = shaka.util.TXml;

    const tracking = shaka.ads.Utils.createTracking();

    for (const error of TXml.findChildren(inline, 'Error')) {
      const url = TXml.getTextContents(error);
      if (url) {
        if (!tracking.error) {
          tracking.error = [];
        }
        tracking.error.push(url);
      }
    }
    for (const impression of TXml.findChildren(inline, 'Impression')) {
      const url = TXml.getTextContents(impression);
      if (url) {
        if (!tracking.impression) {
          tracking.impression = [];
        }
        tracking.impression.push(url);
      }
    }

    return tracking;
  }

  /**
   * @param {!Array<!shaka.extern.xml.Node>} trackingEvents
   * @param {!shaka.extern.AdTrackingEvent} tracking
   */
  static processTrackingEvents(trackingEvents, tracking) {
    const TXml = shaka.util.TXml;
    for (const trackingEvent of trackingEvents) {
      const eventName = trackingEvent.attributes['event'];
      if (eventName in tracking) {
        const url = TXml.getTextContents(trackingEvent);
        if (url) {
          if (!tracking[eventName]) {
            tracking[eventName] = [];
          }
          tracking[eventName].push(url);
        }
      }
    }
  }
};

/**
 * The playback mode of interstitials whose media is already present in the
 * content.
 *
 * @const {string}
 * @noinline
 */
shaka.ads.Utils.EMBEDDED = 'embedded';

/**
 * The event name for when a sequence of ads has been loaded.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.ADS_LOADED = 'ads-loaded';

/**
 * The event name for when an ad has started playing.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_STARTED = 'ad-started';


/**
 * The event name for when an ad actually starts playback.
 *
 * This is fired when the ad's media element enters the 'playing' state,
 * indicating that playback has begun with media data available.
 *
 * Unlike AD_STARTED, which signals the intent to start an ad,
 * this event guarantees that the ad is truly rendering and advancing
 * its playhead.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_PLAYING = 'ad-playing';


/**
 * The event name for when an ad playhead crosses first quartile.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_FIRST_QUARTILE = 'ad-first-quartile';


/**
 * The event name for when an ad playhead crosses midpoint.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_MIDPOINT = 'ad-midpoint';


/**
 * The event name for when an ad playhead crosses third quartile.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_THIRD_QUARTILE = 'ad-third-quartile';


/**
 * The event name for when an ad has completed playing.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_COMPLETE = 'ad-complete';


/**
 * The event name for when an ad has finished playing
 * (played all the way through, was skipped, or was unable to proceed
 * due to an error).
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_STOPPED = 'ad-stopped';


/**
 * The event name for when an ad is skipped by the user..
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_SKIPPED = 'ad-skipped';


/**
 * The event name for when the ad volume has changed.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_VOLUME_CHANGED = 'ad-volume-changed';


/**
 * The event name for when the ad was muted.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_MUTED = 'ad-muted';


/**
 * The event name for when the ad was paused.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_PAUSED = 'ad-paused';


/**
 * The event name for when the ad was resumed after a pause.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_RESUMED = 'ad-resumed';


/**
 * The event name for when the ad's skip status changes
 * (usually it becomes skippable when it wasn't before).
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_SKIP_STATE_CHANGED = 'ad-skip-state-changed';


/**
 * The event name for when the ad's cue points (start/end markers)
 * have changed.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.CUEPOINTS_CHANGED = 'ad-cue-points-changed';


/**
 * The event name for when the native IMA ad manager object has
 * loaded and become available.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.IMA_AD_MANAGER_LOADED = 'ima-ad-manager-loaded';


/**
 * The event name for when the native IMA stream manager object has
 * loaded and become available.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.IMA_STREAM_MANAGER_LOADED = 'ima-stream-manager-loaded';


/**
 * The event name for when the ad was clicked.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_CLICKED = 'ad-clicked';


/**
 * The event name for when there is an update to the current ad's progress.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_PROGRESS = 'ad-progress';


/**
 * The event name for when the ad is buffering.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_BUFFERING = 'ad-buffering';


/**
 * The event name for when the ad's URL was hit.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_IMPRESSION = 'ad-impression';


/**
 * The event name for when the ad's duration changed.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_DURATION_CHANGED = 'ad-duration-changed';


/**
 * The event name for when the ad was closed by the user.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_CLOSED = 'ad-closed';


/**
 * The event name for when the ad data becomes available.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_LOADED = 'ad-loaded';


/**
 * The event name for when all the ads were completed.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.ALL_ADS_COMPLETED = 'all-ads-completed';


/**
 * The event name for when the ad changes from or to linear.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_LINEAR_CHANGED = 'ad-linear-changed';


/**
 * The event name for when the ad's metadata becomes available.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_METADATA = 'ad-metadata';


/**
 * The event name for when the ad display encountered a recoverable
 * error.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_RECOVERABLE_ERROR = 'ad-recoverable-error';

/**
 * The event name for when the ad manager dispatch errors.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_ERROR = 'ad-error';

/**
 * The event name for when the client side SDK signalled its readiness
 * to play a VPAID ad or an ad rule.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_BREAK_READY = 'ad-break-ready';

/**
 * The event name for when the ad manager starts an ad break.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_BREAK_STARTED = 'ad-break-started';

/**
 * The event name for when the ad manager ends an ad break.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_BREAK_ENDED = 'ad-break-ended';

/**
 * The event name for when the ad manager starts the preload of an interstitial.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_INTERSTITIAL_PRELOAD = 'ad-interstitial-preload';

/**
 * The event name for when the ad manager finish the preload of an interstitial.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_INTERSTITIAL_PRELOADED = 'ad-interstitial-preloaded';


/**
 * The event name for when the interaction callback for the ad was
 * triggered.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_INTERACTION = 'ad-interaction';


/**
 * The name of the event for when an ad requires the main content to be paused.
 * Fired when the platform does not support multiple media elements.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_CONTENT_PAUSE_REQUESTED = 'ad-content-pause-requested';


/**
 * The name of the event for when an ad requires the main content to be resumed.
 * Fired when the platform does not support multiple media elements.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_CONTENT_RESUME_REQUESTED = 'ad-content-resume-requested';


/**
 * The name of the event for when an ad requires the video of the main content
 * to be attached.
 *
 * @const {string}
 * @noinline
 * @export
 */
shaka.ads.Utils.AD_CONTENT_ATTACH_REQUESTED = 'ad-content-attach-requested';