createTimeline_(streamIndex, timescale, duration) {
    goog.asserts.assert(
        timescale > 0 && timescale < Infinity,
        'timescale must be a positive, finite integer');
    goog.asserts.assert(
        duration > 0, 'duration must be a positive integer');

    const TXml = shaka.util.TXml;

    const timePoints = TXml.findChildren(streamIndex, 'c');

    /** @type {!Array<shaka.mss.MssParser.TimeRange>} */
    const timeline = [];
    let lastEndTime = 0;

    for (let i = 0; i < timePoints.length; ++i) {
      const timePoint = timePoints[i];
      const next = timePoints[i + 1];
      const t =
          TXml.parseAttr(timePoint, 't', TXml.parseNonNegativeInt);
      const d =
          TXml.parseAttr(timePoint, 'd', TXml.parseNonNegativeInt);
      const r = TXml.parseAttr(timePoint, 'r', TXml.parseInt);

      if (!d) {
        shaka.log.warning(
            '"c" element must have a duration:',
            'ignoring the remaining "c" elements.', timePoint);
        return timeline;
      }

      let startTime = t != null ? t : lastEndTime;

      let repeat = r || 0;
      // Unlike in DASH, in MSS r does not start counting repetitions at 0 but
      // at 1, to maintain the code equivalent to DASH if r exists we
      // subtract 1.
      if (repeat) {
        repeat--;
      }
      if (repeat < 0) {
        if (next) {
          const nextStartTime =
              TXml.parseAttr(next, 't', TXml.parseNonNegativeInt);
          if (nextStartTime == null) {
            shaka.log.warning(
                'An "c" element cannot have a negative repeat',
                'if the next "c" element does not have a valid start time:',
                'ignoring the remaining "c" elements.', timePoint);
            return timeline;
          } else if (startTime >= nextStartTime) {
            shaka.log.warning(
                'An "c" element cannot have a negative repeat if its start ',
                'time exceeds the next "c" element\'s start time:',
                'ignoring the remaining "c" elements.', timePoint);
            return timeline;
          }
          repeat = Math.ceil((nextStartTime - startTime) / d) - 1;
        } else {
          if (duration == Infinity) {
            // The MSS spec. actually allows the last "c" element to have a
            // negative repeat value even when it has an infinite
            // duration.  No one uses this feature and no one ever should,
            // ever.
            shaka.log.warning(
                'The last "c" element cannot have a negative repeat',
                'if the Period has an infinite duration:',
                'ignoring the last "c" element.', timePoint);
            return timeline;
          } else if (startTime / timescale >= duration) {
            shaka.log.warning(
                'The last "c" element cannot have a negative repeat',
                'if its start time exceeds the duration:',
                'ignoring the last "c" element.', timePoint);
            return timeline;
          }
          repeat = Math.ceil((duration * timescale - startTime) / d) - 1;
        }
      }

      for (let j = 0; j <= repeat; ++j) {
        const endTime = startTime + d;
        const item = {
          start: startTime / timescale,
          end: endTime / timescale,
          unscaledStart: startTime,
        };
        timeline.push(item);

        startTime = endTime;
        lastEndTime = endTime;
      }
    }

    return timeline;
  }
Expands a streamIndex into an array-based timeline.  The results are in
seconds.

@param {!shaka.extern.xml.Node} streamIndex
@param {number} timescale
@param {number} duration The duration in seconds.
@return {!Array<shaka.mss.MssParser.TimeRange>}
@private