Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Publish an event from DrmEngine if the CDM accepts the license. #1049

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 42 additions & 22 deletions lib/media/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ goog.require('shaka.net.NetworkingEngine');
goog.require('shaka.util.ArrayUtils');
goog.require('shaka.util.Error');
goog.require('shaka.util.EventManager');
goog.require('shaka.util.FakeEvent');
goog.require('shaka.util.Functional');
goog.require('shaka.util.IDestroyable');
goog.require('shaka.util.ManifestParserUtils');
Expand All @@ -36,17 +37,16 @@ goog.require('shaka.util.Uint8ArrayUtils');


/**
* @param {shaka.media.DrmEngine.PlayerInterface} playerInterface
*
* @constructor
* @param {!shaka.net.NetworkingEngine} networkingEngine
* @param {function(!shaka.util.Error)} onError Called when an error occurs.
* @param {function(!Object.<string, string>)} onKeyStatus Called when key
* status changes. Argument is a map of hex key IDs to statuses.
* @param {function(string, number)} onExpirationUpdated
* @struct
* @implements {shaka.util.IDestroyable}
*/
shaka.media.DrmEngine = function(
networkingEngine, onError, onKeyStatus, onExpirationUpdated) {
shaka.media.DrmEngine = function(playerInterface) {
/** @private {?shaka.media.DrmEngine.PlayerInterface} */
this.playerInterface_ = playerInterface;

/** @private {Array.<string>} */
this.supportedTypes_ = null;

Expand Down Expand Up @@ -74,27 +74,18 @@ shaka.media.DrmEngine = function(
/** @private {!shaka.util.PublicPromise} */
this.allSessionsLoaded_ = new shaka.util.PublicPromise();

/** @private {shaka.net.NetworkingEngine} */
this.networkingEngine_ = networkingEngine;

/** @private {?shakaExtern.DrmConfiguration} */
this.config_ = null;

/** @private {?function(!shaka.util.Error)} */
this.onError_ = (function(err) {
this.allSessionsLoaded_.reject(err);
onError(err);
playerInterface.onError(err);
}.bind(this));

/** @private {!Object.<string, string>} */
this.keyStatusByKeyId_ = {};

/** @private {?function(!Object.<string, string>)} */
this.onKeyStatus_ = onKeyStatus;

/** @private {?function(string, number)} */
this.onExpirationUpdated_ = onExpirationUpdated;

/** @private {shaka.util.Timer} */
this.keyStatusTimer_ = new shaka.util.Timer(
this.processKeyStatusChanges_.bind(this));
Expand Down Expand Up @@ -147,6 +138,32 @@ shaka.media.DrmEngine = function(
shaka.media.DrmEngine.ActiveSession;


/**
* @typedef {{
* netEngine: shaka.net.NetworkingEngine,
* onError: function(!shaka.util.Error),
* onKeyStatus: function(!Object.<string,string>),
* onExpirationUpdated: function(string,number),
* onEvent: function(!Event)
* }}
*
* @property {shaka.net.NetworkingEngine} netEngine
* The NetworkingEngine instance to use. The caller retains ownership.
* @property {function(!shaka.util.Error)} onError
* Called when an error occurs. If the error is recoverable (see
* @link{shaka.util.Error}) then the caller may invoke either
* StreamingEngine.switch*() or StreamingEngine.seeked() to attempt recovery.
* @property {function(!Object.<string,string>)} onKeyStatus
* Called when key status changes. Argument is a map of hex key IDs to
* statuses.
* @property {function(string,number)} onExpirationUpdated
* Called when the session expiration value changes.
* @property {function(!Event)} onEvent
* Called when an event occurs that should be sent to the app.
*/
shaka.media.DrmEngine.PlayerInterface;


/** @override */
shaka.media.DrmEngine.prototype.destroy = function() {
var Functional = shaka.util.Functional;
Expand Down Expand Up @@ -187,10 +204,9 @@ shaka.media.DrmEngine.prototype.destroy = function() {
this.eventManager_ = null;
this.activeSessions_ = [];
this.offlineSessionIds_ = [];
this.networkingEngine_ = null; // We don't own it, don't destroy() it.
this.config_ = null;
this.onError_ = null;
this.onExpirationUpdated_ = null;
this.playerInterface_ = null;

return Promise.all(async);
};
Expand Down Expand Up @@ -1073,12 +1089,15 @@ shaka.media.DrmEngine.prototype.sendLicenseRequest_ = function(event) {
this.unpackPlayReadyRequest_(request);
}

this.networkingEngine_.request(requestType, request)
this.playerInterface_.netEngine.request(requestType, request)
.then(function(response) {
if (this.destroyed_) return Promise.reject();

// Request succeeded, now pass the response to the CDM.
return session.update(response.data).then(function() {
var event = new shaka.util.FakeEvent('drmsessionupdate');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document this event type in lib/player.js. You'll see several examples toward the top of the file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

this.playerInterface_.onEvent(event);

if (activeSession) {
if (activeSession.updatePromise)
activeSession.updatePromise.resolve();
Expand Down Expand Up @@ -1307,7 +1326,7 @@ shaka.media.DrmEngine.prototype.processKeyStatusChanges_ = function() {
shaka.util.Error.Code.EXPIRED));
}

this.onKeyStatus_(this.keyStatusByKeyId_);
this.playerInterface_.onKeyStatus(this.keyStatusByKeyId_);
};


Expand Down Expand Up @@ -1515,7 +1534,8 @@ shaka.media.DrmEngine.prototype.pollExpiration_ = function() {
new_ = Infinity;

if (new_ != old) {
this.onExpirationUpdated_(session.session.sessionId, new_);
this.playerInterface_.onExpirationUpdated(
session.session.sessionId, new_);
session.oldExpiration = new_;
}
}.bind(this));
Expand Down
18 changes: 14 additions & 4 deletions lib/offline/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,13 @@ shaka.offline.Storage.prototype.remove = function(content) {
shaka.offline.OfflineManifestParser.reconstructManifest(manifestDb);
var netEngine = this.player_.getNetworkingEngine();
goog.asserts.assert(netEngine, 'Player must not be destroyed');
drmEngine = new shaka.media.DrmEngine(
netEngine, onError, function() {}, function() {});
drmEngine = new shaka.media.DrmEngine({
netEngine: netEngine,
onError: onError,
onKeyStatus: function() {},
onExpirationUpdated: function() {},
onEvent: function() {}
});
drmEngine.configure(this.player_.getConfiguration().drm);
var isOffline = this.config_.usePersistentLicense || false;
return drmEngine.init(manifest, isOffline);
Expand Down Expand Up @@ -424,8 +429,13 @@ shaka.offline.Storage.prototype.loadInternal = function(
.then(function(data) {
this.checkDestroyed_();
manifest = data;
drmEngine = new shaka.media.DrmEngine(
netEngine, onError, onKeyStatusChange, function() {});
drmEngine = new shaka.media.DrmEngine({
netEngine: netEngine,
onError: onError,
onKeyStatus: onKeyStatusChange,
onExpirationUpdated: function() {},
onEvent: function() {}
});
drmEngine.configure(config.drm);
var isOffline = this.config_.usePersistentLicense || false;
return drmEngine.init(manifest, isOffline);
Expand Down
23 changes: 18 additions & 5 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,15 @@ shaka.Player.version = GIT_VERSION;
*/


/**
* @event shaka.Player.DrmSessionUpdateEvent
* @description Fired when the CDM has accepted the license response.
* @property {string} type
* 'drmsessionupdate'
* @exportDoc
*/


/**
* @event shaka.Player.TimelineRegionAdded
* @description Fired when a media timeline region is added.
Expand Down Expand Up @@ -772,11 +781,15 @@ shaka.Player.prototype.chooseCodecsAndFilterManifest_ = function() {
*/
shaka.Player.prototype.createDrmEngine = function() {
goog.asserts.assert(this.networkingEngine_, 'Must not be destroyed');
return new shaka.media.DrmEngine(
this.networkingEngine_,
this.onError_.bind(this),
this.onKeyStatus_.bind(this),
this.onExpirationUpdated_.bind(this));

var playerInterface = {
netEngine: this.networkingEngine_,
onError: this.onError_.bind(this),
onKeyStatus: this.onKeyStatus_.bind(this),
onExpirationUpdated: this.onExpirationUpdated_.bind(this),
onEvent: this.onEvent_.bind(this)
};
return new shaka.media.DrmEngine(playerInterface);
};


Expand Down
16 changes: 12 additions & 4 deletions test/media/drm_engine_integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ describe('DrmEngine', function() {
var onKeyStatusSpy;
/** @type {!jasmine.Spy} */
var onExpirationSpy;
/** @type {!jasmine.Spy} */
var onEventSpy;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created this spy but don't actually use it for anything. Suggestions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need a separate integration test for this, but you could always expect that onEvent was called at the appropriate time in the "basic flow" test below.

Note that DRM integration tests are only run if you use ./build/test.py --drm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left this as is. The new behaviour is already covered by a unit test, and I noticed that onExpirationSpy is similarly unused in drm_engine_integration. I'll leave it to you guys to decide whether you need these spies.


/** @type {!shaka.media.DrmEngine} */
var drmEngine;
Expand Down Expand Up @@ -94,6 +96,7 @@ describe('DrmEngine', function() {
onErrorSpy = jasmine.createSpy('onError');
onKeyStatusSpy = jasmine.createSpy('onKeyStatus');
onExpirationSpy = jasmine.createSpy('onExpirationUpdated');
onEventSpy = jasmine.createSpy('onEvent');

mediaSource = new MediaSource();
video.src = window.URL.createObjectURL(mediaSource);
Expand All @@ -111,10 +114,15 @@ describe('DrmEngine', function() {
].join('');
});

drmEngine = new shaka.media.DrmEngine(
networkingEngine, shaka.test.Util.spyFunc(onErrorSpy),
shaka.test.Util.spyFunc(onKeyStatusSpy),
shaka.test.Util.spyFunc(onExpirationSpy));
var playerInterface = {
netEngine: networkingEngine,
onError: shaka.test.Util.spyFunc(onErrorSpy),
onKeyStatus: shaka.test.Util.spyFunc(onKeyStatusSpy),
onExpirationUpdated: shaka.test.Util.spyFunc(onExpirationSpy),
onEvent: shaka.test.Util.spyFunc(onEventSpy)
};

drmEngine = new shaka.media.DrmEngine(playerInterface);
var config = {
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
clearKeys: {},
Expand Down
34 changes: 30 additions & 4 deletions test/media/drm_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ describe('DrmEngine', function() {
var onKeyStatusSpy;
/** @type {!jasmine.Spy} */
var onExpirationSpy;
/** @type {!jasmine.Spy} */
var onEventSpy;

/** @type {!shaka.test.FakeNetworkingEngine} */
var fakeNetEngine;
Expand Down Expand Up @@ -65,6 +67,7 @@ describe('DrmEngine', function() {
onErrorSpy = jasmine.createSpy('onError');
onKeyStatusSpy = jasmine.createSpy('onKeyStatus');
onExpirationSpy = jasmine.createSpy('onExpirationUpdated');
onEventSpy = jasmine.createSpy('onEvent');
});

beforeEach(function() {
Expand All @@ -83,6 +86,7 @@ describe('DrmEngine', function() {
logErrorSpy.calls.reset();
onKeyStatusSpy.calls.reset();
onExpirationSpy.calls.reset();
onEventSpy.calls.reset();

// By default, error logs and callbacks result in failure.
onErrorSpy.and.callFake(fail);
Expand Down Expand Up @@ -113,10 +117,15 @@ describe('DrmEngine', function() {
license = (new Uint8Array(0)).buffer;
fakeNetEngine.setResponseMap({ 'http://abc.drm/license': license });

drmEngine = new shaka.media.DrmEngine(
fakeNetEngine, shaka.test.Util.spyFunc(onErrorSpy),
shaka.test.Util.spyFunc(onKeyStatusSpy),
shaka.test.Util.spyFunc(onExpirationSpy));
var playerInterface = {
netEngine: fakeNetEngine,
onError: shaka.test.Util.spyFunc(onErrorSpy),
onKeyStatus: shaka.test.Util.spyFunc(onKeyStatusSpy),
onExpirationUpdated: shaka.test.Util.spyFunc(onExpirationSpy),
onEvent: shaka.test.Util.spyFunc(onEventSpy)
};

drmEngine = new shaka.media.DrmEngine(playerInterface);
config = {
retryParameters: retryParameters,
delayLicenseRequestUntilPlayed: false,
Expand Down Expand Up @@ -1067,6 +1076,23 @@ describe('DrmEngine', function() {
}).catch(fail).then(done);
});

it('publishes an event if update succeeds', function(done) {
initAndAttach().then(function() {
var initData = new Uint8Array(1);
mockVideo.on['encrypted'](
{ initDataType: 'webm', initData: initData, keyId: null });
var message = new Uint8Array(0);
session1.on['message']({ target: session1, message: message});
session1.update.and.returnValue(Promise.resolve());

return shaka.test.Util.delay(0.5);
}).then(function() {
expect(onEventSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ type: 'drmsessionupdate' }));
done();
}).catch(fail);
});

it('dispatches an error if update fails', function(done) {
onErrorSpy.and.stub();

Expand Down
9 changes: 7 additions & 2 deletions test/offline/offline_integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,13 @@ describe('Offline', /** @suppress {accessControls} */ function() {
var manifest = OfflineManifestParser.reconstructManifest(manifestDb);
var netEngine = player.getNetworkingEngine();
goog.asserts.assert(netEngine, 'Must have a NetworkingEngine');
drmEngine = new shaka.media.DrmEngine(
netEngine, onError, function() {}, function() {});
drmEngine = new shaka.media.DrmEngine({
netEngine: netEngine,
onError: onError,
onKeyStatus: function() {},
onExpirationUpdated: function() {},
onEvent: function() {}
});
drmEngine.configure(player.getConfiguration().drm);
return drmEngine.init(manifest, true /* isOffline */);
})
Expand Down