2020/lib/eme/emeManager.js (179 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';
goog.require('proto.yts_eme.LicenseRequest');
var EMEHandler = function() {};
EMEHandler.prototype.init = function(video, licenseManager) {
this.video = video;
this.licenseManager = licenseManager;
this.keySystem = licenseManager.keySystem;
this.keyUnusable = false;
this.keyCount = 0;
this.keySessions = [];
this.licenseDelay = 10; // In milliseconds.
this.messageEncrypted = false;
this.serverCertificateRequested = false;
this.setServerCertificateResult = '';
this.certificateSrc;
video.addEventListener('encrypted', this.onEncrypted.bind(this));
return this;
};
EMEHandler.prototype.setCertificateSrc = function(cert) {
this.certificateSrc = cert;
}
EMEHandler.prototype.addEventSpies = function(eventSpies) {
for (var spy in eventSpies) {
this['_' + spy] = this[spy];
this[spy] = eventSpies[spy];
}
};
/** @return Promise */
EMEHandler.prototype.checkKeySystem = function() {
if (typeof navigator.requestMediaKeySystemAccess != 'function') {
return new Promise((resolve, reject) => {
reject('requestMediaKeySystemAccess is not defined (requires HTTPS)');
});
}
var config = this.licenseManager.makeKeySystemConfig();
return navigator.requestMediaKeySystemAccess(this.keySystem, config);
};
/**
* Default callback for onEncrypted event from EME system.
* @param {Event} e Event passed in by the EME system.
*/
EMEHandler.prototype.onEncrypted = function(event) {
if (!this.keySystem) {
throw 'Not initialized! Bad manifest parse?';
}
dlog(2, 'onEncrypted()');
var self = this;
var initData = this.licenseManager.getExternalPSSH();
if (!initData) {
initData = event.initData;
}
var initDataType = event.initDataType
var video = event.target;
this.checkKeySystem().then(function(keySystemAccess) {
keySystemAccess.createMediaKeys().then(
function(createdMediaKeys) {
var mediaKeys = video.mediaKeys;
if (!mediaKeys) {
video.setMediaKeys(createdMediaKeys);
mediaKeys = createdMediaKeys;
}
if (self.certificateSrc) {
if (createdMediaKeys.setServerCertificate) {
self.setServerCertificate(createdMediaKeys, self.certificateSrc);
} else {
self.setServerCertificateResult =
'setServerCertificate() is not supported';
}
}
var keySession = mediaKeys.createSession();
keySession.addEventListener(
'message', self.onMessage.bind(self), false);
keySession.addEventListener(
'keystatuseschange', self.onKeyStatusesChange.bind(self), false);
keySession.generateRequest(initDataType, initData);
self.keySessions.push(keySession);
}
);
}).catch(function(error) {
dlog(2, 'error requesting media keys system access');
});
};
/**
* Sends HTTP request to get the certification and apply it to
* setServerCertificate.
*/
EMEHandler.prototype.setServerCertificate = function(mediaKeys, cert) {
dlog(2, 'setServerCertificate()');
var self = this;
var xhr = new XMLHttpRequest();
xhr.open('GET', cert);
xhr.addEventListener('readystatechange', function(evt) {
if (evt.target.readyState != 4) {
return;
}
var responseStatus = evt.target.status;
if (responseStatus < 200 || responseStatus > 299) {
return;
}
mediaKeys.setServerCertificate(evt.target.response).then(
(result) => {
if (result != true) {
self.setServerCertificateResult = `setServerCertificate failed`;
dlog(2, self.setServerCertificateResult);
}
},
(rejected) => {
self.setServerCertificateResult =
`setServerCertificate rejected ${rejected}`;
dlog(2, self.setServerCertificateResult);
});
});
xhr.responseType = 'arraybuffer';
xhr.send();
}
/**
* Default callback for onMessage event from EME system.
* @param {Event} e Event passed in by the EME system.
*/
EMEHandler.prototype.onMessage = function(event) {
dlog(2, 'onMessage()');
var keySession = event.target;
var message = event.message;
var messageType = event.messageType;
var licenseDelay = this.licenseDelay;
var updateSession = function(response) {
setTimeout(function() {
keySession.update(response).catch(() =>
dlog(2, 'keySession.update failed'))
}, licenseDelay);
}
if (messageType == 'individualization-request') {
this.licenseManager.requestIndividualization(message, updateSession);
} else if (messageType == 'license-request') {
this.checkServerCertificateRequest(message);
this.validateEncryptedMessage(message);
this.licenseManager.acquireLicense(message, updateSession);
} else {
dlog(2, 'Unknown message:' + messageType);
}
};
/**
* Checks if the message is server certificate request.
*/
EMEHandler.prototype.checkServerCertificateRequest = function(message) {
dlog(2, 'checkServerCertificateRequest()');
var msg = proto.yts_eme.Message.deserializeBinary(message);
if (msg.getId() == 4) {
this.serverCertificateRequested = true;
}
}
/**
* Checks if the license request is encrypted.
*/
EMEHandler.prototype.validateEncryptedMessage = function(message) {
dlog(2, 'validateEncryptedMessage()');
var msg = proto.yts_eme.Message.deserializeBinary(message);
if (msg.getId() != 1) {
return;
}
var licenseRequest =
proto.yts_eme.LicenseRequest.deserializeBinary(msg.getMsg());
if (!licenseRequest.hasRequestInfo() && licenseRequest.hasRequestId()) {
this.messageEncrypted = true;
}
}
/**
* Default callback for keystatuseschange event from EME system.
* @param {Event} event Event passed in by the EME system.
*/
EMEHandler.prototype.onKeyStatusesChange = function(event) {
dlog(2, 'onKeyStatusesChange()');
var self = this;
event.target.keyStatuses.forEach(function(status, kid) {
self.keyCount++;
if (status != 'usable') {
self.keyUnusable = true;
}
});
};
EMEHandler.prototype.closeAllKeySessions = function (cb) {
if (this.keySessions === undefined) {
cb();
return;
}
var self = this;
var closeAllSessions = function() {
if (self.keySessions.length == 0) {
cb();
return;
}
dlog(2, 'Closing key session');
var keySession = self.keySessions.pop();
var promise = keySession.close();
promise.then(closeAllSessions);
};
closeAllSessions();
};
try {
exports.EMEHandler = EMEHandler;
} catch (e) {
// do nothing, this function is not supposed to work for browser, but it's for
// Node js to generate json file instead.
}