UNPKG

shaka-player

Version:
480 lines (421 loc) 15.4 kB
/** * @license * Copyright 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ goog.provide('shaka.player.DrmInfo'); goog.require('shaka.asserts'); goog.require('shaka.util.ArrayUtils'); goog.require('shaka.util.Uint8ArrayUtils'); /** * Creates a DrmInfo, which represents a set of DRM configuration options. * DrmInfo is an internal class, please see * {@link shaka.player.DashVideoSource.ContentProtectionCallback}, * {@link shaka.player.HttpVideoSource}, and * {@link shaka.player.DrmInfo.Config} for information on how to configure the * key system from the application. * * @constructor * @struct * @see {shaka.player.DashVideoSource.ContentProtectionCallback} * @see {shaka.player.DrmInfo.Config} * @exportDoc */ shaka.player.DrmInfo = function() { /** * An empty string indicates no key system. * @type {string} */ this.keySystem = ''; /** @type {string} */ this.licenseServerUrl = ''; /** @type {boolean} */ this.withCredentials = false; /** @type {?shaka.player.DrmInfo.LicensePostProcessor} */ this.licensePostProcessor = null; /** @type {?shaka.player.DrmInfo.LicensePreProcessor} */ this.licensePreProcessor = null; /** @type {boolean} */ this.distinctiveIdentifierRequired = false; /** @type {boolean} */ this.persistentStateRequired = false; /** @type {string} */ this.audioRobustness = ''; /** @type {string} */ this.videoRobustness = ''; /** @type {Uint8Array} */ this.serverCertificate = null; /** @type {!Array.<shaka.player.DrmInfo.InitData>} */ this.initDatas = []; }; /** * @typedef {{initData: !Uint8Array, initDataType: string}} * @exportDoc */ shaka.player.DrmInfo.InitData; /** * <p> * An object which represents a set of application provided DRM configuration * options. * </p> * * <p> * For encrypted content, an application must provide one or more DrmInfo.Config * objects to its VideoSource. For DASH content, the application can provide * Config objects to {@link shaka.player.DashVideoSource} via a callback * (see {@link shaka.player.DashVideoSource.ContentProtectionCallback}), * and for HTTP content, the application can provide a single Config object to * {@link shaka.player.HttpVideoSource} via HttpVideoSource's constructor. * </p> * * The following options are supported: * <ul> * <li> * <b>keySystem</b>: string (required) <br> * The key system, e.g., "com.widevine.alpha". * A blank string indicates unencrypted content. * * <li> * <b>licenseServerUrl</b>: string (required for streaming encrypted content) * <br> * The license server URL. * * <li> * <b>withCredentials:</b> boolean <br> * True if license requests should include cookies when sent cross-domain * (see {@link http://goo.gl/pzY9F7}). Defaults to false. * * <li> * <b>licensePostProcessor:</b> shaka.player.DrmInfo.LicensePostProcessor <br> * A license post-processor that does application-specific post-processing on * license responses. * * <li> * <b>licensePreProcessor:</b> shaka.player.DrmInfo.LicensePreProcessor <br> * A license pre-processor that does application-specific pre-processing on * license requests. * * <li> * <b>distinctiveIdentifierRequired:</b> boolean <br> * True if the application requires the key system to support distinctive * identifiers. Defaults to false. * * <li> * <b>persistentStateRequired:</b> boolean <br> * True if the application requires the key system to support persistent * state, e.g., for persistent license storage. Defaults to false. * * <li> * <b>audioRobustness:</b> string <br> * A key system specific string that specifies an audio decrypt/decode * security level. * * <li> * <b>videoRobustness:</b> string <br> * A key system specific string that specifies a video decrypt/decode * security level. * * <li> * <b>serverCertificate:</b> Uint8Array <br> * An key system specific server certificate for authenticating license * requests. * * <li> * <b>initData:</b> shaka.player.DrmInfo.InitData <br> * Explicit key system initialization data (initData value), which overrides * both the initData given in the manifest, if any, and the initData in the * actual content (which may be inspected via an EME 'encrypted' event). The * initDataType values and the formats that they correspond to are specified * {@link http://goo.gl/hKBdff here}. * </ul> * * @typedef {Object.<string, *>} * @exportDoc */ shaka.player.DrmInfo.Config; /** * <p> * A callback which does application-specific post-processing on license * responses before they are passed to the key system. The application can * set this callback in a {@link shaka.player.DrmInfo.Config} object. * </p> * * <p> * The parameter is the license response from the license server. * Must return the raw license. * </p> * * @example * // Suppose the license server provides a JSON encoded license response * // with the format {"header": header_string, "license": license_string}. * // The application would need to use a license post-processor like * // the following: * var postProcessor = function(serverResponse) { * // serverResponse is a Uint8Array, so decode it into an object. * var json = String.fromCharCode.apply(null, serverResponse); * var obj = JSON.parse(json); * * var headerString = obj['header']; * // Do something with the header... * * // obj['license'] is a string, so encode it into a Uint8Array. * var licenseString = obj['license']; * var license = new Uint8Array(licenseString.split('').map( * function(ch) { return ch.charCodeAt(0); })); * return license; * }; * * @typedef {function(!Uint8Array):!Uint8Array} * @exportDoc */ shaka.player.DrmInfo.LicensePostProcessor; /** * An object that describes a license request for license request * pre-processing (see {@link shaka.player.DrmInfo.LicensePreProcessor}). * <br> * * The following options are supported: * <ul> * <li> * <b>url</b>: string (required) <br> * The license server URL. * * <li> * <b>body</b>: (ArrayBuffer|?string) <br> * The license request's body. * * <li> * <b>method</b>: string (required) <br> * The HTTP request method, which must be either 'GET' or 'POST'. * * <li> * <b>headers</b>: Object.<string, string> <br> * Extra HTTP request headers as key-value pairs. * </ul> * * @typedef {Object.<string, *>} * @exportDoc */ shaka.player.DrmInfo.LicenseRequestInfo; /** * <p> * A callback which does application-specific pre-processing on license * requests before they are sent to the license server. The application can * set this callback in a {@link shaka.player.DrmInfo.Config} object. * </p> * * <p> * The parameter is a {@link shaka.player.DrmInfo.LicenseRequestInfo} * object. The callback may modify the object's fields as it requires; some * fields are set to initial values: * <ul> * <li> * The |url| field is initially set to the license server URL provided by the * license request pre-processor's corresponding DrmInfo; it may be set * to an arbitrary URL, or may be extended with extra query parameters. * * <li> * The |body| field is initially set to the raw license request (an * ArrayBuffer) emitted by the browser; it may be replaced (to another * ArrayBuffer or string) or be removed entirely (e.g., if the server expects * the raw license to be encoded in the URL). * * <li> * The |method| field is initially set to 'POST', but may be set to 'GET'. * * <li> * The |headers| field is initially an empty map; arbitrary request headers * may be added as key-value pairs, e.g., * headers['Content-Type'] = 'application/x-www-form-urlencoded'; * </ul> * </p> * * @example * // Suppose the license server expects a license request to use a * // base64 encoded payload and include special query parameters. * // The application would need to use a license pre-processor like * // the following: * var preProcessor = function(requestInfo) { * // Add query parameters. * requestInfo.url += '?session=123&token=abc' * // Encode the payload as base64. * requestInfo.body = window.btoa( * String.fromCharCode.apply(null, new Uint8Array(requestInfo.body))); * }; * @typedef {function(!shaka.player.DrmInfo.LicenseRequestInfo)} * @exportDoc */ shaka.player.DrmInfo.LicensePreProcessor; /** * Creates a DrmInfo object from a Config object. * * @param {shaka.player.DrmInfo.Config} config * @throws TypeError if a configuration option has the wrong type. * @throws Error if the application fails to provide any required fields. * @return {!shaka.player.DrmInfo} */ shaka.player.DrmInfo.createFromConfig = function(config) { var drmInfo = new shaka.player.DrmInfo(); if (!config) return drmInfo; // Alias. var MapUtils = shaka.util.MapUtils; var keySystem = MapUtils.getString(config, 'keySystem'); if (keySystem != null) { drmInfo.keySystem = keySystem; } else { throw new Error('\'keySystem\' cannot be null.'); } var licenseServerUrl = MapUtils.getString(config, 'licenseServerUrl'); if (licenseServerUrl != null) { drmInfo.licenseServerUrl = licenseServerUrl; } else if (keySystem) { throw new Error('For encrypted streaming content, \'licenseServerUrl\' ' + 'cannot be null or empty.'); } var withCredentials = MapUtils.getBoolean(config, 'withCredentials'); if (withCredentials != null) { drmInfo.withCredentials = withCredentials; } var licensePostProcessor = MapUtils.getAsInstanceType( config, 'licensePostProcessor', Function); if (licensePostProcessor != null) { drmInfo.licensePostProcessor = licensePostProcessor; } var licensePreProcessor = MapUtils.getAsInstanceType( config, 'licensePreProcessor', Function); if (licensePreProcessor != null) { drmInfo.licensePreProcessor = licensePreProcessor; } var distinctiveIdentifierRequired = MapUtils.getBoolean( config, 'distinctiveIdentifierRequired'); if (distinctiveIdentifierRequired != null) { drmInfo.distinctiveIdentifierRequired = distinctiveIdentifierRequired; } var persistentStateRequired = MapUtils.getBoolean( config, 'persistentStateRequired'); if (persistentStateRequired != null) { drmInfo.persistentStateRequired = persistentStateRequired; } var audioRobustness = MapUtils.getString(config, 'audioRobustness'); if (audioRobustness != null) { drmInfo.audioRobustness = audioRobustness; } var videoRobustness = MapUtils.getString(config, 'videoRobustness'); if (videoRobustness != null) { drmInfo.videoRobustness = videoRobustness; } var serverCertificate = MapUtils.getAsInstanceType( config, 'serverCertificate', Uint8Array); if (serverCertificate != null) { drmInfo.serverCertificate = serverCertificate; } var initData = MapUtils.getAsInstanceType(config, 'initData', Object); if (initData) { var data = MapUtils.getAsInstanceType(initData, 'initData', Uint8Array); if (data == null) { throw new Error('\'initData.initData\' cannot be null.'); } var dataType = MapUtils.getString(initData, 'initDataType'); if (dataType == null) { throw new Error('\'initData.initDataType\' cannot be null.'); } drmInfo.initDatas.push({ 'initData': new Uint8Array(data.buffer), 'initDataType': dataType }); } return drmInfo; }; /** * Combines this DrmInfo object with another DrmInfo object to create a new * DrmInfo object. * * @param {!shaka.player.DrmInfo} other The other DrmInfo object, which should * be compatible with this DrmInfo object. * @return {!shaka.player.DrmInfo} */ shaka.player.DrmInfo.prototype.combine = function(other) { if (!COMPILED && !this.isCompatible(other)) { shaka.log.warning( 'combine() should only be called with a compatible DrmInfo object:', 'this', this, 'other', other); } var drmInfo = new shaka.player.DrmInfo(); drmInfo.keySystem = this.keySystem; drmInfo.licenseServerUrl = this.licenseServerUrl; drmInfo.withCredentials = this.withCredentials; drmInfo.licensePostProcessor = this.licensePostProcessor; drmInfo.licensePreProcessor = this.licensePreProcessor; drmInfo.distinctiveIdentifierRequired = this.distinctiveIdentifierRequired; drmInfo.persistentStateRequired = this.persistentStateRequired; drmInfo.audioRobustness = this.audioRobustness; drmInfo.videoRobustness = this.videoRobustness; drmInfo.serverCertificate = this.serverCertificate ? new Uint8Array(this.serverCertificate.buffer) : null; drmInfo.addInitDatas(this.initDatas); drmInfo.addInitDatas(other.initDatas); return drmInfo; }; /** * @param {!shaka.player.DrmInfo} other * @return {boolean} True if this DrmInfo is compatible with |other|; * otherwise, return false. */ shaka.player.DrmInfo.prototype.isCompatible = function(other) { // NOTE: We can't check pre/post-processors, since this causes failures with // bound functions. return (this.keySystem == other.keySystem) && (this.licenseServerUrl == other.licenseServerUrl) && (this.withCredentials == other.withCredentials) && (this.distinctiveIdentifierRequired == other.distinctiveIdentifierRequired) && (this.persistentStateRequired == other.persistentStateRequired) && (this.audioRobustness == other.audioRobustness) && (this.videoRobustness == other.videoRobustness) && (shaka.util.Uint8ArrayUtils.equal(this.serverCertificate, other.serverCertificate)); }; /** * Adds the given initDatas (removing duplicates). * * @param {!Array.<shaka.player.DrmInfo.InitData>} otherInitDatas */ shaka.player.DrmInfo.prototype.addInitDatas = function( otherInitDatas) { var unfilteredInitDatas = this.initDatas.concat(otherInitDatas.map( function(initData) { return { 'initData': new Uint8Array(initData.initData.buffer), 'initDataType': initData.initDataType }; })); this.initDatas = shaka.util.ArrayUtils.removeDuplicates( unfilteredInitDatas, shaka.player.DrmInfo.compareInitDatas_); }; /** * @param {!shaka.player.DrmInfo.InitData} initDataA * @param {!shaka.player.DrmInfo.InitData} initDataB * @return {boolean} * @private */ shaka.player.DrmInfo.compareInitDatas_ = function(initDataA, initDataB) { return initDataA.initDataType == initDataB.initDataType && shaka.util.Uint8ArrayUtils.equal(initDataA.initData, initDataB.initData); };