2020/lib/eme/licenseManager.js (143 lines of code) (raw):
/**
* @license
* Copyright 2018 Google Inc. All rights reserved.
*
* 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.
*/
'use strict';
var LicenseManager = function(video, mediaStreams, flavor) {
this.video = video;
// LincenseManager does not handle mixed streams with VP9 video and aac audio
this.mediaStreams = mediaStreams instanceof Array ?
mediaStreams : [mediaStreams];
this.mime = this.mediaStreams[0].mimetype;
this.flavor = flavor;
this.keySystem = this.findCompatibleKeySystem_();
this.failedLicenseServerRequests = 0;
this.failedIndividualizationRequests = 0;
// This doesn't handle situtations with multiple licenses.
this.licenseServer = this.mediaStreams[0].get('license_server');
if (!this.licenseServer) {
this.licenseServer = 'https://dash-mse-test.appspot.com/api/drm/' +
this.flavor + '?drm_system=' + this.flavor +
'&source=YOUTUBE&ip=0.0.0.0&ipbits=0&' +
'expire=19000000000&key=test_key1&' +
'sparams=ip,ipbits,expire,drm_system,' +
'source,video_id&' +
'video_id=' + this.mediaStreams[0].get('video_id') +
'&signature=' +
this.mediaStreams[0].get(this.flavor + '_signature');
}
this.provisionServer =
'https://content.googleapis.com/certificateprovisioning' +
'/v1/devicecertificates/create' +
'?key=AIzaSyA_znRUCwvj-c06YrNcOlhhDtmLkRcVkj8' +
'&yts=eme';
};
LicenseManager.CLEARKEY = 'clearkey';
LicenseManager.WIDEVINE = 'widevine';
LicenseManager.PLAYREADY = 'playready';
/**
* Mapping between DRM flavors to the accepted key systems.
*/
LicenseManager.flavorToSystem = {
'clearkey': ['org.w3.clearkey', 'webkit-org.w3.clearkey'],
'widevine': ['com.widevine.alpha'],
'playready': ['com.youtube.playready']
};
/**
* Gets an external PSSH atom if it is being used.
*/
LicenseManager.prototype.getExternalPSSH = function() {
var externalPSSH = this.mediaStreams[0].get('pssh');
if (!!externalPSSH) {
return externalPSSH.buffer;
}
return false;
};
/**
* Makes configuration for KeySystem.
*/
LicenseManager.prototype.makeKeySystemConfig = function() {
var config = {
'initDataTypes': ['cenc', 'webm'],
};
if (this.flavor == LicenseManager.PLAYREADY) {
config['initDataTypes'] = ['keyids', 'cenc'];
}
var capabilities = [];
capabilities.push({
'contentType': this.mediaStreams[0].mimetype,
});
if (this.mediaStreams[0].mediatype == 'audio') {
config['audioCapabilities'] = capabilities;
} else {
config['videoCapabilities'] = capabilities;
}
return [config];
};
/**
* Internal function to determine the DRM key system to use.
*/
LicenseManager.prototype.findCompatibleKeySystem_ = function() {
var systems = LicenseManager.flavorToSystem[this.flavor];
for (var i in systems) {
if (!MediaSource.isTypeSupported(this.mime)) continue;
return systems[i];
}
throw 'Could not find a compatible key system';
};
/**
* Function to acquire the key and initData/key id for the media.
*/
LicenseManager.prototype.acquireLicense = function(message, cb) {
if (LicenseManager.WIDEVINE == this.flavor ||
LicenseManager.PLAYREADY == this.flavor) {
this.requestLicense(message, function(license) {
cb(license);
});
} else {
dlog(2, 'Unsupported DRM flavor.');
}
};
/**
* Function to request a Widevine or PlayReady license from the license server.
*/
LicenseManager.prototype.requestLicense = function(message, cb) {
if (this.failedLicenseServerRequests > 2) {
dlog(2, 'Repeated license request failures. Retries exhausted.');
return;
}
var self = this;
var xhr = new XMLHttpRequest();
xhr.open('POST', this.licenseServer);
xhr.addEventListener('readystatechange', function(evt) {
if (evt.target.readyState != 4) {
return;
}
var responseStatus = evt.target.status;
if (responseStatus < 200 || responseStatus > 299) {
dlog(2, 'License request failure, status ' + responseStatus +
'. Retrying...');
self.failedLicenseServerRequests++;
self.requestLicense(message, cb);
return;
}
var responseString = arrayToString(new Uint8Array(evt.target.response));
// Remove body header for responses from specific license servers.
var licenseBodyHeaderMark = 'GLS/1.0 0 OK';
if (responseString.substr(0, licenseBodyHeaderMark.length) ==
licenseBodyHeaderMark) {
var headerMark = '\r\n\r\n';
var headerIdx = responseString.indexOf(headerMark) + headerMark.length;
responseString = responseString.slice(headerIdx);
}
var license = stringToArray(responseString);
cb(license);
});
xhr.responseType = 'arraybuffer';
xhr.send(message);
};
/**
* Function to send individualization request to provisioning server.
*/
LicenseManager.prototype.requestIndividualization = function(message, cb) {
if (this.failedIndividualizationRequests > 2) {
dlog(2, 'Repeated individualization request failures. Retries exhausted.');
return;
}
var self = this;
var xhr = new XMLHttpRequest();
xhr.open('POST', this.provisionServer);
xhr.setRequestHeader('Content-type', 'application/json')
xhr.addEventListener('readystatechange', function(evt) {
if (evt.target.readyState != 4) {
return;
}
var responseStatus = evt.target.status;
if (responseStatus < 200 || responseStatus > 299) {
dlog(2, 'Individualization request failure, status ' + responseStatus +
'. Retrying...');
self.failedIndividualizationRequests++;
self.requestIndividualization(message, cb);
return;
}
cb(stringToArray(evt.target.responseText));
});
xhr.send(JSON.stringify(
{signedRequest: arrayToString(new Uint8Array(message))}));
};
try {
exports.LicenseManager = LicenseManager;
} catch (e) {
// do nothing, this function is not supposed to work for browser, but it's for
// Node js to generate json file instead.
}