text: /** @license
   * Copyright 2016 Google LLC
   * SPDX-License-Identifier: Apache-2.0
   */
  
  describe('DashParser Live', () => {
    const Util = shaka.test.Util;
    const ManifestParser = shaka.test.ManifestParser;
  
    const oldNow = Date.now;
    const updateTime = 5;
    const originalUri = 'http://example.com/';
  
  
    /** @type {!shaka.test.FakeNetworkingEngine} */
    let fakeNetEngine;
    /** @type {!shaka.dash.DashParser} */
    let parser;
    /** @type {shaka.extern.ManifestParser.PlayerInterface} */
    let playerInterface;
  
    beforeEach(() => {
      fakeNetEngine = new shaka.test.FakeNetworkingEngine();
      parser = new shaka.dash.DashParser();
      parser.configure(shaka.util.PlayerConfiguration.createDefault().manifest);
      playerInterface = {
        networkingEngine: fakeNetEngine,
        filter: (manifest) => Promise.resolve(),
        onTimelineRegionAdded: fail,  // Should not have any EventStream elements.
        onEvent: fail,
        onError: fail,
      };
    });
  
    afterEach(() => {
      // Dash parser stop is synchronous.
      parser.stop();
      Date.now = oldNow;
    });
  
    /**
     * Trigger a manifest update.
     * @suppress {accessControls}
     */
    async function updateManifest() {
      if (parser.updateTimer_) {
        parser.updateTimer_.tickNow();
      }
      await Util.shortDelay();  // Allow update to complete.
    }
  
    /**
     * Gets a spy on the function that sets the update period.
     * @return {!jasmine.Spy}
     * @suppress {accessControls}
     */
    function updateTickSpy() {
      return spyOn(parser.updateTimer_, 'tickAfter');
    }
  
    /**
     * Makes a simple live manifest with the given representation contents.
     *
     * @param {!Array.<string>} lines
     * @param {number?} updateTime
     * @param {number=} duration
     * @return {string}
     */
    function makeSimpleLiveManifestText(lines, updateTime, duration) {
      const updateAttr = updateTime != null ?
          'minimumUpdatePeriod="PT' + updateTime + 'S"' : '';
      const durationAttr = duration != undefined ?
          'duration="PT' + duration + 'S"' : '';
      const template = [
        '<MPD type="dynamic" %(updateAttr)s',
        '    availabilityStartTime="1970-01-01T00:00:00Z">',
        '  <Period id="1" %(durationAttr)s>',
        '    <AdaptationSet mimeType="video/mp4">',
        '      <Representation id="3" bandwidth="500">',
        '        <BaseURL>http://example.com</BaseURL>',
        '%(contents)s',
        '      </Representation>',
        '    </AdaptationSet>',
        '  </Period>',
        '</MPD>',
      ].join('\n');
      const text = sprintf(template, {
        updateAttr: updateAttr,
        durationAttr: durationAttr,
        contents: lines.join('\n'),
        updateTime: updateTime,
      });
      return text;
    }
  
    /**
     * Make clones of a list of references so that they can be modified without
     * affecting the originals.
     *
     * @param {!Array.<!shaka.media.SegmentReference>} references
     * @return {!Array.<!shaka.media.SegmentReference>}
     */
    function cloneRefs(references) {
      return references.map((ref) => {
        return new shaka.media.SegmentReference(
            ref.startTime,
            ref.endTime,
            ref.getUris,
            ref.startByte,
            ref.endByte,
            ref.initSegmentReference,
            ref.timestampOffset,
            ref.appendWindowStart,
            ref.appendWindowEnd);
      });
    }
  
    /**
     * Creates tests that test the behavior common between SegmentList and
     * SegmentTemplate.
     *
     * @param {!Array.<string>} basicLines
     * @param {!Array.<!shaka.media.SegmentReference>} basicRefs
     * @param {!Array.<string>} updateLines
     * @param {!Array.<!shaka.media.SegmentReference>} updateRefs
     * @param {!Array.<string>} partialUpdateLines
     */
    function testCommonBehaviors(
        basicLines, basicRefs, updateLines, updateRefs, partialUpdateLines) {
      /**
       * Tests that an update will show the given references.
       *
       * @param {!Array.<string>} firstLines The Representation contents for the
       *   first manifest.
       * @param {!Array.<!shaka.media.SegmentReference>} firstReferences The media
       *   references for the first parse.
       * @param {!Array.<string>} secondLines The Representation contents for the
       *   updated manifest.
       * @param {!Array.<!shaka.media.SegmentReference>} secondReferences The
       *   media references for the updated manifest.
       */
      async function testBasicUpdate(
          firstLines, firstReferences, secondLines, secondReferences) {
        const firstManifest = makeSimpleLiveManifestText(firstLines, updateTime);
        const secondManifest =
            makeSimpleLiveManifestText(secondLines, updateTime);
  
        fakeNetEngine.setResponseText('dummy://foo', firstManifest);
        const manifest = await parser.start('dummy://foo', playerInterface);
        const stream = manifest.variants[0].video;
        await stream.createSegmentIndex();
        ManifestParser.verifySegmentIndex(stream, firstReferences);
  
        fakeNetEngine.setResponseText('dummy://foo', secondManifest);
        await updateManifest();
        ManifestParser.verifySegmentIndex(stream, secondReferences);
      }
  
      it('basic support', async () => {
        await testBasicUpdate(basicLines, basicRefs, updateLines, updateRefs);
      });
  
      it('new manifests don\'t need to include old references', async () => {
        await testBasicUpdate(
            basicLines, basicRefs, partialUpdateLines, updateRefs);
      });
  
      it('evicts old references for single-period live stream', async () => {
        const template = [
          '<MPD type="dynamic" minimumUpdatePeriod="PT%(updateTime)dS"',
          '    timeShiftBufferDepth="PT30S"',
          '    suggestedPresentationDelay="PT5S"',
          '    availabilityStartTime="1970-01-01T00:00:00Z">',
          '  <Period id="1">',
          '    <AdaptationSet mimeType="video/mp4">',
          '      <Representation id="3" bandwidth="500">',
          '        <BaseURL>http://example.com</BaseURL>',
          '%(contents)s',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '</MPD>',
        ].join('\n');
        const text = sprintf(
            template, {updateTime: updateTime, contents: basicLines.join('\n')});
  
        fakeNetEngine.setResponseText('dummy://foo', text);
        Date.now = () => 0;
        const manifest = await parser.start('dummy://foo', playerInterface);
  
        expect(manifest).toBeTruthy();
        const stream = manifest.variants[0].video;
        expect(stream).toBeTruthy();
  
        await stream.createSegmentIndex();
        expect(stream.segmentIndex).toBeTruthy();
        const firstPosition = stream.segmentIndex.find(0);
        ManifestParser.verifySegmentIndex(stream, basicRefs);
  
        // The 30 second availability window is initially full in all cases
        // (SegmentTemplate+Timeline, etc.)  The first segment is always 10
        // seconds long in all of these cases.  So 11 seconds after the
        // manifest was parsed, the first segment should have fallen out of
        // the availability window.
        Date.now = () => 11 * 1000;
        await updateManifest();
        // The first reference should have been evicted.
        expect(stream.segmentIndex.find(0)).toBe(firstPosition + 1);
        ManifestParser.verifySegmentIndex(stream, basicRefs.slice(1));
      });
  
      it('evicts old references for multi-period live stream', async () => {
        const template = [
          '<MPD type="dynamic" minimumUpdatePeriod="PT%(updateTime)dS"',
          '    timeShiftBufferDepth="PT60S"',
          '    suggestedPresentationDelay="PT5S"',
          '    availabilityStartTime="1970-01-01T00:00:00Z">',
          '  <Period id="1">',
          '    <AdaptationSet mimeType="video/mp4">',
          '      <Representation id="3" bandwidth="500">',
          '        <BaseURL>http://example.com</BaseURL>',
          '%(contents)s',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '  <Period id="2" start="PT%(pStart)dS">',
          '    <AdaptationSet mimeType="video/mp4">',
          '      <Representation id="4" bandwidth="500">',
          '        <BaseURL>http://example.com</BaseURL>',
          '%(contents)s',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '</MPD>',
        ].join('\n');
        // Set the period start to the sum of the durations of the references
        // in the previous period.
        const durs = basicRefs.map((r) => {
          return r.endTime - r.startTime;
        });
        const pStart = durs.reduce((p, d) => p + d, 0);
        const args = {
          updateTime: updateTime,
          pStart: pStart,
          contents: basicLines.join('\n'),
        };
        const text = sprintf(template, args);
  
        fakeNetEngine.setResponseText('dummy://foo', text);
        Date.now = () => 0;
        const manifest = await parser.start('dummy://foo', playerInterface);
  
        /** @const {!Array.<!shaka.media.SegmentReference>} */
        const period1Refs = cloneRefs(basicRefs);
        /** @const {!Array.<!shaka.media.SegmentReference>} */
        const period2Refs = cloneRefs(basicRefs);
        for (const ref of period2Refs) {
          ref.timestampOffset = pStart;
          ref.startTime += pStart;
          ref.endTime += pStart;
        }
        /** @const {!Array.<!shaka.media.SegmentReference>} */
        const allRefs = period1Refs.concat(period2Refs);
  
        const stream1 = manifest.variants[0].video;
        await stream1.createSegmentIndex();
        ManifestParser.verifySegmentIndex(stream1, allRefs);
  
        // The 60 second availability window is initially full in all cases
        // (SegmentTemplate+Timeline, etc.)  The first segment is always 10
        // seconds long in all of these cases.  So 11 seconds after the
        // manifest was parsed, the first segment should have fallen out of
        // the availability window.
        Date.now = () => 11 * 1000;
        await updateManifest();
        // The first reference should have been evicted.
        ManifestParser.verifySegmentIndex(stream1, allRefs.slice(1));
  
        // Same as above, but 1 period length later
        Date.now = () => (11 + pStart) * 1000;
        await updateManifest();
        ManifestParser.verifySegmentIndex(stream1, period2Refs.slice(1));
      });
  
      it('sets infinite duration for single-period live streams', async () => {
        const template = [
          '<MPD type="dynamic" minimumUpdatePeriod="PT%(updateTime)dS"',
          '    timeShiftBufferDepth="PT1S"',
          '    suggestedPresentationDelay="PT5S"',
          '    availabilityStartTime="1970-01-01T00:00:00Z">',
          '  <Period id="1">',
          '    <AdaptationSet mimeType="video/mp4">',
          '      <Representation id="3" bandwidth="500">',
          '        <BaseURL>http://example.com</BaseURL>',
          '%(contents)s',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '</MPD>',
        ].join('\n');
        const text = sprintf(
            template, {updateTime: updateTime, contents: basicLines.join('\n')});
  
        fakeNetEngine.setResponseText('dummy://foo', text);
        Date.now = () => 0;
        const manifest = await parser.start('dummy://foo', playerInterface);
  
        const timeline = manifest.presentationTimeline;
        expect(timeline.getDuration()).toBe(Infinity);
      });
  
      it('sets infinite duration for multi-period live streams', async () => {
        const template = [
          '<MPD type="dynamic" minimumUpdatePeriod="PT%(updateTime)dS"',
          '    timeShiftBufferDepth="PT1S"',
          '    suggestedPresentationDelay="PT5S"',
          '    availabilityStartTime="1970-01-01T00:00:00Z">',
          '  <Period id="1">',
          '    <AdaptationSet mimeType="video/mp4">',
          '      <Representation id="3" bandwidth="500">',
          '        <BaseURL>http://example.com</BaseURL>',
          '%(contents)s',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '  <Period id="2" start="PT60S">',
          '    <AdaptationSet mimeType="video/mp4">',
          '      <Representation id="4" bandwidth="500">',
          '        <BaseURL>http://example.com</BaseURL>',
          '%(contents)s',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '</MPD>',
        ].join('\n');
        const text = sprintf(
            template, {updateTime: updateTime, contents: basicLines.join('\n')});
  
        fakeNetEngine.setResponseText('dummy://foo', text);
        Date.now = () => 0;
        const manifest = await parser.start('dummy://foo', playerInterface);
  
        const timeline = manifest.presentationTimeline;
        expect(timeline.getDuration()).toBe(Infinity);
      });
    }
  
    it('can add Periods', async () => {
      const template1 = [
        '<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
        '    suggestedPresentationDelay="PT5S"',
        '    minimumUpdatePeriod="PT%(updateTime)dS">',
        '  <Period id="1">',
        '    <AdaptationSet mimeType="video/mp4">',
        '      <Representation id="1" bandwidth="500">',
        '        <BaseURL>http://example.com</BaseURL>',
        '        <SegmentTemplate media="s$Number$.mp4">',
        '          <SegmentTimeline>',
        '            <S t="0" d="2" r="1" />',
        '          </SegmentTimeline>',
        '        </SegmentTemplate>',
        '      </Representation>',
        '    </AdaptationSet>',
        '  </Period>',
        '</MPD>',
      ].join('\n');
      const template2 = [
        '<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
        '    suggestedPresentationDelay="PT5S"',
        '    minimumUpdatePeriod="PT%(updateTime)dS">',
        '  <Period id="1" duration="PT10S">',
        '    <AdaptationSet mimeType="video/mp4">',
        '      <Representation id="1" bandwidth="500">',
        '        <BaseURL>http://example.com</BaseURL>',
        '        <SegmentTemplate media="s$Number$.mp4">',
        '          <SegmentTimeline>',
        '            <S t="0" d="2" r="4" />',
        '          </SegmentTimeline>',
        '        </SegmentTemplate>',
        '      </Representation>',
        '    </AdaptationSet>',
        '  </Period>',
        '  <Period id="2">',
        '    <AdaptationSet mimeType="video/mp4">',
        '      <Representation id="2" bandwidth="500">',
        '        <BaseURL>http://example.com</BaseURL>',
        '        <SegmentTemplate media="s$Number$.mp4">',
        '          <SegmentTimeline>',
        '            <S t="0" d="2" r="1" />',
        '          </SegmentTimeline>',
        '        </SegmentTemplate>',
        '      </Representation>',
        '    </AdaptationSet>',
        '  </Period>',
        '</MPD>',
      ].join('\n');
      const firstManifest = sprintf(template1, {updateTime: updateTime});
      const secondManifest = sprintf(template2, {updateTime: updateTime});
  
      fakeNetEngine.setResponseText('dummy://foo', firstManifest);
      // First two segments should exist
      Date.now = () => 5;
  
      const manifest = await parser.start('dummy://foo', playerInterface);
      const variant = manifest.variants[0];
      const stream = variant.video;
      await stream.createSegmentIndex();
  
      // First two segments exist, but not the third.
      expect(stream.segmentIndex.find(3)).not.toBe(null);
      expect(stream.segmentIndex.find(5)).toBe(null);
  
      fakeNetEngine.setResponseText('dummy://foo', secondManifest);
      // First period (10s) is complete, plus first two segments of next period
      Date.now = () => 15;
  
      await updateManifest();
  
      // The update should have affected the same variant object we captured
      // before.  Now the entire first period should exist (0-10s), plus the next
      // two segments (10-14s).
      expect(stream.segmentIndex.find(9)).not.toBe(null);
      expect(stream.segmentIndex.find(13)).not.toBe(null);
    });
  
    it('uses redirect URL for manifest BaseURL and updates', async () => {
      const template = [
        '<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
        '    suggestedPresentationDelay="PT5S"',
        '    minimumUpdatePeriod="PT%(updateTime)dS">',
        '  <Period id="1" duration="PT30S">',
        '    <AdaptationSet mimeType="video/mp4">',
        '      <Representation id="3" bandwidth="500">',
        '        <SegmentTemplate startNumber="1" media="s$Number$.mp4">',
        '          <SegmentTimeline>',
        '            <S d="10" t="0" />',
        '            <S d="5" />',
        '            <S d="15" />',
        '          </SegmentTimeline>',
        '        </SegmentTemplate>',
        '      </Representation>',
        '    </AdaptationSet>',
        '  </Period>',
        '</MPD>',
      ].join('\n');
      const manifestText = sprintf(template, {updateTime: updateTime});
      const manifestData = shaka.util.StringUtils.toUTF8(manifestText);
      const redirectedUri = 'http://redirected.com/';
  
      // The initial manifest request will be redirected.
      fakeNetEngine.request.and.returnValue(
          shaka.util.AbortableOperation.completed({
            uri: redirectedUri,
            data: manifestData,
          }));
  
      const manifest = await parser.start(originalUri, playerInterface);
  
      // The manifest request was made to the original URL.
      // But includes a redirect
      expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
      const netRequest = fakeNetEngine.request.calls.argsFor(0)[1];
      expect(netRequest.uris).toEqual([redirectedUri, originalUri]);
  
      // Since the manifest request was redirected, the segment refers to
      // the redirected base.
      const stream = manifest.variants[0].video;
      await stream.createSegmentIndex();
      goog.asserts.assert(stream.segmentIndex != null, 'Null segmentIndex!');
  
      const ref = Array.from(stream.segmentIndex)[0];
      const segmentUri = ref.getUris()[0];
      expect(segmentUri).toBe(redirectedUri + 's1.mp4');
    });
  
    it('calls the error callback if an update fails', async () => {
      const lines = [
        '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
      ];
      const manifestText = makeSimpleLiveManifestText(lines, updateTime);
      /** @type {!jasmine.Spy} */
      const onError = jasmine.createSpy('onError');
      playerInterface.onError = Util.spyFunc(onError);
  
      fakeNetEngine.setResponseText('dummy://foo', manifestText);
      await parser.start('dummy://foo', playerInterface);
  
      expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
  
      const error = new shaka.util.Error(
          shaka.util.Error.Severity.CRITICAL,
          shaka.util.Error.Category.NETWORK,
          shaka.util.Error.Code.BAD_HTTP_STATUS);
      const operation = shaka.util.AbortableOperation.failed(error);
      fakeNetEngine.request.and.returnValue(operation);
  
      await updateManifest();
      expect(onError).toHaveBeenCalledTimes(1);
    });
  
    it('uses @minimumUpdatePeriod', async () => {
      const lines = [
        '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
      ];
      // updateTime parameter sets @minimumUpdatePeriod in the manifest.
      const manifestText = makeSimpleLiveManifestText(lines, updateTime);
  
      /** @type {!jasmine.Spy} */
      const tickAfter = updateTickSpy();
      Date.now = () => 0;
  
      fakeNetEngine.setResponseText('dummy://foo', manifestText);
      await parser.start('dummy://foo', playerInterface);
  
      expect(tickAfter).toHaveBeenCalledTimes(1);
      const delay = tickAfter.calls.mostRecent().args[0];
      expect(delay).toBe(updateTime);
    });
  
    it('still updates when @minimumUpdatePeriod is zero', async () => {
      const lines = [
        '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
      ];
      // updateTime parameter sets @minimumUpdatePeriod in the manifest.
      const manifestText = makeSimpleLiveManifestText(lines, /* updateTime= */ 0);
  
      /** @type {!jasmine.Spy} */
      const tickAfter = updateTickSpy();
      Date.now = () => 0;
  
      fakeNetEngine.setResponseText('dummy://foo', manifestText);
      await parser.start('dummy://foo', playerInterface);
  
      const waitTime = shaka.dash.DashParser['MIN_UPDATE_PERIOD_'];
      expect(tickAfter).toHaveBeenCalledTimes(1);
      const delay = tickAfter.calls.mostRecent().args[0];
      expect(delay).toBe(waitTime);
    });
  
    it('does not update when @minimumUpdatePeriod is missing', async () => {
      const lines = [
        '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
      ];
      // updateTime parameter sets @minimumUpdatePeriod in the manifest.
      const manifestText =
          makeSimpleLiveManifestText(lines, /* updateTime= */ null);
  
      /** @type {!jasmine.Spy} */
      const tickAfter = updateTickSpy();
  
      fakeNetEngine.setResponseText('dummy://foo', manifestText);
      await parser.start('dummy://foo', playerInterface);
  
      expect(tickAfter).not.toHaveBeenCalled();
    });
  
    it('delays subsequent updates when an update is slow', async () => {
      const lines = [
        '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
      ];
      const extraWaitTime = 15.0;
      const idealUpdateTime = shaka.dash.DashParser['MIN_UPDATE_PERIOD_'];
      const manifestText = makeSimpleLiveManifestText(lines, idealUpdateTime);
  
      let now = 0;
      Date.now = () => now;
      /** @type {!jasmine.Spy} */
      const tickAfter = updateTickSpy();
  
      fakeNetEngine.setResponseText('dummy://foo', manifestText);
      /** @type {!shaka.util.PublicPromise} */
      const delay = fakeNetEngine.delayNextRequest();
      const p = parser.start('dummy://foo', playerInterface);
      now += extraWaitTime * 1000;  // Make the update appear to take longer.
      delay.resolve();
      await p;
  
      // Check the last update was scheduled close to how long the update took.
      const realDelay = tickAfter.calls.mostRecent().args[0];
      expect(realDelay).toBeCloseTo(extraWaitTime, 0);
    });
  
    it('uses Mpd.Location', async () => {
      const manifestText = [
        '<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
        '    suggestedPresentationDelay="PT5S"',
        '    minimumUpdatePeriod="PT' + updateTime + 'S">',
        '  <Location>http://foobar</Location>',
        '  <Location>http://foobar2</Location>',
        '  <Location>foobar3</Location>',
        '  <Period id="1" duration="PT10S">',
        '    <AdaptationSet mimeType="video/mp4">',
        '      <Representation id="3" bandwidth="500">',
        '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
        '      </Representation>',
        '    </AdaptationSet>',
        '  </Period>',
        '</MPD>',
      ].join('\n');
      fakeNetEngine.setResponseText('dummy://foo', manifestText);
  
      const manifestRequest = shaka.net.NetworkingEngine.RequestType.MANIFEST;
      await parser.start('dummy://foo', playerInterface);
  
      expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
      fakeNetEngine.expectRequest('dummy://foo', manifestRequest);
      fakeNetEngine.request.calls.reset();
  
      // Create a mock so we can verify it gives two URIs.
      // The third location is a relative url, and should be resolved as an
      // absolute url.
      fakeNetEngine.request.and.callFake((type, request) => {
        expect(type).toBe(manifestRequest);
        expect(request.uris).toEqual(
            ['http://foobar', 'http://foobar2', 'dummy://foo/foobar3']);
        const data = shaka.util.StringUtils.toUTF8(manifestText);
        return shaka.util.AbortableOperation.completed(
            {uri: request.uris[0], data: data, headers: {}});
      });
  
      await updateManifest();
      expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
    });
  
    it('uses @suggestedPresentationDelay', async () => {
      const manifestText = [
        '<MPD type="dynamic" suggestedPresentationDelay="PT60S"',
        '    minimumUpdatePeriod="PT5S"',
        '    timeShiftBufferDepth="PT2M"',
        '    maxSegmentDuration="PT10S"',
        '    availabilityStartTime="1970-01-01T00:05:00Z">',
        '  <Period id="1">',
        '    <AdaptationSet mimeType="video/mp4">',
        '      <Representation id="3" bandwidth="500">',
        '        <BaseURL>http://example.com</BaseURL>',
        '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
        '      </Representation>',
        '    </AdaptationSet>',
        '  </Period>',
        '</MPD>',
      ].join('\n');
      fakeNetEngine.setResponseText('dummy://foo', manifestText);
  
      Date.now = () => 600000; /* 10 minutes */
      const manifest = await parser.start('dummy://foo', playerInterface);
  
      expect(manifest).toBeTruthy();
      const timeline = manifest.presentationTimeline;
      expect(timeline).toBeTruthy();
  
      //  We are 5 minutes into the presentation, with a
      //  @timeShiftBufferDepth of 120 seconds and a @maxSegmentDuration of
      //  10 seconds, the start will be 2:50.
      expect(timeline.getSegmentAvailabilityStart()).toBe(170);
      // Normally the end should be 4:50; but with a 60 second
      // @suggestedPresentationDelay it will be 3:50 minutes.
      expect(timeline.getSegmentAvailabilityEnd()).toBe(290);
      expect(timeline.getSeekRangeEnd()).toBe(230);
    });
  
    describe('availabilityWindowOverride', () => {
      it('overrides @timeShiftBufferDepth', async () => {
        const manifestText = [
          '<MPD type="dynamic" suggestedPresentationDelay="PT60S"',
          '    minimumUpdatePeriod="PT5S"',
          '    timeShiftBufferDepth="PT2M"',
          '    maxSegmentDuration="PT10S"',
          '    availabilityStartTime="1970-01-01T00:05:00Z">',
          '  <Period id="1">',
          '    <AdaptationSet mimeType="video/mp4">',
          '      <Representation id="3" bandwidth="500">',
          '        <BaseURL>http://example.com</BaseURL>',
          '        <SegmentTemplate media="s$Number$.mp4" duration="2" />',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '</MPD>',
        ].join('\n');
        fakeNetEngine.setResponseText('dummy://foo', manifestText);
  
        const config = shaka.util.PlayerConfiguration.createDefault().manifest;
        config.availabilityWindowOverride = 4 * 60;
        parser.configure(config);
  
        Date.now = () => 600000; /* 10 minutes */
        const manifest = await parser.start('dummy://foo', playerInterface);
  
        expect(manifest).toBeTruthy();
        const timeline = manifest.presentationTimeline;
        expect(timeline).toBeTruthy();
  
        // The parser was configured to have a manifest availability window
        // of 4 minutes.
        const end = timeline.getSegmentAvailabilityEnd();
        const start = timeline.getSegmentAvailabilityStart();
        expect(end - start).toBe(4 * 60);
      });
    });
  
    describe('maxSegmentDuration', () => {
      it('uses @maxSegmentDuration', async () => {
        const manifestText = [
          '<MPD type="dynamic" suggestedPresentationDelay="PT0S"',
          '    minimumUpdatePeriod="PT5S"',
          '    timeShiftBufferDepth="PT2M"',
          '    maxSegmentDuration="PT15S"',
          '    availabilityStartTime="1970-01-01T00:05:00Z">',
          '  <Period id="1">',
          '    <AdaptationSet id="2" mimeType="video/mp4">',
          '      <Representation id="3" bandwidth="500">',
          '        <BaseURL>http://example.com</BaseURL>',
          '<SegmentTemplate media="s$Number$.mp4" duration="2" />',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '</MPD>',
        ].join('\n');
        fakeNetEngine.setResponseText('dummy://foo', manifestText);
  
        Date.now = () => 600000; /* 10 minutes */
        const manifest = await parser.start('dummy://foo', playerInterface);
  
        expect(manifest).toBeTruthy();
        const timeline = manifest.presentationTimeline;
        expect(timeline).toBeTruthy();
        expect(timeline.getMaxSegmentDuration()).toBe(15);
      });
  
      it('derived from SegmentTemplate w/ SegmentTimeline', async () => {
        const lines = [
          '<SegmentTemplate media="s$Number$.mp4">',
          '  <SegmentTimeline>',
          '    <S t="0" d="7" />',
          '    <S d="8" />',
          '    <S d="6" />',
          '  </SegmentTimeline>',
          '</SegmentTemplate>',
        ];
        await testDerived(lines);
      });
  
      it('derived from SegmentTemplate w/ @duration', async () => {
        const lines = [
          '<SegmentTemplate media="s$Number$.mp4" duration="8" />',
        ];
        await testDerived(lines);
      });
  
      it('derived from SegmentList', async () => {
        const lines = [
          '<SegmentList duration="8">',
          '  <SegmentURL media="s1.mp4" />',
          '  <SegmentURL media="s2.mp4" />',
          '</SegmentList>',
        ];
        await testDerived(lines);
      });
  
      it('derived from SegmentList w/ SegmentTimeline', async () => {
        const lines = [
          '<SegmentList duration="8">',
          '  <SegmentTimeline>',
          '    <S t="0" d="5" />',
          '    <S d="4" />',
          '    <S d="8" />',
          '  </SegmentTimeline>',
          '  <SegmentURL media="s1.mp4" />',
          '  <SegmentURL media="s2.mp4" />',
          '</SegmentList>',
        ];
        await testDerived(lines);
      });
  
      async function testDerived(lines) {
        const template = [
          '<MPD type="dynamic" suggestedPresentationDelay="PT0S"',
          '    minimumUpdatePeriod="PT5S"',
          '    timeShiftBufferDepth="PT2M"',
          '    availabilityStartTime="1970-01-01T00:05:00Z">',
          '  <Period id="1">',
          '    <AdaptationSet id="2" mimeType="video/mp4">',
          '      <Representation id="3" bandwidth="500">',
          '        <BaseURL>http://example.com</BaseURL>',
          '%(contents)s',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '</MPD>',
        ].join('\n');
        const manifestText = sprintf(template, {contents: lines.join('\n')});
  
        fakeNetEngine.setResponseText('dummy://foo', manifestText);
        Date.now = () => 600000; /* 10 minutes */
        const manifest = await parser.start('dummy://foo', playerInterface);
  
        expect(manifest).toBeTruthy();
        const timeline = manifest.presentationTimeline;
        expect(timeline).toBeTruthy();
  
        // NOTE: the largest segment is 8 seconds long in each test.
        expect(timeline.getMaxSegmentDuration()).toBe(8);
      }
    });  // describe('maxSegmentDuration')
  
    describe('stop', () => {
      const manifestRequestType = shaka.net.NetworkingEngine.RequestType.MANIFEST;
      const dateRequestType = shaka.net.NetworkingEngine.RequestType.TIMING;
      const manifestUri = 'dummy://foo';
      const dateUri = 'http://foo.bar/date';
  
      beforeEach(() => {
        const manifest = [
          '<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
          '    minimumUpdatePeriod="PT' + updateTime + 'S">',
          '  <UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-xsdate:2014"',
          '      value="http://foo.bar/date" />',
          '  <UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-xsdate:2014"',
          '      value="http://foo.bar/date" />',
          '  <Period id="1">',
          '    <AdaptationSet mimeType="video/mp4">',
          '      <Representation id="3" bandwidth="500">',
          '        <BaseURL>http://example.com</BaseURL>',
          '        <SegmentTemplate startNumber="1" media="s$Number$.mp4"',
          '            duration="2" />',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '</MPD>',
        ].join('\n');
        fakeNetEngine
            .setResponseText('http://foo.bar/date', '1970-01-01T00:00:30Z')
            .setResponseText('dummy://foo', manifest);
      });
  
      it('stops updates', async () => {
        await parser.start(manifestUri, playerInterface);
  
        fakeNetEngine.expectRequest(manifestUri, manifestRequestType);
        fakeNetEngine.request.calls.reset();
  
        parser.stop();
        await updateManifest();
        expect(fakeNetEngine.request).not.toHaveBeenCalled();
      });
  
      it('stops initial parsing', async () => {
        const expectation =
            expectAsync(parser.start('dummy://foo', playerInterface))
                .toBeRejected();
        // start will only begin the network request, calling stop here will be
        // after the request has started but before any parsing has been done.
        expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
        parser.stop();
        await expectation;
  
        fakeNetEngine.expectRequest(manifestUri, manifestRequestType);
        fakeNetEngine.request.calls.reset();
        await updateManifest();
        // An update should not occur.
        expect(fakeNetEngine.request).not.toHaveBeenCalled();
      });
  
      it('interrupts manifest updates', async () => {
        const manifest = await parser.start('dummy://foo', playerInterface);
  
        expect(manifest).toBeTruthy();
        fakeNetEngine.expectRequest(manifestUri, manifestRequestType);
        fakeNetEngine.request.calls.reset();
        /** @type {!shaka.util.PublicPromise} */
        const delay = fakeNetEngine.delayNextRequest();
  
        await updateManifest();
        // The request was made but should not be resolved yet.
        expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
        fakeNetEngine.expectRequest(manifestUri, manifestRequestType);
        fakeNetEngine.request.calls.reset();
        parser.stop();
        delay.resolve();
  
        // Wait for another update period.
        await updateManifest();
        // A second update should not occur.
        expect(fakeNetEngine.request).not.toHaveBeenCalled();
      });
  
      it('interrupts UTCTiming requests', async () => {
        /** @type {!shaka.util.PublicPromise} */
        let delay = fakeNetEngine.delayNextRequest();
        const expectation =
            expectAsync(parser.start('dummy://foo', playerInterface))
                .toBeRejected();
  
        await Util.shortDelay();
        // This is the initial manifest request.
        expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
        fakeNetEngine.expectRequest(manifestUri, manifestRequestType);
        fakeNetEngine.request.calls.reset();
        // Resolve the manifest request and wait on the UTCTiming request.
        delay.resolve();
        delay = fakeNetEngine.delayNextRequest();
        await Util.shortDelay();
  
        // This is the first UTCTiming request.
        expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
        fakeNetEngine.expectRequest(dateUri, dateRequestType);
        fakeNetEngine.request.calls.reset();
        // Interrupt the parser, then fail the request.
        parser.stop();
        delay.reject();
        await expectation;
  
        // Wait for another update period.
        await updateManifest();
  
        // No more updates should occur.
        expect(fakeNetEngine.request).not.toHaveBeenCalled();
      });
    });
  
    describe('SegmentTemplate w/ SegmentTimeline', () => {
      const basicLines = [
        '<SegmentTemplate startNumber="1" media="s$Number$.mp4">',
        '  <SegmentTimeline>',
        '    <S d="10" t="0" />',
        '    <S d="5" />',
        '    <S d="15" />',
        '  </SegmentTimeline>',
        '</SegmentTemplate>',
      ];
      const basicRefs = [
        shaka.test.ManifestParser.makeReference('s1.mp4', 0, 10, originalUri),
        shaka.test.ManifestParser.makeReference('s2.mp4', 10, 15, originalUri),
        shaka.test.ManifestParser.makeReference('s3.mp4', 15, 30, originalUri),
      ];
      const updateLines = [
        '<SegmentTemplate startNumber="1" media="s$Number$.mp4">',
        '  <SegmentTimeline>',
        '    <S d="10" t="0" />',
        '    <S d="5" />',
        '    <S d="15" />',
        '    <S d="10" />',
        '  </SegmentTimeline>',
        '</SegmentTemplate>',
      ];
      const updateRefs = [
        shaka.test.ManifestParser.makeReference('s1.mp4', 0, 10, originalUri),
        shaka.test.ManifestParser.makeReference('s2.mp4', 10, 15, originalUri),
        shaka.test.ManifestParser.makeReference('s3.mp4', 15, 30, originalUri),
        shaka.test.ManifestParser.makeReference('s4.mp4', 30, 40, originalUri),
      ];
      const partialUpdateLines = [
        '<SegmentTemplate startNumber="3" media="s$Number$.mp4">',
        '  <SegmentTimeline>',
        '    <S d="15" t="15" />',
        '    <S d="10" />',
        '  </SegmentTimeline>',
        '</SegmentTemplate>',
      ];
  
      testCommonBehaviors(
          basicLines, basicRefs, updateLines, updateRefs, partialUpdateLines);
    });
  
    describe('SegmentList w/ SegmentTimeline', () => {
      const basicLines = [
        '<SegmentList>',
        '  <SegmentURL media="s1.mp4" />',
        '  <SegmentURL media="s2.mp4" />',
        '  <SegmentURL media="s3.mp4" />',
        '  <SegmentTimeline>',
        '    <S d="10" t="0" />',
        '    <S d="5" />',
        '    <S d="15" />',
        '  </SegmentTimeline>',
        '</SegmentList>',
      ];
      const basicRefs = [
        shaka.test.ManifestParser.makeReference('s1.mp4', 0, 10, originalUri),
        shaka.test.ManifestParser.makeReference('s2.mp4', 10, 15, originalUri),
        shaka.test.ManifestParser.makeReference('s3.mp4', 15, 30, originalUri),
      ];
      const updateLines = [
        '<SegmentList>',
        '  <SegmentURL media="s1.mp4" />',
        '  <SegmentURL media="s2.mp4" />',
        '  <SegmentURL media="s3.mp4" />',
        '  <SegmentURL media="s4.mp4" />',
        '  <SegmentTimeline>',
        '    <S d="10" t="0" />',
        '    <S d="5" />',
        '    <S d="15" />',
        '    <S d="10" />',
        '  </SegmentTimeline>',
        '</SegmentList>',
      ];
      const updateRefs = [
        shaka.test.ManifestParser.makeReference('s1.mp4', 0, 10, originalUri),
        shaka.test.ManifestParser.makeReference('s2.mp4', 10, 15, originalUri),
        shaka.test.ManifestParser.makeReference('s3.mp4', 15, 30, originalUri),
        shaka.test.ManifestParser.makeReference('s4.mp4', 30, 40, originalUri),
      ];
      const partialUpdateLines = [
        '<SegmentList startNumber="3">',
        '  <SegmentURL media="s3.mp4" />',
        '  <SegmentURL media="s4.mp4" />',
        '  <SegmentTimeline>',
        '    <S d="15" t="15" />',
        '    <S d="10" />',
        '  </SegmentTimeline>',
        '</SegmentList>',
      ];
  
      testCommonBehaviors(
          basicLines, basicRefs, updateLines, updateRefs, partialUpdateLines);
    });
  
    describe('SegmentList w/ @duration', () => {
      const basicLines = [
        '<SegmentList duration="10">',
        '  <SegmentURL media="s1.mp4" />',
        '  <SegmentURL media="s2.mp4" />',
        '  <SegmentURL media="s3.mp4" />',
        '</SegmentList>',
      ];
      const basicRefs = [
        shaka.test.ManifestParser.makeReference('s1.mp4', 0, 10, originalUri),
        shaka.test.ManifestParser.makeReference('s2.mp4', 10, 20, originalUri),
        shaka.test.ManifestParser.makeReference('s3.mp4', 20, 30, originalUri),
      ];
      const updateLines = [
        '<SegmentList duration="10">',
        '  <SegmentURL media="s1.mp4" />',
        '  <SegmentURL media="s2.mp4" />',
        '  <SegmentURL media="s3.mp4" />',
        '  <SegmentURL media="s4.mp4" />',
        '</SegmentList>',
      ];
      const updateRefs = [
        shaka.test.ManifestParser.makeReference('s1.mp4', 0, 10, originalUri),
        shaka.test.ManifestParser.makeReference('s2.mp4', 10, 20, originalUri),
        shaka.test.ManifestParser.makeReference('s3.mp4', 20, 30, originalUri),
        shaka.test.ManifestParser.makeReference('s4.mp4', 30, 40, originalUri),
      ];
      const partialUpdateLines = [
        '<SegmentList startNumber="3" duration="10">',
        '  <SegmentURL media="s3.mp4" />',
        '  <SegmentURL media="s4.mp4" />',
        '</SegmentList>',
      ];
  
      testCommonBehaviors(
          basicLines, basicRefs, updateLines, updateRefs, partialUpdateLines);
    });
  
    describe('SegmentTemplate w/ duration', () => {
      const templateLines = [
        '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
      ];
  
      it('produces sane references without assertions', async () => {
        const manifestText =
            makeSimpleLiveManifestText(templateLines, updateTime);
  
        fakeNetEngine.setResponseText('dummy://foo', manifestText);
        const manifest = await parser.start('dummy://foo', playerInterface);
  
        const stream = manifest.variants[0].video;
        await stream.createSegmentIndex();
  
        const liveEdge =
            manifest.presentationTimeline.getSegmentAvailabilityEnd();
  
        // In https://github.com/google/shaka-player/issues/1204, a get on the
        // final segment failed an assertion and returned endTime == 0.
        // Find the last segment by looking just before the live edge.  Looking
        // right on the live edge creates test flake, and the segments are 2
        // seconds in duration.
        const idx = stream.segmentIndex.find(liveEdge - 0.5);
        goog.asserts.assert(idx != null, 'Live edge not found!');
  
        // This should not throw an assertion.
        const ref = stream.segmentIndex.get(idx);
        // The segment's endTime should definitely not be 0.
        expect(ref.endTime).toBeGreaterThan(0);
      });
    });
  
    describe('EventStream', () => {
      const originalManifest = [
        '<MPD type="dynamic" minimumUpdatePeriod="PT' + updateTime + 'S"',
        '    availabilityStartTime="1970-01-01T00:00:00Z">',
        '  <Period id="1" duration="PT60S" start="PT10S">',
        '    <EventStream schemeIdUri="http://example.com" value="foobar"',
        '        timescale="100">',
        '      <Event duration="5000" />',
        '      <Event id="abc" presentationTime="300" duration="1000" />',
        '    </EventStream>',
        '    <AdaptationSet mimeType="video/mp4">',
        '      <Representation bandwidth="1">',
        '        <SegmentTemplate startNumber="1" media="s$Number$.mp4"',
        '                         duration="2" />',
        '      </Representation>',
        '    </AdaptationSet>',
        '  </Period>',
        '</MPD>',
      ].join('\n');
  
      /** @type {!jasmine.Spy} */
      let onTimelineRegionAddedSpy;
  
      beforeEach(() => {
        onTimelineRegionAddedSpy = jasmine.createSpy('onTimelineRegionAdded');
        playerInterface.onTimelineRegionAdded =
            shaka.test.Util.spyFunc(onTimelineRegionAddedSpy);
      });
  
      it('will parse EventStream nodes', async () => {
        fakeNetEngine.setResponseText('dummy://foo', originalManifest);
        await parser.start('dummy://foo', playerInterface);
  
        expect(onTimelineRegionAddedSpy).toHaveBeenCalledTimes(2);
  
        expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith({
          schemeIdUri: 'http://example.com',
          value: 'foobar',
          startTime: 10,
          endTime: 60,
          id: '',
          eventElement: jasmine.any(Element),
        });
        expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith({
          schemeIdUri: 'http://example.com',
          value: 'foobar',
          startTime: 13,
          endTime: 23,
          id: 'abc',
          eventElement: jasmine.any(Element),
        });
      });
  
      it('will add timeline regions on manifest update', async () => {
        const newManifest = [
          '<MPD type="dynamic" minimumUpdatePeriod="PT' + updateTime + 'S"',
          '    availabilityStartTime="1970-01-01T00:00:00Z">',
          '  <Period id="1" duration="PT30S">',
          '    <EventStream schemeIdUri="http://example.com" timescale="100">',
          '      <Event id="100" />',
          '    </EventStream>',
          '    <AdaptationSet mimeType="video/mp4">',
          '      <Representation bandwidth="1">',
          '        <SegmentTemplate startNumber="1" media="s$Number$.mp4"',
          '                         duration="2" />',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '</MPD>',
        ].join('\n');
  
        fakeNetEngine.setResponseText('dummy://foo', originalManifest);
        await parser.start('dummy://foo', playerInterface);
  
        expect(onTimelineRegionAddedSpy).toHaveBeenCalledTimes(2);
        onTimelineRegionAddedSpy.calls.reset();
  
        fakeNetEngine.setResponseText('dummy://foo', newManifest);
        await updateManifest();
  
        expect(onTimelineRegionAddedSpy).toHaveBeenCalledTimes(1);
      });
  
      it('will not let an event exceed the Period duration', async () => {
        const newManifest = [
          '<MPD>',
          '  <Period id="1" duration="PT30S">',
          '    <EventStream schemeIdUri="http://example.com" timescale="1">',
          '      <Event presentationTime="10" duration="15"/>',
          '      <Event presentationTime="25" duration="50"/>',
          '      <Event presentationTime="50" duration="10"/>',
          '    </EventStream>',
          '    <AdaptationSet mimeType="video/mp4">',
          '      <Representation bandwidth="1">',
          '        <SegmentTemplate startNumber="1" media="s$Number$.mp4"',
          '                         duration="2" />',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '</MPD>',
        ].join('\n');
  
        fakeNetEngine.setResponseText('dummy://foo', newManifest);
        await parser.start('dummy://foo', playerInterface);
  
        expect(onTimelineRegionAddedSpy).toHaveBeenCalledTimes(3);
        expect(onTimelineRegionAddedSpy)
            .toHaveBeenCalledWith(
                jasmine.objectContaining({startTime: 10, endTime: 25}));
        expect(onTimelineRegionAddedSpy)
            .toHaveBeenCalledWith(
                jasmine.objectContaining({startTime: 25, endTime: 30}));
        expect(onTimelineRegionAddedSpy)
            .toHaveBeenCalledWith(
                jasmine.objectContaining({startTime: 30, endTime: 30}));
      });
  
      it('will parse multiple events at same offset', async () => {
        const newManifest = [
          '<MPD>',
          '  <Period id="1" duration="PT30S">',
          '    <EventStream schemeIdUri="http://example.com" timescale="1">',
          '      <Event id="1" presentationTime="10" duration="15"/>',
          '      <Event id="2" presentationTime="10" duration="15"/>',
          '    </EventStream>',
          '    <AdaptationSet mimeType="video/mp4">',
          '      <Representation bandwidth="1">',
          '        <SegmentTemplate startNumber="1" media="s$Number$.mp4"',
          '                         duration="2" />',
          '      </Representation>',
          '    </AdaptationSet>',
          '  </Period>',
          '</MPD>',
        ].join('\n');
  
        fakeNetEngine.setResponseText('dummy://foo', newManifest);
        await parser.start('dummy://foo', playerInterface);
  
        expect(onTimelineRegionAddedSpy).toHaveBeenCalledTimes(2);
        expect(onTimelineRegionAddedSpy)
            .toHaveBeenCalledWith(
                jasmine.objectContaining({id: '1', startTime: 10, endTime: 25}));
        expect(onTimelineRegionAddedSpy)
            .toHaveBeenCalledWith(
                jasmine.objectContaining({id: '2', startTime: 10, endTime: 25}));
      });
    });
  
    it('honors clockSyncUri for in-progress recordings', async () => {
      const manifestText = [
        '<MPD type="dynamic" availabilityStartTime="1970-01-01T00:05:00Z"',
        '      mediaPresentationDuration="PT3600S">',
        '  <Period id="1">',
        '    <AdaptationSet mimeType="video/mp4">',
        '      <Representation id="3" bandwidth="500">',
        '        <BaseURL>http://example.com</BaseURL>',
        '        <SegmentTemplate media="s$Number$.mp4" duration="2" />',
        '      </Representation>',
        '    </AdaptationSet>',
        '  </Period>',
        '</MPD>',
      ].join('\n');
      fakeNetEngine.setResponseText('dummy://foo', manifestText);
  
      // Simulate a realistic clock sync URI.
      fakeNetEngine.setResponseText('dummy://time', '');
      fakeNetEngine.setHeaders('dummy://time', {
        'date': (new Date()).toUTCString(),
      });
  
      const config = shaka.util.PlayerConfiguration.createDefault().manifest;
      config.dash.clockSyncUri = 'dummy://time';
      parser.configure(config);
  
      const manifest = await parser.start('dummy://foo', playerInterface);
  
      // Make sure we're testing what we think we're testing.
      // This should be seen as in-progress.
      expect(manifest.presentationTimeline.isInProgress()).toBe(true);
  
      // Now make sure we made the time sync request.
      const timingRequest = shaka.net.NetworkingEngine.RequestType.TIMING;
      fakeNetEngine.expectRequest('dummy://time', timingRequest);
    });
  
    it('adds segments to in-progress recordings', async () => {
      const manifestText = [
        '<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
        '      mediaPresentationDuration="PT10S">',
        '  <Period id="1">',
        '    <AdaptationSet mimeType="video/mp4">',
        '      <Representation id="3" bandwidth="500">',
        '        <BaseURL>http://example.com</BaseURL>',
        '        <SegmentTemplate media="s$Number$.mp4" duration="2" />',
        '      </Representation>',
        '    </AdaptationSet>',
        '  </Period>',
        '</MPD>',
      ].join('\n');
      fakeNetEngine.setResponseText('dummy://foo', manifestText);
  
      // The presentation started just over 4 seconds ago.  There should be 2
      // segments so far.
      Date.now = () => 4.1 * 1000;
      const manifest = await parser.start('dummy://foo', playerInterface);
  
      // Make sure we're testing what we think we're testing.
      // This should be seen as in-progress.
      expect(manifest.presentationTimeline.isInProgress()).toBe(true);
  
      const stream = manifest.variants[0].video;
      expect(stream).toBeTruthy();
  
      await stream.createSegmentIndex();
      expect(stream.segmentIndex).toBeTruthy();
  
      // Check for the 2 initial segments we're expecting.
      ManifestParser.verifySegmentIndex(stream, [
        shaka.test.ManifestParser.makeReference('s1.mp4', 0, 2, originalUri),
        shaka.test.ManifestParser.makeReference('s2.mp4', 2, 4, originalUri),
      ]);
  
      // Just over 10 seconds after the presentation started, we should now have
      // all 5 segments.  In order to trick the system into updating, we must both
      // fake the current time and wait long enough for the timer to fire and
      // update the references.
      Date.now = () => 10.1 * 1000;
      await shaka.test.Util.delay(2.1);  // A little longer than the segments are.
  
      ManifestParser.verifySegmentIndex(stream, [
        shaka.test.ManifestParser.makeReference('s1.mp4', 0, 2, originalUri),
        shaka.test.ManifestParser.makeReference('s2.mp4', 2, 4, originalUri),
        shaka.test.ManifestParser.makeReference('s3.mp4', 4, 6, originalUri),
        shaka.test.ManifestParser.makeReference('s4.mp4', 6, 8, originalUri),
        shaka.test.ManifestParser.makeReference('s5.mp4', 8, 10, originalUri),
      ]);
    });
  });
  