constructor(manifest, playerInterface) {
    /** @private {?shaka.media.StreamingEngine.PlayerInterface} */
    this.playerInterface_ = playerInterface;

    /** @private {?shaka.extern.Manifest} */
    this.manifest_ = manifest;

    /** @private {?shaka.extern.StreamingConfiguration} */
    this.config_ = null;

    /**
     * Retains a reference to the function used to close SegmentIndex objects
     * for streams which were switched away from during an ongoing update_().
     * @private {!Map<string, !function()>}
     */
    this.deferredCloseSegmentIndex_ = new Map();

    /** @private {number} */
    this.bufferingScale_ = 1;

    /** @private {?shaka.extern.Variant} */
    this.currentVariant_ = null;

    /** @private {?shaka.extern.Stream} */
    this.currentTextStream_ = null;

    /** @private {number} */
    this.textStreamSequenceId_ = 0;

    /**
     * Maps a content type, e.g., 'audio', 'video', or 'text', to a MediaState.
     *
     * @private {!Map<shaka.util.ManifestParserUtils.ContentType,
     *                 !shaka.media.StreamingEngine.MediaState_>}
     */
    this.mediaStates_ = new Map();

    /**
     * Set to true once the initial media states have been created.
     *
     * @private {boolean}
     */
    this.startupComplete_ = false;

    /**
     * Used for delay and backoff of failure callbacks, so that apps do not
     * retry instantly.
     *
     * @private {shaka.net.Backoff}
     */
    this.failureCallbackBackoff_ = null;

    /**
     * Set to true on fatal error.  Interrupts fetchAndAppend_().
     *
     * @private {boolean}
     */
    this.fatalError_ = false;

    /** @private {!shaka.util.Destroyer} */
    this.destroyer_ = new shaka.util.Destroyer(() => this.doDestroy_());

    /** @private {number} */
    this.lastMediaSourceReset_ = Date.now() / 1000;

    /**
     * @private {!Map<shaka.extern.Stream, !shaka.media.SegmentPrefetch>}
     */
    this.audioPrefetchMap_ = new Map();

    /** @private {!shaka.extern.SpatialVideoInfo} */
    this.spatialVideoInfo_ = {
      projection: null,
      hfov: null,
    };

    /** @private {number} */
    this.playRangeStart_ = 0;

    /** @private {number} */
    this.playRangeEnd_ = Infinity;

    /** @private {?shaka.media.StreamingEngine.MediaState_} */
    this.lastTextMediaStateBeforeUnload_ = null;

    /** @private {!Array} */
    this.requestedDependencySegments_ = [];

    /** @private {?shaka.util.Timer} */
    this.updateLiveSeekableRangeTimer_ = new shaka.util.Timer(() => {
      if (!this.manifest_ || !this.playerInterface_) {
        if (this.updateLiveSeekableRangeTimer_) {
          this.updateLiveSeekableRangeTimer_.stop();
        }
        return;
      }
      if (!this.manifest_.presentationTimeline.isLive()) {
        this.playerInterface_.mediaSourceEngine.clearLiveSeekableRange();
        if (this.updateLiveSeekableRangeTimer_) {
          this.updateLiveSeekableRangeTimer_.stop();
        }
        return;
      }
      const startTime = this.manifest_.presentationTimeline.getSeekRangeStart();
      const endTime = this.manifest_.presentationTimeline.getSeekRangeEnd();
      // Some older devices require the range to be greater than 1 or exceptions
      // are thrown, due to an old and buggy implementation.
      if (endTime - startTime > 1) {
        this.playerInterface_.mediaSourceEngine.setLiveSeekableRange(
            startTime, endTime);
      } else {
        this.playerInterface_.mediaSourceEngine.clearLiveSeekableRange();
      }
    });

    /** @private {?number} */
    this.boundaryTime_ = null;

    /** @private {?shaka.util.Timer} */
    this.crossBoundaryTimer_ = new shaka.util.Timer(() => {
      const video = this.playerInterface_.video;
      if (video.ended) {
        return;
      }
      if (this.boundaryTime_) {
        shaka.log.info('Crossing boundary at', this.boundaryTime_);
        video.currentTime = this.boundaryTime_;
        this.boundaryTime_ = null;
      }
    });
  }
@param {shaka.extern.Manifest} manifest
@param {shaka.media.StreamingEngine.PlayerInterface} playerInterface