async setupVideoAd_(interstitial, sequenceLength, adPosition, initialTime,
      oncePlayed) {
    goog.asserts.assert(this.video_, 'Must have video');
    const startTime = Date.now();

    this.playingAd_ = true;

    if (this.usingBaseVideo_ && adPosition == 1) {
      this.onEvent_(new shaka.util.FakeEvent(
          shaka.ads.Utils.AD_CONTENT_PAUSE_REQUESTED,
          (new Map()).set('saveLivePosition', true)));
      const detachBasePlayerPromise = new shaka.util.PublicPromise();
      const checkState = async (e) => {
        if (e['state'] == 'detach') {
          if (this.isSmartTV_()) {
            await new Promise(
                (resolve) => new shaka.util.Timer(resolve).tickAfter(0.1));
          }
          detachBasePlayerPromise.resolve();
          this.adEventManager_.unlisten(
              this.basePlayer_, 'onstatechange', checkState);
        }
      };
      this.adEventManager_.listen(
          this.basePlayer_, 'onstatechange', checkState);
      await detachBasePlayerPromise;
    }
    this.setBaseStyles_(interstitial);
    if (!this.usingBaseVideo_) {
      this.video_.style.display = '';
      if (interstitial.overlay) {
        this.video_.loop = interstitial.loop;
        const viewport = interstitial.overlay.viewport;
        const topLeft = interstitial.overlay.topLeft;
        const size = interstitial.overlay.size;
        this.video_.style.height = (size.y / viewport.y * 100) + '%';
        this.video_.style.left = (topLeft.x / viewport.x * 100) + '%';
        this.video_.style.top = (topLeft.y / viewport.y * 100) + '%';
        this.video_.style.width = (size.x / viewport.x * 100) + '%';
      } else {
        this.baseVideo_.pause();
        if (interstitial.resumeOffset != null &&
            interstitial.resumeOffset != 0) {
          this.baseVideo_.currentTime += interstitial.resumeOffset;
        }
        this.video_.loop = false;
        this.video_.style.height = '100%';
        this.video_.style.left = '0';
        this.video_.style.top = '0';
        this.video_.style.width = '100%';
      }
    }

    let unloadingInterstitial = false;

    const updateBaseVideoTime = () => {
      if (!this.usingBaseVideo_ && !interstitial.overlay) {
        if (interstitial.resumeOffset == null) {
          if (interstitial.timelineRange && interstitial.endTime &&
              interstitial.endTime != Infinity) {
            if (this.baseVideo_.currentTime != interstitial.endTime) {
              this.baseVideo_.currentTime = interstitial.endTime;
            }
          } else {
            const now = Date.now();
            this.baseVideo_.currentTime += (now - initialTime) / 1000;
            initialTime = now;
          }
        }
      }
    };

    const basicTask = async (isSkip) => {
      updateBaseVideoTime();
      // Optimization to avoid returning to main content when there is another
      // interstitial below.
      let nextCurrentInterstitial = this.getCurrentInterstitial_(
          interstitial.pre, adPosition - oncePlayed);
      if (isSkip && interstitial.groupId) {
        while (nextCurrentInterstitial &&
            nextCurrentInterstitial.groupId == interstitial.groupId) {
          adPosition++;
          nextCurrentInterstitial = this.getCurrentInterstitial_(
              interstitial.pre, adPosition - oncePlayed);
        }
      }
      if (this.playoutLimitTimer_ && (!interstitial.groupId ||
          (nextCurrentInterstitial &&
            nextCurrentInterstitial.groupId != interstitial.groupId))) {
        this.playoutLimitTimer_.stop();
        this.playoutLimitTimer_ = null;
      }
      this.removeBaseStyles_(interstitial);
      if (!nextCurrentInterstitial || nextCurrentInterstitial.overlay) {
        if (interstitial.post) {
          this.lastTime_ = null;
          this.lastPlayedAd_ = null;
        }
        if (this.usingBaseVideo_) {
          await this.player_.detach();
        } else {
          await this.player_.unload();
        }
        if (this.usingBaseVideo_) {
          let offset = interstitial.resumeOffset;
          if (offset == null) {
            if (interstitial.timelineRange && interstitial.endTime &&
                interstitial.endTime != Infinity) {
              offset = interstitial.endTime - (this.lastTime_ || 0);
            } else {
              offset = (Date.now() - initialTime) / 1000;
            }
          }
          this.onEvent_(new shaka.util.FakeEvent(
              shaka.ads.Utils.AD_CONTENT_RESUME_REQUESTED,
              (new Map()).set('offset', offset)));
        }
        this.onEvent_(
            new shaka.util.FakeEvent(shaka.ads.Utils.AD_STOPPED));
        this.adEventManager_.removeAll();
        this.playingAd_ = false;
        if (!this.usingBaseVideo_) {
          this.video_.style.display = 'none';
          updateBaseVideoTime();
          if (!this.baseVideo_.ended) {
            this.baseVideo_.play();
          }
        } else {
          this.cuepointsChanged_();
        }
      }
      this.determineIfUsingBaseVideo_();
      if (nextCurrentInterstitial) {
        this.onEvent_(
            new shaka.util.FakeEvent(shaka.ads.Utils.AD_STOPPED));
        this.adEventManager_.removeAll();
        this.setupAd_(nextCurrentInterstitial, sequenceLength,
            ++adPosition, initialTime, oncePlayed);
      }
    };
    const error = async (e) => {
      if (unloadingInterstitial) {
        return;
      }
      unloadingInterstitial = true;
      this.onEvent_(new shaka.util.FakeEvent(shaka.ads.Utils.AD_ERROR,
          (new Map()).set('originalEvent', e)));
      await basicTask(/* isSkip= */ false);
    };
    const complete = async () => {
      if (unloadingInterstitial) {
        return;
      }
      unloadingInterstitial = true;
      await basicTask(/* isSkip= */ false);
      this.onEvent_(
          new shaka.util.FakeEvent(shaka.ads.Utils.AD_COMPLETE));
    };
    this.lastOnSkip_ = async () => {
      if (unloadingInterstitial) {
        return;
      }
      unloadingInterstitial = true;
      this.onEvent_(new shaka.util.FakeEvent(shaka.ads.Utils.AD_SKIPPED));
      await basicTask(/* isSkip= */ true);
    };

    const ad = new shaka.ads.InterstitialAd(this.video_,
        interstitial, this.lastOnSkip_, sequenceLength, adPosition,
        !this.usingBaseVideo_);
    if (!this.usingBaseVideo_) {
      ad.setMuted(this.baseVideo_.muted);
      ad.setVolume(this.baseVideo_.volume);
    }

    this.onEvent_(
        new shaka.util.FakeEvent(shaka.ads.Utils.AD_STARTED,
            (new Map()).set('ad', ad)));

    let prevCanSkipNow = ad.canSkipNow();
    if (prevCanSkipNow) {
      this.onEvent_(new shaka.util.FakeEvent(
          shaka.ads.Utils.AD_SKIP_STATE_CHANGED));
    }
    this.adEventManager_.listenOnce(this.player_, 'error', error);
    this.adEventManager_.listen(this.video_, 'timeupdate', () => {
      const duration = this.video_.duration;
      if (!duration) {
        return;
      }
      const currentCanSkipNow = ad.canSkipNow();
      if (prevCanSkipNow != currentCanSkipNow &&
          ad.getRemainingTime() > 0 && ad.getDuration() > 0) {
        this.onEvent_(
            new shaka.util.FakeEvent(shaka.ads.Utils.AD_SKIP_STATE_CHANGED));
      }
      prevCanSkipNow = currentCanSkipNow;
    });
    this.adEventManager_.listenOnce(this.player_, 'firstquartile', () => {
      updateBaseVideoTime();
      this.onEvent_(
          new shaka.util.FakeEvent(shaka.ads.Utils.AD_FIRST_QUARTILE));
    });
    this.adEventManager_.listenOnce(this.player_, 'midpoint', () => {
      updateBaseVideoTime();
      this.onEvent_(
          new shaka.util.FakeEvent(shaka.ads.Utils.AD_MIDPOINT));
    });
    this.adEventManager_.listenOnce(this.player_, 'thirdquartile', () => {
      updateBaseVideoTime();
      this.onEvent_(
          new shaka.util.FakeEvent(shaka.ads.Utils.AD_THIRD_QUARTILE));
    });
    this.adEventManager_.listenOnce(this.player_, 'complete', complete);
    this.adEventManager_.listen(this.video_, 'play', () => {
      this.onEvent_(
          new shaka.util.FakeEvent(shaka.ads.Utils.AD_RESUMED));
    });
    this.adEventManager_.listen(this.video_, 'pause', () => {
      // playRangeEnd in src= causes the ended event not to be fired when that
      // position is reached, instead pause event is fired.
      const currentConfig = this.player_.getConfiguration();
      if (this.video_.currentTime >= currentConfig.playRangeEnd) {
        complete();
        return;
      }
      this.onEvent_(
          new shaka.util.FakeEvent(shaka.ads.Utils.AD_PAUSED));
    });
    this.adEventManager_.listen(this.video_, 'volumechange', () => {
      if (this.video_.muted) {
        this.onEvent_(
            new shaka.util.FakeEvent(shaka.ads.Utils.AD_MUTED));
      } else {
        this.onEvent_(
            new shaka.util.FakeEvent(shaka.ads.Utils.AD_VOLUME_CHANGED));
      }
    });
    try {
      this.updatePlayerConfig_();
      if (interstitial.startTime && interstitial.endTime &&
          interstitial.endTime != Infinity &&
          interstitial.startTime != interstitial.endTime) {
        const duration = interstitial.endTime - interstitial.startTime;
        if (duration > 0) {
          this.player_.configure('playRangeEnd', duration);
        }
      }
      if (interstitial.playoutLimit && !this.playoutLimitTimer_) {
        this.playoutLimitTimer_ = new shaka.util.Timer(() => {
          this.lastOnSkip_();
        }).tickAfter(interstitial.playoutLimit);
        this.player_.configure('playRangeEnd', interstitial.playoutLimit);
      }
      await this.player_.attach(this.video_);
      let playerStartTime = null;
      if (this.config_.allowStartInMiddleOfInterstitial &&
          this.lastTime_ != null) {
        const newPosition = this.lastTime_ - interstitial.startTime;
        if (Math.abs(newPosition) > 0.25) {
          playerStartTime = newPosition;
        }
      }
      if (this.preloadManagerInterstitials_.has(interstitial)) {
        const preloadManager =
            await this.preloadManagerInterstitials_.get(interstitial);
        this.preloadManagerInterstitials_.delete(interstitial);
        if (preloadManager) {
          await this.player_.load(preloadManager);
        } else {
          await this.player_.load(
              interstitial.uri,
              playerStartTime,
              interstitial.mimeType || undefined);
        }
      } else {
        await this.player_.load(
            interstitial.uri,
            playerStartTime,
            interstitial.mimeType || undefined);
      }
      this.video_.play();
      const loadTime = (Date.now() - startTime) / 1000;
      this.onEvent_(new shaka.util.FakeEvent(shaka.ads.Utils.ADS_LOADED,
          (new Map()).set('loadTime', loadTime)));
      if (this.usingBaseVideo_) {
        this.baseVideo_.play();
      }
      if (interstitial.overlay) {
        const setPosition = () => {
          const newPosition =
              this.baseVideo_.currentTime - interstitial.startTime;
          if (Math.abs(newPosition - this.video_.currentTime) > 0.1) {
            this.video_.currentTime = newPosition;
          }
        };
        this.adEventManager_.listenOnce(this.video_, 'playing', setPosition);
        this.adEventManager_.listen(this.baseVideo_, 'seeking', setPosition);
        this.adEventManager_.listen(this.baseVideo_, 'seeked', () => {
          const currentTime = this.baseVideo_.currentTime;
          if (currentTime < interstitial.startTime ||
              (interstitial.endTime && currentTime > interstitial.endTime)) {
            this.lastOnSkip_();
          }
        });
      }
    } catch (e) {
      if (!this.playingAd_) {
        return;
      }
      error(e);
    }
  }
@param {shaka.extern.AdInterstitial} interstitial
@param {number} sequenceLength
@param {number} adPosition
@param {number} initialTime the clock time the ad started at
@param {number} oncePlayed
@private