Files
shaka-player/spec/mpd_processor_spec.js
T
Jacob Trimble 899c322995 Added support for multiple BaseURL elements.
Added a new class called FailoverUri which handles multiple URLs and
switches to others when one fails.  This is also handles the request
itself.  This replaces SegmentMetadata as well as a number of manual
requests.

The MPD parser now produces arrays of URIs rather than just one.  The
MPD Processor then converts it to a FailoverUri inside the manifest.

Added unit tests to test the new functionality and updated the old ones
to the new changes.

This does not support failover on subtitles since subtitles are handled
by the browser.

Closes #68

Change-Id: I5410104827b9e4102b243444b1b5a3f01dcaf10d
2015-08-10 16:01:06 -07:00

1013 lines
30 KiB
JavaScript

/**
* Copyright 2014 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.
*
* @fileoverview mpd_processor.js unit tests.
*/
goog.require('shaka.dash.MpdProcessor');
goog.require('shaka.dash.mpd');
goog.require('shaka.player.Player');
goog.require('shaka.util.Clock');
describe('MpdProcessor', function() {
var mpd;
var processor;
beforeAll(function() {
// Hijack assertions and convert failed assertions into failed tests.
assertsToFailures.install();
});
afterAll(function() {
assertsToFailures.uninstall();
});
beforeEach(function() {
mpd = shaka.dash.mpd;
processor = new shaka.dash.MpdProcessor(null);
});
describe('validateSegmentInfo_', function() {
var m;
var p;
var as;
var r;
var sb;
var sl;
var st;
beforeEach(function() {
m = new mpd.Mpd();
p = new mpd.Period();
as = new mpd.AdaptationSet();
r = new mpd.Representation();
sb = new mpd.SegmentBase();
sl = new mpd.SegmentList();
st = new mpd.SegmentTemplate();
});
it('handles a single SegmentBase', function() {
r.segmentBase = sb;
as.representations.push(r);
p.adaptationSets.push(as);
m.periods.push(p);
processor.validateSegmentInfo_(m);
expect(r.segmentBase).not.toBeNull();
expect(r.segmentList).toBeNull();
expect(r.segmentTemplate).toBeNull();
});
it('handles a single SegmentList', function() {
r.segmentList = sl;
as.representations.push(r);
p.adaptationSets.push(as);
m.periods.push(p);
processor.validateSegmentInfo_(m);
expect(r.segmentBase).toBeNull();
expect(r.segmentList).not.toBeNull();
expect(r.segmentTemplate).toBeNull();
});
it('handles a single SegmentTemplate', function() {
r.segmentTemplate = st;
as.representations.push(r);
p.adaptationSets.push(as);
m.periods.push(p);
processor.validateSegmentInfo_(m);
expect(r.segmentBase).toBeNull();
expect(r.segmentList).toBeNull();
expect(r.segmentTemplate).not.toBeNull();
});
it('handles a SegmentBase and a SegmentList', function() {
r.segmentBase = sb;
r.segmentList = sl;
as.representations.push(r);
p.adaptationSets.push(as);
m.periods.push(p);
// SegmentList should be removed.
processor.validateSegmentInfo_(m);
expect(r.segmentBase).not.toBeNull();
expect(r.segmentList).toBeNull();
expect(r.segmentTemplate).toBeNull();
});
it('handles a SegmentBase and a SegmentTemplate', function() {
r.segmentBase = sb;
r.segmentTemplate = st;
as.representations.push(r);
p.adaptationSets.push(as);
m.periods.push(p);
// SegmentTemplate should be removed.
processor.validateSegmentInfo_(m);
expect(r.segmentBase).not.toBeNull();
expect(r.segmentList).toBeNull();
expect(r.segmentTemplate).toBeNull();
});
it('handles a SegmentList and a SegmentTemplate', function() {
r.segmentList = sl;
r.segmentTemplate = st;
as.representations.push(r);
p.adaptationSets.push(as);
m.periods.push(p);
// SegmentTemplate should be removed.
processor.validateSegmentInfo_(m);
expect(r.segmentBase).toBeNull();
expect(r.segmentList).not.toBeNull();
expect(r.segmentTemplate).toBeNull();
});
it('handles SegmentBase, SegmentList, and SegmentTemplate', function() {
r.segmentBase = sb;
r.segmentList = sl;
r.segmentTemplate = st;
as.representations.push(r);
p.adaptationSets.push(as);
m.periods.push(p);
// SegmentList and SegmentTemplate should be removed.
processor.validateSegmentInfo_(m);
expect(r.segmentBase).not.toBeNull();
expect(r.segmentList).toBeNull();
expect(r.segmentTemplate).toBeNull();
});
it('handles no SegmentBase, SegmentList, or SegmentTemplate', function() {
as.representations.push(r);
p.adaptationSets.push(as);
m.periods.push(p);
// The Representation should be removed.
processor.validateSegmentInfo_(m);
expect(as.representations.length).toBe(0);
});
}); // describe validateSegmentInfo_
describe('createStreamInfoFromSegmentTemplate_', function() {
var m;
var p;
var as;
var r1;
var r2;
var st;
var manifestInfo;
beforeEach(function() {
m = new mpd.Mpd();
p = new mpd.Period();
as = new mpd.AdaptationSet();
r1 = new mpd.Representation();
r2 = new mpd.Representation();
st = new mpd.SegmentTemplate();
r1.segmentTemplate = st;
r2.segmentTemplate = st;
as.group = 1;
as.segmentTemplate = st;
as.representations.push(r1);
as.representations.push(r2);
p.start = 0;
p.adaptationSets.push(as);
m.periods.push(p);
});
afterEach(function() {
if (manifestInfo) {
manifestInfo.destroy();
manifestInfo = null;
}
});
it('index URL template', function() {
st.mediaUrlTemplate = 'http://example.com/$Bandwidth$-media.mp4';
st.indexUrlTemplate = 'http://example.com/$Bandwidth$-index.sidx';
st.initializationUrlTemplate = 'http://example.com/$Bandwidth$-init.mp4';
r1.baseUrl = [new goog.Uri('http://example.com/')];
r1.bandwidth = 250000;
r1.mimeType = 'video/mp4';
r2.baseUrl = [new goog.Uri('http://example.com/')];
r2.bandwidth = 500000;
r2.mimeType = 'video/mp4';
manifestInfo = processor.createManifestInfo_(m, 1000);
var periodInfo = manifestInfo.periodInfos[0];
expect(periodInfo).toBeTruthy();
// Check the first StreamInfo.
var si1 = periodInfo.streamSetInfos[0].streamInfos[0];
expect(si1).toBeTruthy();
expect(si1.bandwidth).toBe(250000);
var indexMetadata1 = si1.segmentIndexSource.indexMetadata_;
expect(indexMetadata1).toBeTruthy();
expect(indexMetadata1.urls).toBeTruthy();
expect(indexMetadata1.urls[0].toString())
.toBe('http://example.com/250000-index.sidx');
expect(indexMetadata1.startByte).toBe(0);
expect(indexMetadata1.endByte).toBeNull();
var initMetadata1 = si1.segmentInitSource.metadata_;
expect(initMetadata1.urls).toBeTruthy();
expect(initMetadata1.urls[0].toString())
.toBe('http://example.com/250000-init.mp4');
expect(initMetadata1.startByte).toBe(0);
expect(initMetadata1.endByte).toBeNull();
// Check the second StreamInfo.
var si2 = periodInfo.streamSetInfos[0].streamInfos[1];
expect(si2).toBeTruthy();
expect(si2.bandwidth).toBe(500000);
var indexMetadata2 = si2.segmentIndexSource.indexMetadata_;
expect(indexMetadata2).toBeTruthy();
expect(indexMetadata2.urls).toBeTruthy();
expect(indexMetadata2.urls[0].toString())
.toBe('http://example.com/500000-index.sidx');
expect(indexMetadata2.startByte).toBe(0);
expect(indexMetadata2.endByte).toBeNull();
var initMetadata2 = si2.segmentInitSource.metadata_;
expect(initMetadata2.urls).toBeTruthy();
expect(initMetadata2.urls[0].toString())
.toBe('http://example.com/500000-init.mp4');
expect(initMetadata2.startByte).toBe(0);
expect(initMetadata2.endByte).toBeNull();
});
it('SegmentTimeline w/o start times', function(done) {
var tp1 = new mpd.SegmentTimePoint();
tp1.duration = 9000 * 10;
tp1.repeat = 1;
var tp2 = new mpd.SegmentTimePoint();
tp2.duration = 9000 * 20;
tp2.repeat = 0;
var timeline = new mpd.SegmentTimeline();
timeline.timePoints.push(tp1);
timeline.timePoints.push(tp2);
st.timescale = 9000;
st.presentationTimeOffset = 0;
st.segmentDuration = null;
st.startNumber = 1;
st.mediaUrlTemplate = '$Number$-$Time$-$Bandwidth$-media.mp4';
st.initializationUrlTemplate = '$Bandwidth$-init.mp4';
st.timeline = timeline;
r1.baseUrl = [new goog.Uri('http://example.com/')];
r1.bandwidth = 250000;
r2.baseUrl = [new goog.Uri('http://example.com/')];
r2.bandwidth = 500000;
manifestInfo = processor.createManifestInfo_(m, 1000);
var periodInfo = manifestInfo.periodInfos[0];
expect(periodInfo).toBeTruthy();
// Check the first StreamInfo.
var si1 = periodInfo.streamSetInfos[0].streamInfos[0];
expect(si1).toBeTruthy();
var initMetadata1 = si1.segmentInitSource.metadata_;
expect(initMetadata1).toBeTruthy();
expect(initMetadata1.urls).toBeTruthy();
expect(initMetadata1.urls[0].toString())
.toBe('http://example.com/250000-init.mp4');
expect(initMetadata1.startByte).toBe(0);
expect(initMetadata1.endByte).toBe(null);
si1.segmentIndexSource.create().then(function(segmentIndex1) {
var references1 = segmentIndex1.references;
expect(references1).toBeTruthy();
expect(references1.length).toBe(3);
checkReference(
references1[0],
'http://example.com/1-0-250000-media.mp4',
0, 10);
checkReference(
references1[1],
'http://example.com/2-90000-250000-media.mp4',
10, 20);
checkReference(
references1[2],
'http://example.com/3-180000-250000-media.mp4',
20, 40);
// Check the second StreamInfo.
var si2 = periodInfo.streamSetInfos[0].streamInfos[1];
expect(si2).toBeTruthy();
var initMetadata2 = si2.segmentInitSource.metadata_;
expect(initMetadata2).toBeTruthy();
expect(initMetadata2.urls).toBeTruthy();
expect(initMetadata2.urls[0].toString())
.toBe('http://example.com/500000-init.mp4');
expect(initMetadata2.startByte).toBe(0);
expect(initMetadata2.endByte).toBe(null);
return si2.segmentIndexSource.create();
}).then(function(segmentIndex2) {
var references2 = segmentIndex2.references;
expect(references2).toBeTruthy();
expect(references2.length).toBe(3);
checkReference(
references2[0],
'http://example.com/1-0-500000-media.mp4',
0, 10);
checkReference(
references2[1],
'http://example.com/2-90000-500000-media.mp4',
10, 20);
checkReference(
references2[2],
'http://example.com/3-180000-500000-media.mp4',
20, 40);
done();
});
});
it('SegmentTimeline w/ start times', function(done) {
var tp1 = new mpd.SegmentTimePoint();
tp1.startTime = 9000 * 100;
tp1.duration = 9000 * 10;
tp1.repeat = 1;
var tp2 = new mpd.SegmentTimePoint();
tp2.startTime = (9000 * 100) + (9000 * 20);
tp2.duration = 9000 * 20;
tp2.repeat = 0;
var timeline = new mpd.SegmentTimeline();
timeline.timePoints.push(tp1);
timeline.timePoints.push(tp2);
st.timescale = 9000;
st.presentationTimeOffset = 0;
st.segmentDuration = null;
st.startNumber = 10;
st.mediaUrlTemplate = '$Number$-$Time$-$Bandwidth$-media.mp4';
st.initializationUrlTemplate = '$Bandwidth$-init.mp4';
st.timeline = timeline;
r1.baseUrl = [new goog.Uri('http://example.com/')];
r1.bandwidth = 250000;
// Only process the first Representation.
as.representations.splice(1, 1);
manifestInfo = processor.createManifestInfo_(m, 1000);
var periodInfo = manifestInfo.periodInfos[0];
expect(periodInfo).toBeTruthy();
// Check the first StreamInfo.
var si1 = periodInfo.streamSetInfos[0].streamInfos[0];
expect(si1).toBeTruthy();
var initMetadata1 = si1.segmentInitSource.metadata_;
expect(initMetadata1).toBeTruthy();
expect(initMetadata1.urls).toBeTruthy();
expect(initMetadata1.urls[0].toString())
.toBe('http://example.com/250000-init.mp4');
expect(initMetadata1.startByte).toBe(0);
expect(initMetadata1.endByte).toBe(null);
si1.segmentIndexSource.create().then(function(segmentIndex1) {
var references1 = segmentIndex1.references;
expect(references1).toBeTruthy();
expect(references1.length).toBe(3);
checkReference(
references1[0],
'http://example.com/10-900000-250000-media.mp4',
100, 110);
checkReference(
references1[1],
'http://example.com/11-990000-250000-media.mp4',
110, 120);
checkReference(
references1[2],
'http://example.com/12-1080000-250000-media.mp4',
120, 140);
done();
});
});
it('segment duration w/o AST', function(done) {
st.timescale = 9000;
st.presentationTimeOffset = 0;
st.segmentDuration = 9000 * 10;
st.startNumber = 5;
st.mediaUrlTemplate = '$Number$-$Time$-$Bandwidth$-media.mp4';
st.initializationUrlTemplate = '$Bandwidth$-init.mp4';
r1.baseUrl = [new goog.Uri('http://example.com/')];
r1.bandwidth = 250000;
r2.baseUrl = [new goog.Uri('http://example.com/')];
r2.bandwidth = 500000;
// If the MPD is static and uses segment duration then there must be
// an explicit Period duration.
p.duration = 30;
manifestInfo = processor.createManifestInfo_(m, 1000);
var periodInfo = manifestInfo.periodInfos[0];
expect(periodInfo).toBeTruthy();
// Check the first StreamInfo.
var si1 = periodInfo.streamSetInfos[0].streamInfos[0];
expect(si1).toBeTruthy();
var initMetadata1 = si1.segmentInitSource.metadata_;
expect(initMetadata1).toBeTruthy();
expect(initMetadata1.urls).toBeTruthy();
expect(initMetadata1.urls[0].toString())
.toBe('http://example.com/250000-init.mp4');
expect(initMetadata1.startByte).toBe(0);
expect(initMetadata1.endByte).toBe(null);
si1.segmentIndexSource.create().then(function(segmentIndex1) {
var references1 = segmentIndex1.references;
expect(references1).toBeTruthy();
expect(references1.length).toBe(3);
checkReference(
references1[0],
'http://example.com/5-0-250000-media.mp4',
0, 10);
checkReference(
references1[1],
'http://example.com/6-90000-250000-media.mp4',
10, 20);
checkReference(
references1[2],
'http://example.com/7-180000-250000-media.mp4',
20, 30);
// Check the second StreamInfo.
var si2 = periodInfo.streamSetInfos[0].streamInfos[1];
expect(si2).toBeTruthy();
var initMetadata2 = si2.segmentInitSource.metadata_;
expect(initMetadata2).toBeTruthy();
expect(initMetadata2.urls).toBeTruthy();
expect(initMetadata2.urls[0].toString())
.toBe('http://example.com/500000-init.mp4');
expect(initMetadata2.startByte).toBe(0);
expect(initMetadata2.endByte).toBe(null);
return si2.segmentIndexSource.create();
}).then(function(segmentIndex2) {
var references2 = segmentIndex2.references;
expect(references2).toBeTruthy();
expect(references2.length).toBe(3);
checkReference(
references2[0],
'http://example.com/5-0-500000-media.mp4',
0, 10);
checkReference(
references2[1],
'http://example.com/6-90000-500000-media.mp4',
10, 20);
checkReference(
references2[2],
'http://example.com/7-180000-500000-media.mp4',
20, 30);
done();
});
});
it('segment duration w/ AST', function(done) {
st.timescale = 9000;
st.presentationTimeOffset = 0;
st.segmentDuration = 9000 * 10;
st.startNumber = 5; // Ensure startNumber > 1 works.
st.mediaUrlTemplate = '$Number$-$Time$-$Bandwidth$-media.mp4';
st.initializationUrlTemplate = '$Bandwidth$-init.mp4';
r1.baseUrl = [new goog.Uri('http://example.com/')];
r1.bandwidth = 250000;
// Only process the first Representation.
as.representations.splice(1, 1);
// Hijack shaka.util.Clock.now()
var originalNow = shaka.util.Clock.now;
var manifestCreationTime = Math.round(originalNow() / 1000.0);
shaka.util.Clock.now = function() {
return 1000.0 * manifestCreationTime;
};
var secondsSinceStart = 60 * 60;
m.type = 'dynamic';
m.url = [new goog.Uri('http://example.com/')];
m.availabilityStartTime = manifestCreationTime - secondsSinceStart;
m.suggestedPresentationDelay =
shaka.dash.mpd.DEFAULT_SUGGESTED_PRESENTATION_DELAY_;
m.timeShiftBufferDepth = 60;
m.minBufferTime = 0;
manifestInfo = processor.createManifestInfo_(m, manifestCreationTime);
var periodInfo = manifestInfo.periodInfos[0];
expect(periodInfo).toBeTruthy();
var scaledSegmentDuration = st.segmentDuration / st.timescale;
// The first segment is the earliest available one.
var earliestSegmentNumber =
Math.ceil((secondsSinceStart -
(2 * scaledSegmentDuration) -
m.timeShiftBufferDepth) / scaledSegmentDuration) + 1;
var latestAvailableTimestamp = secondsSinceStart - scaledSegmentDuration;
var latestAvailableSegmentStartTime =
Math.floor(latestAvailableTimestamp / scaledSegmentDuration) *
scaledSegmentDuration;
var currentSegmentNumber =
Math.floor((latestAvailableSegmentStartTime -
m.suggestedPresentationDelay) /
scaledSegmentDuration) + 1;
var expectedNumSegments =
currentSegmentNumber - earliestSegmentNumber + 1;
// Check the first StreamInfo.
var si1 = periodInfo.streamSetInfos[0].streamInfos[0];
expect(si1).toBeTruthy();
si1.segmentIndexSource.create().then(function(segmentIndex) {
var references = segmentIndex.references;
expect(references).toBeTruthy();
expect(references.length).toBe(expectedNumSegments);
for (var segmentNumber = earliestSegmentNumber;
segmentNumber - earliestSegmentNumber < expectedNumSegments;
++segmentNumber) {
var expectedNumberReplacement = (segmentNumber - 1) + st.startNumber;
var expectedTimeReplacement =
(segmentNumber - 1) * st.segmentDuration;
var expectedUrl = 'http://example.com/' +
expectedNumberReplacement + '-' +
expectedTimeReplacement + '-250000-media.mp4';
var expectedStartTime = (segmentNumber - 1) * st.segmentDuration;
var expectedScaledStartTime = expectedStartTime / st.timescale;
checkReference(
references[segmentNumber - earliestSegmentNumber],
expectedUrl,
expectedScaledStartTime, expectedScaledStartTime + 10);
}
// Replace fake now().
shaka.util.Clock.now = originalNow;
done();
});
});
it('SegmentTimeline w/ gaps', function(done) {
var tp1 = new mpd.SegmentTimePoint();
tp1.startTime = 10;
tp1.duration = 10;
var tp2 = new mpd.SegmentTimePoint();
tp2.startTime = 21;
tp2.duration = 10;
var tp3 = new mpd.SegmentTimePoint();
tp3.startTime = 32;
tp3.duration = 10;
var timeline = new mpd.SegmentTimeline();
timeline.timePoints.push(tp1);
timeline.timePoints.push(tp2);
timeline.timePoints.push(tp3);
st.timescale = 1;
st.presentationTimeOffset = 0;
st.segmentDuration = null;
st.startNumber = 1;
st.mediaUrlTemplate = '$Number$-$Time$-$Bandwidth$-media.mp4';
st.timeline = timeline;
r1.baseUrl = [new goog.Uri('http://example.com/')];
r1.bandwidth = 250000;
// Only process the first Representation.
as.representations.splice(1, 1);
manifestInfo = processor.createManifestInfo_(m, 1000);
var periodInfo = manifestInfo.periodInfos[0];
expect(periodInfo).toBeTruthy();
// Check the first StreamInfo.
var si1 = periodInfo.streamSetInfos[0].streamInfos[0];
expect(si1).toBeTruthy();
si1.segmentIndexSource.create().then(function(segmentIndex) {
var references = segmentIndex.references;
expect(references).toBeTruthy();
expect(references.length).toBe(3);
// Duration should stretch to the beginning of the next segment.
checkReference(
references[0],
'http://example.com/1-10-250000-media.mp4',
10, 21);
// Duration should stretch to the beginning of the next segment.
checkReference(
references[1],
'http://example.com/2-21-250000-media.mp4',
21, 32);
checkReference(
references[2],
'http://example.com/3-32-250000-media.mp4',
32, 42);
done();
});
});
it('SegmentTimeline w/ overlaps', function(done) {
var tp1 = new mpd.SegmentTimePoint();
tp1.startTime = 10;
tp1.duration = 11;
var tp2 = new mpd.SegmentTimePoint();
tp2.startTime = 20;
tp2.duration = 10;
var tp3 = new mpd.SegmentTimePoint();
tp3.startTime = 29;
tp3.duration = 10;
var timeline = new mpd.SegmentTimeline();
timeline.timePoints.push(tp1);
timeline.timePoints.push(tp2);
timeline.timePoints.push(tp3);
st.timescale = 1;
st.presentationTimeOffset = 0;
st.segmentDuration = null;
st.startNumber = 1;
st.mediaUrlTemplate = '$Number$-$Time$-$Bandwidth$-media.mp4';
st.timeline = timeline;
r1.baseUrl = [new goog.Uri('http://example.com/')];
r1.bandwidth = 250000;
// Only process the first Representation.
as.representations.splice(1, 1);
manifestInfo = processor.createManifestInfo_(m, 1000);
var periodInfo = manifestInfo.periodInfos[0];
expect(periodInfo).toBeTruthy();
// Check the first StreamInfo.
var si1 = periodInfo.streamSetInfos[0].streamInfos[0];
expect(si1).toBeTruthy();
si1.segmentIndexSource.create().then(function(segmentIndex) {
var references = segmentIndex.references;
expect(references).toBeTruthy();
expect(references.length).toBe(3);
// Duration should compress to the beginning of the next segment.
checkReference(
references[0],
'http://example.com/1-10-250000-media.mp4',
10, 20);
// Duration should compress to the beginning of the next segment.
checkReference(
references[1],
'http://example.com/2-20-250000-media.mp4',
20, 29);
checkReference(
references[2],
'http://example.com/3-29-250000-media.mp4',
29, 39);
done();
});
});
}); // describe createStreamInfoFromSegmentTemplate_
describe('process', function() {
var originalIsTypeSupported;
var manifestInfo;
beforeAll(function() {
// For the purposes of these tests, we will avoid querying the browser's
// format and codec support and pretend we support everything. This way,
// we will do all processing other than removal of unsupported formats.
originalIsTypeSupported = shaka.player.Player.isTypeSupported;
shaka.player.Player.isTypeSupported = function() { return true; };
});
afterEach(function() {
if (manifestInfo) {
manifestInfo.destroy();
manifestInfo = null;
}
});
afterAll(function() {
// Restore isTypeSupported.
shaka.player.Player.isTypeSupported = originalIsTypeSupported;
});
describe('SegmentTemplate', function() {
var m;
var p;
var as;
var r;
var st;
beforeEach(function() {
m = new mpd.Mpd();
p = new mpd.Period();
as = new mpd.AdaptationSet();
r = new mpd.Representation();
st = new mpd.SegmentTemplate();
r.segmentTemplate = st;
r.baseUrl = [new goog.Uri('http://example.com')];
r.bandwidth = 250000;
r.mimeType = 'video/mp4';
as.group = 1;
as.representations.push(r);
p.adaptationSets.push(as);
m.periods.push(p);
m.url = [new goog.Uri('http://example.com/mpd')];
});
it('allows MPD and segment duration', function(done) {
st.mediaUrlTemplate = '$Number$-$Bandwidth$-media.mp4';
st.timescale = 9000;
st.segmentDuration = 90000;
p.start = 0;
m.mediaPresentationDuration = 100;
manifestInfo = processor.process(m);
var periodInfo = manifestInfo.periodInfos[0];
var si1 = periodInfo.streamSetInfos[0].streamInfos[0];
si1.segmentIndexSource.create().then(function(segmentIndex) {
var references = segmentIndex.references;
expect(references.length).toBe(10);
done();
});
});
it('allows Period and segment duration', function(done) {
st.mediaUrlTemplate = '$Number$-$Bandwidth$-media.mp4';
st.timescale = 9000;
st.segmentDuration = 90000;
p.start = 0;
p.duration = 100;
manifestInfo = processor.process(m);
var periodInfo = manifestInfo.periodInfos[0];
var si1 = periodInfo.streamSetInfos[0].streamInfos[0];
si1.segmentIndexSource.create().then(function(segmentIndex) {
var references = segmentIndex.references;
expect(references.length).toBe(10);
done();
});
});
it('derives period duration from MPD', function() {
st.mediaUrlTemplate = '$Number$-$Bandwidth$-media.mp4';
st.timescale = 9000;
st.segmentDuration = 90000;
p.start = 0;
m.mediaPresentationDuration = 100;
processor.process(m);
expect(p.duration).toBe(100);
});
// Live content is not dependent on using SegmentTemplate but it is the
// most common use case.
it('creates live manifest w/ Location element', function() {
m.type = 'dynamic';
m.minUpdatePeriod = 10;
m.updateLocation = [new goog.Uri('http://example.com/updated_mpd')];
manifestInfo = processor.process(m);
expect(manifestInfo.live).toBe(true);
expect(manifestInfo.updatePeriod).toBe(10);
expect(manifestInfo.updateUrl.toString()).toBe(
'http://example.com/updated_mpd');
});
it('creates live manifest w/o Location element', function() {
m.type = 'dynamic';
m.minUpdatePeriod = 10;
manifestInfo = processor.process(m);
expect(manifestInfo.live).toBe(true);
expect(manifestInfo.updatePeriod).toBe(10);
expect(manifestInfo.updateUrl.toString()).toBe(
'http://example.com/mpd');
});
}); // describe SegmentTemplate
describe('SegmentList', function() {
var m;
var p;
var as;
var r;
var sl;
beforeEach(function() {
m = new mpd.Mpd();
p = new mpd.Period();
as = new mpd.AdaptationSet();
r = new mpd.Representation();
sl = new mpd.SegmentList();
p.duration = 100;
r.segmentList = sl;
r.baseUrl = [new goog.Uri('http://example.com')];
r.bandwidth = 250000;
r.mimeType = 'video/mp4';
as.group = 1;
as.representations.push(r);
p.adaptationSets.push(as);
m.periods.push(p);
});
it('allows no segment duration with one segment', function(done) {
sl.timescale = 9000;
// Add just one SegmentUrl.
var segmentUrl = new shaka.dash.mpd.SegmentUrl();
segmentUrl.mediaUrl = [new goog.Uri('http://example.com/video.mp4')];
sl.segmentUrls.push(segmentUrl);
p.start = 0;
manifestInfo = processor.process(m);
var periodInfo = manifestInfo.periodInfos[0];
var si1 = periodInfo.streamSetInfos[0].streamInfos[0];
si1.segmentIndexSource.create().then(function(segmentIndex) {
var references = segmentIndex.references;
expect(references.length).toBe(1);
checkReference(
references[0],
'http://example.com/video.mp4',
0, 100);
done();
});
});
it('disallows no segment duration with multiple segments', function() {
sl.timescale = 9000;
// Add two SegmentUrls, which isn't allowed.
var segmentUrl1 = new shaka.dash.mpd.SegmentUrl();
segmentUrl1.mediaUrl = [new goog.Uri('http://example.com/video-1.mp4')];
var segmentUrl2 = new shaka.dash.mpd.SegmentUrl();
segmentUrl2.mediaUrl = [new goog.Uri('http://example.com/video-2.mp4')];
sl.segmentUrls.push(segmentUrl1);
sl.segmentUrls.push(segmentUrl2);
p.start = 0;
manifestInfo = processor.process(m);
var periodInfo = manifestInfo.periodInfos[0];
expect(periodInfo.streamSetInfos[0].streamInfos.length).toBe(0);
});
}); // describe SegmentList
describe('SegmentBase', function() {
var m;
var p;
var as;
var r;
var sb;
beforeEach(function() {
m = new mpd.Mpd();
p = new mpd.Period();
as = new mpd.AdaptationSet();
r = new mpd.Representation();
sb = new mpd.SegmentBase();
r.segmentBase = sb;
r.baseUrl = [new goog.Uri('http://example.com')];
r.bandwidth = 250000;
r.mimeType = 'video/mp4';
as.group = 1;
as.representations.push(r);
p.adaptationSets.push(as);
m.periods.push(p);
});
it('disallows no segment metadata', function() {
p.start = 0;
manifestInfo = processor.process(m);
var periodInfo = manifestInfo.periodInfos[0];
expect(periodInfo.streamSetInfos[0].streamInfos.length).toBe(0);
});
it('disallows no base URL', function() {
r.baseUrl = null;
p.start = 0;
manifestInfo = processor.process(m);
var periodInfo = manifestInfo.periodInfos[0];
expect(periodInfo.streamSetInfos[0].streamInfos.length).toBe(0);
});
}); // describe SegmentBase
}); // describe process
});