constructor(timeline, startsPastZero) {
    super();

    /** @private {shaka.media.RegionTimeline<T>} */
    this.timeline_ = timeline;

    /**
     * Whether the asset is expected to start at a time beyond 0 seconds.
     * For example, if the asset is a live stream.
     * If true, we will not start polling for regions until the playhead has
     * moved past 0 seconds, to avoid bad behaviors where the current time is
     * briefly 0 before we have enough data to play.
     * @private {boolean}
     */
    this.startsPastZero_ = startsPastZero;

    /**
     * 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<T, 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) => {
          if (seeking) {
            this.onEvent_('skip', region, seeking);
          } else {
            // This is the case of regions whose duration is smaller than the
            // resolution of our polling.
            this.onEvent_('enter', region, seeking);
            this.onEvent_('exit', 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 {T} */
      const region = event['region'];
      this.oldPosition_.delete(region);
    });
  }
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<T>} timeline
@param {boolean} startsPastZero