processDateRangeTags_(tags, contentType, variables, getUris) {
    const ContentType = shaka.util.ManifestParserUtils.ContentType;
    if (contentType != ContentType.VIDEO && contentType != ContentType.AUDIO) {
      // DATE-RANGE should only appear in AUDIO or VIDEO playlists.
      // We ignore those that appear in other playlists.
      return;
    }
    const Utils = shaka.hls.Utils;
    const initialProgramDateTime =
        this.presentationTimeline_.getInitialProgramDateTime();
    if (!initialProgramDateTime ||
        this.ignoreManifestProgramDateTimeFor_(contentType)) {
      return;
    }
    let dateRangeTags =
        shaka.hls.Utils.filterTagsByName(tags, 'EXT-X-DATERANGE');
    dateRangeTags = dateRangeTags.filter((tag) => {
      return tag.getAttribute('START-DATE') != null;
    }).sort((a, b) => {
      const aStartDateValue = a.getRequiredAttrValue('START-DATE');
      const bStartDateValue = b.getRequiredAttrValue('START-DATE');
      if (aStartDateValue < bStartDateValue) {
        return -1;
      }
      if (aStartDateValue > bStartDateValue) {
        return 1;
      }
      return 0;
    });
    for (let i = 0; i < dateRangeTags.length; i++) {
      const tag = dateRangeTags[i];
      try {
        const startDateValue = tag.getRequiredAttrValue('START-DATE');
        const startDate = shaka.util.TXml.parseDate(startDateValue);
        if (isNaN(startDate)) {
          // Invalid START-DATE
          continue;
        }
        goog.asserts.assert(startDate != null,
            'Start date should not be null!');
        const startTime = Math.max(0, startDate - initialProgramDateTime);

        let endTime = null;
        const endDateValue = tag.getAttributeValue('END-DATE');
        if (endDateValue) {
          const endDate = shaka.util.TXml.parseDate(endDateValue);
          if (!isNaN(endDate)) {
            goog.asserts.assert(endDate != null,
                'End date should not be null!');
            endTime = endDate - initialProgramDateTime;
            if (endTime < 0) {
              // Date range in the past
              continue;
            }
          }
        }
        if (endTime == null) {
          const durationValue = tag.getAttributeValue('DURATION') ||
              tag.getAttributeValue('PLANNED-DURATION');
          if (durationValue) {
            const duration = parseFloat(durationValue);
            if (!isNaN(duration)) {
              endTime = startTime + duration;
            }
            const realEndTime = startDate - initialProgramDateTime + duration;
            if (realEndTime < 0) {
              // Date range in the past
              continue;
            }
          }
        }
        const type =
            tag.getAttributeValue('CLASS') || 'com.apple.quicktime.HLS';

        const endOnNext = tag.getAttributeValue('END-ON-NEXT') == 'YES';
        if (endTime == null && endOnNext) {
          for (let j = i + 1; j < dateRangeTags.length; j++) {
            const otherDateRangeType =
                dateRangeTags[j].getAttributeValue('CLASS') ||
                'com.apple.quicktime.HLS';
            if (type != otherDateRangeType) {
              continue;
            }
            const otherDateRangeStartDateValue =
                dateRangeTags[j].getRequiredAttrValue('START-DATE');
            const otherDateRangeStartDate =
                shaka.util.TXml.parseDate(otherDateRangeStartDateValue);
            if (isNaN(otherDateRangeStartDate)) {
              // Invalid START-DATE
              continue;
            }
            if (otherDateRangeStartDate &&
                otherDateRangeStartDate > startDate) {
              endTime = Math.max(0,
                  otherDateRangeStartDate - initialProgramDateTime);
              break;
            }
          }
          if (endTime == null) {
            // Since we cannot know when it ends, we omit it for now and in the
            // future with an update we will be able to have more information.
            continue;
          }
        }

        // Exclude these attributes from the metadata since they already go into
        // other fields (eg: startTime or endTime) or are not necessary..
        const excludedAttributes = [
          'CLASS',
          'START-DATE',
          'END-DATE',
          'DURATION',
          'END-ON-NEXT',
        ];

        /* @type {!Array<shaka.extern.MetadataFrame>} */
        const values = [];
        for (const attribute of tag.attributes) {
          if (excludedAttributes.includes(attribute.name)) {
            continue;
          }
          let data = Utils.variableSubstitution(attribute.value, variables);
          if (attribute.name == 'X-ASSET-URI' ||
              attribute.name == 'X-ASSET-LIST') {
            data = Utils.constructSegmentUris(
                getUris(), attribute.value, variables)[0];
          }
          const metadataFrame = {
            key: attribute.name,
            description: '',
            data,
            mimeType: null,
            pictureType: null,
          };
          values.push(metadataFrame);
        }

        // ID is always required. So we need more than 1 value.
        if (values.length > 1) {
          this.playerInterface_.onMetadata(type, startTime, endTime, values);
        }
      } catch (e) {
        shaka.log.warning('Ignoring DATERANGE with errors', tag.toString());
      }
    }
  }
@param {!Array<!shaka.hls.Tag>} tags
@param {string} contentType
@param {!Map<string, string>} variables
@param {function(): !Array<string>} getUris
@private