/**
* @license
* Copyright 2015 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.
*/
goog.require('shaka.dash.mpd');
describe('mpd', function() {
beforeEach(function() {
jasmine.addMatchers(customMatchers);
});
it('parses an MPD duration string', function() {
var parseDuration = shaka.dash.mpd.parseDuration_;
// Years only. 1 year has 365 or 366 days.
expect(parseDuration('P3Y')).toBeLessThan(3 * (60 * 60 * 24 * 366) + 1);
expect(parseDuration('P3Y')).toBeGreaterThan(3 * (60 * 60 * 24 * 365) - 1);
// Months only. 1 month has 28 to 31 days.
expect(parseDuration('P2M')).toBeLessThan(2 * (60 * 60 * 24 * 31) + 1);
expect(parseDuration('P2M')).toBeGreaterThan(2 * (60 * 60 * 24 * 28) - 1);
// Days only.
expect(parseDuration('P7D')).toBe(604800);
// Hours only.
expect(parseDuration('PT1H')).toBe(3600);
// Minutes only.
expect(parseDuration('PT1M')).toBe(60);
// Seconds only (with no fractional part).
expect(parseDuration('PT1S')).toBe(1);
// Seconds only (with no whole part).
expect(parseDuration('PT0.1S')).toBe(0.1);
expect(parseDuration('PT.1S')).toBe(0.1);
// Seconds only (with whole part and fractional part).
expect(parseDuration('PT1.1S')).toBe(1.1);
// Hours, and minutes.
expect(parseDuration('PT1H2M')).toBe(3720);
// Hours, and seconds.
expect(parseDuration('PT1H2S')).toBe(3602);
expect(parseDuration('PT1H2.2S')).toBe(3602.2);
// Minutes, and seconds.
expect(parseDuration('PT1M2S')).toBe(62);
expect(parseDuration('PT1M2.2S')).toBe(62.2);
// Hours, minutes, and seconds.
expect(parseDuration('PT1H2M3S')).toBe(3723);
expect(parseDuration('PT1H2M3.3S')).toBe(3723.3);
// Days, hours, minutes, and seconds.
expect(parseDuration('P1DT1H2M3S')).toBe(90123);
expect(parseDuration('P1DT1H2M3.3S')).toBe(90123.3);
// Months, hours, minutes, and seconds.
expect(parseDuration('P1M1DT1H2M3S')).toBeLessThan(
(60 * 60 * 24 * 31) + 90123 + 1);
expect(parseDuration('P1M1DT1H2M3S')).toBeGreaterThan(
(60 * 60 * 24 * 28) + 90123 - 1);
// Years, Months, hours, minutes, and seconds.
expect(parseDuration('P1Y1M1DT1H2M3S')).toBeLessThan(
(60 * 60 * 24 * 366) + (60 * 60 * 24 * 31) + 90123 + 1);
expect(parseDuration('P1Y1M1DT1H2M3S')).toBeGreaterThan(
(60 * 60 * 24 * 365) + (60 * 60 * 24 * 28) + 90123 - 1);
expect(parseDuration('PT')).toBe(0);
expect(parseDuration('P')).toBe(0);
// Error cases.
expect(parseDuration('-PT3S')).toBeNull();
expect(parseDuration('PT-3S')).toBeNull();
expect(parseDuration('P1Sasdf')).toBeNull();
expect(parseDuration('1H2M3S')).toBeNull();
expect(parseDuration('123')).toBeNull();
expect(parseDuration('abc')).toBeNull();
expect(parseDuration('')).toBeNull();
});
it('parses an MPD range string', function() {
var parseRange = shaka.dash.mpd.parseRange_;
var Range = shaka.dash.mpd.Range;
expect(parseRange('0-0')).toMatchRange(new Range(0, 0));
expect(parseRange('1-1')).toMatchRange(new Range(1, 1));
expect(parseRange('1-50')).toMatchRange(new Range(1, 50));
expect(parseRange('50-1')).toMatchRange(new Range(50, 1));
expect(parseRange('-1')).toBeNull();
expect(parseRange('1-')).toBeNull();
expect(parseRange('1')).toBeNull();
expect(parseRange('-')).toBeNull();
expect(parseRange('')).toBeNull();
});
it('parses basic MPD XML which has a SegmentBase', function() {
var source = [
'',
' ',
' ',
' ',
' http://example.com',
' ',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
expect(mpd).toBeTruthy();
expect(mpd.periods.length).toBe(1);
var period = mpd.periods[0];
expect(period).toBeTruthy();
expect(period.id).toBe('1');
expect(period.duration).toBe(181.63);
expect(period.start).toBe(0);
expect(period.adaptationSets.length).toBe(1);
var adaptationSet = period.adaptationSets[0];
expect(adaptationSet).toBeTruthy();
expect(adaptationSet.id).toBe('1');
expect(adaptationSet.lang).toBe('en');
expect(adaptationSet.contentType).toBe('video');
expect(adaptationSet.representations.length).toBe(1);
var representation = adaptationSet.representations[0];
expect(representation).toBeTruthy();
expect(representation.id).toBe('r1');
expect(representation.codecs).toBe('mp4a.40.2');
var baseUrl = representation.baseUrl;
expect(baseUrl).toBeTruthy();
expect(baseUrl.toString()).toBe('http://example.com');
var segmentBase = representation.segmentBase;
expect(segmentBase).toBeTruthy();
expect(segmentBase.indexRange.begin).toBe(1000);
expect(segmentBase.indexRange.end).toBe(3540);
var initialization = segmentBase.initialization;
expect(initialization).toBeTruthy();
expect(initialization.range.begin).toBe(0);
expect(initialization.range.end).toBe(999);
});
it('parses basic MPD XML which has a SegmentTemplate', function() {
var source = [
'',
' ',
' ',
' http://example.com',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
expect(mpd).toBeTruthy();
expect(mpd.periods.length).toBe(1);
var period = mpd.periods[0];
expect(period).toBeTruthy();
expect(period.id).toBe('1');
expect(period.duration).toBe(181.63);
expect(period.start).toBe(0);
expect(period.adaptationSets.length).toBe(1);
var adaptationSet = period.adaptationSets[0];
expect(adaptationSet).toBeTruthy();
expect(adaptationSet.id).toBe('1');
expect(adaptationSet.lang).toBe('en');
expect(adaptationSet.contentType).toBe('video');
expect(adaptationSet.representations.length).toBe(2);
var baseUrl = adaptationSet.baseUrl;
expect(baseUrl).toBeTruthy();
expect(baseUrl.toString()).toBe('http://example.com');
var segmentTemplate = adaptationSet.segmentTemplate;
expect(segmentTemplate).toBeTruthy();
expect(segmentTemplate.timescale).toBe(9000);
expect(segmentTemplate.initializationUrlTemplate).toBe(
'$Bandwidth$/init.mp4');
expect(segmentTemplate.mediaUrlTemplate).toBe('$Bandwidth$/frames.mp4');
var timeline = segmentTemplate.timeline;
expect(timeline).toBeTruthy();
var timePoints = timeline.timePoints;
expect(timePoints).toBeTruthy();
expect(timePoints.length).toBe(1);
var tp = timePoints[0];
expect(tp.startTime).toBe(0);
expect(tp.duration).toBe(1000);
expect(tp.repeat).toBe(400);
var representation = adaptationSet.representations[0];
expect(representation).toBeTruthy();
expect(representation.id).toBe('r1');
expect(representation.bandwidth).toBe(250000);
expect(representation.codecs).toBe('mp4a.40.2');
representation = adaptationSet.representations[1];
expect(representation).toBeTruthy();
expect(representation.id).toBe('r2');
expect(representation.bandwidth).toBe(500000);
expect(representation.codecs).toBe('mp4a.40.2');
});
it('inherits ContentComponent attributes in AdaptationSet', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
expect(mpd).toBeTruthy();
expect(mpd.periods.length).toBe(1);
var period = mpd.periods[0];
expect(period).toBeTruthy();
expect(period.adaptationSets.length).toBe(1);
var adaptationSet = period.adaptationSets[0];
expect(adaptationSet).toBeTruthy();
expect(adaptationSet.lang).toBe('en');
expect(adaptationSet.contentType).toBe('video');
});
it('overrides ContentComponent attributes in AdaptationSet', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
expect(mpd).toBeTruthy();
expect(mpd.periods.length).toBe(1);
var period = mpd.periods[0];
expect(period).toBeTruthy();
expect(period.adaptationSets.length).toBe(1);
var adaptationSet = period.adaptationSets[0];
expect(adaptationSet).toBeTruthy();
expect(adaptationSet.lang).toBe('fr');
expect(adaptationSet.contentType).toBe('audio');
});
it('inherits mimeType attribute from AdaptationSet', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
checkRepresentationMimeType(source, 'video/mp4');
});
it('overrides mimeType attribute from AdaptationSet', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
checkRepresentationMimeType(source, 'video/webm');
});
/**
* Checks that the first Representation in |source| has |mimeType|.
* @param {string} source
* @param {string} mimeType
*/
var checkRepresentationMimeType = function(source, mimeType) {
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
expect(mpd).toBeTruthy();
expect(mpd.periods.length).toBe(1);
var period = mpd.periods[0];
expect(period).toBeTruthy();
expect(period.adaptationSets.length).toBe(1);
var adaptationSet = period.adaptationSets[0];
expect(adaptationSet).toBeTruthy();
expect(adaptationSet.representations.length).toBe(1);
var representation = adaptationSet.representations[0];
expect(representation).toBeTruthy();
expect(representation.mimeType).toBe(mimeType);
};
it('infers contentType attribute from mimeType attribute', function() {
var source = [
'',
' ',
' ',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
expect(mpd).toBeTruthy();
expect(mpd.periods.length).toBe(1);
var period = mpd.periods[0];
expect(period).toBeTruthy();
expect(period.adaptationSets.length).toBe(1);
var adaptationSet = period.adaptationSets[0];
expect(adaptationSet).toBeTruthy();
expect(adaptationSet.contentType).toBe('video');
});
it('infers mimeType attribute from Representation', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
expect(mpd).toBeTruthy();
expect(mpd.periods.length).toBe(1);
var period = mpd.periods[0];
expect(period).toBeTruthy();
expect(period.adaptationSets.length).toBe(1);
var adaptationSet = period.adaptationSets[0];
expect(adaptationSet).toBeTruthy();
expect(adaptationSet.mimeType).toBe('video/mp4');
expect(adaptationSet.contentType).toBe('video');
});
it('inherits codecs attribute from AdaptationSet', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
checkCodecs(source, 'vp8');
});
it('overrides codecs attribute from AdaptationSet', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
checkCodecs(source, 'vp9');
});
/**
* Checks that the first Representation in |source| has |codecs|.
* @param {string} source
* @param {string} codecs
*/
var checkCodecs = function(source, codecs) {
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
expect(mpd).toBeTruthy();
expect(mpd.periods.length).toBe(1);
var period = mpd.periods[0];
expect(period).toBeTruthy();
expect(period.adaptationSets.length).toBe(1);
var adaptationSet = period.adaptationSets[0];
expect(adaptationSet).toBeTruthy();
expect(adaptationSet.representations.length).toBe(1);
var representation = adaptationSet.representations[0];
expect(representation).toBeTruthy();
expect(representation.codecs).toBe(codecs);
};
it('parses group attribute', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
expect(mpd.periods[0].adaptationSets[0].group).toBe(0);
expect(mpd.periods[0].adaptationSets[1].group).toBe(1);
expect(mpd.periods[0].adaptationSets[2].group).toBe(2);
});
it('defaults group to a unique non-zero value', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
var groups = mpd.periods[0].adaptationSets.map(
function(set) { return set.group; });
for (var i = 0; i < groups.length; ++i) {
expect(groups[i]).not.toBe(0);
for (var j = i + 1; j < groups.length; ++j) {
expect(groups[i] != groups[j]).toBe(true);
}
}
});
it('defaults startNumber to 1 when missing', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
var period = mpd.periods[0];
var adaptationSet = period.adaptationSets[0];
var segmentTemplate = adaptationSet.segmentTemplate;
expect(segmentTemplate.startNumber).toBe(1);
});
it('defaults startNumber to 1 when invalid', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
var period = mpd.periods[0];
var adaptationSet = period.adaptationSets[0];
var segmentTemplate = adaptationSet.segmentTemplate;
expect(segmentTemplate.startNumber).toBe(1);
});
it('allows startNumber to be 0', function() {
var source = [
'',
' ',
' ',
' ',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
var period = mpd.periods[0];
var adaptationSet = period.adaptationSets[0];
var segmentTemplate = adaptationSet.segmentTemplate;
expect(segmentTemplate.startNumber).toBe(0);
});
it('does not override valid zeros with defaults', function() {
var source = [
'',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
expect(mpd.minBufferTime).toBe(0);
});
it('handles a Location element', function() {
var source = [
'',
' http://example.com/updated_mpd',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover(
'http://example.com/mpd').urls);
expect(mpd.updateLocation.toString()).toBe(
'http://example.com/updated_mpd');
});
it('handles a Location element with a BaseURL', function() {
var source = [
'',
' http://example.com',
' updated_mpd',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover(
'http://example.com/mpd').urls);
expect(mpd.updateLocation.toString()).toBe(
'http://example.com/updated_mpd');
});
it('handles adding external captions', function() {
var source = [
'',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
mpd.addExternalCaptions('http://example.com/');
var period = mpd.periods[0];
expect(period.adaptationSets.length).toBe(1);
var adaptationSet = period.adaptationSets[0];
expect(adaptationSet.contentType).toBe('text');
expect(adaptationSet.lang).toBe('en');
expect(adaptationSet.representations.length).toBe(1);
var representation = adaptationSet.representations[0];
expect(representation.baseUrl.toString()).toBe('http://example.com/');
});
it('handles adding multiple external captions', function() {
var source = [
'',
' ',
' ',
''].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, createFailover('').urls);
mpd.addExternalCaptions('http://example.com/');
mpd.addExternalCaptions('http://example.com/', 'es');
var period = mpd.periods[0];
expect(period.adaptationSets.length).toBe(2);
var adaptationSet = period.adaptationSets[0];
expect(adaptationSet.contentType).toBe('text');
expect(adaptationSet.lang).toBe('en');
expect(adaptationSet.representations.length).toBe(1);
var representation = adaptationSet.representations[0];
expect(representation.baseUrl.toString()).toBe('http://example.com/');
var adaptationSet2 = period.adaptationSets[1];
expect(adaptationSet2.contentType).toBe('text');
expect(adaptationSet2.lang).toBe('es');
expect(adaptationSet2.representations.length).toBe(1);
var representation2 = adaptationSet2.representations[0];
expect(representation.baseUrl.toString()).toBe('http://example.com/');
});
});