Socket
Socket
Sign inDemoInstall

shaka-player

Package Overview
Dependencies
Maintainers
1
Versions
327
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

shaka-player - npm Package Compare versions

Comparing version 1.1.0 to 1.2.0

.gitattributes

34

app.js

@@ -68,2 +68,5 @@ /**

app.init = function() {
// Display the version number.
document.getElementById('version').innerText = shaka.player.Player.version;
// Set default values.

@@ -75,3 +78,3 @@ document.getElementById('forcePrefixed').checked = false;

'assets/test_license.json';
document.getElementById('mediaUrlInput').value = 'assets/bear-av-enc.mp4';
document.getElementById('mediaUrlInput').value = 'assets/bear-av-enc.webm';
document.getElementById('subtitlesUrlInput').value = 'assets/test_subs.vtt';

@@ -267,3 +270,3 @@

drmSchemeInfo = new shaka.player.DrmSchemeInfo(
keySystem, false, licenseServerUrl, false, null, null);
keySystem, true, licenseServerUrl, false, null, null);
}

@@ -500,3 +503,3 @@

app.interpretContentProtection_ = function(contentProtection) {
var StringUtils = shaka.util.StringUtils;
var Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;

@@ -515,9 +518,9 @@ var override = document.getElementById('wvLicenseServerUrlInput');

var child = contentProtection.children[0];
var keyid = StringUtils.fromHex(child.getAttribute('keyid'));
var key = StringUtils.fromHex(child.getAttribute('key'));
var keyid = Uint8ArrayUtils.fromHex(child.getAttribute('keyid'));
var key = Uint8ArrayUtils.fromHex(child.getAttribute('key'));
var keyObj = {
kty: 'oct',
alg: 'A128KW',
kid: StringUtils.toBase64(keyid, false),
k: StringUtils.toBase64(key, false)
kid: Uint8ArrayUtils.toBase64(keyid, false),
k: Uint8ArrayUtils.toBase64(key, false)
};

@@ -527,7 +530,7 @@ var jwkSet = {keys: [keyObj]};

var initData = {
initData: StringUtils.toUint8Array(keyid),
initData: keyid,
initDataType: 'cenc'
};
var licenseServerUrl = 'data:application/json;base64,' +
StringUtils.toBase64(license);
window.btoa(license);
return new shaka.player.DrmSchemeInfo(

@@ -567,3 +570,3 @@ 'org.w3.clearkey', false, licenseServerUrl, false, initData, null);

// This is the UUID which represents Widevine in the edash-packager.
var licenseServerUrl = 'http://widevine-proxy.appspot.com/proxy';
var licenseServerUrl = '//widevine-proxy.appspot.com/proxy';
return new shaka.player.DrmSchemeInfo(

@@ -573,2 +576,7 @@ 'com.widevine.alpha', true, licenseServerUrl, false, null, null);

if (contentProtection.schemeIdUri == 'urn:mpeg:dash:mp4protection:2011') {
// Ignore without a warning.
return null;
}
console.warn('Unrecognized scheme: ' + contentProtection.schemeIdUri);

@@ -589,4 +597,4 @@ return null;

app.postProcessYouTubeLicenseResponse_ = function(response, restrictions) {
var StringUtils = shaka.util.StringUtils;
var responseStr = StringUtils.fromUint8Array(response);
var Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
var responseStr = Uint8ArrayUtils.toString(response);
var index = responseStr.indexOf('\r\n\r\n');

@@ -613,3 +621,3 @@ if (index >= 0) {

}
return StringUtils.toUint8Array(responseStr);
return Uint8ArrayUtils.fromString(responseStr);
};

@@ -616,0 +624,0 @@

@@ -1,1 +0,1 @@

{"keys":[{"kty":"oct","kid":"MDEyMzQ1Njc4OTAxMjM0NQ","k":"691i8WgU0nto7xIq/OSuPA"}]}
{"keys":[{"kty":"oct","alg":"A128KW","kid":"MDEyMzQ1Njc4OTAxMjM0NQ","k":"691i8WgU0nto7xIq/OSuPA"}]}

@@ -0,1 +1,62 @@

## 1.2.0 (2015-02-24)
Lots of internal refactoring and bugfixes, and a few new features.
Bugfixes:
- Buffer eviction no longer causes hangs on seek.
- https://github.com/google/shaka-player/issues/15
- Adaptation no longer causes hangs on looping and seeking backward.
- https://github.com/google/shaka-player/issues/26
- StreamStats no longer shows null for width and height before adaptation.
- https://github.com/google/shaka-player/issues/16
- Content with differing start times for the audio & video streams no longer
exhibits A/V sync issues.
- https://github.com/google/shaka-player/issues/17
- DrmSchemeInfo's suppressMultipleEncryptedEvents flag is now correctly
honored regardless of the timing of events.
- Calculations for the $Time$ placeholder in MPD SegmentTemplates has been
corrected.
- The test app no longer causes mixed-content errors when served over HTTPS.
- Small mistakes in URLs and asset names in the test app have been corrected.
- Windows checkouts now have consistent newline style.
- https://github.com/google/shaka-player/issues/12
- Windows build steps documented.
- https://github.com/google/shaka-player/issues/13
Features:
- The isTypeSupported polyfill has been removed and all EME APIs have been
updated to the [Feb 9 2015 EME spec].
- https://github.com/google/shaka-player/issues/2
- Gaps and overlaps in SegmentTimeline are no longer treated as an error.
Large gaps/overlaps will still generate a warning.
- https://github.com/google/shaka-player/issues/24
- HDCP-related failures are now translated into error events in Chrome 42+.
- https://github.com/google/shaka-player/issues/14
- The MPD Role tag is now supported as a way of indicating the main
AdaptationSet for the purposes of language matching.
- https://github.com/google/shaka-player/issues/20
- More detail added to AJAX error events.
- https://github.com/google/shaka-player/issues/18
- The Player now dispatches buffering events.
- https://github.com/google/shaka-player/issues/25
- Parser support for the new v1 PSSH layout, including parsing of key IDs.
- https://github.com/google/shaka-player/issues/19
- The fullscreen polyfill has been updated and expanded.
- DashVideoSource refactored to split DASH-independent functionality into the
generic StreamVideoSource. This should simplify the implementation of new
video sources for non-DASH manifest formats. (Contributions welcome.)
- Automatic build numbering has been added, with version numbers appearing in
the test app UI.
- The library has been published on [npm] and [cdnjs].
- Release version numbering follows the [semantic versioning spec].
Broken Compatibility:
- System IDs in PSSH objects are now hex strings instead of raw strings.
[Feb 9 2015 EME spec]: http://goo.gl/5gifok
[npm]: https://www.npmjs.com/package/shaka-player
[cdnjs]: https://cdnjs.com/libraries/shaka-player
[semantic versioning spec]: http://semver.org/
## 1.1 (2015-01-14)

@@ -2,0 +63,0 @@

@@ -21,2 +21,6 @@ /**

HTMLMediaElement.prototype.mozRequestFullscreen = function() {};
Document.prototype.msExitFullscreen = function() {};
Document.prototype.webkitExitFullscreen = function() {};

@@ -17,3 +17,3 @@ /**

* @fileoverview MediaKey externs.
* Based on the Dec 1, 2014 draft of the EME spec.
* Based on {@link http://goo.gl/5gifok EME draft 09 February 2015}.
* @externs

@@ -27,19 +27,26 @@ */

/** @typedef {!Object.<BufferSource, string>} */
var MediaKeyStatuses;
/** @typedef {{contentType: string, robustness: string}} */
var MediaKeySystemMediaCapability;
/** @typedef {{
* initDataTypes: Array.<string>,
* audioCapabilities: Array.<!MediaKeySystemMediaCapability>,
* videoCapabilities: Array.<!MediaKeySystemMediaCapability>,
* distinctiveIdentifier: string,
* persistentState: string
* }} */
var MediaKeySystemConfiguration;
/**
* @param {string} keySystem
* @param {Array.<Object>=} opt_supportedConfigurations
* @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
* @return {!Promise.<!MediaKeySystemAccess>}
*/
Navigator.prototype.requestMediaKeySystemAccess =
function(keySystem, opt_supportedConfigurations) {};
function(keySystem, supportedConfigurations) {};
/**
* @type {MediaKeys}
* @const
*/
/** @const {MediaKeys} */
HTMLMediaElement.prototype.mediaKeys;

@@ -49,10 +56,2 @@

/**
* NOTE: Not yet implemented as of Chrome 40. Type may be incorrect.
* @type {string}
* @const
*/
HTMLMediaElement.prototype.waitingFor;
/**
* @param {MediaKeys} mediaKeys

@@ -73,10 +72,7 @@ * @return {!Promise}

/**
* NOTE: Not yet implemented as of Chrome 40. Type may be incorrect.
* @return {Object}
*/
/** @return {!MediaKeySystemConfiguration} */
MediaKeySystemAccess.prototype.getConfiguration = function() {};
/** @type {string} */
/** @const {string} */
MediaKeySystemAccess.prototype.keySystem;

@@ -91,3 +87,3 @@

/**
* @param {string=} opt_sessionType
* @param {string=} opt_sessionType defaults to "temporary"
* @return {!MediaKeySession}

@@ -109,33 +105,71 @@ * @throws {TypeError} if opt_sessionType is invalid.

* @interface
* @extends {EventTarget}
*/
function MediaKeySession() {}
function MediaKeyStatusMap() {}
/** @const {number} */
MediaKeyStatusMap.prototype.size;
/**
* @type {string}
* @const
* Array entry 0 is the key, 1 is the value.
* @return {Iterator.<Array.<!BufferSource|string>>}
*/
MediaKeySession.prototype.sessionId;
MediaKeyStatusMap.prototype.entries = function() {};
/**
* @type {number}
* @const
* The functor is called with each value.
* @param {function(string)} fn
*/
MediaKeySession.prototype.expiration;
MediaKeyStatusMap.prototype.forEach = function(fn) {};
/**
* @type {!Promise}
* @const
* @param {!BufferSource} keyId
* @return {string|undefined}
*/
MediaKeySession.prototype.closed;
MediaKeyStatusMap.prototype.get = function(keyId) {};
/**
* NOTE: Not yet implemented as of Chrome 40. Type may be incorrect.
* @type {!MediaKeyStatuses}
* @const
* @param {!BufferSource} keyId
* @return {boolean}
*/
MediaKeyStatusMap.prototype.has = function(keyId) {};
/**
* @return {Iterator.<!BufferSource>}
*/
MediaKeyStatusMap.prototype.keys = function() {};
/**
* @return {Iterator.<string>}
*/
MediaKeyStatusMap.prototype.values = function() {};
/**
* @interface
* @extends {EventTarget}
*/
function MediaKeySession() {}
/** @const {string} */
MediaKeySession.prototype.sessionId;
/** @const {number} */
MediaKeySession.prototype.expiration;
/** @const {!Promise} */
MediaKeySession.prototype.closed;
/** @const {!MediaKeyStatusMap} */
MediaKeySession.prototype.keyStatuses;

@@ -166,5 +200,3 @@

/**
* @return {!Promise}
*/
/** @return {!Promise} */
MediaKeySession.prototype.close = function() {};

@@ -201,20 +233,11 @@

/**
* @type {string}
* @const
*/
/** @const {string} */
MediaKeyMessageEvent.prototype.messageType;
/**
* @type {!ArrayBuffer}
* @const
*/
/** @const {!ArrayBuffer} */
MediaKeyMessageEvent.prototype.message;
/**
* @type {!MediaKeySession}
* @const
*/
/** @const {!MediaKeySession} */
MediaKeyMessageEvent.prototype.target;

@@ -233,14 +256,12 @@

/**
* @type {string}
* @const
*/
/** @const {string} */
MediaEncryptedEvent.prototype.initDataType;
/**
* @type {ArrayBuffer}
* @const
*/
/** @const {ArrayBuffer} */
MediaEncryptedEvent.prototype.initData;
/** @const {!HTMLMediaElement} */
MediaEncryptedEvent.prototype.target;

@@ -26,3 +26,3 @@ /**

goog.require('shaka.util.Pssh');
goog.require('shaka.util.StringUtils');
goog.require('shaka.util.Uint8ArrayUtils');

@@ -55,6 +55,2 @@

// Reset the unique IDs so that IDs are predictable no matter how many MPDs
// are parsed in this browser session.
shaka.dash.mpd.nextUniqueId_ = 0;
// Construct a virtual parent for the MPD to use in resolving relative URLs.

@@ -67,6 +63,2 @@ var parent = { baseUrl: new goog.Uri(url) };

/** @private {number} */
shaka.dash.mpd.nextUniqueId_ = 0;
/**

@@ -210,5 +202,2 @@ * @private {number}

/** @type {*} */
this.userData = null;
/** @type {!Array.<!shaka.dash.mpd.Representation>} */

@@ -221,2 +210,10 @@ this.representations = [];

/** @constructor */
shaka.dash.mpd.Role = function() {
/** @type {?string} */
this.value = null;
};
/** @constructor */
shaka.dash.mpd.ContentComponent = function() {

@@ -266,3 +263,3 @@ /** @type {?string} */

* Bandwidth required, in bits per second, to assure uninterrupted playback,
* assuming that Mpd.minBufferTime seconds of video are in buffer before
* assuming that |minBufferTime| seconds of video are in buffer before
* playback begins.

@@ -300,10 +297,4 @@ * @type {?number}

/** @type {*} */
this.userData = null;
/**
* A unique ID independent of |id| or other attributes.
* @type {number}
*/
this.uniqueId = ++shaka.dash.mpd.nextUniqueId_;
/** @type {boolean} */
this.main = false;
};

@@ -321,6 +312,12 @@

/** @type {?string} */
/**
* @type {?string}
* @expose
*/
this.value = null;
/** @type {!Array.<!Node>} */
/**
* @type {!Array.<!Node>}
* @expose
*/
this.children = [];

@@ -379,2 +376,8 @@

/** @type {number} */
this.timescale = 1;
/** @type {number} */
this.presentationTimeOffset = 0;
/** @type {shaka.dash.mpd.Range} */

@@ -436,3 +439,3 @@ this.indexRange = null;

/** @type {number} */
this.firstSegmentNumber = 1;
this.startNumber = 1;

@@ -444,5 +447,2 @@ /** @type {shaka.dash.mpd.Initialization} */

this.segmentUrls = [];
/** @type {*} */
this.userData = null;
};

@@ -489,3 +489,3 @@

/** @type {number} */
this.firstSegmentNumber = 1;
this.startNumber = 1;

@@ -564,2 +564,6 @@ /** @type {?string} */

/** @const {string} */
shaka.dash.mpd.Role.TAG_NAME = 'Role';
/** @const {string} */
shaka.dash.mpd.ContentComponent.TAG_NAME = 'ContentComponent';

@@ -686,2 +690,3 @@

{};
var role = mpd.parseChild_(this, elem, mpd.Role);

@@ -698,2 +703,3 @@ // Parse attributes.

this.codecs = mpd.parseAttr_(elem, 'codecs', mpd.parseString_);
this.main = role && role.value == 'main';

@@ -742,2 +748,15 @@ // Normalize the language tag.

/**
* Parses a "Role" tag.
* @param {!shaka.dash.mpd.AdaptationSet} parent The parent AdaptationSet.
* @param {!Node} elem The Role XML element.
*/
shaka.dash.mpd.Role.prototype.parse = function(parent, elem) {
var mpd = shaka.dash.mpd;
// Parse attributes.
this.value = mpd.parseAttr_(elem, 'value', mpd.parseString_);
};
/**
* Parses a "ContentComponent" tag.

@@ -835,3 +854,3 @@ * @param {!shaka.dash.mpd.AdaptationSet} parent The parent AdaptationSet.

var mpd = shaka.dash.mpd;
var StringUtils = shaka.util.StringUtils;
var Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;

@@ -843,3 +862,3 @@ var contents = mpd.getContents_(elem);

this.psshBox = StringUtils.toUint8Array(StringUtils.fromBase64(contents));
this.psshBox = Uint8ArrayUtils.fromBase64(contents);

@@ -877,2 +896,8 @@ try {

this.timescale =
mpd.parseAttr_(elem, 'timescale', mpd.parsePositiveInt_) || 1;
this.presentationTimeOffset = mpd.parseAttr_(
elem, 'presentationTimeOffset', mpd.parseNonNegativeInt_) || 0;
// Parse attributes.

@@ -954,3 +979,3 @@ this.indexRange = mpd.parseAttr_(elem, 'indexRange', mpd.parseRange_);

this.firstSegmentNumber =
this.startNumber =
mpd.parseAttr_(elem, 'startNumber', mpd.parsePositiveInt_) || 1;

@@ -998,3 +1023,3 @@

this.firstSegmentNumber =
this.startNumber =
mpd.parseAttr_(elem, 'startNumber', mpd.parsePositiveInt_) || 1;

@@ -1001,0 +1026,0 @@

@@ -16,3 +16,3 @@ /**

*
* @fileoverview Processes, filters, and interprets an MPD.
* @fileoverview Implements MpdProcessor.
*/

@@ -24,11 +24,10 @@

goog.require('shaka.asserts');
goog.require('shaka.dash.SegmentIndex');
goog.require('shaka.dash.SegmentReference');
goog.require('shaka.dash.mpd');
goog.require('shaka.log');
goog.require('shaka.player.DrmSchemeInfo');
goog.require('shaka.player.Player');
goog.require('shaka.util.ArrayUtils');
goog.require('shaka.util.LanguageUtils');
goog.require('shaka.util.MultiMap');
goog.require('shaka.media.PeriodInfo');
goog.require('shaka.media.SegmentIndex');
goog.require('shaka.media.SegmentMetadataInfo');
goog.require('shaka.media.SegmentReference');
goog.require('shaka.media.StreamInfo');
goog.require('shaka.media.StreamSetInfo');

@@ -38,5 +37,9 @@

/**
* Set up an MPD processor.
* Creates an MpdProcessor, which validates MPDs, generates segment information
* from SegmentTemplate elements, calculates start/duration attributes, removes
* invalid Representations, and ultimately generates a ManifestInfo.
*
* @param {shaka.player.DashVideoSource.ContentProtectionCallback}
* interpretContentProtection
*
* @constructor

@@ -49,6 +52,4 @@ * @struct

/**
* @private {!Array.<shaka.dash.MpdProcessor.AdaptationSetMapAndDrmScheme>}
*/
this.adaptationSetMapAndDrmSchemeByPeriod_ = [];
/** @type {!shaka.media.ManifestInfo} */
this.manifestInfo = new shaka.media.ManifestInfo();
};

@@ -58,65 +59,29 @@

/**
* Maps content types to collections of AdaptationSets.
* @typedef {!shaka.util.MultiMap.<!shaka.dash.mpd.AdaptationSet>}
* Any gap/overlap within a SegmentTimeline that is greater than or equal to
* this value (in seconds) will generate a warning message.
* @const {number}
*/
shaka.dash.MpdProcessor.AdaptationSetMap;
shaka.dash.MpdProcessor.GAP_OVERLAP_WARNING_THRESHOLD = 1.0 / 32.0;
/**
* @typedef {{adaptationSetMap: !shaka.dash.MpdProcessor.AdaptationSetMap,
* drmScheme: shaka.player.DrmSchemeInfo}}
*/
shaka.dash.MpdProcessor.AdaptationSetMapAndDrmScheme;
/**
* Determine the full MIME type of a Representation.
* Processes the given MPD. Sets |this.periodInfos|.
*
* @param {!shaka.dash.mpd.Representation} representation
* @return {string}
*/
shaka.dash.MpdProcessor.representationMimeType = function(representation) {
var type = representation.mimeType || '';
if (representation.codecs) {
type += '; codecs="' + representation.codecs + '"';
}
return type;
};
/**
* Process the MPD. The MPD will be modified by having unsupported
* Representations and AdaptationSets removed, setting userData fields on
* Representations and AdaptationSets, and sorting Representations.
*
* @param {!shaka.dash.mpd.Mpd} mpd
*/
shaka.dash.MpdProcessor.prototype.process = function(mpd) {
// First, check that each Representation has only one of SegmentBase,
// SegmentList, or SegmentTemplate.
this.manifestInfo = new shaka.media.ManifestInfo();
this.validateSegmentInfo_(mpd);
// Next, fix up period start/duration and MPD duration attributes.
// Calculate MPD and period durations before and after expanding any
// SegmentTemplates. A SegmentTemplate with a segmentDuration attribute
// requires a Period duration to be expanded. However, other types
// of SegmentTemplates can be used to derive the Period duration.
this.calculateDurations_(mpd);
// Next, generate concrete Representations from SegmentTemplates.
this.processSegmentTemplates_(mpd);
// Next, fix up durations again since SegmentLists have been generated.
this.calculateDurations_(mpd);
// Next, filter out any invalid Representations.
this.filterRepresentations_(mpd);
// Next, build segment indexes for each SegmentList.
this.buildSegmentIndexes_(mpd);
// Next, bubble up DRM scheme info to the AdaptationSet level.
this.bubbleUpDrmSchemes_(mpd);
// Next, sort the Representations by bandwidth.
this.sortRepresentations_(mpd);
// Finally, choose AdaptationSets for each period.
this.chooseAdaptationSets_(mpd);
this.filterPeriods_(mpd);
this.createManifestInfo_(mpd);
};

@@ -126,184 +91,5 @@

/**
* Get the number of processed periods.
* Calculates each Period's start attribute and duration attribute, and
* calcuates the MPD's duration attribute.
*
* @return {number}
*/
shaka.dash.MpdProcessor.prototype.getNumPeriods = function() {
return this.adaptationSetMapAndDrmSchemeByPeriod_.length;
};
/**
* Get the processed AdaptationSets for a given period.
*
* @param {number} periodIdx
* @param {string=} opt_type Optional content type. If left undefined then all
* AdaptationSets are returned for the given period.
* @return {!Array.<!shaka.dash.mpd.AdaptationSet>}
*/
shaka.dash.MpdProcessor.prototype.getAdaptationSets = function(
periodIdx, opt_type) {
shaka.asserts.assert(
periodIdx >= 0 &&
periodIdx < this.adaptationSetMapAndDrmSchemeByPeriod_.length);
var tuple = this.adaptationSetMapAndDrmSchemeByPeriod_[periodIdx];
if (!tuple) {
return [];
}
return opt_type ?
tuple.adaptationSetMap.get(opt_type) || [] :
tuple.adaptationSetMap.getAll();
};
/**
* Get the common DRM scheme for a given period.
*
* @param {number} periodIdx
* @return {shaka.player.DrmSchemeInfo}
*/
shaka.dash.MpdProcessor.prototype.getDrmScheme = function(periodIdx) {
shaka.asserts.assert(
periodIdx >= 0 &&
periodIdx < this.adaptationSetMapAndDrmSchemeByPeriod_.length);
var tuple = this.adaptationSetMapAndDrmSchemeByPeriod_[periodIdx];
if (!tuple) {
return null;
}
return tuple.drmScheme;
};
/**
* Select AdaptationSets for a given period.
*
* @param {number} periodIdx
* @param {string} preferredLang The preferred language.
* @return {!Array.<!shaka.dash.mpd.AdaptationSet>}
*/
shaka.dash.MpdProcessor.prototype.selectAdaptationSets = function(
periodIdx, preferredLang) {
shaka.asserts.assert(
periodIdx >= 0 &&
periodIdx < this.adaptationSetMapAndDrmSchemeByPeriod_.length);
var tuple = this.adaptationSetMapAndDrmSchemeByPeriod_[periodIdx];
if (!tuple) {
return [];
}
var sets = [];
// Add a video AdaptationSet.
var videoSets = tuple.adaptationSetMap.get('video');
if (videoSets && videoSets.length > 0) {
shaka.asserts.assert(videoSets.length == 1);
sets.push(videoSets[0]);
}
// Add an audio AdaptationSet.
var audioSets = tuple.adaptationSetMap.get('audio');
if (audioSets && audioSets.length > 0) {
var favoredAudioSets =
this.filterSetsByLanguage_(audioSets, preferredLang);
// If no matches were found, take the first audio set.
sets.push(favoredAudioSets.length > 0 ? favoredAudioSets[0] : audioSets[0]);
}
// Add a text AdaptationSet.
var textSets = tuple.adaptationSetMap.get('text');
if (textSets && textSets.length > 0) {
var favoredTextSets =
this.filterSetsByLanguage_(textSets, preferredLang);
// If no matches were found, take the first subtitle set.
var textSet = favoredTextSets.length > 0 ? favoredTextSets[0] :
textSets[0];
sets.push(textSet);
}
return sets;
};
/**
* Enforces restrictions on the video Representations can be used.
* Representations which exceed any of these restrictions will be removed.
*
* @param {!shaka.dash.mpd.Mpd} mpd
* @param {!shaka.player.DrmSchemeInfo.Restrictions} restrictions
*/
shaka.dash.MpdProcessor.prototype.enforceRestrictions =
function(mpd, restrictions) {
for (var i = 0; i < mpd.periods.length; ++i) {
var period = mpd.periods[i];
for (var j = 0; j < period.adaptationSets.length; ++j) {
var adaptationSet = period.adaptationSets[j];
for (var k = 0; k < adaptationSet.representations.length; ++k) {
var representation = adaptationSet.representations[k];
var remove = false;
if (restrictions.maxWidth &&
representation.width > restrictions.maxWidth) {
remove = true;
}
if (restrictions.maxHeight &&
representation.height > restrictions.maxHeight) {
remove = true;
}
if (remove) {
adaptationSet.representations.splice(k, 1);
--k;
}
} // for k
} // for j
} // for i
};
/**
* Returns a list of sets matching the preferred language.
*
* @param {Array.<!shaka.dash.mpd.AdaptationSet>} sets
* @param {string} preferredLang The preferred language.
* @return {!Array.<!shaka.dash.mpd.AdaptationSet>}
* @private
*/
shaka.dash.MpdProcessor.prototype.filterSetsByLanguage_ =
function(sets, preferredLang) {
// Alias.
var LanguageUtils = shaka.util.LanguageUtils;
if (sets && sets.length > 0) {
// Do a fuzzy match and stop on the lowest successful fuzz level.
var favoredSets;
for (var fuzz = LanguageUtils.MatchType.MIN;
fuzz <= LanguageUtils.MatchType.MAX;
++fuzz) {
favoredSets = sets.filter(
function(set) {
var candidate = set.lang || '';
return LanguageUtils.match(fuzz, preferredLang, candidate);
});
if (favoredSets.length) {
return favoredSets;
}
}
}
return [];
};
/**
* Calculate each Period's start and duration as well as the MPD's duration.
*
* @see ISO/IEC 23009-1:2014 section 5.3.2.1

@@ -387,10 +173,9 @@ *

/**
* Calculate |period|'s duration based upon its Representations.
* Calculates |period|'s duration based upon its Representations.
*
* @param {!shaka.dash.mpd.Period} period
*
* @private
*/
shaka.dash.MpdProcessor.prototype.calculatePeriodDuration_ = function(period) {
if (period.duration) {
if (period.duration != null) {
return;

@@ -422,5 +207,5 @@ }

/**
* Calculates the duration of a SegmentList.
* Calculates the given SegmentList's duration.
*
* @param {!shaka.dash.mpd.SegmentList} segmentList
*
* @return {number} The duration of |segmentList|.

@@ -431,2 +216,6 @@ * @private

segmentList) {
if (segmentList.segmentUrls.length == 0) {
return 0;
}
if (segmentList.segmentDuration) {

@@ -438,3 +227,6 @@ return segmentList.segmentDuration /

var totalUnscaledDuration = 0;
// Add the time before the SegmentList's first segment to the SegmentList's
// duration.
shaka.asserts.assert(segmentList.segmentUrls[0].startTime != null);
var totalUnscaledDuration = segmentList.segmentUrls[0].startTime;

@@ -453,3 +245,3 @@ for (var i = 0; i < segmentList.segmentUrls.length; ++i) {

/**
* Ensure that each Representation has only one of SegmentBase, SegmentList,
* Ensures that each Representation has only one of SegmentBase, SegmentList,
* or SegmentTemplate.

@@ -479,4 +271,5 @@ *

'Representation does not contain any segment information. ' +
'Representation must contain one of SegmentBase, ' +
'SegmentList, or SegmentTemplate.');
'A Representation must contain one of SegmentBase, ' +
'SegmentList, or SegmentTemplate.',
representation);
adaptationSet.representations.splice(k, 1);

@@ -487,4 +280,5 @@ --k;

'Representation contains multiple segment information sources. ' +
'Representation should only contain one of SegmentBase, ' +
'SegmenstList, or SegmentTemplate.');
'A Representation should only contain one of SegmentBase, ' +
'SegmenstList, or SegmentTemplate.',
representation);
if (representation.segmentBase) {

@@ -501,2 +295,16 @@ shaka.log.info('Using SegmentBase by default.');

}
if (representation.segmentBase) {
if (!representation.segmentBase.representationIndex ||
!representation.segmentBase.representationIndex.range ||
!representation.segmentBase.mediaUrl) {
shaka.log.warning(
'Representation is missing critical segment information: ' +
'A Representation that uses a SegmentBase must contain a ' +
'segment index URL and a media URL.',
representation);
adaptationSet.representations.splice(k, 1);
--k;
}
}
} // for k

@@ -539,3 +347,3 @@ }

}
if (segmentTemplate.duration) {
if (segmentTemplate.segmentDuration) {
shaka.log.warning(

@@ -564,3 +372,3 @@ 'Ignoring segment duration because an explicit segment index ' +

} else if (segmentTemplate.segmentDuration) {
if (period.duration) {
if (period.duration != null) {
this.generateSegmentListFromDuration_(

@@ -599,3 +407,2 @@ representation,

* @param {!shaka.dash.mpd.Representation} representation
*
* @private

@@ -656,3 +463,2 @@ */

* @param {!shaka.dash.mpd.Representation} representation
*
* @private

@@ -679,3 +485,3 @@ */

segmentList.presentationTimeOffset = segmentTemplate.presentationTimeOffset;
segmentList.firstSegmentNumber = segmentTemplate.firstSegmentNumber;
segmentList.startNumber = segmentTemplate.startNumber;
segmentList.initialization = this.generateInitialization_(representation);

@@ -715,11 +521,29 @@ segmentList.segmentUrls = [];

if (lastEndTime >= 0 && startTime > lastEndTime) {
// The start of the current segment may begin before the end of the
// last segment, but there should not be a gap between them.
shaka.log.warning(
'SegmentTimeline "S" element does not have a valid start time. ' +
'There is no segment information for the time range ' +
lastEndTime + ' to ' + startTime + '.',
timePoints[i]);
return;
// The end of the last segment may end before the start of the current
// segment (a gap) or may end after the start of the current segment (an
// overlap). If there is a gap/overlap then stretch/compress the end of
// the last segment to the start of the current segment.
//
// Note: it is possible to move the start of the current segment to the
// end of the last segment, but this complicates the computation of the
// $Time$ placeholder.
if ((lastEndTime >= 0) && (startTime != lastEndTime)) {
var numSegmentUrls = segmentList.segmentUrls.length;
shaka.asserts.assert(numSegmentUrls > 0);
var lastSegmentUrl = segmentList.segmentUrls[numSegmentUrls - 1];
var delta = startTime - lastEndTime;
if (Math.abs(delta / segmentList.timescale) >=
shaka.dash.MpdProcessor.GAP_OVERLAP_WARNING_THRESHOLD) {
shaka.log.warning('SegmentTimeline contains a large gap/overlap, ' +
'the content may have errors in it.',
timePoints[i]);
}
lastSegmentUrl.duration += delta;
shaka.asserts.assert(
(lastSegmentUrl.startTime + lastSegmentUrl.duration) ==
startTime);
}

@@ -734,3 +558,3 @@

representation.id,
segmentNumber - 1 + segmentTemplate.firstSegmentNumber,
(segmentNumber - 1) + segmentTemplate.startNumber,
representation.bandwidth,

@@ -770,3 +594,2 @@ startTime);

* @param {number} periodDuration
*
* @private

@@ -789,15 +612,17 @@ */

// Note: do not copy |segmentDuration| since the segments may have different
// lengths.
segmentList.timescale = segmentTemplate.timescale;
segmentList.presentationTimeOffset = segmentTemplate.presentationTimeOffset;
segmentList.firstSegmentNumber = segmentTemplate.firstSegmentNumber;
segmentList.segmentDuration = segmentTemplate.segmentDuration;
segmentList.startNumber = segmentTemplate.startNumber;
segmentList.initialization = this.generateInitialization_(representation);
segmentList.segmentUrls = [];
// The current segment number.
var segmentNumber = 1;
var startTime = 0;
var numSegments =
Math.floor(periodDuration / segmentTemplate.segmentDuration);
while ((startTime / segmentList.timescale) < periodDuration) {
for (var segmentNumber = 1; segmentNumber <= numSegments; ++segmentNumber) {
var time =
((segmentNumber - 1) + (segmentTemplate.startNumber - 1)) *
segmentTemplate.segmentDuration;
// Generate the media URL.

@@ -808,5 +633,5 @@ shaka.asserts.assert(segmentTemplate.mediaUrlTemplate);

representation.id,
segmentNumber - 1 + segmentTemplate.firstSegmentNumber,
segmentNumber - 1 + segmentTemplate.startNumber,
representation.bandwidth,
startTime);
time);

@@ -825,9 +650,6 @@ if (!filledUrlTemplate) {

segmentUrl.mediaUrl = mediaUrl;
segmentUrl.startTime = startTime;
segmentUrl.startTime = time;
segmentUrl.duration = segmentTemplate.segmentDuration;
segmentList.segmentUrls.push(segmentUrl);
++segmentNumber;
startTime += segmentTemplate.segmentDuration;
}

@@ -843,3 +665,2 @@

* @param {!shaka.dash.mpd.Representation} representation
*
* @return {shaka.dash.mpd.RepresentationIndex} A RepresentationIndex on

@@ -888,3 +709,2 @@ * success, null if no index URL template exists or an error occurred.

* @param {!shaka.dash.mpd.Representation} representation
*
* @return {shaka.dash.mpd.Initialization} An Initialization on success, null

@@ -940,3 +760,2 @@ * if no initialization URL template exists or an error occurred.

* @param {?number} time
*
* @return {goog.Uri} A URL on success; null if the resulting URL contains

@@ -1004,10 +823,8 @@ * illegal characters.

/**
* Builds a SegmentIndex for each SegmentList.
* Clears each SegmentList's SegmentUrls.
* Removes invalid Representations from |mpd|.
*
* @param {!shaka.dash.mpd.Mpd} mpd
*
* @private
*/
shaka.dash.MpdProcessor.prototype.buildSegmentIndexes_ = function(mpd) {
shaka.dash.MpdProcessor.prototype.filterPeriods_ = function(mpd) {
for (var i = 0; i < mpd.periods.length; ++i) {

@@ -1017,135 +834,6 @@ var period = mpd.periods[i];

var adaptationSet = period.adaptationSets[j];
for (var k = 0; k < adaptationSet.representations.length; ++k) {
var representation = adaptationSet.representations[k];
if (!representation.segmentList) {
continue;
}
var segmentList = representation.segmentList;
var segmentIndex = this.createSegmentIndex_(segmentList);
if (!segmentIndex) {
// An error has already been logged.
adaptationSet.representations.splice(k, 1);
--k;
}
// There could be hundreds of SegmentUrls; no need to keep them around.
segmentList.segmentUrls = [];
segmentList.userData = segmentIndex;
} // for k
}
}
};
/**
* Creates a SegmentIndex from a SegmentList.
*
* @param {!shaka.dash.mpd.SegmentList} segmentList
*
* @return {shaka.dash.SegmentIndex} A SegmentIndex on success; otherwise,
* return null.
* @private
*/
shaka.dash.MpdProcessor.prototype.createSegmentIndex_ = function(segmentList) {
var timescale = segmentList.timescale;
var presentationTimeOffset = segmentList.presentationTimeOffset;
var firstSegmentNumber = segmentList.firstSegmentNumber;
var segmentDuration = segmentList.segmentDuration;
/** @type {!Array.<!shaka.dash.SegmentReference>} */
var references = [];
for (var i = 0; i < segmentList.segmentUrls.length; ++i) {
var segmentUrl = segmentList.segmentUrls[i];
/** @type {number} */
var startTime = 0;
/** @type {?number} */
var endTime = null;
/** @type {number} */
var startByte = 0;
/** @type {?number} */
var endByte = null;
// Note that |startTime| may be 0.
if (segmentUrl.startTime != null) {
shaka.asserts.assert(segmentUrl.mediaRange == null);
shaka.asserts.assert(segmentUrl.duration);
if (i > 0 && segmentList.segmentUrls[i - 1].startTime != null) {
// Sanity check: there should not be a gap between the end of the last
// segment and the start of the current segment.
var lastTime = segmentList.segmentUrls[i - 1].startTime;
var lastDuration = segmentList.segmentUrls[i - 1].duration;
shaka.asserts.assert(lastTime + lastDuration >= segmentUrl.startTime);
}
startTime = (presentationTimeOffset + segmentUrl.startTime) / timescale;
endTime = startTime + (segmentUrl.duration / timescale);
} else {
shaka.asserts.assert(segmentUrl.duration == null);
if (!segmentDuration) {
shaka.log.warning(
'SegmentList does not contain an explicit segment duration.',
segmentList);
return null;
}
if (i == 0) {
startTime = presentationTimeOffset / timescale;
} else {
var lastTime = references[i - 1].startTime;
startTime = lastTime + (segmentDuration / timescale);
}
endTime = startTime + (segmentDuration / timescale);
if (segmentUrl.mediaRange) {
startByte = segmentUrl.mediaRange.begin;
endByte = segmentUrl.mediaRange.end;
}
}
shaka.asserts.assert(segmentUrl.mediaUrl);
references.push(
new shaka.dash.SegmentReference(
i,
startTime,
endTime,
startByte,
endByte,
/** @type {!goog.Uri} */ (segmentUrl.mediaUrl)));
}
return new shaka.dash.SegmentIndex(references);
};
/**
* Remove the Representations from the given MPD that have inconsistent mime
* types, that specify unsupported types, or that specify unsupported DRM
* schemes.
*
* @param {!shaka.dash.mpd.Mpd} mpd
* @private
*/
shaka.dash.MpdProcessor.prototype.filterRepresentations_ =
function(mpd) {
for (var i = 0; i < mpd.periods.length; ++i) {
var period = mpd.periods[i];
for (var j = 0; j < period.adaptationSets.length; ++j) {
var adaptationSet = period.adaptationSets[j];
this.removeUnsupportedRepresentations_(adaptationSet);
this.removeInconsistentRepresentations_(adaptationSet);
this.filterAdaptationSet_(adaptationSet);
if (adaptationSet.representations.length == 0) {
// Drop any AdaptationSet in which all Representations have been
// filtered out. An error has already been logged.
// Drop any AdaptationSet that is empty.
// An error has already been logged.
period.adaptationSets.splice(j, 1);

@@ -1160,5 +848,5 @@ --j;

/**
* Remove the Representations from the given AdaptationSet that have a
* different mime type than the mime type of the first Representation of the
* given AdaptationSet.
* Removes any Representation from the given AdaptationSet that has a different
* MIME type than the MIME type of the first Representation of the
* AdaptationSet.
*

@@ -1168,8 +856,10 @@ * @param {!shaka.dash.mpd.AdaptationSet} adaptationSet

*/
shaka.dash.MpdProcessor.prototype.removeInconsistentRepresentations_ =
function(adaptationSet) {
shaka.dash.MpdProcessor.prototype.filterAdaptationSet_ = function(
adaptationSet) {
var desiredMimeType = null;
for (var i = 0; i < adaptationSet.representations.length; ++i) {
var representation = adaptationSet.representations[i];
var mimeType = representation.mimeType || '';
if (!desiredMimeType) {

@@ -1189,44 +879,58 @@ desiredMimeType = mimeType;

/**
* Remove the Representations from the given AdaptationSet that have
* unsupported types or that only use unsupported DRM schemes.
* Creates a ManifestInfo from |mpd|.
*
* @param {!shaka.dash.mpd.AdaptationSet} adaptationSet
* @param {!shaka.dash.mpd.Mpd} mpd
* @private
*/
shaka.dash.MpdProcessor.prototype.removeUnsupportedRepresentations_ =
function(adaptationSet) {
var Player = shaka.player.Player;
shaka.dash.MpdProcessor.prototype.createManifestInfo_ = function(mpd) {
this.manifestInfo.minBufferTime = mpd.minBufferTime || 0;
for (var i = 0; i < adaptationSet.representations.length; ++i) {
var representation = adaptationSet.representations[i];
var type = shaka.dash.MpdProcessor.representationMimeType(representation);
for (var i = 0; i < mpd.periods.length; ++i) {
var period = mpd.periods[i];
// Check which schemes the application understands.
var approvedSchemes = this.getApprovedSchemes_(representation);
// Filter through those now to find only the ones which use key systems
// and MIME types the browser supports.
var supportedSchemes = [];
var numSupported = 0;
for (var j = 0; j < approvedSchemes.length; ++j) {
var scheme = approvedSchemes[j];
if (Player.isTypeSupported(scheme.keySystem, type)) {
supportedSchemes.push(scheme);
++numSupported;
var periodInfo = new shaka.media.PeriodInfo();
shaka.asserts.assert(period.start != null);
periodInfo.start = period.start || 0;
shaka.asserts.assert(period.duration != null);
periodInfo.duration = period.duration || 0;
for (var j = 0; j < period.adaptationSets.length; ++j) {
var adaptationSet = period.adaptationSets[j];
var streamSetInfo = new shaka.media.StreamSetInfo();
streamSetInfo.main = adaptationSet.main;
streamSetInfo.contentType = adaptationSet.contentType || '';
streamSetInfo.lang = adaptationSet.lang || '';
for (var k = 0; k < adaptationSet.representations.length; ++k) {
var representation = adaptationSet.representations[k];
// Get common DRM schemes.
var commonDrmSchemes = streamSetInfo.drmSchemes.slice(0);
this.updateCommonDrmSchemes_(representation, commonDrmSchemes);
if (commonDrmSchemes.length == 0 &&
streamSetInfo.drmSchemes.length > 0) {
shaka.log.warning(
'Representation does not contain any DRM schemes that are in ' +
'common with other Representations within its AdaptationSet.',
representation);
continue;
}
var streamInfo = this.createStreamInfo_(representation);
if (!streamInfo) {
// An error has already been logged.
continue;
}
streamSetInfo.streamInfos.push(streamInfo);
streamSetInfo.drmSchemes = commonDrmSchemes;
}
}
// Drop any encrypted Representation whose MIME types and DRM schemes
// can't be supported by the browser.
if (numSupported == 0) {
shaka.log.warning(
'Representation has an unsupported mime type and DRM ' +
'scheme combination.',
adaptationSet.representations[i]);
adaptationSet.representations.splice(i, 1);
--i;
continue;
periodInfo.streamSetInfos.push(streamSetInfo);
}
// Store the list of schemes for this Representation.
representation.userData = supportedSchemes;
this.manifestInfo.periodInfos.push(periodInfo);
}

@@ -1237,102 +941,56 @@ };

/**
* Get all application-approved DRM schemes for a representation.
* Creates a StreamInfo from the given Representation.
*
* @param {!shaka.dash.mpd.Representation} representation
* @return {!Array.<!shaka.player.DrmSchemeInfo>} A list of application-approved
* schemes. A dummy scheme structure will be used for unencrypted content.
* This dummy scheme will have an empty string for |keySystem| and is used
* to simplify calculations later.
* @return {shaka.media.StreamInfo} The new StreamInfo on success; otherwise,
* return null.
* @private
*/
shaka.dash.MpdProcessor.prototype.getApprovedSchemes_ =
function(representation) {
var approvedSchemes = [];
if (representation.contentProtections.length == 0) {
// Return a single item which indicates that the content is unencrypted.
approvedSchemes.push(shaka.player.DrmSchemeInfo.createUnencrypted());
} else if (this.interpretContentProtection_) {
for (var i = 0; i < representation.contentProtections.length; ++i) {
var contentProtection = representation.contentProtections[i];
var schemeInfo = this.interpretContentProtection_(contentProtection);
if (schemeInfo) {
approvedSchemes.push(schemeInfo);
}
}
}
return approvedSchemes;
};
shaka.dash.MpdProcessor.prototype.createStreamInfo_ = function(representation) {
var streamInfo = new shaka.media.StreamInfo();
streamInfo.id = representation.id;
streamInfo.minBufferTime = representation.minBufferTime;
streamInfo.bandwidth = representation.bandwidth;
streamInfo.width = representation.width;
streamInfo.height = representation.height;
streamInfo.mimeType = representation.mimeType || '';
streamInfo.codecs = representation.codecs || '';
/**
* Populate each AdaptationSet with the DRM schemes common to all of its
* Representations. This is because we cannot change DRM schemes during
* playback, so we can only consider the schemes common to all of the
* Representations within an AdaptationSet.
*
* @param {!shaka.dash.mpd.Mpd} mpd
* @private
*/
shaka.dash.MpdProcessor.prototype.bubbleUpDrmSchemes_ = function(mpd) {
for (var i = 0; i < mpd.periods.length; ++i) {
var period = mpd.periods[i];
for (var j = 0; j < period.adaptationSets.length; ++j) {
var adaptationSet = period.adaptationSets[j];
this.bubbleUpDrmSchemesInAdaptationSet_(adaptationSet);
}
}
};
streamInfo.mediaUrl = representation.baseUrl;
if (representation.segmentBase) {
shaka.asserts.assert(representation.segmentBase.representationIndex);
shaka.asserts.assert(representation.segmentBase.representationIndex.range);
shaka.asserts.assert(representation.segmentBase.mediaUrl);
/**
* Populate the given AdaptationSet with the DRM schemes common to all of its
* Representations.
*
* @param {!shaka.dash.mpd.AdaptationSet} adaptationSet
* @private
*/
shaka.dash.MpdProcessor.prototype.bubbleUpDrmSchemesInAdaptationSet_ =
function(adaptationSet) {
// Alias.
var DrmSchemeInfo = shaka.player.DrmSchemeInfo;
streamInfo.timestampOffset =
representation.segmentBase.presentationTimeOffset /
representation.segmentBase.timescale;
// Start by building a map of all DRM schemes from the representations.
/** @type {!Object.<string, {drmScheme: !DrmSchemeInfo, count: number}>} */
var schemeMap = {};
streamInfo.mediaUrl = representation.segmentBase.mediaUrl;
for (var i = 0; i < adaptationSet.representations.length; ++i) {
var representation = adaptationSet.representations[i];
var drmSchemes = /** @type {!Array.<!shaka.player.DrmSchemeInfo>} */ (
representation.userData);
streamInfo.segmentIndexInfo = this.createSegmentMetadataInfo_(
representation.segmentBase.representationIndex);
// Collect all unique keys. The same key may appear more than once in
// drmSchemes, in which case it should only be counted once.
var keyMap = {};
for (var j = 0; j < drmSchemes.length; ++j) {
var drmScheme = drmSchemes[j];
var key = drmScheme.key();
keyMap[key] = drmScheme;
}
streamInfo.segmentInitializationInfo = this.createSegmentMetadataInfo_(
representation.segmentBase.initialization);
} else if (representation.segmentList) {
streamInfo.timestampOffset =
representation.segmentList.presentationTimeOffset /
representation.segmentList.timescale;
for (var key in keyMap) {
if (!schemeMap.hasOwnProperty(key)) {
schemeMap[key] = {drmScheme: keyMap[key], count: 1};
} else {
schemeMap[key].count++;
}
}
}
streamInfo.segmentInitializationInfo = this.createSegmentMetadataInfo_(
representation.segmentList.initialization);
// Find the key systems which appear in all Representations.
var numRepresentations = adaptationSet.representations.length;
var commonDrmSchemes = [];
for (var key in schemeMap) {
var entry = schemeMap[key];
if (entry.count == numRepresentations) {
// This scheme is common to all representations.
commonDrmSchemes.push(entry.drmScheme);
// Create SegmentIndex.
streamInfo.segmentIndex =
this.createSegmentIndex_(representation.segmentList);
if (!streamInfo.segmentIndex) {
// An error has already been logged.
return null;
}
}
// Store the list of schemes for this AdaptationSet.
adaptationSet.userData = commonDrmSchemes;
return streamInfo;
};

@@ -1342,51 +1000,39 @@

/**
* Sort Representations by bandwidth within each AdaptationSet in the MPD.
* Updates |commonDrmSchemes|.
*
* @param {!shaka.dash.mpd.Mpd} mpd
* If |commonDrmSchemes| is empty then after this function is called
* |commonDrmSchemes| will equal |representation|'s application provided DRM
* schemes.
*
* Otherwise, if |commonDrmSchemes| is non-empty then after this function is
* called |commonDrmSchemes| will equal the intersection between
* |representation|'s application provided DRM schemes and |commonDrmSchemes|
* at the time this function was called.
*
* @param {!shaka.dash.mpd.Representation} representation
* @param {!Array.<!shaka.player.DrmSchemeInfo>} commonDrmSchemes
*
* @private
*/
shaka.dash.MpdProcessor.prototype.sortRepresentations_ = function(mpd) {
for (var i = 0; i < mpd.periods.length; ++i) {
var period = mpd.periods[i];
for (var j = 0; j < period.adaptationSets.length; ++j) {
var adaptationSet = period.adaptationSets[j];
adaptationSet.representations.sort(
shaka.dash.MpdProcessor.compareByBandwidth_);
}
}
};
shaka.dash.MpdProcessor.prototype.updateCommonDrmSchemes_ = function(
representation, commonDrmSchemes) {
var drmSchemes = this.getDrmSchemeInfos_(representation);
/**
* @param {!shaka.dash.mpd.Representation} rep1
* @param {!shaka.dash.mpd.Representation} rep2
* @return {number}
* @private
*/
shaka.dash.MpdProcessor.compareByBandwidth_ = function(rep1, rep2) {
var b1 = rep1.bandwidth || Number.MAX_VALUE;
var b2 = rep2.bandwidth || Number.MAX_VALUE;
if (b1 < b2) {
return -1;
} else if (b1 > b2) {
return 1;
if (commonDrmSchemes.length == 0) {
Array.prototype.push.apply(commonDrmSchemes, drmSchemes);
return;
}
return 0;
};
/**
* Choose AdaptationSets for each period.
*
* @param {!shaka.dash.mpd.Mpd} mpd
* @private
*/
shaka.dash.MpdProcessor.prototype.chooseAdaptationSets_ = function(mpd) {
this.adaptationSetMapAndDrmSchemeByPeriod_ = [];
for (var i = 0; i < mpd.periods.length; ++i) {
var period = mpd.periods[i];
this.adaptationSetMapAndDrmSchemeByPeriod_.push(
this.chooseAdaptationSetsForPeriod_(period));
for (var i = 0; i < commonDrmSchemes.length; ++i) {
var found = false;
for (var j = 0; j < drmSchemes.length; ++j) {
if (commonDrmSchemes[i].key() == drmSchemes[j].key()) {
found = true;
break;
}
}
if (!found) {
commonDrmSchemes.splice(i, 1);
--i;
}
}

@@ -1397,71 +1043,26 @@ };

/**
* Choose AdaptationSets for a given period.
* Creates a SegmentMetadataInfo from either a RepresentationIndex or an
* Initialization.
*
* @param {!shaka.dash.mpd.Period} period
* @return {?shaka.dash.MpdProcessor.AdaptationSetMapAndDrmScheme}
* @param {shaka.dash.mpd.RepresentationIndex|
* shaka.dash.mpd.Initialization} urlTypeObject
* @return {shaka.media.SegmentMetadataInfo}
* @private
*/
shaka.dash.MpdProcessor.prototype.chooseAdaptationSetsForPeriod_ =
function(period) {
/** @type {!shaka.dash.MpdProcessor.AdaptationSetMap} */
var byType = new shaka.util.MultiMap();
/** @type {!shaka.util.MultiMap.<!shaka.dash.mpd.AdaptationSet>} */
var byKeySystem = new shaka.util.MultiMap();
// Build multi-maps by both type and key system.
for (var i = 0; i < period.adaptationSets.length; ++i) {
var adaptationSet = period.adaptationSets[i];
var type = adaptationSet.contentType || '';
byType.push(type, adaptationSet);
var drmSchemes = /** @type {!Array.<!shaka.player.DrmSchemeInfo>} */ (
adaptationSet.userData);
for (var j = 0; j < drmSchemes.length; ++j) {
var drmScheme = drmSchemes[j];
byKeySystem.push(drmScheme.keySystem, adaptationSet);
}
shaka.dash.MpdProcessor.prototype.createSegmentMetadataInfo_ = function(
urlTypeObject) {
if (!urlTypeObject) {
return null;
}
// For each desired type, make a list of key systems which can supply it.
// Keep track of the intersection of all of these lists.
var desiredTypes = ['audio', 'video', 'text'];
var intersection = null;
var allKeySystems = byKeySystem.keys();
for (var i = 0; i < desiredTypes.length; ++i) {
var type = desiredTypes[i];
var adaptationSets = byType.get(type);
if (!adaptationSets) {
// There is no such type available, so ignore it and move on.
shaka.log.warning('No AdaptationSets available for ' + type);
continue;
}
var segmentMetadataInfo = new shaka.media.SegmentMetadataInfo();
var keySystems = this.buildKeySystemList_(adaptationSets, allKeySystems);
if (!intersection) {
intersection = keySystems;
} else {
intersection = shaka.util.ArrayUtils.intersect(intersection, keySystems);
}
}
segmentMetadataInfo.url = urlTypeObject.url;
if (!intersection) {
// There are no key systems which can provide all desired types.
return null;
if (urlTypeObject.range) {
segmentMetadataInfo.startByte = urlTypeObject.range.begin;
segmentMetadataInfo.endByte = urlTypeObject.range.end;
}
// Any of the key systems in |intersection| is suitable.
var keySystem = intersection[0];
// But if unencrypted for everything is an option, prefer that.
if (intersection.indexOf('') >= 0) {
keySystem = '';
}
var tuple = this.chooseAdaptationSetsByKeySystem_(byType.getAll(), keySystem);
tuple.adaptationSetMap =
this.chooseAdaptationSetsByMimeType_(tuple.adaptationSetMap);
return tuple;
return segmentMetadataInfo;
};

@@ -1471,88 +1072,85 @@

/**
* Build a list of key systems which appear in the list of adaptation sets.
* If there is an unencrypted adaptation set, all key systems will appear in
* the output. This allows an unencrypted source to mix in with all other key
* systems.
* Creates a SegmentIndex from a SegmentList.
*
* @param {!Array.<!shaka.dash.mpd.AdaptationSet>} adaptationSets
* @param {!Array.<string>} allKeySystems
* @return {!Array.<string>}
* @param {!shaka.dash.mpd.SegmentList} segmentList
* @return {shaka.media.SegmentIndex} A SegmentIndex on success; otherwise,
* return null.
* @private
*/
shaka.dash.MpdProcessor.prototype.buildKeySystemList_ =
function(adaptationSets, allKeySystems) {
/** @type {!Object.<string, null>} */
var keySystemMap = {};
shaka.dash.MpdProcessor.prototype.createSegmentIndex_ = function(segmentList) {
var timescale = segmentList.timescale;
var presentationTimeOffset = segmentList.presentationTimeOffset;
var startNumber = segmentList.startNumber;
var segmentDuration = segmentList.segmentDuration;
for (var i = 0; i < adaptationSets.length; ++i) {
var adaptationSet = adaptationSets[i];
var drmSchemes = /** @type {!Array.<!shaka.player.DrmSchemeInfo>} */ (
adaptationSet.userData);
for (var j = 0; j < drmSchemes.length; ++j) {
var drmScheme = drmSchemes[j];
keySystemMap[drmScheme.keySystem] = null;
}
}
/** @type {!Array.<!shaka.media.SegmentReference>} */
var references = [];
if (keySystemMap.hasOwnProperty('')) {
// There is an unencrypted set in the list, so this list can match with
// any key system.
return allKeySystems;
}
for (var i = 0; i < segmentList.segmentUrls.length; ++i) {
var segmentUrl = segmentList.segmentUrls[i];
return shaka.util.ArrayUtils.fromObjectKeys(keySystemMap);
};
/** @type {number} */
var startTime = 0;
/** @type {?number} */
var endTime = null;
/**
* Get the AdaptationSets that support the given key system, and get those
* AdaptationSets' common DRM scheme.
*
* @param {!Array.<!shaka.dash.mpd.AdaptationSet>} adaptationSets
* @param {string} keySystem
* @return {shaka.dash.MpdProcessor.AdaptationSetMapAndDrmScheme}
* @private
*/
shaka.dash.MpdProcessor.prototype.chooseAdaptationSetsByKeySystem_ =
function(adaptationSets, keySystem) {
/**
* The AdaptationSets that support |keySystem|.
* @type {!shaka.util.MultiMap.<!shaka.dash.mpd.AdaptationSet>}
*/
var allowableAdaptationSetMap = new shaka.util.MultiMap();
/** @type {number} */
var startByte = 0;
/**
* The DRM scheme shared by |allowableAdaptationSetMap|.
* @type {shaka.player.DrmSchemeInfo}
*/
var commonDrmScheme = null;
/** @type {?number} */
var endByte = null;
for (var i = 0; i < adaptationSets.length; ++i) {
var adaptationSet = adaptationSets[i];
var drmSchemes = /** @type {!Array.<!shaka.player.DrmSchemeInfo>} */ (
adaptationSet.userData);
for (var j = 0; j < drmSchemes.length; ++j) {
var drmScheme = drmSchemes[j];
// Note that |startTime| may be 0.
if (segmentUrl.startTime != null) {
shaka.asserts.assert(segmentUrl.mediaRange == null);
shaka.asserts.assert(segmentUrl.duration);
// Unencrypted mixes with everything, so the empty keySystem is okay.
if (drmScheme.keySystem != keySystem && drmScheme.keySystem != '')
continue;
if ((i > 0) && (segmentList.segmentUrls[i - 1].startTime != null)) {
// Sanity check: there should not be a gap/overlap between the end of
// the last segment and the start of the current segment.
var lastTime = segmentList.segmentUrls[i - 1].startTime;
var lastDuration = segmentList.segmentUrls[i - 1].duration;
shaka.asserts.assert(lastTime + lastDuration == segmentUrl.startTime);
}
shaka.asserts.assert(adaptationSet.contentType != null);
var type = /** @type {string} */ (adaptationSet.contentType);
allowableAdaptationSetMap.push(type, adaptationSet);
startTime = segmentUrl.startTime / timescale;
endTime = startTime + (segmentUrl.duration / timescale);
} else {
shaka.asserts.assert(segmentUrl.duration == null);
if (!commonDrmScheme || !commonDrmScheme.keySystem) {
commonDrmScheme = drmScheme;
} else if (drmScheme.keySystem) {
commonDrmScheme =
shaka.player.DrmSchemeInfo.combine(commonDrmScheme, drmScheme);
if (!segmentDuration) {
shaka.log.warning(
'SegmentList does not contain an explicit segment duration.',
segmentList);
return null;
}
if (i == 0) {
startTime = 0;
} else {
var lastTime = references[i - 1].startTime;
startTime = lastTime + (segmentDuration / timescale);
}
endTime = startTime + (segmentDuration / timescale);
if (segmentUrl.mediaRange) {
startByte = segmentUrl.mediaRange.begin;
endByte = segmentUrl.mediaRange.end;
}
}
shaka.asserts.assert(segmentUrl.mediaUrl);
references.push(
new shaka.media.SegmentReference(
i,
startTime,
endTime,
startByte,
endByte,
/** @type {!goog.Uri} */ (segmentUrl.mediaUrl)));
}
return {
adaptationSetMap: allowableAdaptationSetMap,
drmScheme: commonDrmScheme
};
return new shaka.media.SegmentIndex(references);
};

@@ -1562,41 +1160,27 @@

/**
* Choose a single video AdaptationSet and a collection of audio AdaptationSets
* that each have the same mime type. It's assumed that within an
* AdaptationSet, each Representation has the same mime type as the first
* Representation within that AdaptationSet.
* Gets the application provided DrmSchemeInfos for the given Representation.
*
* @param {!shaka.dash.MpdProcessor.AdaptationSetMap} byType
* @return {!shaka.dash.MpdProcessor.AdaptationSetMap}
* @param {!shaka.dash.mpd.Representation} representation
* @return {!Array.<!shaka.player.DrmSchemeInfo>} The application provided
* DrmSchemeInfos. A dummy scheme, which has an empty |keySystem| string,
* is used for unencrypted content.
* @private
*/
shaka.dash.MpdProcessor.prototype.chooseAdaptationSetsByMimeType_ =
function(byType) {
var allowableAdaptationSetMap = new shaka.util.MultiMap();
// Add one video AdaptationSet.
var videoSets = byType.get('video');
if (videoSets) {
allowableAdaptationSetMap.push('video', videoSets[0]);
}
// Add audio AdaptationSets.
var audioSets = byType.get('audio');
if (audioSets) {
for (var i = 0; i < audioSets.length; ++i) {
if (audioSets[i].mimeType == audioSets[0].mimeType) {
allowableAdaptationSetMap.push('audio', audioSets[i]);
shaka.dash.MpdProcessor.prototype.getDrmSchemeInfos_ =
function(representation) {
var drmSchemes = [];
if (representation.contentProtections.length == 0) {
// Return a single item which indicates that the content is unencrypted.
drmSchemes.push(shaka.player.DrmSchemeInfo.createUnencrypted());
} else if (this.interpretContentProtection_) {
for (var i = 0; i < representation.contentProtections.length; ++i) {
var contentProtection = representation.contentProtections[i];
var drmSchemeInfo = this.interpretContentProtection_(contentProtection);
if (drmSchemeInfo) {
drmSchemes.push(drmSchemeInfo);
}
}
}
// Add text AdaptationSets.
var textSets = byType.get('text');
if (textSets) {
for (var i = 0; i < textSets.length; ++i) {
allowableAdaptationSetMap.push('text', textSets[i]);
}
}
return allowableAdaptationSetMap;
return drmSchemes;
};

@@ -22,6 +22,2 @@ /**

goog.require('shaka.asserts');
goog.require('shaka.dash.AbrManager');
goog.require('shaka.dash.DashStream');
goog.require('shaka.dash.DashTextStream');
goog.require('shaka.dash.IDashStream');
goog.require('shaka.dash.MpdProcessor');

@@ -31,12 +27,8 @@ goog.require('shaka.dash.MpdRequest');

goog.require('shaka.log');
goog.require('shaka.player.AudioTrack');
goog.require('shaka.media.Stream');
goog.require('shaka.player.DrmSchemeInfo');
goog.require('shaka.player.IVideoSource');
goog.require('shaka.player.VideoTrack');
goog.require('shaka.util.ArrayUtils');
goog.require('shaka.util.EWMABandwidthEstimator');
goog.require('shaka.util.EventManager');
goog.require('shaka.player.StreamVideoSource');
goog.require('shaka.util.FakeEventTarget');
goog.require('shaka.util.IBandwidthEstimator');
goog.require('shaka.util.LanguageUtils');
goog.require('shaka.util.PublicPromise');

@@ -52,7 +44,4 @@ goog.require('shaka.util.TypedBind');

* interpretContentProtection A callback to interpret the ContentProtection
* elements in the DASH MPD.
* elements in the MPD.
*
* @listens shaka.dash.DashStream.EndedEvent
* @listens shaka.util.IBandwidthEstimator.BandwidthEvent
*
* @struct

@@ -67,43 +56,16 @@ * @constructor

/** @private {shaka.dash.mpd.Mpd}. */
this.mpd_ = null;
/** @private {string} */
this.mpdUrl_ = mpdUrl;
/** @private {!shaka.dash.MpdProcessor} */
this.processor_ = new shaka.dash.MpdProcessor(interpretContentProtection);
/** @private {shaka.player.DashVideoSource.ContentProtectionCallback} */
this.interpretContentProtection_ = interpretContentProtection;
/** @private {!MediaSource} */
this.mediaSource_ = new MediaSource();
/** @private {HTMLVideoElement} */
this.video_ = null;
/**
* The active DASH streams.
* @private {!Object.<string, !shaka.dash.IDashStream>}
* The underlying StreamVideoSource to delegate to.
* @private {shaka.player.StreamVideoSource}
*/
this.streamsByType_ = {};
this.streamVideoSource_ = null;
/** @private {!shaka.util.EventManager} */
this.eventManager_ = new shaka.util.EventManager();
/** @private {!shaka.util.PublicPromise} */
this.attachPromise_ = new shaka.util.PublicPromise();
/** @private {string} */
this.lang_ = '';
/** @private {!shaka.util.IBandwidthEstimator} */
this.estimator_ = new shaka.util.EWMABandwidthEstimator();
// TODO(story 1925894): Seed the estimator with data from the previous
// playback in the same browser session, unless that data is more than 1
// hour old.
/** @private {shaka.player.Stats} */
this.stats_ = null;
/** @private {!shaka.dash.AbrManager} */
this.abrManager_ = new shaka.dash.AbrManager(this.estimator_, this);
/** @private {boolean} */
this.adaptationEnabled_ = true;
};

@@ -134,19 +96,9 @@ goog.inherits(shaka.player.DashVideoSource, shaka.util.FakeEventTarget);

shaka.player.DashVideoSource.prototype.destroy = function() {
this.eventManager_.destroy();
this.eventManager_ = null;
if (this.streamVideoSource_) {
this.streamVideoSource_.destroy();
this.streamVideoSource_ = null;
}
this.abrManager_.destroy();
this.abrManager_ = null;
this.destroyStreams_();
this.streamsByType_ = null;
this.video_ = null;
this.mediaSource_ = null;
this.processor_ = null;
this.mpdUrl_ = null;
this.mpd_ = null;
this.interpretContentProtection_ = null;
this.parent = null;
this.attachPromise_ = null;
this.estimator_ = null;
};

@@ -157,50 +109,11 @@

shaka.player.DashVideoSource.prototype.attach = function(player, video) {
this.parent = player;
this.video_ = video;
this.stats_ = player.getStats();
// The "sourceopen" event fires after setting the video element's "src"
// attribute.
this.eventManager_.listen(
this.mediaSource_,
'sourceopen',
this.onMediaSourceOpen_.bind(this));
this.eventManager_.listen(
this.video_,
'seeking',
this.onSeeking_.bind(this));
this.eventManager_.listen(
this.estimator_,
'bandwidth',
this.onBandwidth_.bind(this));
// When re-using a video tag in Chrome, mediaKeys can get cleared by Chrome
// when src is set for the second (or subsequent) time. This feels like a
// bug in Chrome.
// To work around this, back up the old value and ensure that it is set again
// before the attach promise is resolved. This fixes bug #18614098.
var backupMediaKeys = this.video_.mediaKeys;
this.video_.src = window.URL.createObjectURL(this.mediaSource_);
var restorePromise = this.video_.setMediaKeys(backupMediaKeys);
// Return a promise which encompasses both attach and the restoration of
// mediaKeys.
return Promise.all([this.attachPromise_, restorePromise]);
};
/** @override */
shaka.player.DashVideoSource.prototype.getDrmSchemeInfo = function() {
if (this.processor_.getNumPeriods() == 0) {
return null;
if (!this.streamVideoSource_) {
var error = new Error('Manifest has not been loaded.');
error.type = 'stream';
return Promise.reject(error);
}
// TODO(story 1890046): Support multiple periods.
var drmScheme = this.processor_.getDrmScheme(0);
// Externally unencrypted is signalled by null.
return (drmScheme && drmScheme.keySystem) ? drmScheme : null;
// Note that streamVideoSource_.attach() will return a rejected promise if it
// has not been loaded yet.
return this.streamVideoSource_.attach(player, video);
};

@@ -211,27 +124,15 @@

shaka.player.DashVideoSource.prototype.load = function(preferredLanguage) {
this.lang_ = preferredLanguage;
var mpdRequest = new shaka.dash.MpdRequest(this.mpdUrl_);
var mpdRequest = new shaka.dash.MpdRequest(this.mpdUrl_);
return mpdRequest.send().then(shaka.util.TypedBind(this,
/** @param {!shaka.dash.mpd.Mpd} mpd */
function(mpd) {
if (mpd.periods.length == 0) {
var error = new Error('Unplayable MPD: no periods.');
error.type = 'mpd';
return Promise.reject(error);
}
var mpdProcessor =
new shaka.dash.MpdProcessor(this.interpretContentProtection_);
mpdProcessor.process(mpd);
this.processor_.process(mpd);
this.mpd_ = mpd;
// TODO(story 1890046): Support multiple periods.
if ((this.processor_.getNumPeriods() == 0) ||
(this.processor_.getAdaptationSets(0).length == 0)) {
var error = new Error(
'This content cannot be displayed on this browser/platform.');
error.type = 'mpd';
return Promise.reject(error);
}
return Promise.resolve();
this.streamVideoSource_ =
new shaka.player.StreamVideoSource(mpdProcessor.manifestInfo);
this.streamVideoSource_.enableAdaptation(this.adaptationEnabled_);
return this.streamVideoSource_.load(preferredLanguage);
})

@@ -244,37 +145,5 @@ );

shaka.player.DashVideoSource.prototype.getVideoTracks = function() {
if (this.processor_.getNumPeriods() == 0) {
return [];
}
var stream = this.streamsByType_['video'];
var activeRepresentation = stream ? stream.getRepresentation() : null;
var activeId = activeRepresentation ? activeRepresentation.uniqueId : 0;
/** @type {!Array.<!shaka.player.VideoTrack>} */
var tracks = [];
// TODO(story 1890046): Support multiple periods.
var adaptationSets = this.processor_.getAdaptationSets(0, 'video');
for (var i = 0; i < adaptationSets.length; ++i) {
var adaptationSet = adaptationSets[i];
for (var j = 0; j < adaptationSet.representations.length; ++j) {
var representation = adaptationSet.representations[j];
var id = representation.uniqueId;
var bandwidth = representation.bandwidth;
var width = representation.width;
var height = representation.height;
var videoTrack =
new shaka.player.VideoTrack(id, bandwidth, width, height);
if (id == activeId) {
videoTrack.active = true;
}
tracks.push(videoTrack);
}
}
return tracks;
return this.streamVideoSource_ ?
this.streamVideoSource_.getVideoTracks() :
[];
};

@@ -285,35 +154,21 @@

shaka.player.DashVideoSource.prototype.getAudioTracks = function() {
if (this.processor_.getNumPeriods() == 0) {
return [];
}
return this.streamVideoSource_ ?
this.streamVideoSource_.getAudioTracks() :
[];
};
var stream = this.streamsByType_['audio'];
var activeRepresentation = stream ? stream.getRepresentation() : null;
var activeId = activeRepresentation ? activeRepresentation.uniqueId : 0;
/** @type {!Array.<!shaka.player.AudioTrack>} */
var tracks = [];
/** @override */
shaka.player.DashVideoSource.prototype.getTextTracks = function() {
return this.streamVideoSource_ ?
this.streamVideoSource_.getTextTracks() :
[];
};
// TODO(story 1890046): Support multiple periods.
var adaptationSets = this.processor_.getAdaptationSets(0, 'audio');
for (var i = 0; i < adaptationSets.length; ++i) {
var adaptationSet = adaptationSets[i];
var lang = adaptationSet.lang;
for (var j = 0; j < adaptationSet.representations.length; ++j) {
var representation = adaptationSet.representations[j];
var id = representation.uniqueId;
var bandwidth = representation.bandwidth;
var audioTrack = new shaka.player.AudioTrack(id, bandwidth, lang);
if (id == activeId) {
audioTrack.active = true;
}
tracks.push(audioTrack);
}
}
return tracks;
/** @override */
shaka.player.DashVideoSource.prototype.getResumeThreshold = function() {
return this.streamVideoSource_ ?
this.streamVideoSource_.getResumeThreshold() :
0;
};

@@ -323,36 +178,11 @@

/** @override */
shaka.player.DashVideoSource.prototype.getTextTracks = function() {
if (this.processor_.getNumPeriods() == 0) {
shaka.player.DashVideoSource.prototype.getConfigurations = function() {
shaka.asserts.assert(this.streamVideoSource_);
if (!this.streamVideoSource_) {
// An empty array is invalid, but so is calling this method without waiting
// for load() to be resolved.
return [];
}
var stream = this.streamsByType_['text'];
var activeRepresentation = stream ? stream.getRepresentation() : null;
var activeId = activeRepresentation ? activeRepresentation.uniqueId : 0;
/** @type {!Array.<!shaka.player.TextTrack>} */
var tracks = [];
// TODO(story 1890046): Support multiple periods.
var adaptationSets = this.processor_.getAdaptationSets(0, 'text');
for (var i = 0; i < adaptationSets.length; ++i) {
var adaptationSet = adaptationSets[i];
var lang = adaptationSet.lang;
for (var j = 0; j < adaptationSet.representations.length; ++j) {
var representation = adaptationSet.representations[j];
var id = representation.uniqueId;
var textTrack = new shaka.player.TextTrack(id, lang);
if (id == activeId) {
textTrack.active = true;
shaka.asserts.assert(stream != null);
textTrack.enabled = stream.getEnabled();
}
tracks.push(textTrack);
}
}
return tracks;
return this.streamVideoSource_.getConfigurations();
};

@@ -362,4 +192,6 @@

/** @override */
shaka.player.DashVideoSource.prototype.getResumeThreshold = function() {
return this.mpd_.minBufferTime;
shaka.player.DashVideoSource.prototype.selectConfigurations =
function(configs) {
shaka.asserts.assert(this.streamVideoSource_);
return this.streamVideoSource_.selectConfigurations(configs);
};

@@ -371,3 +203,5 @@

function(id, immediate) {
return this.selectTrack_('video', id, immediate);
return this.streamVideoSource_ ?
this.streamVideoSource_.selectVideoTrack(id, immediate) :
false;
};

@@ -379,3 +213,5 @@

function(id, immediate) {
return this.selectTrack_('audio', id, immediate);
return this.streamVideoSource_ ?
this.streamVideoSource_.selectAudioTrack(id, immediate) :
false;
};

@@ -387,3 +223,5 @@

function(id, immediate) {
return this.selectTrack_('text', id, immediate);
return this.streamVideoSource_ ?
this.streamVideoSource_.selectTextTrack(id, immediate) :
false;
};

@@ -394,5 +232,4 @@

shaka.player.DashVideoSource.prototype.enableTextTrack = function(enabled) {
var textStream = this.streamsByType_['text'];
if (textStream) {
textStream.setEnabled(enabled);
if (this.streamVideoSource_) {
this.streamVideoSource_.enableTextTrack(enabled);
}

@@ -404,3 +241,6 @@ };

shaka.player.DashVideoSource.prototype.enableAdaptation = function(enabled) {
this.abrManager_.enable(enabled);
this.adaptationEnabled_ = enabled;
if (this.streamVideoSource_) {
this.streamVideoSource_.enableAdaptation(enabled);
}
};

@@ -412,259 +252,6 @@

function(restrictions) {
shaka.asserts.assert(this.mpd_);
if (this.mpd_) {
this.processor_.enforceRestrictions(this.mpd_, restrictions);
if (this.streamVideoSource_) {
this.streamVideoSource_.setRestrictions(restrictions);
}
};
/**
* Select a track by ID.
*
* @param {string} type The type of track to change, such as 'video', 'audio',
* or 'text'.
* @param {number} id The |uniqueId| field of the desired representation.
* @param {boolean} immediate If true, switch immediately.
*
* @return {boolean} True if the specified track was found.
* @private
*/
shaka.player.DashVideoSource.prototype.selectTrack_ =
function(type, id, immediate) {
if (this.processor_.getNumPeriods() == 0) {
return false;
}
if (!this.streamsByType_[type]) {
return false;
}
// TODO(story 1890046): Support multiple periods.
var adaptationSets = this.processor_.getAdaptationSets(0, type);
for (var i = 0; i < adaptationSets.length; ++i) {
var adaptationSet = adaptationSets[i];
for (var j = 0; j < adaptationSet.representations.length; ++j) {
var representation = adaptationSet.representations[j];
if (representation.uniqueId == id) {
this.stats_.logRepresentationChange(representation);
this.streamsByType_[type].switch(representation, immediate);
return true;
}
}
}
return false;
};
/**
* MediaSource callback.
*
* @param {!Event} event The MediaSource event.
* @private
*/
shaka.player.DashVideoSource.prototype.onMediaSourceOpen_ =
function(event) {
shaka.asserts.assert(this.mpd_ != null);
shaka.asserts.assert(this.mediaSource_.sourceBuffers.length == 0);
shaka.asserts.assert(this.mpd_.periods.length != 0);
this.eventManager_.unlisten(this.mediaSource_, 'sourceopen');
// TODO(story 1890046): Support multiple periods.
var duration = this.mpd_.periods[0].duration || this.mpd_.duration;
shaka.asserts.assert(duration != null);
this.mediaSource_.duration = /** @type {number} */ (duration);
shaka.asserts.assert(this.processor_.getNumPeriods() > 0);
var adaptationSets = this.processor_.selectAdaptationSets(0, this.lang_);
/** @type {!Object.<string, !shaka.dash.mpd.Representation>} */
var representationsByType = {};
// Create DASH streams.
for (var i = 0; i < adaptationSets.length; ++i) {
var adaptationSet = adaptationSets[i];
var contentType = adaptationSet.contentType || '';
// Start by assuming we will use the first Representation.
var representation = adaptationSet.representations[0];
if (contentType == 'video') {
// Ask AbrManager which video Representation to start with.
var trackId = this.abrManager_.getInitialVideoTrackId();
shaka.asserts.assert(trackId != null);
var found = false;
for (var j = 0; j < adaptationSet.representations.length; ++j) {
representation = adaptationSet.representations[j];
if (representation.uniqueId == trackId) {
found = true;
break;
}
}
shaka.asserts.assert(found);
} else if (contentType == 'audio') {
// In lieu of audio adaptation, choose the middle representation from
// the audio adaptation set. If we have high, medium, and low quality
// audio, this is medium. If we only have high and low, this is high.
var length = adaptationSet.representations.length;
var index = Math.floor(length / 2);
representation = adaptationSet.representations[index];
}
// Log the initial representation choice.
this.stats_.logRepresentationChange(representation);
var mimeType =
shaka.dash.MpdProcessor.representationMimeType(representation);
var stream = (contentType == 'text') ? this.createTextStream_() :
this.createStream_(mimeType);
if (!stream) {
// An error has already been dispatched and the promise rejected.
return;
}
this.streamsByType_[contentType] = stream;
representationsByType[contentType] = representation;
}
// Start DASH streams.
for (var type in this.streamsByType_) {
this.eventManager_.listen(
this.streamsByType_[type],
'ended',
this.onStreamEnded_.bind(this));
var representation = representationsByType[type];
this.streamsByType_[type].start(representation);
}
// Assume subs will be needed.
var subsNeeded = true;
// If there is an audio track, and the language matches the user's
// preference, then subtitles are not needed.
var audioRepresentation = representationsByType['audio'];
if (audioRepresentation) {
// If the MPD did not specify a language, assume it is the right one.
// This means that content creators who omit language because they serve a
// monolingual demographic will not have annoyed users who have to disable
// subtitles every single time they play a video.
var lang = audioRepresentation.lang || this.lang_;
// Alias.
var LanguageUtils = shaka.util.LanguageUtils;
if (LanguageUtils.match(LanguageUtils.MatchType.MAX, this.lang_, lang)) {
// It's a match, so subs are not needed.
subsNeeded = false;
}
}
// Enable the subtitle display by default iff the subs are needed.
this.enableTextTrack(subsNeeded);
this.attachPromise_.resolve();
};
/**
* Creates a DashStream object.
*
* @param {string} mimeType
* @return {shaka.dash.DashStream} or null on failure.
* @private
*/
shaka.player.DashVideoSource.prototype.createStream_ = function(mimeType) {
// Create source buffer.
var buf;
try {
buf = this.mediaSource_.addSourceBuffer(mimeType);
shaka.asserts.assert(buf != null);
} catch (exception) {
this.destroyStreams_();
var error = new Error('Failed to create DASH stream for ' + mimeType + '.');
error.type = 'dash';
error.exception = exception;
this.attachPromise_.reject(error);
return null;
}
// Create stream.
return new shaka.dash.DashStream(
this,
/** @type {!HTMLVideoElement} */ (this.video_),
this.mediaSource_,
/** @type {!SourceBuffer} */ (buf),
this.estimator_);
};
/**
* Creates a DashTextStream object.
*
* @return {!shaka.dash.DashTextStream}
* @private
*/
shaka.player.DashVideoSource.prototype.createTextStream_ = function() {
var video = /** @type {!HTMLVideoElement} */ (this.video_);
return new shaka.dash.DashTextStream(this, video);
};
/**
* Destroy all streams.
*
* @private
*/
shaka.player.DashVideoSource.prototype.destroyStreams_ = function() {
for (var type in this.streamsByType_) {
this.streamsByType_[type].destroy();
}
this.streamsByType_ = {};
};
/**
* DashStream EOF callback.
*
* @param {!Event} event
* @private
*/
shaka.player.DashVideoSource.prototype.onStreamEnded_ = function(event) {
shaka.log.v1('onStreamEnded_', event);
// Check the state, otherwise this throws an exception.
if (this.mediaSource_.readyState == 'open') {
for (var type in this.streamsByType_) {
if (!this.streamsByType_[type].hasEnded()) {
// Not all streams have ended, so ignore.
return;
}
}
// All streams have ended, so signal EOF to the |mediaSource_|.
this.mediaSource_.endOfStream();
}
};
/**
* Video seeking callback.
*
* @param {!Event} event
* @private
*/
shaka.player.DashVideoSource.prototype.onSeeking_ = function(event) {
// Resync each stream to the new timestamp.
for (var type in this.streamsByType_) {
this.streamsByType_[type].resync();
}
};
/**
* Bandwidth statistics update callback.
*
* @param {!Event} event
* @private
*/
shaka.player.DashVideoSource.prototype.onBandwidth_ = function(event) {
this.stats_.logBandwidth(this.estimator_.getBandwidth());
};

@@ -22,3 +22,4 @@ /**

goog.require('shaka.asserts');
goog.require('shaka.util.StringUtils');
goog.require('shaka.util.ArrayUtils');
goog.require('shaka.util.Uint8ArrayUtils');

@@ -156,3 +157,3 @@

var initDataKey = function(o) {
return shaka.util.StringUtils.uint8ArrayKey(o.initData);
return shaka.util.Uint8ArrayUtils.key(o.initData);
};

@@ -159,0 +160,0 @@ c.initDatas = shaka.util.ArrayUtils.removeDuplicates(initDatas, initDataKey);

@@ -21,2 +21,3 @@ /**

goog.require('shaka.media.StreamConfig');
goog.require('shaka.player.DrmSchemeInfo');

@@ -32,3 +33,3 @@ goog.require('shaka.player.IVideoSource');

* @param {string} textUrl The text URL, or empty string if no subtitles.
* @param {shaka.player.DrmSchemeInfo} drmSchemeInfo Description of the DRM
* @param {shaka.player.DrmSchemeInfo} drmScheme Description of the DRM
* scheme, or null for non-encrypted sources.

@@ -41,3 +42,3 @@ * @struct

*/
shaka.player.HttpVideoSource = function(mediaUrl, textUrl, drmSchemeInfo) {
shaka.player.HttpVideoSource = function(mediaUrl, textUrl, drmScheme) {
shaka.util.FakeEventTarget.call(this, null);

@@ -52,3 +53,4 @@

/** @private {shaka.player.DrmSchemeInfo} */
this.drmSchemeInfo_ = drmSchemeInfo;
this.drmScheme_ = drmScheme ? drmScheme :
shaka.player.DrmSchemeInfo.createUnencrypted();

@@ -68,3 +70,3 @@ /** @private {HTMLTrackElement} */

this.drmSchemeInfo_ = null;
this.drmScheme_ = null;
this.parent = null;

@@ -98,8 +100,2 @@ };

/** @override */
shaka.player.HttpVideoSource.prototype.getDrmSchemeInfo = function() {
return this.drmSchemeInfo_;
};
/** @override */
shaka.player.HttpVideoSource.prototype.load = function(preferredLanguage) {

@@ -135,2 +131,17 @@ return Promise.resolve();

/** @override */
shaka.player.HttpVideoSource.prototype.getConfigurations = function() {
var cfg = new shaka.media.StreamConfig();
cfg.drmScheme = this.drmScheme_;
return [cfg];
};
/** @override */
shaka.player.HttpVideoSource.prototype.selectConfigurations =
function(configs) {
// nop
};
/** @override */
shaka.player.HttpVideoSource.prototype.selectVideoTrack =

@@ -137,0 +148,0 @@ function(id, immediate) {

@@ -21,2 +21,3 @@ /**

goog.require('shaka.media.StreamConfig');
goog.require('shaka.player.AudioTrack');

@@ -27,2 +28,3 @@ goog.require('shaka.player.DrmSchemeInfo');

goog.require('shaka.player.VideoTrack');
goog.require('shaka.util.MultiMap');

@@ -46,7 +48,7 @@

* Attaches the video source to the specified video element.
* This allows the Player to avoid setting the video's src attribute until it
* is ready. Should not be called until after the load() Promise is resolved.
* This allows the Player to avoid setting the video's |src| attribute until it
* is ready. Should not be called until after load() has been resolved.
*
* @param {shaka.player.Player} player The associated Player, used for event
* bubbling and stats.
* @param {shaka.player.Player} player The associated Player, which may be used
* for event bubbling and stats.
* @param {!HTMLVideoElement} video The video element.

@@ -59,11 +61,2 @@ * @return {!Promise}

/**
* Returns an object containing everything you need to know about the DRM
* scheme. If null, indicates that the source is not encrypted.
* Should not be called until after the load() Promise is resolved.
* @return {shaka.player.DrmSchemeInfo}
*/
shaka.player.IVideoSource.prototype.getDrmSchemeInfo = function() {};
/**
* Load any intermediate source material (manifest, etc.)

@@ -112,2 +105,25 @@ *

/**
* Get a list of configurations supported by this source.
* The array may not be empty.
*
* Should not be called before the load() promise is resolved.
*
* @return {!Array.<!shaka.media.StreamConfig>}
*/
shaka.player.IVideoSource.prototype.getConfigurations = function() {};
/**
* Select streams based on the given configurations.
* Should not be called before the load() promise is resolved.
*
* @param {!shaka.util.MultiMap.<!shaka.media.StreamConfig>} configs
* The keys are content types, such as 'audio', 'video', or 'text'.
* The implementation may ignore the keys if they are not helpful.
*/
shaka.player.IVideoSource.prototype.selectConfigurations =
function(configs) {};
/**
* Select a video track by ID.

@@ -152,3 +168,4 @@ *

/**
* Enable or disable the text track.
* Enable or disable the text track. Has no effect if called before
* load() resolves.
*

@@ -161,3 +178,3 @@ * @param {boolean} enabled

/**
* Enable or disable bitrate adaptation.
* Enable or disable bitrate adaptation. May be called at any time.
*

@@ -170,4 +187,5 @@ * @param {boolean} enabled

/**
* Sets restrictions on the video tracks which can be selected. Tracks which
* exceed any of these restrictions will be ignored.
* Sets restrictions on the video tracks which can be selected. Video tracks
* that exceed any of these restrictions will be ignored. Has no effect if
* called before IVideoSource.load() resolves.
*

@@ -174,0 +192,0 @@ * @param {!shaka.player.DrmSchemeInfo.Restrictions} restrictions

@@ -23,2 +23,3 @@ /**

goog.require('shaka.log');
goog.require('shaka.media.StreamConfig');
goog.require('shaka.player.AudioTrack');

@@ -34,3 +35,4 @@ goog.require('shaka.player.Stats');

goog.require('shaka.util.LicenseRequest');
goog.require('shaka.util.StringUtils');
goog.require('shaka.util.MultiMap');
goog.require('shaka.util.Uint8ArrayUtils');

@@ -45,2 +47,6 @@

* @property {!Error} detail An object which contains details on the error.
* The error's 'type' property will help you identify the specific error
* condition and display an appropriate message or error indicator to the
* user. The error's 'message' property contains English text which can
* be useful during debugging.
* @export

@@ -50,3 +56,11 @@ */

/**
* @event shaka.player.Player.BufferingEvent
* @description Fired when the player's buffering state changes.
* @property {string} type 'bufferingStart' or 'bufferingEnd'
* @export
*/
/**

@@ -57,4 +71,5 @@ * Creates a Player.

*
* @fires shaka.media.Stream.AdaptationEvent
* @fires shaka.player.Player.BufferingEvent
* @fires shaka.player.Player.ErrorEvent
* @fires shaka.dash.DashStream.AdaptationEvent
*

@@ -80,8 +95,8 @@ * @constructor

/**
* The MediaKeys object, non-null if using the OO EME API, null otherwise.
* @private {MediaKeys}
*/
/** @private {MediaKeys} */
this.mediaKeys_ = null;
/** @private {shaka.player.DrmSchemeInfo} */
this.drmScheme_ = null;
/** @private {!shaka.util.EventManager} */

@@ -121,6 +136,12 @@ this.eventManager_ = new shaka.util.EventManager();

/**
* @define {string} A version number taken from git at compile time.
*/
goog.define('GIT_VERSION', 'v1.2.0-debug');
/**
* @const {string}
* @export
*/
shaka.player.Player.version = 'v1.1';
shaka.player.Player.version = GIT_VERSION;

@@ -144,2 +165,4 @@

!!window.navigator.requestMediaKeySystemAccess &&
!!window.MediaKeySystemAccess &&
!!window.MediaKeySystemAccess.prototype.getConfiguration &&
// Promises are used frequently for asynchronous operations.

@@ -150,3 +173,5 @@ !!window.Promise &&

// Fullscreen API.
!!HTMLMediaElement.prototype.requestFullscreen &&
!!Element.prototype.requestFullscreen &&
!!document.exitFullscreen &&
document.hasOwnProperty('fullscreenElement') &&
// Node.children is used by mpd_parser.js, and body is a Node instance.

@@ -158,13 +183,9 @@ !!document.body.children;

/**
* Determines if the specified codec is supported with the given key system.
* Determines if the specified MIME type and codec is supported by the browser.
*
* @param {string} keySystem The key system. Use the empty string for
* unencrypted content.
* @param {string} mimeType A media MIME type, possibly including codec info.
*
* @return {boolean} true if the codec is supported by the key system,
* false otherwise.
* @param {string} fullMimeType A MIME type, which should include codec info.
* @return {boolean} true if the type is supported.
* @export
*/
shaka.player.Player.isTypeSupported = function(keySystem, mimeType) {
shaka.player.Player.isTypeSupported = function(fullMimeType) {
var supported;

@@ -175,21 +196,13 @@

// so disable support for them until a solution can be found.
if (mimeType.indexOf('mp4a.40.5') >= 0) {
if (fullMimeType.indexOf('mp4a.40.5') >= 0) {
return false;
}
if (mimeType == 'text/vtt') {
if (fullMimeType == 'text/vtt') {
supported = !!window.VTTCue;
} else {
supported = MediaSource.isTypeSupported(mimeType);
if (supported && keySystem) {
// Strip off the codec info, if any, leaving just a basic MIME type.
var basicType = mimeType.split(';')[0];
// TODO: isTypeSupported is deprecated
supported = MediaKeys.isTypeSupported(keySystem, basicType);
}
supported = MediaSource.isTypeSupported(fullMimeType);
}
shaka.log.info(keySystem, '+', mimeType,
supported ? 'is' : 'is not', 'supported');
shaka.log.info('+', fullMimeType, supported ? 'is' : 'is not', 'supported');
return supported;

@@ -243,6 +256,6 @@ };

this.mediaKeys_ = null;
this.drmScheme_ = null;
// Remove the video source.
this.video_.src = '';
this.video_.load();
var p = this.video_.setMediaKeys(null);

@@ -333,3 +346,4 @@ if (this.videoSource_) {

/**
* Initializes the DRM scheme. This function sets |mediaKeys_|.
* Initializes the DRM scheme by choosing from stream configurations provided
* by the video source. This function sets |mediaKeys_| and |drmScheme_|.
* @return {!Promise}

@@ -341,27 +355,215 @@ * @private

shaka.asserts.assert(this.video_.mediaKeys == null);
shaka.asserts.assert(this.drmScheme_ == null);
// TODO(story 2544736): Support multiple DASH periods with different schemes?
var drmScheme = this.videoSource_.getDrmSchemeInfo();
if (!drmScheme) {
shaka.log.info('No encryption.');
/** @type {!shaka.util.MultiMap.<!shaka.media.StreamConfig>} */
var chosenStreams = new shaka.util.MultiMap(); // indexed by content type
var configs = this.videoSource_.getConfigurations();
this.chooseUnencrypted_(configs, chosenStreams);
var mediaKeySystemConfigs =
this.buildKeySystemQueries_(configs, chosenStreams);
if (Object.keys(mediaKeySystemConfigs).length == 0) {
// All streams are unencrypted.
this.videoSource_.selectConfigurations(chosenStreams);
return Promise.resolve();
}
var p = navigator.requestMediaKeySystemAccess(drmScheme.keySystem);
return p.then(shaka.util.TypedBind(this,
/** @param {!MediaKeySystemAccess} mediaKeySystemAccess */
function(mediaKeySystemAccess) {
return mediaKeySystemAccess.createMediaKeys();
})
).then(shaka.util.TypedBind(this,
/** @param {!MediaKeys} mediaKeys */
function(mediaKeys) {
this.mediaKeys_ = mediaKeys;
return this.video_.setMediaKeys(this.mediaKeys_);
})
).then(shaka.util.TypedBind(this,
function() {
// Build a Promise chain which tries all MediaKeySystemConfigurations.
// Don't use Promise.reject(), since that will cause Chrome to complain about
// uncaught errors. Build the entire chain first, then reject instigator.
var instigator = new shaka.util.PublicPromise();
var p = this.buildKeySystemPromiseChain_(mediaKeySystemConfigs, instigator);
// Cast as a workaround for a Closure bug: google/closure-compiler#715
p = /** @type {!Promise.<!MediaKeys>} */(
p.then(this.chooseEncrypted_.bind(this, configs, chosenStreams)));
p = p.then(this.setupMediaKeys_.bind(this));
// Start the key system search process and return the chain.
instigator.reject(null);
// This chain is only the DRM section of the overall load() chain.
// Final error handling is done at the end of load().
return p;
};
/**
* Choose unencrypted streams for each type if possible. Store chosen streams
* into chosenStreams.
*
* @param {!Array.<!shaka.media.StreamConfig>} configs A list of configurations
* supported by the video source.
* @param {!shaka.util.MultiMap.<!shaka.media.StreamConfig>} chosenStreams
* Chosen streams indexed by content type.
* @private
*/
shaka.player.Player.prototype.chooseUnencrypted_ =
function(configs, chosenStreams) {
for (var i = 0; i < configs.length; ++i) {
var cfg = configs[i];
shaka.asserts.assert(cfg.drmScheme != null);
if (cfg.drmScheme.keySystem) continue;
// Ideally, the source would have already screened contents for basic type
// support, but assume that hasn't happened and check the MIME type.
if (cfg.fullMimeType &&
!shaka.player.Player.isTypeSupported(cfg.fullMimeType)) continue;
chosenStreams.push(cfg.contentType, cfg);
}
};
/**
* Build a set of MediaKeySystemConfigs to query for encrypted stream support.
*
* @param {!Array.<!shaka.media.StreamConfig>} configs A list of configurations
* supported by the video source.
* @param {!shaka.util.MultiMap.<!shaka.media.StreamConfig>} chosenStreams
* Chosen streams indexed by content type.
* @return {!Object.<string, !MediaKeySystemConfiguration>} Key system configs,
* indexed by key system.
* @private
*/
shaka.player.Player.prototype.buildKeySystemQueries_ =
function(configs, chosenStreams) {
/** @type {!Object.<string, !MediaKeySystemConfiguration>} */
var mediaKeySystemConfigs = {}; // indexed by key system
var anythingSpecified = false;
for (var i = 0; i < configs.length; ++i) {
var cfg = configs[i];
shaka.asserts.assert(cfg.drmScheme != null);
if (!cfg.drmScheme.keySystem) continue;
if (chosenStreams.has(cfg.contentType)) continue;
var keySystem = cfg.drmScheme.keySystem;
var mksc = mediaKeySystemConfigs[keySystem];
if (!mksc) {
mksc = mediaKeySystemConfigs[keySystem] = {
audioCapabilities: [],
videoCapabilities: [],
initDataTypes: [],
distinctiveIdentifier: 'optional',
persistentState: 'optional'
};
}
// Only check for an empty MIME type after creating mksc.
// This allows an empty mksc for sources which don't know their MIME types,
// which EME treats as "no restrictions."
if (!cfg.fullMimeType) continue;
var capName = cfg.contentType + 'Capabilities';
if (!mksc[capName]) continue; // not a capability we can check for!
anythingSpecified = true;
mksc[capName].push({ contentType: cfg.fullMimeType });
}
// If nothing is specified, we will never match anything up later.
// This little hack fixes support for HTTPVideoSource.
if (!anythingSpecified) {
this.drmScheme_ = configs[0].drmScheme;
}
return mediaKeySystemConfigs;
};
/**
* Build a promise chain to check each MediaKey configuration. If the first
* config fails, the next will be checked as a series of fallbacks.
*
* @param {!Object.<string, !MediaKeySystemConfiguration>} mediaKeySystemConfigs
* MediaKeySystemConfiguration} Key system configs, indexed by key system.
* @param {!Promise} p The beginning of the promise chain, which should be
* rejected to start the series of fallback queries.
* @return {!Promise.<!MediaKeySystemAccess>}
* @private
*/
shaka.player.Player.prototype.buildKeySystemPromiseChain_ =
function(mediaKeySystemConfigs, p) {
for (var keySystem in mediaKeySystemConfigs) {
var mksc = mediaKeySystemConfigs[keySystem];
p = p.catch(function() {
// If the prior promise was rejected, try the next key system in the list.
return navigator.requestMediaKeySystemAccess(keySystem, [mksc]);
});
}
return p;
};
/**
* When a key system query succeeds, chooses encrypted streams which match the
* chosen MediaKeySystemConfiguration, then creates a MediaKeys instance.
*
* @param {!Array.<!shaka.media.StreamConfig>} configs A list of configurations
* supported by the video source.
* @param {!shaka.util.MultiMap.<!shaka.media.StreamConfig>} chosenStreams
* Chosen streams indexed by content type.
* @param {!MediaKeySystemAccess} mediaKeySystemAccess
* @return {!Promise.<!MediaKeys>}
* @private
*/
shaka.player.Player.prototype.chooseEncrypted_ =
function(configs, chosenStreams, mediaKeySystemAccess) {
var keySystem = mediaKeySystemAccess.keySystem;
var mksc = mediaKeySystemAccess.getConfiguration();
var emeTypes = ['audio', 'video'];
for (var i = 0; i < emeTypes.length; ++i) {
var contentType = emeTypes[i];
if (chosenStreams.has(contentType)) continue; // not needed!
var capName = contentType + 'Capabilities';
var caps = mksc[capName][0];
if (!caps) continue; // type not found!
// Find which StreamConfigs match the selected MediaKeySystemConfiguration.
var chosenCfgs = [];
for (var j = 0; j < configs.length; ++j) {
var cfg = configs[j];
if (cfg.drmScheme.keySystem == keySystem &&
cfg.fullMimeType == caps.contentType) {
chosenCfgs.push(cfg);
// Accumulate the DRM scheme info from all chosen StreamConfigs.
if (!this.drmScheme_) {
this.drmScheme_ = cfg.drmScheme;
} else {
var newScheme = /** @type {!shaka.player.DrmSchemeInfo} */(
cfg.drmScheme);
this.drmScheme_ = shaka.player.DrmSchemeInfo.combine(
this.drmScheme_, newScheme);
}
break;
}
}
shaka.asserts.assert(chosenCfgs.length);
chosenStreams.set(contentType, chosenCfgs);
}
this.videoSource_.selectConfigurations(chosenStreams);
return mediaKeySystemAccess.createMediaKeys();
};
/**
* Sets up MediaKeys after it has been created. The MediaKeys instance will be
* attached to the video, any fake events will be generated, and any event
* listeners will be attached to the video.
*
* @param {!MediaKeys} mediaKeys
* @return {!Promise}
* @private
*/
shaka.player.Player.prototype.setupMediaKeys_ = function(mediaKeys) {
this.mediaKeys_ = mediaKeys;
return this.video_.setMediaKeys(this.mediaKeys_).then(
shaka.util.TypedBind(this, function() {
shaka.asserts.assert(this.video_.mediaKeys);
shaka.asserts.assert(this.video_.mediaKeys == this.mediaKeys_);
this.generateFakeEncryptedEvents_(drmScheme);
this.generateFakeEncryptedEvents_();

@@ -377,4 +579,3 @@ // Explicit init data for any one stream is sufficient to suppress

}
})
);
}));
};

@@ -387,11 +588,10 @@

*
* @param {shaka.player.DrmSchemeInfo} drmScheme
* @private
*/
shaka.player.Player.prototype.generateFakeEncryptedEvents_ =
function(drmScheme) {
shaka.player.Player.prototype.generateFakeEncryptedEvents_ = function() {
shaka.asserts.assert(this.drmScheme_);
this.fakeEncryptedEvents_ = [];
for (var i = 0; i < drmScheme.initDatas.length; ++i) {
var initData = drmScheme.initDatas[i];
for (var i = 0; i < this.drmScheme_.initDatas.length; ++i) {
var initData = this.drmScheme_.initDatas[i];

@@ -419,2 +619,3 @@ // This DRM scheme has init data information which should override that

shaka.player.Player.prototype.setVideoEventListeners_ = function() {
this.eventManager_.listen(this.video_, 'error', this.onError_.bind(this));
// TODO(story 1891509): Connect these events to the UI.

@@ -439,6 +640,6 @@ this.eventManager_.listen(this.video_, 'play', this.onPlay_.bind(this));

var initData = new Uint8Array(event.initData);
var initDataKey = shaka.util.StringUtils.uint8ArrayKey(initData);
var initDataKey = shaka.util.Uint8ArrayUtils.key(initData);
var drmScheme = this.videoSource_.getDrmSchemeInfo();
if (drmScheme.suppressMultipleEncryptedEvents) {
shaka.asserts.assert(this.drmScheme_);
if (this.drmScheme_.suppressMultipleEncryptedEvents) {
// In this scheme, all 'encrypted' events are equivalent.

@@ -458,14 +659,16 @@ // Never create more than one session.

this.eventManager_.listen(
session, 'message', /** @type {shaka.util.EventManager.ListenerType} */(
this.eventManager_.listen(session, 'message',
/** @type {shaka.util.EventManager.ListenerType} */(
this.onSessionMessage_.bind(this)));
this.eventManager_.listen(session, 'keystatuseschange',
/** @type {shaka.util.EventManager.ListenerType} */(
this.onKeyStatusChange_.bind(this)));
var p = session.generateRequest(event.initDataType, event.initData);
p.then(shaka.util.TypedBind(this,
function() {
this.requestGenerated_[initDataKey] = true;
})
).catch(shaka.util.TypedBind(this,
this.requestGenerated_[initDataKey] = true;
p.catch(shaka.util.TypedBind(this,
/** @param {!Error} error */
function(error) {
this.requestGenerated_[initDataKey] = false;
var event = shaka.util.FakeEvent.createErrorEvent(error);

@@ -486,6 +689,6 @@ this.dispatchEvent(event);

shaka.log.info('onSessionMessage_', event);
var drmScheme = this.videoSource_.getDrmSchemeInfo();
this.requestLicense_(event.target, drmScheme.licenseServerUrl, event.message,
drmScheme.withCredentials,
drmScheme.licensePostProcessor);
shaka.asserts.assert(this.drmScheme_);
this.requestLicense_(event.target, this.drmScheme_.licenseServerUrl,
event.message, this.drmScheme_.withCredentials,
this.drmScheme_.licensePostProcessor);
};

@@ -495,2 +698,25 @@

/**
* EME status-change handler.
*
* @param {!Event} event
* @private
*/
shaka.player.Player.prototype.onKeyStatusChange_ = function(event) {
shaka.log.info('onKeyStatusChange_', event);
var session = /** @type {!MediaKeySession} */(event.target);
var map = session.keyStatuses;
var i = map.values();
for (var v = i.next(); !v.done; v = i.next()) {
var message = shaka.player.Player.KEY_STATUS_ERROR_MAP_[v.value];
if (message) {
var error = new Error(message);
error.type = v.value;
var event = shaka.util.FakeEvent.createErrorEvent(error);
this.dispatchEvent(event);
}
}
};
/**
* Requests a license.

@@ -558,2 +784,27 @@ *

/**
* Video error event handler.
*
* @param {!Event} event
* @private
*/
shaka.player.Player.prototype.onError_ = function(event) {
var code = this.video_.error.code;
if (code == MediaError['MEDIA_ERR_ABORTED']) {
// Ignore this error code, which should only occur when navigating away or
// deliberately stopping playback of HTTP content.
return;
}
shaka.log.debug('onError_', event, code);
var message = shaka.player.Player.MEDIA_ERROR_MAP_[code] ||
'Unknown playback error.';
var error = new Error(message);
error.type = 'playback';
var event = shaka.util.FakeEvent.createErrorEvent(error);
this.dispatchEvent(event);
};
/**
* Video play event handler.

@@ -746,3 +997,4 @@ *

/**
* Enable or disable the text track. Has no effect before Player.load().
* Enable or disable the text track. Has no effect if called before
* load() resolves.
*

@@ -759,3 +1011,3 @@ * @param {boolean} enabled

/**
* Enable or disable automatic bitrate adaptation.
* Enable or disable automatic bitrate adaptation. May be called at any time.
*

@@ -975,2 +1227,3 @@ * @param {boolean} enabled

shaka.log.debug('Buffering...');
this.dispatchEvent(shaka.util.FakeEvent.create({type: 'bufferingStart'}));
}

@@ -985,2 +1238,3 @@ } else {

this.buffering_ = false;
this.dispatchEvent(shaka.util.FakeEvent.create({type: 'bufferingEnd'}));
this.video_.play();

@@ -1001,1 +1255,38 @@ }

/**
* A map of key statuses to errors. Not every key status appears in the map,
* in which case that key status is not treated as an error.
*
* @private {!Object.<string, string>}
* @const
*/
shaka.player.Player.KEY_STATUS_ERROR_MAP_ = {
'output-not-allowed': 'The required output protection is not available.',
'expired': 'A required key has expired and the content cannot be decrypted.',
'internal-error': 'An unknown error has occurred in the CDM.'
};
/**
* A map of MediaError codes to error messages. The JS interpreter won't take
* a symbolic name as a key, so the symbolic names for these error codes appear
* in comments after the number.
*
* @private {!Object.<number, string>}
* @const
*/
shaka.player.Player.MEDIA_ERROR_MAP_ = {
// This should not occur for DASH sources, but may occur for HTTP sources.
2: // MediaError.MEDIA_ERR_NETWORK
'A network failure occured while loading media content.',
3: // MediaError.MEDIA_ERR_DECODE
'The browser failed to decode the media content.',
// This is also unlikely for DASH sources, but HTTP sources do not check
// browser support before beginning playback.
4: // MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED
'The browser does not support the media content.'
};

@@ -22,3 +22,3 @@ /**

goog.require('shaka.asserts');
goog.require('shaka.dash.mpd');
goog.require('shaka.media.StreamInfo');

@@ -35,6 +35,6 @@

/**
* @type {shaka.player.Stats.RepresentationStats}
* @type {shaka.player.Stats.StreamStats}
* @expose
*/
this.representation = null;
this.streamStats = null;

@@ -108,10 +108,10 @@ /**

/**
* Representation history. Each timestamped value is a representation chosen
* StreamInfo history. Each timestamped value is a stream chosen
* by the player.
*
* @type {!Array.<shaka.player.Stats.TimedValue.<
* shaka.player.Stats.RepresentationStats>>}
* shaka.player.Stats.StreamStats>>}
* @expose
*/
this.representationHistory = [];
this.streamHistory = [];
};

@@ -164,12 +164,15 @@

/**
* Logs a representation change.
* Logs a stream change.
*
* @param {!shaka.dash.mpd.Representation} representation
* @param {!shaka.media.StreamInfo} streamInfo
*/
shaka.player.Stats.prototype.logRepresentationChange =
function(representation) {
this.representation =
new shaka.player.Stats.RepresentationStats(representation);
this.representationHistory.push(
new shaka.player.Stats.TimedValue(this.representation));
shaka.player.Stats.prototype.logStreamChange = function(streamInfo) {
// Record all stream stats to the history.
var streamStats = new shaka.player.Stats.StreamStats(streamInfo);
this.streamHistory.push(new shaka.player.Stats.TimedValue(streamStats));
// Prefer video stats in this.streamStats.
if (streamStats.videoHeight || !this.streamStats) {
this.streamStats = streamStats;
}
};

@@ -202,5 +205,5 @@

/**
* A collection of video representation stats.
* A collection of stream stats.
*
* @param {!shaka.dash.mpd.Representation} representation
* @param {!shaka.media.StreamInfo} streamInfo
*

@@ -210,5 +213,5 @@ * @constructor

*/
shaka.player.Stats.RepresentationStats = function(representation) {
shaka.player.Stats.StreamStats = function(streamInfo) {
/**
* Representation width in pixels.
* StreamInfo width in pixels, if a video stream.
*

@@ -218,6 +221,6 @@ * @type {?number}

*/
this.videoWidth = representation.width;
this.videoWidth = streamInfo.width;
/**
* Representation height in pixels.
* StreamInfo height in pixels, if an audio stream.
*

@@ -227,6 +230,6 @@ * @type {?number}

*/
this.videoHeight = representation.height;
this.videoHeight = streamInfo.height;
/**
* Representation MIME type.
* StreamInfo MIME type.
*

@@ -236,6 +239,6 @@ * @type {?string}

*/
this.videoMimeType = representation.mimeType;
this.videoMimeType = streamInfo.mimeType;
/**
* Representation bandwidth requirement in bits per second.
* StreamInfo bandwidth requirement in bits per second.
*

@@ -245,3 +248,3 @@ * @type {?number}

*/
this.videoBandwidth = representation.bandwidth;
this.videoBandwidth = streamInfo.bandwidth;
};

@@ -248,0 +251,0 @@

@@ -29,3 +29,3 @@ /**

* @summary A polyfill to unify fullscreen APIs across browsers.
* Many browsers have a prefixed fullscreen method on HTMLMediaElement.
* Many browsers have prefixed fullscreen methods on Element and document.
* See {@link http://goo.gl/n7TYl0 Using fullscreen mode} on MDN for more

@@ -41,8 +41,24 @@ * information.

shaka.polyfill.Fullscreen.install = function() {
var proto = HTMLMediaElement.prototype;
var proto = Element.prototype;
proto.requestFullscreen = proto.requestFullscreen ||
proto.mozRequestFullscreen ||
proto.mozRequestFullScreen ||
proto.msRequestFullscreen ||
proto.webkitRequestFullscreen;
proto = Document.prototype;
proto.exitFullscreen = proto.exitFullscreen ||
proto.mozCancelFullScreen ||
proto.msExitFullscreen ||
proto.webkitExitFullscreen;
if (!document.fullscreenElement) {
Object.defineProperty(document, 'fullscreenElement', {
get: function() {
return document.mozFullScreenElement ||
document.msFullscreenElement ||
document.webkitFullscreenElement;
}
});
}
};

@@ -24,3 +24,2 @@ /**

goog.require('shaka.log');
goog.require('shaka.polyfill.PatchedMediaKeys.isTypeSupported');
goog.require('shaka.polyfill.PatchedMediaKeys.nop');

@@ -50,13 +49,8 @@ goog.require('shaka.polyfill.PatchedMediaKeys.v01b');

if (HTMLMediaElement.prototype.webkitGenerateKeyRequest) {
if (Navigator.prototype.requestMediaKeySystemAccess &&
MediaKeySystemAccess.prototype.getConfiguration) {
shaka.log.info('Using native EME as-is.');
} else if (HTMLMediaElement.prototype.webkitGenerateKeyRequest) {
shaka.log.info('Using prefixed EME v0.1b.');
shaka.polyfill.PatchedMediaKeys.v01b.install();
} else if (Navigator.prototype.requestMediaKeySystemAccess) {
if (MediaKeys.isTypeSupported) {
shaka.log.info('Using native EME as-is.');
} else {
// TODO: remove when issue #2 is fixed.
shaka.log.info('Patching in MediaKeys.isTypeSupported.');
shaka.polyfill.PatchedMediaKeys.isTypeSupported.install();
}
} else {

@@ -63,0 +57,0 @@ shaka.log.info('EME not available.');

@@ -16,4 +16,4 @@ /**

*
* @fileoverview A polyfill to stub out {@link http://goo.gl/sgJHNN EME draft
* 01 December 2014} on browsers without EME. All methods will fail.
* @fileoverview A polyfill to stub out {@link http://goo.gl/5gifok EME draft
* 09 February 2015} on browsers without EME. All methods will fail.
*

@@ -45,5 +45,5 @@ * @see http://enwp.org/polyfill

HTMLMediaElement.prototype.setMediaKeys = nop.setMediaKeys;
// These are not usable, but allow Player.isBrowserSupported to pass.
window.MediaKeys = nop.MediaKeys;
// TODO: isTypeSupported is deprecated
window.MediaKeys.isTypeSupported = nop.MediaKeys.isTypeSupported;
window.MediaKeySystemAccess = nop.MediaKeySystemAccess;
};

@@ -58,7 +58,7 @@

* @param {string} keySystem
* @param {Array.<Object>=} opt_supportedConfigurations
* @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
* @return {!Promise.<!MediaKeySystemAccess>}
*/
shaka.polyfill.PatchedMediaKeys.nop.requestMediaKeySystemAccess =
function(keySystem, opt_supportedConfigurations) {
function(keySystem, supportedConfigurations) {
shaka.log.debug('PatchedMediaKeys.nop.requestMediaKeySystemAccess');

@@ -92,4 +92,7 @@ shaka.asserts.assert(this instanceof Navigator);

/**
* An unusable constructor for MediaKeys. Hosts isTypeSupported.
* An unusable constructor for MediaKeys.
* @constructor
* @implements {MediaKeys}
*/

@@ -101,15 +104,36 @@ shaka.polyfill.PatchedMediaKeys.nop.MediaKeys = function() {

/** @override */
shaka.polyfill.PatchedMediaKeys.nop.MediaKeys.prototype.createSession =
function() {};
/** @override */
shaka.polyfill.PatchedMediaKeys.nop.MediaKeys.prototype.setServerCertificate =
function() {};
/**
* Determines if a key system and MIME type are supported by the browser.
*
* @param {string} keySystem
* @param {string=} opt_mimeType
* @return {boolean}
* An unusable constructor for MediaKeySystemAccess.
* @constructor
* @implements {MediaKeySystemAccess}
*/
shaka.polyfill.PatchedMediaKeys.nop.MediaKeys.isTypeSupported =
function(keySystem, opt_mimeType) {
// TODO: isTypeSupported is deprecated
shaka.log.debug('PatchedMediaKeys.nop.isTypeSupported');
return false;
shaka.polyfill.PatchedMediaKeys.nop.MediaKeySystemAccess = function() {
throw new TypeError('Illegal constructor.');
};
/** @override */
shaka.polyfill.PatchedMediaKeys.nop.MediaKeySystemAccess.prototype.
getConfiguration = function() {};
/** @override */
shaka.polyfill.PatchedMediaKeys.nop.MediaKeySystemAccess.prototype.
createMediaKeys = function() {};
/** @override */
shaka.polyfill.PatchedMediaKeys.nop.MediaKeySystemAccess.prototype.
keySystem;

@@ -16,4 +16,4 @@ /**

*
* @fileoverview A polyfill to implement {@link http://goo.gl/sgJHNN EME draft
* 01 December 2014} on top of {@link http://goo.gl/FSpoAo EME v0.1b}.
* @fileoverview A polyfill to implement {@link http://goo.gl/5gifok EME draft
* 09 February 2015} on top of {@link http://goo.gl/FSpoAo EME v0.1b}.
*

@@ -31,3 +31,3 @@ * @see http://enwp.org/polyfill

goog.require('shaka.util.PublicPromise');
goog.require('shaka.util.StringUtils');
goog.require('shaka.util.Uint8ArrayUtils');

@@ -54,4 +54,3 @@

window.MediaKeys = v01b.MediaKeys;
// TODO: isTypeSupported is deprecated
window.MediaKeys.isTypeSupported = v01b.MediaKeys.isTypeSupported;
window.MediaKeySystemAccess = v01b.MediaKeySystemAccess;
};

@@ -66,16 +65,15 @@

* @param {string} keySystem
* @param {Array.<Object>=} opt_supportedConfigurations
* @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
* @return {!Promise.<!MediaKeySystemAccess>}
*/
shaka.polyfill.PatchedMediaKeys.v01b.requestMediaKeySystemAccess =
function(keySystem, opt_supportedConfigurations) {
function(keySystem, supportedConfigurations) {
shaka.log.debug('v01b.requestMediaKeySystemAccess');
shaka.asserts.assert(this instanceof Navigator);
// TODO(story 1954733): handle opt_supportedConfigurations.
// Alias.
var v01b = shaka.polyfill.PatchedMediaKeys.v01b;
try {
var access = new v01b.MediaKeySystemAccess(keySystem);
var access = new v01b.MediaKeySystemAccess(keySystem,
supportedConfigurations);
return Promise.resolve(/** @type {!MediaKeySystemAccess} */ (access));

@@ -135,5 +133,5 @@ } catch (exception) {

* @return {!HTMLVideoElement}
* @protected
* @private
*/
shaka.polyfill.PatchedMediaKeys.v01b.getVideoElement = function() {
shaka.polyfill.PatchedMediaKeys.v01b.getVideoElement_ = function() {
var videos = document.getElementsByTagName('video');

@@ -152,2 +150,3 @@ /** @type {!HTMLVideoElement} */

* @param {string} keySystem
* @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
* @implements {MediaKeySystemAccess}

@@ -157,3 +156,3 @@ * @throws {Error} if the key system is not supported.

shaka.polyfill.PatchedMediaKeys.v01b.MediaKeySystemAccess =
function(keySystem) {
function(keySystem, supportedConfigurations) {
shaka.log.debug('v01b.MediaKeySystemAccess');

@@ -167,2 +166,5 @@

/** @private {!MediaKeySystemConfiguration} */
this.configuration_;
if (keySystem == 'org.w3.clearkey') {

@@ -173,13 +175,67 @@ // Clearkey's string must be prefixed in v0.1b.

var tmpVideo = shaka.polyfill.PatchedMediaKeys.v01b.getVideoElement();
// v0.1b tests for key system availability with an extra argument on
// canPlayType. This, however, requires you to check video types in order to
// check for key systems. So we check all types we expect might succeed in
// Chrome.
var knownGoodTypes = ['video/mp4', 'video/webm'];
for (var i = 0; i < knownGoodTypes.length; ++i) {
if (tmpVideo.canPlayType(knownGoodTypes[i], this.internalKeySystem_)) {
var success = false;
var tmpVideo = shaka.polyfill.PatchedMediaKeys.v01b.getVideoElement_();
for (var i = 0; i < supportedConfigurations.length; ++i) {
var cfg = supportedConfigurations[i];
// Create a new config object and start adding in the pieces which we
// find support for. We will return this from getConfiguration() if
// asked.
/** @type {!MediaKeySystemConfiguration} */
var newCfg = {
'audioCapabilities': [],
'videoCapabilities': [],
// It is technically against spec to return these as optional, but we
// don't truly know their values from the prefixed API:
'persistentState': 'optional',
'distinctiveIdentifier': 'optional',
// Pretend the requested init data types are supported, since we don't
// really know that either:
'initDataTypes': cfg.initDataTypes
};
// v0.1b tests for key system availability with an extra argument on
// canPlayType.
var ranAnyTests = false;
if (cfg.audioCapabilities) {
for (var j = 0; j < cfg.audioCapabilities.length; ++j) {
var cap = cfg.audioCapabilities[j];
if (cap.contentType) {
ranAnyTests = true;
// In Chrome <= 40, if you ask about Widevine-encrypted audio support,
// you get a false-negative when you specify codec information.
// Work around this by stripping codec info for audio types.
var contentType = cap.contentType.split(';')[0];
if (tmpVideo.canPlayType(contentType, this.internalKeySystem_)) {
newCfg.audioCapabilities.push(cap);
success = true;
}
}
}
}
if (cfg.videoCapabilities) {
for (var j = 0; j < cfg.videoCapabilities.length; ++j) {
var cap = cfg.videoCapabilities[j];
if (cap.contentType) {
ranAnyTests = true;
if (tmpVideo.canPlayType(cap.contentType, this.internalKeySystem_)) {
newCfg.videoCapabilities.push(cap);
success = true;
}
}
}
}
if (!ranAnyTests) {
// If no specific types were requested, we check all common types to find
// out if the key system is present at all.
success = tmpVideo.canPlayType('video/mp4', this.internalKeySystem_) ||
tmpVideo.canPlayType('video/webm', this.internalKeySystem_);
}
if (success) {
this.configuration_ = newCfg;
return;
}
}
} // for each cfg in supportedConfigurations

@@ -207,4 +263,3 @@ // The key system did not report as playable with any known-good video types.

shaka.log.debug('v01b.MediaKeySystemAccess.getConfiguration');
// TODO: getConfiguration unsupported
return null;
return this.configuration_;
};

@@ -247,34 +302,2 @@

/**
* Determines if a key system and MIME type are supported by the browser.
*
* @param {string} keySystem
* @param {string=} opt_mimeType
* @return {boolean}
*/
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeys.isTypeSupported =
function(keySystem, opt_mimeType) {
// TODO: isTypeSupported is deprecated
shaka.log.debug('v01b.MediaKeys.isTypeSupported');
var tmpVideo = shaka.polyfill.PatchedMediaKeys.v01b.getVideoElement();
if (keySystem == 'org.w3.clearkey') {
// Clearkey's string must be prefixed in v0.1b.
keySystem = 'webkit-org.w3.clearkey';
}
if (opt_mimeType) {
return !!tmpVideo.canPlayType(opt_mimeType, keySystem);
}
// v0.1b tests for key system availability with an extra argument on
// canPlayType. This, however, requires you to check video types in order to
// check for key systems. So we check all types we expect might succeed in
// Chrome.
return !!tmpVideo.canPlayType('video/mp4', keySystem) ||
!!tmpVideo.canPlayType('video/webm', keySystem);
};
/**
* @param {HTMLMediaElement} media

@@ -490,5 +513,5 @@ * @protected

/** @type {!MediaKeyStatuses} */
this.keyStatuses = {};
// TODO: key status and 'keyschange' events unsupported
/** @type {!MediaKeyStatusMap} */
this.keyStatuses =
new shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap();
};

@@ -531,3 +554,3 @@ goog.inherits(shaka.polyfill.PatchedMediaKeys.v01b.MediaKeySession,

this.expiration = Number.POSITIVE_INFINITY;
// TODO: key status and 'keyschange' events unsupported
this.updateKeyStatus_('usable');

@@ -567,2 +590,13 @@ if (this.updatePromise_) {

this.updatePromise_ = null;
} else {
// This mapping of key statuses is imperfect at best.
var code = event.errorCode.code;
var systemCode = event.systemCode;
if (code == MediaKeyError['MEDIA_KEYERR_OUTPUT']) {
this.updateKeyStatus_('output-not-allowed');
} else if (systemCode == 1) {
this.updateKeyStatus_('expired');
} else {
this.updateKeyStatus_('internal-error');
}
}

@@ -602,3 +636,3 @@ };

if (this.type_ == 'persistent') {
var StringUtils = shaka.util.StringUtils;
var Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
if (!offlineSessionId) {

@@ -608,4 +642,4 @@ // Persisting the initial license.

var u8InitData = new Uint8Array(initData);
mangledInitData = StringUtils.toUint8Array(
'PERSISTENT|' + StringUtils.fromUint8Array(u8InitData));
mangledInitData = Uint8ArrayUtils.fromString(
'PERSISTENT|' + Uint8ArrayUtils.toString(u8InitData));
} else {

@@ -615,3 +649,3 @@ // Loading a stored license.

// indicate that we are loading a persisted session.
mangledInitData = StringUtils.toUint8Array(
mangledInitData = Uint8ArrayUtils.fromString(
'LOAD_SESSION|' + offlineSessionId);

@@ -668,4 +702,4 @@ }

// extract the key and key ID.
var StringUtils = shaka.util.StringUtils;
var licenseString = StringUtils.fromUint8Array(new Uint8Array(response));
var Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
var licenseString = Uint8ArrayUtils.toString(new Uint8Array(response));
var jwkSet = /** @type {JWKSet} */ (JSON.parse(licenseString));

@@ -680,5 +714,4 @@ var alg = jwkSet.keys[0].alg;

}
key = StringUtils.toUint8Array(StringUtils.fromBase64(jwkSet.keys[0].k));
keyId = StringUtils.toUint8Array(
StringUtils.fromBase64(jwkSet.keys[0].kid));
key = Uint8ArrayUtils.fromBase64(jwkSet.keys[0].k);
keyId = Uint8ArrayUtils.fromBase64(jwkSet.keys[0].kid);
} else {

@@ -700,2 +733,16 @@ // The key ID is not required.

/**
* Update key status and dispatch a 'keystatuseschange' event.
*
* @param {string} status
* @private
*/
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeySession.prototype.
updateKeyStatus_ = function(status) {
this.keyStatuses.setStatus(status);
var event = shaka.util.FakeEvent.create({type: 'keystatuseschange'});
this.dispatchEvent(event);
};
/** @override */

@@ -774,1 +821,159 @@ shaka.polyfill.PatchedMediaKeys.v01b.MediaKeySession.prototype.generateRequest =

/**
* An implementation of Iterator.
*
* @param {!Array.<VALUE>} values
*
* @constructor
* @implements {Iterator}
* @template VALUE
*/
shaka.polyfill.PatchedMediaKeys.v01b.Iterator = function(values) {
/** @private {!Array.<VALUE>} */
this.values_ = values;
/** @private {number} */
this.index_ = 0;
};
/**
* @return {{value:VALUE, done:boolean}}
*/
shaka.polyfill.PatchedMediaKeys.v01b.Iterator.prototype.next = function() {
if (this.index_ >= this.values_.length) {
return {value: undefined, done: true};
}
return {value: this.values_[this.index_++], done: false};
};
/**
* An implementation of MediaKeyStatusMap.
* This fakes a map with a single key ID.
*
* @constructor
* @implements {MediaKeyStatusMap}
*/
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap = function() {
/**
* @type {number}
*/
this.size = 0;
/**
* @private {string|undefined}
*/
this.status_ = undefined;
};
/**
* @const {!Uint8Array}
* @private
*/
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap.KEY_ID_ =
shaka.util.Uint8ArrayUtils.fromString('FAKE_KEY_ID');
/**
* An internal method used by the session to set key status.
* @param {string|undefined} status
*/
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap.prototype.setStatus =
function(status) {
this.size = status == undefined ? 0 : 1;
this.status_ = status;
};
/**
* Array entry 0 is the key, 1 is the value.
* @return {Iterator.<Array.<!BufferSource|string>>}
*/
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap.prototype.entries =
function() {
var fakeKeyId =
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap.KEY_ID_;
/** @type {!Array.<!Array.<!BufferSource|string>>} */
var arr = [];
if (this.status_) {
arr.push([fakeKeyId, this.status_]);
}
return new shaka.polyfill.PatchedMediaKeys.v01b.Iterator(arr);
};
/**
* The functor is called with each value.
* @param {function(string)} fn
*/
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap.prototype.forEach =
function(fn) {
if (this.status_) {
fn(this.status_);
}
};
/**
* @param {!BufferSource} keyId
* @return {string|undefined}
*/
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap.prototype.get =
function(keyId) {
if (this.has(keyId)) {
return this.status_;
}
return undefined;
};
/**
* @param {!BufferSource} keyId
* @return {boolean}
*/
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap.prototype.has =
function(keyId) {
var fakeKeyId =
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap.KEY_ID_;
if (this.status_ &&
shaka.util.Uint8ArrayUtils.equal(new Uint8Array(keyId), fakeKeyId)) {
return true;
}
return false;
};
/**
* @return {Iterator.<!BufferSource>}
*/
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap.prototype.keys =
function() {
var fakeKeyId =
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap.KEY_ID_;
/** @type {!Array.<!BufferSource>} */
var arr = [];
if (this.status_) {
arr.push(fakeKeyId);
}
return new shaka.polyfill.PatchedMediaKeys.v01b.Iterator(arr);
};
/**
* @return {Iterator.<string>}
*/
shaka.polyfill.PatchedMediaKeys.v01b.MediaKeyStatusMap.prototype.values =
function() {
/** @type {!Array.<string>} */
var arr = [];
if (this.status_) {
arr.push(this.status_);
}
return new shaka.polyfill.PatchedMediaKeys.v01b.Iterator(arr);
};

@@ -27,2 +27,3 @@ /**

goog.require('shaka.util.StringUtils');
goog.require('shaka.util.Uint8ArrayUtils');

@@ -266,2 +267,3 @@

var StringUtils = shaka.util.StringUtils;
var Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;

@@ -284,3 +286,3 @@ // Fake the data URI request.

if (this.parameters.responseType == 'arraybuffer') {
data = StringUtils.toUint8Array(data).buffer;
data = Uint8ArrayUtils.fromString(data).buffer;
}

@@ -303,2 +305,21 @@

/**
* Creates an error object with necessary details about the request.
* @param {string} message The error message.
* @param {string} type The error type.
* @return {!Error}
* @private
*/
shaka.util.AjaxRequest.prototype.createError_ = function(message, type) {
var error = new Error(message);
error.type = type;
error.status = this.xhr_.status;
error.url = this.url;
error.method = this.parameters.method;
error.body = this.parameters.body;
error.xhr = this.xhr_;
return error;
};
/**
* Aborts an in-progress request.

@@ -315,7 +336,3 @@ * If a request is not in-progress then this function does nothing.

var error = new Error('Request aborted.');
error.type = 'aborted';
error.status = null;
error.xhr = this.xhr_;
var error = this.createError_('Request aborted.', 'aborted');
this.promise_.reject(error);

@@ -362,7 +379,3 @@ this.destroy_();

} else {
var error = new Error('Network failure.');
error.type = 'net';
error.status = this.xhr_.status;
error.xhr = this.xhr_;
var error = this.createError_('HTTP error.', 'net');
this.promise_.reject(error);

@@ -385,7 +398,3 @@ this.destroy_();

var error = new Error('Network failure.');
error.type = 'net';
error.status = this.xhr_.status;
error.xhr = this.xhr_;
var error = this.createError_('Network failure.', 'net');
this.promise_.reject(error);

@@ -392,0 +401,0 @@ this.destroy_();

@@ -29,45 +29,2 @@ /**

/**
* Intersect two arrays. Assumes that the arrays are already sorted.
* @param {!Array} a
* @param {!Array} b
* @return {!Array} The intersection of a and b.
* @throws {TypeError} if the array contents cannot be sorted or compared.
*/
shaka.util.ArrayUtils.intersect = function(a, b) {
var ai = 0;
var bi = 0;
var result = [];
while (ai < a.length && bi < b.length) {
if (a[ai] < b[bi]) {
++ai;
} else if (a[ai] > b[bi]) {
++bi;
} else if (a[ai] == b[bi]) {
result.push(a[ai]);
++ai;
++bi;
} else {
throw new TypeError('Cannot intersect non-sortable values!');
}
}
return result;
};
/**
* Create an array from object keys.
* @param {!Object} object
* @return {!Array} A sorted list of the keys.
*/
shaka.util.ArrayUtils.fromObjectKeys = function(object) {
var result = [];
for (var k in object) {
result.push(k);
}
result.sort();
return result;
};
/**
* Remove duplicate entries from an array.

@@ -74,0 +31,0 @@ * @param {!Array.<T>} array

@@ -74,2 +74,11 @@ /**

/**
* Gets the byte length of the DataView.
* @return {number}
*/
shaka.util.DataViewReader.prototype.getLength = function() {
return this.dataView_.byteLength;
};
/**
* Reads an unsigned 8 bit integer, and advances the reader.

@@ -162,3 +171,3 @@ * @return {number} The integer.

shaka.util.DataViewReader.prototype.skip = function(bytes) {
shaka.asserts.assert(bytes > 0);
shaka.asserts.assert(bytes >= 0);
if (this.position_ + bytes > this.dataView_.byteLength) {

@@ -165,0 +174,0 @@ throw new RangeError('DataViewReader: Skip past end of DataView.');

@@ -208,3 +208,3 @@ /**

var EbmlParser = shaka.util.EbmlParser;
var uint8ArrayEqual = shaka.util.StringUtils.uint8ArrayEqual;
var uint8ArrayEqual = shaka.util.Uint8ArrayUtils.equal;

@@ -211,0 +211,0 @@ for (var i = 0; i < EbmlParser.DYNAMIC_SIZES.length; i++) {

@@ -48,2 +48,22 @@ /**

/**
* Set an array of values for the key, overwriting any previous data.
* @param {string} key
* @param {!Array.<T>} values
*/
shaka.util.MultiMap.prototype.set = function(key, values) {
this.map_[key] = values;
};
/**
* Check for a key.
* @param {string} key
* @return {boolean} true if the key exists.
*/
shaka.util.MultiMap.prototype.has = function(key) {
return this.map_.hasOwnProperty(key);
};
/**
* Get a list of values by key.

@@ -50,0 +70,0 @@ * @param {string} key

@@ -21,4 +21,5 @@ /**

goog.require('shaka.log');
goog.require('shaka.util.DataViewReader');
goog.require('shaka.util.StringUtils');
goog.require('shaka.util.Uint8ArrayUtils');

@@ -37,2 +38,3 @@

/**
* In hex.
* @type {!Array.<string>}

@@ -43,2 +45,9 @@ * @expose

/**
* In hex.
* @type {!Array.<string>}
* @expose
*/
this.cencKeyIds = [];
// Parse the PSSH box.

@@ -50,22 +59,55 @@ var reader = new shaka.util.DataViewReader(

// There could be multiple boxes concatenated together.
while (reader.hasMoreData()) {
var headerSize = 8;
var size = reader.readUint32();
var type = reader.readUint32();
if (size == 1) {
size = reader.readUint64();
headerSize += 8;
}
// If any of them throws, keep the data parsed from the earlier ones.
try {
while (reader.hasMoreData()) {
var startPosition = reader.getPosition();
var size = reader.readUint32();
var type = reader.readUint32();
if (size == 1) {
size = reader.readUint64();
} else if (size == 0) {
size = reader.getLength() - startPosition;
}
if (type != shaka.util.Pssh.BOX_TYPE) {
reader.skip(size - headerSize);
continue;
}
if (type != shaka.util.Pssh.BOX_TYPE) {
shaka.log.warning('Non-PSSH box found!');
reader.skip(size - (reader.getPosition() - startPosition));
continue;
}
var versionAndFlags = reader.readUint32();
var systemId = shaka.util.StringUtils.fromUint8Array(reader.readBytes(16));
var dataSize = reader.readUint32();
reader.skip(dataSize); // Ignore the data section.
var version = reader.readUint8();
if (version > 1) {
shaka.log.warning('Unrecognized PSSH version found!');
reader.skip(size - (reader.getPosition() - startPosition));
continue;
}
this.systemIds.push(systemId);
reader.skip(3); // Skip flags.
var systemId = shaka.util.Uint8ArrayUtils.toHex(reader.readBytes(16));
var keyIds = [];
if (version > 0) {
var numKeyIds = reader.readUint32();
for (var i = 0; i < numKeyIds; ++i) {
var keyId = shaka.util.Uint8ArrayUtils.toHex(reader.readBytes(16));
keyIds.push(keyId);
}
}
var dataSize = reader.readUint32();
reader.skip(dataSize); // Ignore the data section.
// Now that everything has been succesfully parsed from this box,
// update member variables.
this.cencKeyIds.push.apply(this.cencKeyIds, keyIds);
this.systemIds.push(systemId);
if (reader.getPosition() != startPosition + size) {
shaka.log.warning('Mismatch between box size and data size!');
reader.skip(size - (reader.getPosition() - startPosition));
}
}
} catch (exception) {
shaka.log.warning('PSSH parse failure! Some data may be missing or ' +
'incorrect.');
}

@@ -72,0 +114,0 @@ };

@@ -24,3 +24,2 @@ /**

* @namespace shaka.util.StringUtils
* @export
* @summary A set of string utility functions.

@@ -31,37 +30,7 @@ */

/**
* Convert a string to a Uint8Array.
*
* @param {string} str The input string.
* @return {!Uint8Array} The output array.
* @export
*/
shaka.util.StringUtils.toUint8Array = function(str) {
var result = new Uint8Array(str.length);
for (var i = 0; i < str.length; i++) {
result[i] = str.charCodeAt(i);
}
return result;
};
/**
* Convert a Uint8Array to a string.
*
* @param {Uint8Array} array The input array.
* @return {string} The output string.
* @export
*/
shaka.util.StringUtils.fromUint8Array = function(array) {
return String.fromCharCode.apply(null, array);
};
/**
* Convert a raw string to a base-64 string.
*
* @param {string} str The raw string.
* @param {string} str
* @param {boolean=} opt_padding If true, pad the output with equals signs.
* Defaults to true.
* @return {string} The base-64 string.
* @export
* @return {string}
*/

@@ -77,6 +46,4 @@ shaka.util.StringUtils.toBase64 = function(str, opt_padding) {

* Convert a base-64 string to a raw string.
*
* @param {string} str The base-64 string.
* @return {string} The raw string.
* @export
* @param {string} str
* @return {string}
*/

@@ -87,51 +54,1 @@ shaka.util.StringUtils.fromBase64 = function(str) {

/**
* Convert a hex string to a raw string.
*
* @param {string} str The hex string.
* @return {string} The output string.
* @export
*/
shaka.util.StringUtils.fromHex = function(str) {
var ints = [];
for (var i = 0; i < str.length; i += 2) {
ints.push(window.parseInt(str.substr(i, 2), 16));
}
return String.fromCharCode.apply(null, ints);
};
/**
* Compare two Uint8Arrays for equality.
*
* @param {Uint8Array} array1
* @param {Uint8Array} array2
* @return {boolean}
* @export
*/
shaka.util.StringUtils.uint8ArrayEqual = function(array1, array2) {
if (!array1 && !array2) return true;
if (!array1 || !array2) return false;
if (array1.length != array2.length) return false;
for (var i = 0; i < array1.length; ++i) {
if (array1[i] != array2[i]) return false;
}
return true;
};
/**
* Convert a Uint8Array to a string which can be used as a key in a dictionary.
* @param {!Uint8Array} array
* @return {string}
* @export
*/
shaka.util.StringUtils.uint8ArrayKey = function(array) {
var tmp = [];
for (var i = 0; i < array.length; ++i) {
tmp.push(array[i]);
}
return tmp.join(',');
};
{
"name": "shaka-player",
"description": "DASH/EME video player library",
"version": "1.1.0",
"version": "1.2.0",
"homepage": "https://github.com/google/shaka-player",

@@ -26,3 +26,6 @@ "author": "Google",

}
]
],
"scripts": {
"prepublish": "./build/all.sh"
}
}

@@ -23,5 +23,6 @@ /**

goog.require('shaka.player.Player');
goog.require('shaka.player.StreamVideoSource');
goog.require('shaka.polyfill.Fullscreen');
goog.require('shaka.polyfill.MediaKeys');
goog.require('shaka.polyfill.VideoPlaybackQuality');
goog.require('shaka.util.StringUtils');
goog.require('shaka.util.Uint8ArrayUtils');

@@ -22,6 +22,2 @@ /**

describe('EbmlParser', function() {
beforeEach(function() {
jasmine.addMatchers(customMatchers);
});
it('parses one element', function() {

@@ -28,0 +24,0 @@ // Set ID to 0x1.

@@ -192,3 +192,3 @@ /**

it('generates a SegmentBase from a SegmentTemplate', function() {
it('creates SegmentBase from SegmentTemplate', function() {
st.mediaUrlTemplate = 'http://example.com/$Bandwidth$-media.mp4';

@@ -247,3 +247,3 @@ st.indexUrlTemplate = 'http://example.com/$Bandwidth$-index.sidx';

it('generates a SegmentList from a SegmentTemplate', function() {
it('creates SegmentList from SegmentTemplate+SegmentTimeline', function() {
var tp1 = new mpd.SegmentTimePoint();

@@ -264,3 +264,3 @@ tp1.duration = 10;

st.segmentDuration = null;
st.firstSegmentNumber = 1;
st.startNumber = 1;
st.mediaUrlTemplate = '$Number$-$Time$-$Bandwidth$-media.mp4';

@@ -287,3 +287,3 @@ st.initializationUrlTemplate = '$Bandwidth$-init.mp4';

expect(sl1.segmentDuration).toBe(null);
expect(sl1.firstSegmentNumber).toBe(1);
expect(sl1.startNumber).toBe(1);

@@ -326,3 +326,3 @@ expect(sl1.initialization).toBeTruthy();

expect(sl2.segmentDuration).toBe(null);
expect(sl2.firstSegmentNumber).toBe(1);
expect(sl2.startNumber).toBe(1);

@@ -357,2 +357,213 @@ expect(sl2.initialization).toBeTruthy();

});
it('creates SegmentList from SegmentTemplate+segmentDuration', function() {
p.duration = 30;
st.timescale = 9000;
st.presentationTimeOffset = 0;
st.segmentDuration = 10;
st.startNumber = 5; // Ensure startNumber > 1 works.
st.mediaUrlTemplate = '$Number$-$Time$-$Bandwidth$-media.mp4';
st.initializationUrlTemplate = '$Bandwidth$-init.mp4';
r1.bandwidth = 250000;
r1.baseUrl = new goog.Uri('http://example.com/');
r2.bandwidth = 500000;
r2.baseUrl = new goog.Uri('http://example.com/');
processor.processSegmentTemplates_(m);
// Check |r1|.
expect(r1.segmentBase).toBeNull();
expect(r1.segmentList).toBeTruthy();
var sl1 = r1.segmentList;
expect(sl1.timescale).toBe(9000);
expect(sl1.presentationTimeOffset).toBe(0);
expect(sl1.segmentDuration).toBe(10);
expect(sl1.startNumber).toBe(5);
expect(sl1.initialization).toBeTruthy();
expect(sl1.initialization.url).toBeTruthy();
expect(sl1.initialization.url.toString())
.toBe('http://example.com/250000-init.mp4');
expect(sl1.segmentUrls.length).toBe(3);
expect(sl1.segmentUrls[0].mediaUrl).toBeTruthy();
expect(sl1.segmentUrls[0].mediaUrl.toString())
.toBe('http://example.com/5-40-250000-media.mp4');
expect(sl1.segmentUrls[0].mediaRange).toBeNull();
expect(sl1.segmentUrls[0].startTime).toBe(40);
expect(sl1.segmentUrls[0].duration).toBe(10);
expect(sl1.segmentUrls[1].mediaUrl).toBeTruthy();
expect(sl1.segmentUrls[1].mediaUrl.toString())
.toBe('http://example.com/6-50-250000-media.mp4');
expect(sl1.segmentUrls[1].mediaRange).toBeNull();
expect(sl1.segmentUrls[1].startTime).toBe(50);
expect(sl1.segmentUrls[1].duration).toBe(10);
expect(sl1.segmentUrls[2].mediaUrl).toBeTruthy();
expect(sl1.segmentUrls[2].mediaUrl.toString())
.toBe('http://example.com/7-60-250000-media.mp4');
expect(sl1.segmentUrls[2].mediaRange).toBeNull();
expect(sl1.segmentUrls[2].startTime).toBe(60);
expect(sl1.segmentUrls[2].duration).toBe(10);
// Check |r2|.
expect(r2.segmentBase).toBeNull();
expect(r2.segmentList).toBeTruthy();
var sl2 = r2.segmentList;
expect(sl2.timescale).toBe(9000);
expect(sl2.presentationTimeOffset).toBe(0);
expect(sl2.segmentDuration).toBe(10);
expect(sl2.startNumber).toBe(5);
expect(sl2.initialization).toBeTruthy();
expect(sl2.initialization.url).toBeTruthy();
expect(sl2.initialization.url.toString())
.toBe('http://example.com/500000-init.mp4');
expect(sl2.segmentUrls.length).toBe(3);
expect(sl2.segmentUrls[0].mediaUrl).toBeTruthy();
expect(sl2.segmentUrls[0].mediaUrl.toString())
.toBe('http://example.com/5-40-500000-media.mp4');
expect(sl2.segmentUrls[0].mediaRange).toBeNull();
expect(sl2.segmentUrls[0].startTime).toBe(40);
expect(sl2.segmentUrls[0].duration).toBe(10);
expect(sl2.segmentUrls[1].mediaUrl).toBeTruthy();
expect(sl2.segmentUrls[1].mediaUrl.toString())
.toBe('http://example.com/6-50-500000-media.mp4');
expect(sl2.segmentUrls[1].mediaRange).toBeNull();
expect(sl2.segmentUrls[1].startTime).toBe(50);
expect(sl2.segmentUrls[1].duration).toBe(10);
expect(sl2.segmentUrls[2].mediaUrl).toBeTruthy();
expect(sl2.segmentUrls[2].mediaUrl.toString())
.toBe('http://example.com/7-60-500000-media.mp4');
expect(sl2.segmentUrls[2].mediaRange).toBeNull();
expect(sl2.segmentUrls[2].startTime).toBe(60);
expect(sl2.segmentUrls[2].duration).toBe(10);
});
it('can handle gaps within a SegmentTimeline', function() {
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.bandwidth = 250000;
r1.baseUrl = new goog.Uri('http://example.com/');
processor.processSegmentTemplates_(m);
// Check |r1|.
expect(r1.segmentList).toBeTruthy();
var sl1 = r1.segmentList;
expect(sl1.segmentUrls.length).toBe(3);
expect(sl1.segmentUrls[0].mediaUrl).toBeTruthy();
expect(sl1.segmentUrls[0].mediaUrl.toString())
.toBe('http://example.com/1-10-250000-media.mp4');
expect(sl1.segmentUrls[0].startTime).toBe(10);
// Duration should stretch to the beginning of the next segment.
expect(sl1.segmentUrls[0].duration).toBe(11);
expect(sl1.segmentUrls[1].mediaUrl).toBeTruthy();
expect(sl1.segmentUrls[1].mediaUrl.toString())
.toBe('http://example.com/2-21-250000-media.mp4');
expect(sl1.segmentUrls[1].startTime).toBe(21);
// Duration should stretch to the beginning of the next segment.
expect(sl1.segmentUrls[1].duration).toBe(11);
expect(sl1.segmentUrls[2].mediaUrl).toBeTruthy();
expect(sl1.segmentUrls[2].mediaUrl.toString())
.toBe('http://example.com/3-32-250000-media.mp4');
expect(sl1.segmentUrls[2].startTime).toBe(32);
expect(sl1.segmentUrls[2].duration).toBe(10);
});
it('can handle overlaps within a SegmentTimeline', function() {
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.bandwidth = 250000;
r1.baseUrl = new goog.Uri('http://example.com/');
processor.processSegmentTemplates_(m);
// Check |r1|.
expect(r1.segmentList).toBeTruthy();
var sl1 = r1.segmentList;
expect(sl1.segmentUrls.length).toBe(3);
expect(sl1.segmentUrls[0].mediaUrl).toBeTruthy();
expect(sl1.segmentUrls[0].mediaUrl.toString())
.toBe('http://example.com/1-10-250000-media.mp4');
expect(sl1.segmentUrls[0].startTime).toBe(10);
// Duration should compress to the beginning of the next segment.
expect(sl1.segmentUrls[0].duration).toBe(10);
expect(sl1.segmentUrls[1].mediaUrl).toBeTruthy();
expect(sl1.segmentUrls[1].mediaUrl.toString())
.toBe('http://example.com/2-20-250000-media.mp4');
expect(sl1.segmentUrls[1].startTime).toBe(20);
// Duration should compress to the beginning of the next segment.
expect(sl1.segmentUrls[1].duration).toBe(9);
expect(sl1.segmentUrls[2].mediaUrl).toBeTruthy();
expect(sl1.segmentUrls[2].mediaUrl.toString())
.toBe('http://example.com/3-29-250000-media.mp4');
expect(sl1.segmentUrls[2].startTime).toBe(29);
expect(sl1.segmentUrls[2].duration).toBe(10);
});
});

@@ -359,0 +570,0 @@

@@ -788,3 +788,20 @@ /**

});
it('defaults startNumber to 1', function() {
var source = [
'<MPD>',
' <Period id="1" duration="PT0H1M0.00S">',
' <AdaptationSet id="1" lang="en" contentType="audio">',
' <SegmentTemplate startNumber="0" />',
' </AdaptationSet>',
' </Period>',
'</MPD>'].join('\n');
var mpd = shaka.dash.mpd.parseMpd(source, '');
var period = mpd.periods[0];
var adaptationSet = period.adaptationSets[0];
var segmentTemplate = adaptationSet.segmentTemplate;
expect(segmentTemplate.startNumber).toBe(1);
});
});

@@ -35,2 +35,4 @@ /**

const bogusManifest = 'assets/does_not_exist';
const highBitrateManifest =
'//storage.googleapis.com/widevine-demo-media/sintel-1080p/dash.mpd';
const FUDGE_FACTOR = 0.3;

@@ -307,2 +309,95 @@

// This covers github issue #15, in which seeking to evicted data hangs
// playback.
it('does not hang when seeking to evicted data', function(done) {
var source = newSource(highBitrateManifest);
// Create a temporary shim to intercept and modify manifest info.
var originalStreamVideoSource = shaka.player.StreamVideoSource;
shaka.player.StreamVideoSource = function(manifestInfo) {
// This should force Chrome to evict data quickly after it is played.
// At this asset's bitrate, Chrome should only have enough buffer for
// 310 seconds of data. Tweak the buffer time for audio, since this
// will take much less time and bandwidth to buffer.
const minBufferTime = 300;
var sets = manifestInfo.periodInfos[0].streamSetInfos;
var audioSet = sets[0].contentType == 'audio' ? sets[0] : sets[1];
expect(audioSet.contentType).toBe('audio');
audioSet.streamInfos[0].minBufferTime = minBufferTime;
// Remove the video set to speed things up.
manifestInfo.periodInfos[0].streamSetInfos = [audioSet];
return new originalStreamVideoSource(manifestInfo);
};
var audioStreamBuffer;
player.load(source).then(function() {
// Replace the StreamVideoSource shim.
shaka.player.StreamVideoSource = originalStreamVideoSource;
// Locate the audio stream buffer.
var audioStream = source.streamVideoSource_.streamsByType_['audio'];
audioStreamBuffer = audioStream.sbm_.sourceBuffer_;
// Nothing has buffered yet.
expect(audioStreamBuffer.buffered.length).toBe(0);
// Give the audio time to buffer.
return delay(8.0);
}).then(function() {
// The content is now buffered.
expect(audioStreamBuffer.buffered.length).toBe(1);
// Power through and consume the audio data quickly.
player.play();
player.setPlaybackRate(8);
return delay(4.0);
}).then(function() {
// Ensure that the browser has evicted the beginning of the stream.
// Otherwise, this test hasn't reproduced the circumstances correctly.
expect(audioStreamBuffer.buffered.start(0)).toBeGreaterThan(0);
// Seek to the beginning, which is data we will have to re-download.
player.seek(0);
return delay(3.0);
}).then(function() {
// Expect that we've been able to play some.
expect(video.currentTime).toBeGreaterThan(1.0);
done();
}).catch(function(error) {
// Replace the StreamVideoSource shim.
shaka.player.StreamVideoSource = originalStreamVideoSource;
fail(error);
done();
});
});
// This covers github issue #26.
it('does not hang when seeking to pre-adaptation data', function(done) {
var targetTime;
var source = newSource(plainManifest);
player.load(source).then(function() {
player.play();
// Move quickly past the first two segments.
player.setPlaybackRate(5.0);
return delay(3.0);
}).then(function() {
var track = getVideoTrackByHeight(480);
expect(track.active).toBe(false);
var ok = source.selectVideoTrack(track.id, false);
expect(ok).toBe(true);
// This bug manifests within two segments of the adaptation point. To
// prove that we are not hung, we need to get to a point two segments
// later than where we adapted.
targetTime = video.currentTime + 10.0;
return delay(3.0);
}).then(function() {
player.seek(0);
return delay(1.0 + (targetTime / 5.0));
}).then(function() {
// Expect that we've been able to play past our target point.
expect(video.currentTime).toBeGreaterThan(targetTime);
done();
}).catch(function(error) {
fail(error);
done();
});
});
it('can be used during stream switching', function(done) {

@@ -315,9 +410,10 @@ var source = newSource(plainManifest);

}).then(function() {
var DashStream = shaka.dash.DashStream;
var videoStream = source.streamsByType_['video'];
expect(videoStream.state_).toBe(DashStream.State_.UPDATING);
var Stream = shaka.media.Stream;
var videoStream = source.streamVideoSource_.streamsByType_['video'];
expect(videoStream.state_).toBe(Stream.State_.UPDATING);
var ok = player.selectVideoTrack(3); // 480p stream
var track = getVideoTrackByHeight(480);
var ok = player.selectVideoTrack(track.id);
expect(ok).toBe(true);
expect(videoStream.state_).toBe(DashStream.State_.SWITCHING);
expect(videoStream.state_).toBe(Stream.State_.SWITCHING);

@@ -324,0 +420,0 @@ player.seek(30.0);

@@ -19,6 +19,6 @@ /**

goog.require('shaka.dash.IsobmffSegmentIndexParser');
goog.require('shaka.dash.SegmentIndex');
goog.require('shaka.dash.SegmentReference');
goog.require('shaka.dash.WebmSegmentIndexParser');
goog.require('shaka.media.IsobmffSegmentIndexParser');
goog.require('shaka.media.SegmentIndex');
goog.require('shaka.media.SegmentReference');
goog.require('shaka.media.WebmSegmentIndexParser');
goog.require('shaka.util.PublicPromise');

@@ -49,3 +49,3 @@

if (sidxData) return Promise.resolve();
return fetchArrayBuffer('assets/car-20120827-87.sidx').then(
return fetchArrayBuffer('assets/car-20120827-87.sidx.dat').then(
function(data) { sidxData = data; });

@@ -60,3 +60,3 @@ };

if (sidxDataWithNonZeroStart) return Promise.resolve();
return fetchArrayBuffer('assets/angel_one.sidx').then(
return fetchArrayBuffer('assets/angel_one.sidx.dat').then(
function(data) { sidxDataWithNonZeroStart = data; });

@@ -69,3 +69,3 @@ };

return fetchArrayBuffer(
'assets/feelings_vp9-20130806-171.webm.headers'
'assets/feelings_vp9-20130806-171.webm.headers.dat'
).then(function(data) { webmData = data; });

@@ -77,4 +77,5 @@ };

if (cuesData) return Promise.resolve();
return fetchArrayBuffer('assets/feelings_vp9-20130806-171.webm.cues').then(
function(data) { cuesData = data; });
return fetchArrayBuffer(
'assets/feelings_vp9-20130806-171.webm.cues.dat'
).then(function(data) { cuesData = data; });
};

@@ -99,3 +100,3 @@

// SIDX are known.
var parser = new shaka.dash.IsobmffSegmentIndexParser();
var parser = new shaka.media.IsobmffSegmentIndexParser();
var references = parser.parse(null, new DataView(sidxData), 708);

@@ -132,3 +133,3 @@ expect(references).not.toBeNull();

// SIDX are known.
var parser = new shaka.dash.IsobmffSegmentIndexParser();
var parser = new shaka.media.IsobmffSegmentIndexParser();
var references =

@@ -157,3 +158,3 @@ parser.parse(null, new DataView(sidxDataWithNonZeroStart), 1322);

it('parses a WebM segment index', function() {
var parser = new shaka.dash.WebmSegmentIndexParser();
var parser = new shaka.media.WebmSegmentIndexParser();
var references =

@@ -183,3 +184,3 @@ parser.parse(new DataView(webmData), new DataView(cuesData), 0);

beforeEach(function() {
var parser = new shaka.dash.WebmSegmentIndexParser();
var parser = new shaka.media.WebmSegmentIndexParser();
var references =

@@ -189,3 +190,3 @@ parser.parse(new DataView(webmData), new DataView(cuesData), 0);

index = new shaka.dash.SegmentIndex(references);
index = new shaka.media.SegmentIndex(references);
});

@@ -252,8 +253,8 @@

var references = [
new shaka.dash.SegmentReference(0, 0, 1, 0, 5, url),
new shaka.dash.SegmentReference(1, 1, 2, 6, 9, url),
new shaka.dash.SegmentReference(2, 2, null, 10, null, url)
new shaka.media.SegmentReference(0, 0, 1, 0, 5, url),
new shaka.media.SegmentReference(1, 1, 2, 6, 9, url),
new shaka.media.SegmentReference(2, 2, null, 10, null, url)
];
var index2 = new shaka.dash.SegmentIndex(references);
var index2 = new shaka.media.SegmentIndex(references);
var range = index2.getRangeForInterval(0, 2);

@@ -277,3 +278,3 @@

it('handles no segments', function() {
index = new shaka.dash.SegmentIndex([]);
index = new shaka.media.SegmentIndex([]);
var range = index.getRangeForInterval(31, 40);

@@ -280,0 +281,0 @@ expect(range).toBeNull();

@@ -21,2 +21,4 @@ /**

goog.require('shaka.util.PublicPromise');
goog.require('shaka.util.StringUtils');
goog.require('shaka.util.Uint8ArrayUtils');

@@ -212,3 +214,3 @@

function interpretContentProtection(postProcessor, contentProtection) {
var StringUtils = shaka.util.StringUtils;
var Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;

@@ -218,9 +220,9 @@ // This is the only scheme used in integration tests at the moment.

var child = contentProtection.children[0];
var keyid = StringUtils.fromHex(child.getAttribute('keyid'));
var key = StringUtils.fromHex(child.getAttribute('key'));
var keyid = Uint8ArrayUtils.fromHex(child.getAttribute('keyid'));
var key = Uint8ArrayUtils.fromHex(child.getAttribute('key'));
var keyObj = {
kty: 'oct',
alg: 'A128KW',
kid: StringUtils.toBase64(keyid, false),
k: StringUtils.toBase64(key, false)
kid: Uint8ArrayUtils.toBase64(keyid, false),
k: Uint8ArrayUtils.toBase64(key, false)
};

@@ -230,7 +232,7 @@ var jwkSet = {keys: [keyObj]};

var initData = {
initData: StringUtils.toUint8Array(keyid),
initData: keyid,
initDataType: 'cenc'
};
var licenseServerUrl = 'data:application/json;base64,' +
StringUtils.toBase64(license);
shaka.util.StringUtils.toBase64(license);
return new shaka.player.DrmSchemeInfo(

@@ -237,0 +239,0 @@ 'org.w3.clearkey', false, licenseServerUrl, false, initData,

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc