async onUpdate_(mediaState) {
    this.destroyer_.ensureNotDestroyed();

    const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);

    // Sanity check.
    goog.asserts.assert(
        !mediaState.performingUpdate && (mediaState.updateTimer != null),
        logPrefix + ' unexpected call to onUpdate_()');
    if (mediaState.performingUpdate || (mediaState.updateTimer == null)) {
      return;
    }

    goog.asserts.assert(
        !mediaState.clearingBuffer, logPrefix +
        ' onUpdate_() should not be called when clearing the buffer');
    if (mediaState.clearingBuffer) {
      return;
    }

    mediaState.updateTimer = null;

    // Handle pending buffer clears.
    if (mediaState.waitingToClearBuffer) {
      // Note: clearBuffer_() will schedule the next update.
      shaka.log.debug(logPrefix, 'skipping update and clearing the buffer');
      await this.clearBuffer_(
          mediaState, mediaState.waitingToFlushBuffer,
          mediaState.clearBufferSafeMargin);
      return;
    }

    // If stream switches happened during the previous update_() for this
    // content type, close out the old streams that were switched away from.
    // Even if we had switched away from the active stream 'A' during the
    // update_(), e.g. (A -> B -> A), closing 'A' is permissible here since we
    // will immediately re-create it in the logic below.
    this.handleDeferredCloseSegmentIndexes_(mediaState);

    // Make sure the segment index exists. If not, create the segment index.
    if (!mediaState.stream.segmentIndex) {
      const thisStream = mediaState.stream;

      try {
        await mediaState.stream.createSegmentIndex();
      } catch (error) {
        await this.handleStreamingError_(mediaState, error);
        return;
      }

      if (thisStream != mediaState.stream) {
        // We switched streams while in the middle of this async call to
        // createSegmentIndex.  Abandon this update and schedule a new one if
        // there's not already one pending.
        // Releases the segmentIndex of the old stream.
        if (thisStream.closeSegmentIndex) {
          goog.asserts.assert(!mediaState.stream.segmentIndex,
              'mediaState.stream should not have segmentIndex yet.');
          thisStream.closeSegmentIndex();
        }
        if (!mediaState.performingUpdate && !mediaState.updateTimer) {
          this.scheduleUpdate_(mediaState, 0);
        }
        return;
      }
    }

    // Update the MediaState.
    try {
      const delay = this.update_(mediaState);
      if (delay != null) {
        this.scheduleUpdate_(mediaState, delay);
        mediaState.hasError = false;
      }
    } catch (error) {
      await this.handleStreamingError_(mediaState, error);
      return;
    }

    const mediaStates = Array.from(this.mediaStates_.values());

    // Check if we've buffered to the end of the presentation.  We delay adding
    // the audio and video media states, so it is possible for the text stream
    // to be the only state and buffer to the end.  So we need to wait until we
    // have completed startup to determine if we have reached the end.
    if (this.startupComplete_ &&
        mediaStates.every((ms) => ms.endOfStream)) {
      shaka.log.v1(logPrefix, 'calling endOfStream()...');
      await this.playerInterface_.mediaSourceEngine.endOfStream();
      this.destroyer_.ensureNotDestroyed();

      // If the media segments don't reach the end, then we need to update the
      // timeline duration to match the final media duration to avoid
      // buffering forever at the end.
      // We should only do this if the duration needs to shrink.
      // Growing it by less than 1ms can actually cause buffering on
      // replay, as in https://github.com/shaka-project/shaka-player/issues/979
      // On some platforms, this can spuriously be 0, so ignore this case.
      // https://github.com/shaka-project/shaka-player/issues/1967,
      const duration = this.playerInterface_.mediaSourceEngine.getDuration();
      if (duration != 0 &&
          duration < this.manifest_.presentationTimeline.getDuration()) {
        this.manifest_.presentationTimeline.setDuration(duration);
      }
    }
  }
Called when |mediaState|'s update timer has expired.

@param {!shaka.media.StreamingEngine.MediaState_} mediaState
@suppress {suspiciousCode} The compiler assumes that updateTimer can't
  change during the await, and so complains about the null check.
@private