text: /*! @license
   * Shaka Player
   * Copyright 2016 Google LLC
   * SPDX-License-Identifier: Apache-2.0
   */
  
  goog.provide('shaka.media.RegionObserver');
  
  goog.require('shaka.media.IPlayheadObserver');
  goog.require('shaka.media.RegionTimeline');
  goog.require('shaka.util.EventManager');
  goog.require('shaka.util.FakeEvent');
  goog.require('shaka.util.FakeEventTarget');
  
  
  /**
   * The region observer watches a region timeline and playhead, and fires events
   * ('enter', 'exit', 'skip') as the playhead moves.
   *
   * @implements {shaka.media.IPlayheadObserver}
   * @final
   */
  shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
    /**
     * Create a region observer for the given timeline. The observer does not
     * own the timeline, only uses it. This means that the observer should NOT
     * destroy the timeline.
     *
     * @param {!shaka.media.RegionTimeline} timeline
     */
    constructor(timeline) {
      super();
  
      /** @private {shaka.media.RegionTimeline} */
      this.timeline_ = timeline;
  
      /**
       * A mapping between a region and where we previously were relative to it.
       * When the value here differs from what we calculate, it means we moved and
       * should fire an event.
       *
       * @private {!Map.<shaka.extern.TimelineRegionInfo,
       *                 shaka.media.RegionObserver.RelativePosition_>}
       */
      this.oldPosition_ = new Map();
  
      // To make the rules easier to read, alias all the relative positions.
      const RelativePosition = shaka.media.RegionObserver.RelativePosition_;
      const BEFORE_THE_REGION = RelativePosition.BEFORE_THE_REGION;
      const IN_THE_REGION = RelativePosition.IN_THE_REGION;
      const AFTER_THE_REGION = RelativePosition.AFTER_THE_REGION;
  
      /**
       * A read-only collection of rules for what to do when we change position
       * relative to a region.
       *
       * @private {!Iterable.<shaka.media.RegionObserver.Rule_>}
       */
      this.rules_ = [
        {
          weWere: null,
          weAre: IN_THE_REGION,
          invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
        },
        {
          weWere: BEFORE_THE_REGION,
          weAre: IN_THE_REGION,
          invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
        },
        {
          weWere: AFTER_THE_REGION,
          weAre: IN_THE_REGION,
          invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
        },
        {
          weWere: IN_THE_REGION,
          weAre: BEFORE_THE_REGION,
          invoke: (region, seeking) => this.onEvent_('exit', region, seeking),
        },
        {
          weWere: IN_THE_REGION,
          weAre: AFTER_THE_REGION,
          invoke: (region, seeking) => this.onEvent_('exit', region, seeking),
        },
        {
          weWere: BEFORE_THE_REGION,
          weAre: AFTER_THE_REGION,
          invoke: (region, seeking) => this.onEvent_('skip', region, seeking),
        },
        {
          weWere: AFTER_THE_REGION,
          weAre: BEFORE_THE_REGION,
          invoke: (region, seeking) => this.onEvent_('skip', region, seeking),
        },
      ];
  
      /** @private {shaka.util.EventManager} */
      this.eventManager_ = new shaka.util.EventManager();
  
      this.eventManager_.listen(this.timeline_, 'regionremove', (event) => {
        /** @type {shaka.extern.TimelineRegionInfo} */
        const region = event['region'];
        this.oldPosition_.delete(region);
      });
    }
  
    /** @override */
    release() {
      this.timeline_ = null;
  
      // Clear our maps so that we are not holding onto any more information than
      // needed.
      this.oldPosition_.clear();
  
      this.eventManager_.release();
      this.eventManager_ = null;
  
      super.release();
    }
  
    /** @override */
    poll(positionInSeconds, wasSeeking) {
      const RegionObserver = shaka.media.RegionObserver;
  
      for (const region of this.timeline_.regions()) {
        const previousPosition = this.oldPosition_.get(region);
        const currentPosition = RegionObserver.determinePositionRelativeTo_(
            region, positionInSeconds);
  
        // We will only use |previousPosition| and |currentPosition|, so we can
        // update our state now.
        this.oldPosition_.set(region, currentPosition);
  
        for (const rule of this.rules_) {
          if (rule.weWere == previousPosition && rule.weAre == currentPosition) {
            rule.invoke(region, wasSeeking);
          }
        }
      }
    }
  
    /**
     * Dispatch events of the given type.  All event types in this class have the
     * same parameters: region and seeking.
     *
     * @param {string} eventType
     * @param {shaka.extern.TimelineRegionInfo} region
     * @param {boolean} seeking
     * @private
     */
    onEvent_(eventType, region, seeking) {
      const event = new shaka.util.FakeEvent(eventType, new Map([
        ['region', region],
        ['seeking', seeking],
      ]));
      this.dispatchEvent(event);
    }
  
    /**
     * Get the relative position of the playhead to |region| when the playhead is
     * at |seconds|. We treat the region's start and end times as inclusive
     * bounds.
     *
     * @param {shaka.extern.TimelineRegionInfo} region
     * @param {number} seconds
     * @return {shaka.media.RegionObserver.RelativePosition_}
     * @private
     */
    static determinePositionRelativeTo_(region, seconds) {
      const RelativePosition = shaka.media.RegionObserver.RelativePosition_;
  
      if (seconds < region.startTime) {
        return RelativePosition.BEFORE_THE_REGION;
      }
  
      if (seconds > region.endTime) {
        return RelativePosition.AFTER_THE_REGION;
      }
  
      return RelativePosition.IN_THE_REGION;
    }
  };
  
  /**
   * An enum of relative positions between the playhead and a region. Each is
   * phrased so that it works in "The playhead is X" where "X" is any value in
   * the enum.
   *
   * @enum {number}
   * @private
   */
  shaka.media.RegionObserver.RelativePosition_ = {
    BEFORE_THE_REGION: 1,
    IN_THE_REGION: 2,
    AFTER_THE_REGION: 3,
  };
  
  /**
   * All region observer events (onEnter, onExit, and onSkip) will be passed the
   * region that the playhead is interacting with and whether or not the playhead
   * moving is part of a seek event.
   *
   * @typedef {function(shaka.extern.TimelineRegionInfo, boolean)}
   */
  shaka.media.RegionObserver.EventListener;
  
  /**
   * @typedef {{
   *    weWere: ?shaka.media.RegionObserver.RelativePosition_,
   *    weAre: ?shaka.media.RegionObserver.RelativePosition_,
   *    invoke: shaka.media.RegionObserver.EventListener
   * }}
   *
   * @private
   */
  shaka.media.RegionObserver.Rule_;
  