async parseManifest_(data) {
    const Utils = shaka.hls.Utils;
    const ContentType = shaka.util.ManifestParserUtils.ContentType;

    goog.asserts.assert(this.masterPlaylistUri_,
        'Master playlist URI must be set before calling parseManifest_!');

    const playlist = this.manifestTextParser_.parsePlaylist(data);

    /** @type {!Array<!shaka.hls.Tag>} */
    const variablesTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-DEFINE');

    /** @type {!Array<!shaka.extern.Variant>} */
    let variants = [];
    /** @type {!Array<!shaka.extern.Stream>} */
    let textStreams = [];
    /** @type {!Array<!shaka.extern.Stream>} */
    let imageStreams = [];

    // This assert is our own sanity check.
    goog.asserts.assert(this.presentationTimeline_ == null,
        'Presentation timeline created early!');
    // We don't know if the presentation is VOD or live until we parse at least
    // one media playlist, so make a VOD-style presentation timeline for now
    // and change the type later if we discover this is live.
    // Since the player will load the first variant chosen early in the process,
    // there isn't a window during playback where the live-ness is unknown.
    this.presentationTimeline_ = new shaka.media.PresentationTimeline(
        /* presentationStartTime= */ null, /* delay= */ 0);
    this.presentationTimeline_.setStatic(true);

    const getUris = () => {
      return [this.masterPlaylistUri_];
    };

    /** @type {?string} */
    let mediaPlaylistType = null;

    /** @type {!Map<string, string>} */
    let mediaVariables = new Map();

    // Parsing a media playlist results in a single-variant stream.
    if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
      this.needsClosedCaptionsDetection_ = false;

      /** @type {!Array<!shaka.hls.Tag>} */
      const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
          'EXT-X-DEFINE');

      mediaVariables = this.parseMediaVariables_(
          variablesTags, this.masterPlaylistUri_);

      // By default we assume it is video, but in a later step the correct type
      // is obtained.
      mediaPlaylistType = ContentType.VIDEO;

      // These values can be obtained later so these default values are good.
      const codecs = '';
      const languageValue = '';
      const channelsCount = null;
      const sampleRate = null;
      const closedCaptions = new Map();
      const spatialAudio = false;
      const characteristics = null;
      const forced = false; // Only relevant for text.
      const primary = true; // This is the only stream!
      const name = 'Media Playlist';

      // Make the stream info, with those values.
      const streamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
          this.globalId_++, mediaVariables, playlist, getUris, codecs,
          mediaPlaylistType, languageValue, primary, name, channelsCount,
          closedCaptions, characteristics, forced, sampleRate, spatialAudio);
      this.uriToStreamInfosMap_.set(this.masterPlaylistUri_, streamInfo);


      if (streamInfo.stream) {
        const qualityInfo =
            shaka.media.QualityObserver.createQualityInfo(streamInfo.stream);
        streamInfo.stream.segmentIndex.forEachTopLevelReference(
            (reference) => {
              if (reference.initSegmentReference) {
                reference.initSegmentReference.mediaQuality = qualityInfo;
              }
            });
      }

      mediaPlaylistType = streamInfo.stream.type;

      // Wrap the stream from that stream info with a variant.
      let variantAllowed = true;
      if (this.config_.disableAudio && streamInfo.type == 'audio') {
        variantAllowed = false;
      } else if (this.config_.disableVideo && streamInfo.type == 'video' &&
          !streamInfo.stream.codecs.includes(',')) {
        variantAllowed = false;
      }
      if (variantAllowed) {
        variants.push({
          id: 0,
          language: this.getLanguage_(languageValue),
          disabledUntilTime: 0,
          primary: true,
          audio: streamInfo.type == 'audio' ? streamInfo.stream : null,
          video: streamInfo.type == 'video' ? streamInfo.stream : null,
          bandwidth: streamInfo.stream.bandwidth || 0,
          allowedByApplication: true,
          allowedByKeySystem: true,
          decodingInfos: [],
        });
      }
    } else {
      this.parseMasterVariables_(variablesTags);

      /** @type {!Array<!shaka.hls.Tag>} */
      const mediaTags = Utils.filterTagsByName(
          playlist.tags, 'EXT-X-MEDIA');
      /** @type {!Array<!shaka.hls.Tag>} */
      const variantTags = Utils.filterTagsByName(
          playlist.tags, 'EXT-X-STREAM-INF');
      /** @type {!Array<!shaka.hls.Tag>} */
      const imageTags = Utils.filterTagsByName(
          playlist.tags, 'EXT-X-IMAGE-STREAM-INF');
      /** @type {!Array<!shaka.hls.Tag>} */
      const iFrameTags = Utils.filterTagsByName(
          playlist.tags, 'EXT-X-I-FRAME-STREAM-INF');
      /** @type {!Array<!shaka.hls.Tag>} */
      const sessionKeyTags = Utils.filterTagsByName(
          playlist.tags, 'EXT-X-SESSION-KEY');
      /** @type {!Array<!shaka.hls.Tag>} */
      const sessionDataTags = Utils.filterTagsByName(
          playlist.tags, 'EXT-X-SESSION-DATA');
      /** @type {!Array<!shaka.hls.Tag>} */
      const contentSteeringTags = Utils.filterTagsByName(
          playlist.tags, 'EXT-X-CONTENT-STEERING');

      this.processSessionData_(sessionDataTags);
      await this.processContentSteering_(contentSteeringTags);

      if (!this.config_.ignoreSupplementalCodecs) {
        // Duplicate variant tags with supplementalCodecs
        const newVariantTags = [];
        for (const tag of variantTags) {
          const supplementalCodecsString =
              tag.getAttributeValue('SUPPLEMENTAL-CODECS');
          if (!supplementalCodecsString) {
            continue;
          }
          const supplementalCodecs = supplementalCodecsString.split(/\s*,\s*/)
              .map((codec) => {
                return codec.split('/')[0];
              });
          const newAttributes = tag.attributes.map((attr) => {
            const name = attr.name;
            let value = attr.value;
            if (name == 'CODECS') {
              value = supplementalCodecs.join(',');
              const allCodecs = attr.value.split(',');
              if (allCodecs.length > 1) {
                const audioCodec =
                    shaka.util.ManifestParserUtils.guessCodecsSafe(
                        shaka.util.ManifestParserUtils.ContentType.AUDIO,
                        allCodecs);
                if (audioCodec) {
                  value += ',' + audioCodec;
                }
              }
            }
            return new shaka.hls.Attribute(name, value);
          });
          newVariantTags.push(
              new shaka.hls.Tag(tag.id, tag.name, newAttributes, null));
        }
        variantTags.push(...newVariantTags);

        // Duplicate iFrame tags with supplementalCodecs
        const newIFrameTags = [];
        for (const tag of iFrameTags) {
          const supplementalCodecsString =
              tag.getAttributeValue('SUPPLEMENTAL-CODECS');
          if (!supplementalCodecsString) {
            continue;
          }
          const supplementalCodecs = supplementalCodecsString.split(/\s*,\s*/)
              .map((codec) => {
                return codec.split('/')[0];
              });
          const newAttributes = tag.attributes.map((attr) => {
            const name = attr.name;
            let value = attr.value;
            if (name == 'CODECS') {
              value = supplementalCodecs.join(',');
            }
            return new shaka.hls.Attribute(name, value);
          });
          newIFrameTags.push(
              new shaka.hls.Tag(tag.id, tag.name, newAttributes, null));
        }
        iFrameTags.push(...newIFrameTags);
      }

      this.parseCodecs_(variantTags);

      this.parseClosedCaptions_(mediaTags);
      const iFrameStreams = this.parseIFrames_(iFrameTags);
      variants = await this.createVariantsForTags_(
          variantTags, sessionKeyTags, mediaTags, getUris,
          this.globalVariables_, iFrameStreams);
      textStreams = this.parseTexts_(mediaTags);
      imageStreams = await this.parseImages_(imageTags, iFrameTags);
    }

    // Make sure that the parser has not been destroyed.
    if (!this.playerInterface_) {
      throw new shaka.util.Error(
          shaka.util.Error.Severity.CRITICAL,
          shaka.util.Error.Category.PLAYER,
          shaka.util.Error.Code.OPERATION_ABORTED);
    }

    this.determineStartTime_(playlist);

    // Single-variant streams aren't lazy-loaded, so for them we already have
    // enough info here to determine the presentation type and duration.
    if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
      if (this.isLive_()) {
        this.changePresentationTimelineToLive_(playlist);
        const delay = this.getUpdatePlaylistDelay_();
        this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
      }
      const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
      this.finalizeStreams_(streamInfos);
      this.determineDuration_();

      goog.asserts.assert(mediaPlaylistType,
          'mediaPlaylistType should be non-null');
      this.processDateRangeTags_(
          playlist.tags, mediaPlaylistType, mediaVariables, getUris);
    }

    this.manifest_ = {
      presentationTimeline: this.presentationTimeline_,
      variants,
      textStreams,
      imageStreams,
      offlineSessionIds: [],
      sequenceMode: this.config_.hls.sequenceMode,
      ignoreManifestTimestampsInSegmentsMode:
        this.config_.hls.ignoreManifestTimestampsInSegmentsMode,
      type: shaka.media.ManifestParser.HLS,
      serviceDescription: null,
      nextUrl: null,
      periodCount: 1,
      gapCount: 0,
      isLowLatency: false,
      startTime: this.startTime_,
    };

    // If there is no 'CODECS' attribute in the manifest and codec guessing is
    // disabled, we need to create the segment indexes now so that missing info
    // can be parsed from the media data and added to the stream objects.
    if (!this.codecInfoInManifest_ && this.config_.hls.disableCodecGuessing) {
      const createIndexes = [];
      for (const variant of this.manifest_.variants) {
        if (variant.audio && variant.audio.codecs === '') {
          createIndexes.push(variant.audio.createSegmentIndex());
        }
        if (variant.video && variant.video.codecs === '') {
          createIndexes.push(variant.video.createSegmentIndex());
        }
      }

      await Promise.all(createIndexes);
    }

    this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
  }
Parses the manifest.

@param {BufferSource} data
@return {!Promise}
@private