/** * @license * Copyright 2016 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ describe('DashParser Live', function() { const Util = shaka.test.Util; const ManifestParser = shaka.test.ManifestParser; const realTimeout = window.setTimeout; 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 {shakaExtern.ManifestParser.PlayerInterface} */ let playerInterface; beforeEach(function() { // First, fake the clock so we can control timers. // This does not mock Date.now, which must be done separately. jasmine.clock().install(); // This Promise mock is required for fakeEventLoop. PromiseMock.install(); let retry = shaka.net.NetworkingEngine.defaultRetryParameters(); fakeNetEngine = new shaka.test.FakeNetworkingEngine(); parser = new shaka.dash.DashParser(); parser.configure({ retryParameters: retry, dash: { clockSyncUri: '', customScheme: function(node) { return null; }, ignoreDrmInfo: false, xlinkFailGracefully: false, defaultPresentationDelay: 10 } }); playerInterface = { networkingEngine: fakeNetEngine, filterNewPeriod: function() {}, filterAllPeriods: function() {}, onTimelineRegionAdded: fail, // Should not have any EventStream elements. onEvent: fail, onError: fail }; }); afterEach(function() { // Dash parser stop is synchronous. parser.stop(); // Uninstall the clock() first. This also undoes mockDate(), and should be // done afterEach, not afterAll. Otherwise, we get conflicts when some // tests use mockDate() and others directly overwrite Date.now. jasmine.clock().uninstall(); // Replace Date.now with the browser built-in. This must come AFTER we // uninstall the clock() module, or else mockDate() doesn't get cleaned up // correctly. Date.now = oldNow; // Finally, uninstall the Promise mock. PromiseMock.uninstall(); // TODO: Clean up this suite so that everyone uses mockDate(). }); /** * Simulate time to trigger a manifest update. */ function delayForUpdatePeriod() { // Tick the virtual clock to trigger an update and resolve all Promises. Util.fakeEventLoop(updateTime); } /** * Makes a simple live manifest with the given representation contents. * * @param {!Array.} lines * @param {number?} updateTime * @param {number=} opt_duration * @return {string} */ function makeSimpleLiveManifestText(lines, updateTime, opt_duration) { let updateAttr = updateTime != null ? 'minimumUpdatePeriod="PT' + updateTime + 'S"' : ''; let durationAttr = opt_duration != undefined ? 'duration="PT' + opt_duration + 'S"' : ''; let template = [ '', ' ', ' ', ' ', ' http://example.com', '%(contents)s', ' ', ' ', ' ', '' ].join('\n'); let text = sprintf(template, { updateAttr: updateAttr, durationAttr: durationAttr, contents: lines.join('\n'), updateTime: updateTime }); return text; } /** * Creates tests that test the behavior common between SegmentList and * SegmentTemplate. * * @param {!Array.} basicLines * @param {!Array.} basicRefs * @param {!Array.} updateLines * @param {!Array.} updateRefs * @param {!Array.} partialUpdateLines */ function testCommonBehaviors( basicLines, basicRefs, updateLines, updateRefs, partialUpdateLines) { /** * Tests that an update will show the given references. * * @param {function()} done * @param {!Array.} firstLines The Representation contents for the * first manifest. * @param {!Array.} firstReferences The media * references for the first parse. * @param {!Array.} secondLines The Representation contents for the * updated manifest. * @param {!Array.} secondReferences The * media references for the updated manifest. */ function testBasicUpdate( done, firstLines, firstReferences, secondLines, secondReferences) { let firstManifest = makeSimpleLiveManifestText(firstLines, updateTime); let secondManifest = makeSimpleLiveManifestText(secondLines, updateTime); fakeNetEngine.setResponseMapAsText({'dummy://foo': firstManifest}); parser.start('dummy://foo', playerInterface) .then(function(manifest) { let stream = manifest.periods[0].variants[0].video; ManifestParser.verifySegmentIndex(stream, firstReferences); expect(manifest.periods.length).toBe(1); fakeNetEngine.setResponseMapAsText({'dummy://foo': secondManifest}); delayForUpdatePeriod(); ManifestParser.verifySegmentIndex(stream, secondReferences); // In https://github.com/google/shaka-player/issues/963, we // duplicated periods during the first update. This check covers // this case. expect(manifest.periods.length).toBe(1); }).catch(fail).then(done); PromiseMock.flush(); } it('basic support', function(done) { testBasicUpdate(done, basicLines, basicRefs, updateLines, updateRefs); }); it('new manifests don\'t need to include old references', function(done) { testBasicUpdate( done, basicLines, basicRefs, partialUpdateLines, updateRefs); }); it('evicts old references for single-period live stream', function(done) { let template = [ '', ' ', ' ', ' ', ' http://example.com', '%(contents)s', ' ', ' ', ' ', '' ].join('\n'); let text = sprintf( template, {updateTime: updateTime, contents: basicLines.join('\n')}); fakeNetEngine.setResponseMapAsText({'dummy://foo': text}); Date.now = function() { return 0; }; parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(manifest).toBeTruthy(); let stream = manifest.periods[0].variants[0].video; expect(stream).toBeTruthy(); expect(stream.findSegmentPosition).toBeTruthy(); expect(stream.findSegmentPosition(0)).toBe(1); ManifestParser.verifySegmentIndex(stream, basicRefs); // 15 seconds for @timeShiftBufferDepth and the first segment // duration. Date.now = function() { return 2 * 15 * 1000; }; delayForUpdatePeriod(); // The first reference should have been evicted. expect(stream.findSegmentPosition(0)).toBe(2); ManifestParser.verifySegmentIndex(stream, basicRefs.slice(1)); }).catch(fail).then(done); PromiseMock.flush(); }); it('evicts old references for multi-period live stream', function(done) { let template = [ '', ' ', ' ', ' ', ' http://example.com', '%(contents)s', ' ', ' ', ' ', ' ', ' ', ' ', ' http://example.com', '%(contents)s', ' ', ' ', ' ', '' ].join('\n'); // Set the period start to the sum of the durations of the references // in the previous period. let durs = basicRefs.map(function(r) { return r.endTime - r.startTime; }); let pStart = durs.reduce(function(p, d) { return p + d; }, 0); let args = { updateTime: updateTime, pStart: pStart, contents: basicLines.join('\n') }; let text = sprintf(template, args); fakeNetEngine.setResponseMapAsText({'dummy://foo': text}); Date.now = function() { return 0; }; parser.start('dummy://foo', playerInterface) .then(function(manifest) { let stream1 = manifest.periods[0].variants[0].video; let stream2 = manifest.periods[1].variants[0].video; ManifestParser.verifySegmentIndex(stream1, basicRefs); ManifestParser.verifySegmentIndex(stream2, basicRefs); // 15 seconds for @timeShiftBufferDepth and the first segment // duration. Date.now = function() { return 2 * 15 * 1000; }; delayForUpdatePeriod(); // The first reference should have been evicted. ManifestParser.verifySegmentIndex(stream1, basicRefs.slice(1)); ManifestParser.verifySegmentIndex(stream2, basicRefs); // Same as above, but 1 period length later Date.now = function() { return (2 * 15 + pStart) * 1000; }; delayForUpdatePeriod(); ManifestParser.verifySegmentIndex(stream1, []); ManifestParser.verifySegmentIndex(stream2, basicRefs.slice(1)); }).catch(fail).then(done); PromiseMock.flush(); }); it('sets infinite duration for single-period live streams', function(done) { let template = [ '', ' ', ' ', ' ', ' http://example.com', '%(contents)s', ' ', ' ', ' ', '' ].join('\n'); let text = sprintf( template, {updateTime: updateTime, contents: basicLines.join('\n')}); fakeNetEngine.setResponseMapAsText({'dummy://foo': text}); Date.now = function() { return 0; }; parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(manifest.periods.length).toBe(1); let timeline = manifest.presentationTimeline; expect(timeline.getDuration()).toBe(Infinity); }).catch(fail).then(done); PromiseMock.flush(); }); it('sets infinite duration for multi-period live streams', function(done) { let template = [ '', ' ', ' ', ' ', ' http://example.com', '%(contents)s', ' ', ' ', ' ', ' ', ' ', ' ', ' http://example.com', '%(contents)s', ' ', ' ', ' ', '' ].join('\n'); let text = sprintf( template, {updateTime: updateTime, contents: basicLines.join('\n')}); fakeNetEngine.setResponseMapAsText({'dummy://foo': text}); Date.now = function() { return 0; }; parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(manifest.periods.length).toBe(2); expect(manifest.periods[1].startTime).toBe(60); let timeline = manifest.presentationTimeline; expect(timeline.getDuration()).toBe(Infinity); }).catch(fail).then(done); PromiseMock.flush(); }); } it('can add Periods', function(done) { let lines = [ '' ]; let template = [ '', ' ', ' ', ' ', ' http://example.com', '%(contents)s', ' ', ' ', ' ', '' ].join('\n'); let secondManifest = sprintf(template, {updateTime: updateTime, contents: lines.join('\n')}); let firstManifest = makeSimpleLiveManifestText(lines, updateTime); let filterNewPeriod = jasmine.createSpy('filterNewPeriod'); playerInterface.filterNewPeriod = Util.spyFunc(filterNewPeriod); let filterAllPeriods = jasmine.createSpy('filterAllPeriods'); playerInterface.filterAllPeriods = Util.spyFunc(filterAllPeriods); fakeNetEngine.setResponseMapAsText({'dummy://foo': firstManifest}); parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(manifest.periods.length).toBe(1); // Should call filterAllPeriods for parsing the first manifest expect(filterNewPeriod.calls.count()).toBe(0); expect(filterAllPeriods.calls.count()).toBe(1); fakeNetEngine.setResponseMapAsText({'dummy://foo': secondManifest}); delayForUpdatePeriod(); // Should update the same manifest object. expect(manifest.periods.length).toBe(2); // Should call filterNewPeriod for parsing the new manifest expect(filterAllPeriods.calls.count()).toBe(1); expect(filterNewPeriod.calls.count()).toBe(1); }).catch(fail).then(done); PromiseMock.flush(); }); it('uses redirect URL for manifest BaseURL', function(done) { let template = [ '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '' ].join('\n'); let manifestText = sprintf(template, {updateTime: updateTime}); let manifestData = shaka.util.StringUtils.toUTF8(manifestText); let redirectedUri = 'http://redirected.com/'; // The initial manifest request will be redirected. fakeNetEngine.request.and.returnValue( shaka.util.AbortableOperation.completed({ uri: redirectedUri, data: manifestData, })); parser.start(originalUri, playerInterface) .then(function(manifest) { // The manifest request was made to the original URL. expect(fakeNetEngine.request.calls.count()).toBe(1); let netRequest = fakeNetEngine.request.calls.argsFor(0)[1]; expect(netRequest.uris).toEqual([originalUri]); // Since the manifest request was redirected, the segment refers to // the redirected base. let stream = manifest.periods[0].variants[0].video; let segmentUri = stream.getSegmentReference(1).getUris()[0]; expect(segmentUri).toBe(redirectedUri + 's1.mp4'); }).catch(fail).then(done); PromiseMock.flush(); }); it('calls the error callback if an update fails', function(done) { let lines = [ '' ]; let manifest = makeSimpleLiveManifestText(lines, updateTime); let onError = jasmine.createSpy('onError'); playerInterface.onError = Util.spyFunc(onError); fakeNetEngine.setResponseMapAsText({'dummy://foo': manifest}); parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(fakeNetEngine.request.calls.count()).toBe(1); let error = new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.NETWORK, shaka.util.Error.Code.BAD_HTTP_STATUS); let operation = shaka.util.AbortableOperation.failed(error); fakeNetEngine.request.and.returnValue(operation); delayForUpdatePeriod(); expect(onError.calls.count()).toBe(1); }).catch(fail).then(done); PromiseMock.flush(); }); it('uses @minimumUpdatePeriod', function(done) { let lines = [ '' ]; // updateTime parameter sets @minimumUpdatePeriod in the manifest. let manifest = makeSimpleLiveManifestText(lines, updateTime); fakeNetEngine.setResponseMapAsText({'dummy://foo': manifest}); parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(fakeNetEngine.request.calls.count()).toBe(1); expect(manifest).toBeTruthy(); let partialTime = updateTime * 1000 * 3 / 4; let remainingTime = updateTime * 1000 - partialTime; jasmine.clock().tick(partialTime); PromiseMock.flush(); // Update period has not passed yet. expect(fakeNetEngine.request.calls.count()).toBe(1); jasmine.clock().tick(remainingTime); PromiseMock.flush(); // Update period has passed. expect(fakeNetEngine.request.calls.count()).toBe(2); }).catch(fail).then(done); PromiseMock.flush(); }); it('still updates when @minimumUpdatePeriod is zero', function(done) { let lines = [ '' ]; // updateTime parameter sets @minimumUpdatePeriod in the manifest. let manifest = makeSimpleLiveManifestText(lines, /* updateTime */ 0); fakeNetEngine.setResponseMapAsText({'dummy://foo': manifest}); parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(manifest).toBeTruthy(); fakeNetEngine.request.calls.reset(); let waitTimeMs = shaka.dash.DashParser['MIN_UPDATE_PERIOD_'] * 1000; jasmine.clock().tick(waitTimeMs); PromiseMock.flush(); // Update period has passed. expect(fakeNetEngine.request).toHaveBeenCalled(); }).catch(fail).then(done); PromiseMock.flush(); }); it('does not update when @minimumUpdatePeriod is missing', function(done) { let lines = [ '' ]; // updateTime parameter sets @minimumUpdatePeriod in the manifest. let manifest = makeSimpleLiveManifestText(lines, /* updateTime */ null); fakeNetEngine.setResponseMapAsText({'dummy://foo': manifest}); parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(manifest).toBeTruthy(); fakeNetEngine.request.calls.reset(); let waitTimeMs = shaka.dash.DashParser['MIN_UPDATE_PERIOD_'] * 1000; jasmine.clock().tick(waitTimeMs * 2); PromiseMock.flush(); // Even though we have waited longer than the minimum update period, // the missing attribute means "do not update". So no update should // have happened. expect(fakeNetEngine.request).not.toHaveBeenCalled(); }).catch(fail).then(done); PromiseMock.flush(); }); it('delays subsequent updates when an update is slow', function(done) { // For this test, we want Date.now() to follow the ticks of the fake clock. jasmine.clock().mockDate(); const lines = [ '' ]; const idealUpdateTime = shaka.dash.DashParser['MIN_UPDATE_PERIOD_']; const manifestText = makeSimpleLiveManifestText(lines, idealUpdateTime); fakeNetEngine.setResponseMapAsText({'dummy://foo': manifestText}); parser.start('dummy://foo', playerInterface).then((manifest) => { fakeNetEngine.request.calls.reset(); // Make the first update take a long time. const delay = fakeNetEngine.delayNextRequest(); // Wait for the update to start. jasmine.clock().tick(idealUpdateTime * 1000); PromiseMock.flush(); // Update period has passed, so an update has been requested. expect(fakeNetEngine.request).toHaveBeenCalled(); fakeNetEngine.request.calls.reset(); // Make the update take an extra 5 seconds, then end the delay. const extraWaitTimeMs = 5.0; jasmine.clock().tick(extraWaitTimeMs * 1000); delay.resolve(); PromiseMock.flush(); // No new calls, since we are still working on the same one. expect(fakeNetEngine.request).not.toHaveBeenCalled(); fakeNetEngine.request.calls.reset(); // From now on, the updates should be farther apart. jasmine.clock().tick(idealUpdateTime * 1000); PromiseMock.flush(); // The update should not have happened yet. expect(fakeNetEngine.request).not.toHaveBeenCalled(); fakeNetEngine.request.calls.reset(); // After waiting the extra time, the update request should fire. jasmine.clock().tick(extraWaitTimeMs * 1000); PromiseMock.flush(); expect(fakeNetEngine.request).toHaveBeenCalled(); }).catch(fail).then(done); PromiseMock.flush(); }); it('uses Mpd.Location', function(done) { let manifestText = [ '', ' http://foobar', ' http://foobar2', ' ', ' ', ' ', '', ' ', ' ', ' ', '' ].join('\n'); fakeNetEngine.setResponseMapAsText({'dummy://foo': manifestText}); const manifestRequest = shaka.net.NetworkingEngine.RequestType.MANIFEST; parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(fakeNetEngine.request.calls.count()).toBe(1); fakeNetEngine.expectRequest('dummy://foo', manifestRequest); fakeNetEngine.request.calls.reset(); // Create a mock so we can verify it gives two URIs. fakeNetEngine.request.and.callFake(function(type, request) { expect(type).toBe(manifestRequest); expect(request.uris).toEqual(['http://foobar', 'http://foobar2']); let data = shaka.util.StringUtils.toUTF8(manifestText); return shaka.util.AbortableOperation.completed( {uri: request.uris[0], data: data, headers: {}}); }); delayForUpdatePeriod(); expect(fakeNetEngine.request.calls.count()).toBe(1); }).catch(fail).then(done); PromiseMock.flush(); }); it('uses @suggestedPresentationDelay', function(done) { let manifest = [ '', ' ', ' ', ' ', ' http://example.com', '', ' ', ' ', ' ', '' ].join('\n'); fakeNetEngine.setResponseMapAsText({'dummy://foo': manifest}); Date.now = function() { return 600000; /* 10 minutes */ }; parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(manifest).toBeTruthy(); let 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); }).catch(fail).then(done); PromiseMock.flush(); }); describe('maxSegmentDuration', function() { it('uses @maxSegmentDuration', function(done) { let manifest = [ '', ' ', ' ', ' ', ' http://example.com', '', ' ', ' ', ' ', '' ].join('\n'); fakeNetEngine.setResponseMapAsText({'dummy://foo': manifest}); Date.now = function() { return 600000; /* 10 minutes */ }; parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(manifest).toBeTruthy(); let timeline = manifest.presentationTimeline; expect(timeline).toBeTruthy(); expect(timeline.getSegmentAvailabilityStart()).toBe(165); expect(timeline.getSegmentAvailabilityEnd()).toBe(285); }).catch(fail).then(done); PromiseMock.flush(); }); it('derived from SegmentTemplate w/ SegmentTimeline', function(done) { let lines = [ '', ' ', ' ', ' ', ' ', ' ', '' ]; testDerived(lines, done); }); it('derived from SegmentTemplate w/ @duration', function(done) { let lines = [ '' ]; testDerived(lines, done); }); it('derived from SegmentList', function(done) { let lines = [ '', ' ', ' ', '' ]; testDerived(lines, done); }); it('derived from SegmentList w/ SegmentTimeline', function(done) { let lines = [ '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '' ]; testDerived(lines, done); }); function testDerived(lines, done) { let template = [ '', ' ', ' ', ' ', ' http://example.com', '%(contents)s', ' ', ' ', ' ', '' ].join('\n'); let manifest = sprintf(template, {contents: lines.join('\n')}); fakeNetEngine.setResponseMapAsText({'dummy://foo': manifest}); Date.now = function() { return 600000; /* 10 minutes */ }; parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(manifest).toBeTruthy(); let timeline = manifest.presentationTimeline; expect(timeline).toBeTruthy(); // NOTE: the largest segment is 8 seconds long in each test. expect(timeline.getSegmentAvailabilityStart()).toBe(172); expect(timeline.getSegmentAvailabilityEnd()).toBe(292); }).catch(fail).then(done); PromiseMock.flush(); } }); describe('stop', function() { const manifestRequestType = shaka.net.NetworkingEngine.RequestType.MANIFEST; const dateRequestType = shaka.net.NetworkingEngine.RequestType.MANIFEST; const manifestUri = 'dummy://foo'; const dateUri = 'http://foo.bar/date'; beforeEach(function() { let manifest = [ '', ' ', ' ', ' ', ' ', ' ', ' http://example.com', ' ', ' ', ' ', ' ', '' ].join('\n'); fakeNetEngine.setResponseMapAsText({ 'http://foo.bar/date': '1970-01-01T00:00:30Z', 'dummy://foo': manifest }); }); it('stops updates', function(done) { parser.start(manifestUri, playerInterface) .then(function(manifest) { fakeNetEngine.expectRequest(manifestUri, manifestRequestType); fakeNetEngine.request.calls.reset(); parser.stop(); delayForUpdatePeriod(); expect(fakeNetEngine.request).not.toHaveBeenCalled(); }).catch(fail).then(done); PromiseMock.flush(); }); it('stops initial parsing', function(done) { parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(manifest).toBe(null); fakeNetEngine.expectRequest(manifestUri, manifestRequestType); fakeNetEngine.request.calls.reset(); delayForUpdatePeriod(); // An update should not occur. expect(fakeNetEngine.request).not.toHaveBeenCalled(); }).catch(fail).then(done); // 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.calls.count()).toBe(1); parser.stop(); PromiseMock.flush(); }); it('interrupts manifest updates', function(done) { parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(manifest).toBeTruthy(); fakeNetEngine.expectRequest(manifestUri, manifestRequestType); fakeNetEngine.request.calls.reset(); let delay = fakeNetEngine.delayNextRequest(); delayForUpdatePeriod(); // The request was made but should not be resolved yet. expect(fakeNetEngine.request.calls.count()).toBe(1); fakeNetEngine.expectRequest(manifestUri, manifestRequestType); fakeNetEngine.request.calls.reset(); parser.stop(); delay.resolve(); PromiseMock.flush(); // Wait for another update period. delayForUpdatePeriod(); // A second update should not occur. expect(fakeNetEngine.request).not.toHaveBeenCalled(); }).catch(fail).then(done); PromiseMock.flush(); }); it('interrupts UTCTiming requests', function(done) { /** @type {!shaka.util.PublicPromise} */ let delay = fakeNetEngine.delayNextRequest(); Util.delay(0.2, realTimeout).then(function() { // This is the initial manifest request. expect(fakeNetEngine.request.calls.count()).toBe(1); fakeNetEngine.expectRequest(manifestUri, manifestRequestType); fakeNetEngine.request.calls.reset(); // Resolve the manifest request and wait on the UTCTiming request. delay.resolve(); delay = fakeNetEngine.delayNextRequest(); return Util.delay(0.2, realTimeout); }).then(function() { // This is the first UTCTiming request. expect(fakeNetEngine.request.calls.count()).toBe(1); fakeNetEngine.expectRequest(dateUri, dateRequestType); fakeNetEngine.request.calls.reset(); // Interrupt the parser, then fail the request. parser.stop(); delay.reject(); return Util.delay(0.1, realTimeout); }).then(function() { // Wait for another update period. delayForUpdatePeriod(); // No more updates should occur. expect(fakeNetEngine.request).not.toHaveBeenCalled(); }).catch(fail).then(done); parser.start('dummy://foo', playerInterface).catch(fail); PromiseMock.flush(); }); }); describe('SegmentTemplate w/ SegmentTimeline', function() { let basicLines = [ '', ' ', ' ', ' ', ' ', ' ', '' ]; let basicRefs = [ shaka.test.ManifestParser.makeReference('s1.mp4', 1, 0, 10, originalUri), shaka.test.ManifestParser.makeReference('s2.mp4', 2, 10, 15, originalUri), shaka.test.ManifestParser.makeReference('s3.mp4', 3, 15, 30, originalUri) ]; let updateLines = [ '', ' ', ' ', ' ', ' ', ' ', ' ', '' ]; let updateRefs = [ shaka.test.ManifestParser.makeReference('s1.mp4', 1, 0, 10, originalUri), shaka.test.ManifestParser.makeReference('s2.mp4', 2, 10, 15, originalUri), shaka.test.ManifestParser.makeReference('s3.mp4', 3, 15, 30, originalUri), shaka.test.ManifestParser.makeReference('s4.mp4', 4, 30, 40, originalUri) ]; let partialUpdateLines = [ '', ' ', ' ', ' ', ' ', '' ]; testCommonBehaviors( basicLines, basicRefs, updateLines, updateRefs, partialUpdateLines); }); describe('SegmentList w/ SegmentTimeline', function() { let basicLines = [ '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '' ]; let basicRefs = [ shaka.test.ManifestParser.makeReference('s1.mp4', 1, 0, 10, originalUri), shaka.test.ManifestParser.makeReference('s2.mp4', 2, 10, 15, originalUri), shaka.test.ManifestParser.makeReference('s3.mp4', 3, 15, 30, originalUri) ]; let updateLines = [ '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '' ]; let updateRefs = [ shaka.test.ManifestParser.makeReference('s1.mp4', 1, 0, 10, originalUri), shaka.test.ManifestParser.makeReference('s2.mp4', 2, 10, 15, originalUri), shaka.test.ManifestParser.makeReference('s3.mp4', 3, 15, 30, originalUri), shaka.test.ManifestParser.makeReference('s4.mp4', 4, 30, 40, originalUri) ]; let partialUpdateLines = [ '', ' ', ' ', ' ', ' ', ' ', ' ', '' ]; testCommonBehaviors( basicLines, basicRefs, updateLines, updateRefs, partialUpdateLines); }); describe('SegmentList w/ @duration', function() { let basicLines = [ '', ' ', ' ', ' ', '' ]; let basicRefs = [ shaka.test.ManifestParser.makeReference('s1.mp4', 1, 0, 10, originalUri), shaka.test.ManifestParser.makeReference('s2.mp4', 2, 10, 20, originalUri), shaka.test.ManifestParser.makeReference('s3.mp4', 3, 20, 30, originalUri) ]; let updateLines = [ '', ' ', ' ', ' ', ' ', '' ]; let updateRefs = [ shaka.test.ManifestParser.makeReference('s1.mp4', 1, 0, 10, originalUri), shaka.test.ManifestParser.makeReference('s2.mp4', 2, 10, 20, originalUri), shaka.test.ManifestParser.makeReference('s3.mp4', 3, 20, 30, originalUri), shaka.test.ManifestParser.makeReference('s4.mp4', 4, 30, 40, originalUri) ]; let partialUpdateLines = [ '', ' ', ' ', '' ]; testCommonBehaviors( basicLines, basicRefs, updateLines, updateRefs, partialUpdateLines); }); describe('SegmentTemplate w/ duration', function() { let templateLines = [ '' ]; it('produces sane references without assertions', function(done) { let manifest = makeSimpleLiveManifestText(templateLines, updateTime); fakeNetEngine.setResponseMapAsText({'dummy://foo': manifest}); parser.start('dummy://foo', playerInterface).then(function(manifest) { expect(manifest.periods.length).toBe(1); let stream = manifest.periods[0].variants[0].video; // In https://github.com/google/shaka-player/issues/1204, this // failed an assertion and returned endTime == 0. let ref = stream.getSegmentReference(1); expect(ref.endTime).toBeGreaterThan(0); }).catch(fail).then(done); PromiseMock.flush(); }); }); describe('EventStream', function() { const originalManifest = [ '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '' ].join('\n'); /** @type {!jasmine.Spy} */ let onTimelineRegionAddedSpy; beforeEach(function() { onTimelineRegionAddedSpy = jasmine.createSpy('onTimelineRegionAdded'); playerInterface.onTimelineRegionAdded = shaka.test.Util.spyFunc(onTimelineRegionAddedSpy); }); it('will parse EventStream nodes', function(done) { fakeNetEngine.setResponseMapAsText({'dummy://foo': originalManifest}); parser.start('dummy://foo', playerInterface) .then(function(manifest) { 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) }); }) .catch(fail) .then(done); PromiseMock.flush(); }); it('will add timeline regions on manifest update', function(done) { let newManifest = [ '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '' ].join('\n'); fakeNetEngine.setResponseMapAsText({'dummy://foo': originalManifest}); parser.start('dummy://foo', playerInterface) .then(function(manifest) { expect(onTimelineRegionAddedSpy).toHaveBeenCalledTimes(2); onTimelineRegionAddedSpy.calls.reset(); fakeNetEngine.setResponseMapAsText({'dummy://foo': newManifest}); delayForUpdatePeriod(); expect(onTimelineRegionAddedSpy).toHaveBeenCalledTimes(1); }) .catch(fail) .then(done); PromiseMock.flush(); }); it('will not let an event exceed the Period duration', function(done) { let newManifest = [ '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '' ].join('\n'); fakeNetEngine.setResponseMapAsText({'dummy://foo': newManifest}); parser.start('dummy://foo', playerInterface) .then(function(manifest) { 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})); }) .catch(fail) .then(done); PromiseMock.flush(); }); }); });