async processManifest_(mpd, finalManifestUri) {
    const TXml = shaka.util.TXml;

    goog.asserts.assert(this.config_,
        'Must call configure() before processManifest_()!');

    if (this.contentSteeringManager_) {
      this.contentSteeringManager_.clearPreviousLocations();
    }

    // Get any Location elements.  This will update the manifest location and
    // the base URI.
    /** @type {!Array<string>} */
    let manifestBaseUris = [finalManifestUri];
    /** @type {!Array<string>} */
    const locations = [];
    /** @type {!Map<string, string>} */
    const locationsMapping = new Map();
    const locationsObjs = TXml.findChildren(mpd, 'Location');
    for (const locationsObj of locationsObjs) {
      const serviceLocation = locationsObj.attributes['serviceLocation'];
      const uri = TXml.getContents(locationsObj);
      if (!uri) {
        continue;
      }
      const finalUri = shaka.util.ManifestParserUtils.resolveUris(
          manifestBaseUris, [uri])[0];
      if (serviceLocation) {
        if (this.contentSteeringManager_) {
          this.contentSteeringManager_.addLocation(
              'Location', serviceLocation, finalUri);
        } else {
          locationsMapping.set(serviceLocation, finalUri);
        }
      }
      locations.push(finalUri);
    }
    if (this.contentSteeringManager_) {
      const steeringLocations = this.contentSteeringManager_.getLocations(
          'Location', /* ignoreBaseUrls= */ true);
      if (steeringLocations.length > 0) {
        this.manifestUris_ = steeringLocations;
        manifestBaseUris = steeringLocations;
      }
    } else if (locations.length) {
      this.manifestUris_ = locations;
      manifestBaseUris = locations;
    }

    this.manifestPatchContext_.mpdId = mpd.attributes['id'] || '';
    this.manifestPatchContext_.publishTime =
        TXml.parseAttr(mpd, 'publishTime', TXml.parseDate) || 0;
    this.patchLocationNodes_ = TXml.findChildren(mpd, 'PatchLocation');

    let contentSteeringPromise = Promise.resolve();

    const contentSteering = TXml.findChild(mpd, 'ContentSteering');
    if (contentSteering && this.playerInterface_) {
      const defaultPathwayId =
          contentSteering.attributes['defaultServiceLocation'];
      if (!this.contentSteeringManager_) {
        this.contentSteeringManager_ =
            new shaka.util.ContentSteeringManager(this.playerInterface_);
        this.contentSteeringManager_.configure(this.config_);
        this.contentSteeringManager_.setManifestType(
            shaka.media.ManifestParser.DASH);
        this.contentSteeringManager_.setBaseUris(manifestBaseUris);
        this.contentSteeringManager_.setDefaultPathwayId(defaultPathwayId);
        const uri = TXml.getContents(contentSteering);
        if (uri) {
          const queryBeforeStart =
            TXml.parseAttr(contentSteering, 'queryBeforeStart',
                TXml.parseBoolean, /* defaultValue= */ false);
          if (queryBeforeStart) {
            contentSteeringPromise =
                this.contentSteeringManager_.requestInfo(uri);
          } else {
            this.contentSteeringManager_.requestInfo(uri);
          }
        }
      } else {
        this.contentSteeringManager_.setBaseUris(manifestBaseUris);
        this.contentSteeringManager_.setDefaultPathwayId(defaultPathwayId);
      }
      for (const serviceLocation of locationsMapping.keys()) {
        const uri = locationsMapping.get(serviceLocation);
        this.contentSteeringManager_.addLocation(
            'Location', serviceLocation, uri);
      }
    }

    const uriObjs = TXml.findChildren(mpd, 'BaseURL');
    let someLocationValid = false;
    if (this.contentSteeringManager_) {
      for (const uriObj of uriObjs) {
        const serviceLocation = uriObj.attributes['serviceLocation'];
        const uri = TXml.getContents(uriObj);
        if (serviceLocation && uri) {
          this.contentSteeringManager_.addLocation(
              'BaseURL', serviceLocation, uri);
          someLocationValid = true;
        }
      }
    }
    this.lastCalculatedBaseUris_ = null;
    if (!someLocationValid || !this.contentSteeringManager_) {
      const uris = uriObjs.map(TXml.getContents);
      this.lastCalculatedBaseUris_ = shaka.util.ManifestParserUtils.resolveUris(
          manifestBaseUris, uris);
    }

    const getBaseUris = () => {
      if (this.contentSteeringManager_ && someLocationValid) {
        return this.contentSteeringManager_.getLocations('BaseURL');
      }
      if (this.lastCalculatedBaseUris_) {
        return this.lastCalculatedBaseUris_;
      }
      return [];
    };

    this.manifestPatchContext_.getBaseUris = getBaseUris;

    let availabilityTimeOffset = 0;
    if (uriObjs && uriObjs.length) {
      availabilityTimeOffset = TXml.parseAttr(uriObjs[0],
          'availabilityTimeOffset', TXml.parseFloat) || 0;
    }
    this.manifestPatchContext_.availabilityTimeOffset = availabilityTimeOffset;

    this.updatePeriod_ = /** @type {number} */ (TXml.parseAttr(
        mpd, 'minimumUpdatePeriod', TXml.parseDuration, -1));

    const presentationStartTime = TXml.parseAttr(
        mpd, 'availabilityStartTime', TXml.parseDate);
    let segmentAvailabilityDuration = TXml.parseAttr(
        mpd, 'timeShiftBufferDepth', TXml.parseDuration);

    const ignoreSuggestedPresentationDelay =
      this.config_.dash.ignoreSuggestedPresentationDelay;
    let suggestedPresentationDelay = null;
    if (!ignoreSuggestedPresentationDelay) {
      suggestedPresentationDelay = TXml.parseAttr(
          mpd, 'suggestedPresentationDelay', TXml.parseDuration);
    }

    const ignoreMaxSegmentDuration =
        this.config_.dash.ignoreMaxSegmentDuration;
    let maxSegmentDuration = null;
    if (!ignoreMaxSegmentDuration) {
      maxSegmentDuration = TXml.parseAttr(
          mpd, 'maxSegmentDuration', TXml.parseDuration);
    }
    const mpdType = mpd.attributes['type'] || 'static';

    if (this.manifest_ && this.manifest_.presentationTimeline) {
      this.isTransitionFromDynamicToStatic_ =
          this.manifest_.presentationTimeline.isLive() && mpdType == 'static';
    }

    this.manifestPatchContext_.type = mpdType;
    /** @type {!shaka.media.PresentationTimeline} */
    let presentationTimeline;
    if (this.manifest_) {
      presentationTimeline = this.manifest_.presentationTimeline;

      // Before processing an update, evict from all segment indexes.  Some of
      // them may not get updated otherwise if their corresponding Period
      // element has been dropped from the manifest since the last update.
      // Without this, playback will still work, but this is necessary to
      // maintain conditions that we assert on for multi-Period content.
      // This gives us confidence that our state is maintained correctly, and
      // that the complex logic of multi-Period eviction and period-flattening
      // is correct.  See also:
      // https://github.com/shaka-project/shaka-player/issues/3169#issuecomment-823580634
      const availabilityStart =
          presentationTimeline.getSegmentAvailabilityStart();
      for (const stream of this.streamMap_.values()) {
        if (stream.segmentIndex) {
          stream.segmentIndex.evict(availabilityStart);
        }
      }
    } else {
      const ignoreMinBufferTime = this.config_.dash.ignoreMinBufferTime;
      let minBufferTime = 0;
      if (!ignoreMinBufferTime) {
        minBufferTime =
            TXml.parseAttr(mpd, 'minBufferTime', TXml.parseDuration) || 0;
      }
      // DASH IOP v3.0 suggests using a default delay between minBufferTime
      // and timeShiftBufferDepth.  This is literally the range of all
      // feasible choices for the value.  Nothing older than
      // timeShiftBufferDepth is still available, and anything less than
      // minBufferTime will cause buffering issues.
      let delay = 0;
      if (suggestedPresentationDelay != null) {
        // 1. If a suggestedPresentationDelay is provided by the manifest, that
        // will be used preferentially.
        // This is given a minimum bound of segmentAvailabilityDuration.
        // Content providers should provide a suggestedPresentationDelay
        // whenever possible to optimize the live streaming experience.
        delay = Math.min(
            suggestedPresentationDelay,
            segmentAvailabilityDuration || Infinity);
      } else if (this.config_.defaultPresentationDelay > 0) {
        // 2. If the developer provides a value for
        // "manifest.defaultPresentationDelay", that is used as a fallback.
        delay = this.config_.defaultPresentationDelay;
      } else {
        // 3. Otherwise, we default to the lower of segmentAvailabilityDuration
        // and 1.5 * minBufferTime. This is fairly conservative.
        delay = Math.min(
            minBufferTime * 1.5, segmentAvailabilityDuration || Infinity);
      }
      presentationTimeline = new shaka.media.PresentationTimeline(
          presentationStartTime, delay, this.config_.dash.autoCorrectDrift);
    }

    presentationTimeline.setStatic(mpdType == 'static');

    const isLive = presentationTimeline.isLive();

    // If it's live, we check for an override.
    if (isLive && !isNaN(this.config_.availabilityWindowOverride)) {
      segmentAvailabilityDuration = this.config_.availabilityWindowOverride;
    }

    // If it's null, that means segments are always available.  This is always
    // the case for VOD, and sometimes the case for live.
    if (segmentAvailabilityDuration == null) {
      segmentAvailabilityDuration = Infinity;
    }

    presentationTimeline.setSegmentAvailabilityDuration(
        segmentAvailabilityDuration);

    const profiles = mpd.attributes['profiles'] || '';
    this.manifestPatchContext_.profiles = profiles.split(',');

    /** @type {shaka.dash.DashParser.Context} */
    const context = {
      // Don't base on updatePeriod_ since emsg boxes can cause manifest
      // updates.
      dynamic: mpdType != 'static',
      presentationTimeline: presentationTimeline,
      period: null,
      periodInfo: null,
      adaptationSet: null,
      representation: null,
      bandwidth: 0,
      indexRangeWarningGiven: false,
      availabilityTimeOffset: availabilityTimeOffset,
      mediaPresentationDuration: null,
      profiles: profiles.split(','),
      roles: null,
      urlParams: () => '',
    };

    await contentSteeringPromise;

    this.gapCount_ = 0;
    const periodsAndDuration = this.parsePeriods_(
        context, getBaseUris, mpd, /* newPeriod= */ false);
    const duration = periodsAndDuration.duration;
    const periods = periodsAndDuration.periods;

    if ((mpdType == 'static' && !this.isTransitionFromDynamicToStatic_) ||
        !periodsAndDuration.durationDerivedFromPeriods) {
      // Ignore duration calculated from Period lengths if this is dynamic.
      presentationTimeline.setDuration(duration || Infinity);
    }

    if (this.isLowLatency_ && this.lowLatencyMode_) {
      presentationTimeline.setAvailabilityTimeOffset(
          this.minTotalAvailabilityTimeOffset_);
    }

    // Use @maxSegmentDuration to override smaller, derived values.
    presentationTimeline.notifyMaxSegmentDuration(maxSegmentDuration || 1);
    if (goog.DEBUG && !this.isTransitionFromDynamicToStatic_) {
      presentationTimeline.assertIsValid();
    }

    if (this.isLowLatency_ && this.lowLatencyMode_) {
      const presentationDelay = suggestedPresentationDelay != null ?
          suggestedPresentationDelay : this.config_.defaultPresentationDelay;
      presentationTimeline.setDelay(presentationDelay);
    }

    // These steps are not done on manifest update.
    if (!this.manifest_) {
      await this.periodCombiner_.combinePeriods(periods, context.dynamic);

      this.manifest_ = {
        presentationTimeline: presentationTimeline,
        variants: this.periodCombiner_.getVariants(),
        textStreams: this.periodCombiner_.getTextStreams(),
        imageStreams: this.periodCombiner_.getImageStreams(),
        offlineSessionIds: [],
        sequenceMode: this.config_.dash.sequenceMode,
        ignoreManifestTimestampsInSegmentsMode: false,
        type: shaka.media.ManifestParser.DASH,
        serviceDescription: this.parseServiceDescription_(mpd),
        nextUrl: this.parseMpdChaining_(mpd),
        periodCount: periods.length,
        gapCount: this.gapCount_,
        isLowLatency: this.isLowLatency_,
        startTime: null,
      };

      // We only need to do clock sync when we're using presentation start
      // time. This condition also excludes VOD streams.
      if (presentationTimeline.usingPresentationStartTime()) {
        const TXml = shaka.util.TXml;
        const timingElements = TXml.findChildren(mpd, 'UTCTiming');
        const offset = await this.parseUtcTiming_(getBaseUris, timingElements);
        // Detect calls to stop().
        if (!this.playerInterface_) {
          return;
        }
        presentationTimeline.setClockOffset(offset);
      }

      // This is the first point where we have a meaningful presentation start
      // time, and we need to tell PresentationTimeline that so that it can
      // maintain consistency from here on.
      presentationTimeline.lockStartTime();

      if (this.periodCombiner_ &&
          !this.manifest_.presentationTimeline.isLive()) {
        this.periodCombiner_.release();
      }
    } else {
      this.manifest_.periodCount = periods.length;
      this.manifest_.gapCount = this.gapCount_;
      await this.postPeriodProcessing_(periods, /* isPatchUpdate= */ false);
    }

    // Add text streams to correspond to closed captions.  This happens right
    // after period combining, while we still have a direct reference, so that
    // any new streams will appear in the period combiner.
    this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);

    this.cleanStreamMap_();
    this.cleanContinuityCache_(periods);
  }
Takes a formatted MPD and converts it into a manifest.

@param {!shaka.extern.xml.Node} mpd
@param {string} finalManifestUri The final manifest URI, which may
  differ from this.manifestUri_ if there has been a redirect.
@return {!Promise}
@private