createStallDetector_() {
    if (!this.config_.stallEnabled) {
      return null;
    }
    goog.asserts.assert(this.video_, 'Must have video');

    // Cache the values from the config so that changes to the config won't
    // change the initialized behaviour.
    const threshold = this.config_.stallThreshold;
    const skip = this.config_.stallSkip;

    const onStall = async (at, duration) => {
      goog.asserts.assert(this.video_, 'Must have video');
      const bufferedInfo =
          shaka.media.TimeRangesUtils.getBufferedInfo(this.video_.buffered);
      if (!bufferedInfo.length) {
        // Nothing in the buffer.
        return;
      }
      shaka.log.debug(`Stall detected at ${at} for ${duration} seconds.`);

      if (skip) {
        shaka.log.debug(`Seeking forward ${skip} seconds to break stall.`);
        this.video_.currentTime += skip;
      } else {
        shaka.log.debug(
            'Wait for play to avoid reject a possible other play call.');
        await this.video_.play();
        if (!this.video_) {
          return;
        }
        shaka.log.debug('Pausing and unpausing to break stall.');
        this.video_.pause();
        this.video_.play();
      }
      this.stallsDetected_++;
      this.onEvent_(new shaka.util.FakeEvent(
          shaka.util.FakeEvent.EventName.StallDetected));
    };

    // When we see a stall, we will try to "jump-start" playback by moving the
    // playhead forward.
    const detector = new shaka.media.StallDetector(
        new shaka.media.StallDetector.MediaElementImplementation(this.video_),
        threshold, onStall);

    return detector;
  }
Create and configure a stall detector using the player's streaming
configuration settings. If the player is configured to have no stall
detector, this will return |null|.
@return {shaka.media.StallDetector}
@private