Skip to content

Commit

Permalink
Expose license expiration times through Player.
Browse files Browse the repository at this point in the history
This adds a method on Player to get the license expiration times for
the current EME sessions.  This also adds the expiration times to the
stored content structure for offline content.  This will update the
stored expiration while playing content (e.g. license duration changes
when playback starts).

Closes #727

Change-Id: I18770a79413423695bbb2ed5f31f6b19038a33d2
  • Loading branch information
TheModMaker committed Apr 3, 2017
1 parent 1b3015a commit b4d0fa4
Show file tree
Hide file tree
Showing 18 changed files with 362 additions and 18 deletions.
12 changes: 12 additions & 0 deletions externs/shaka/manifest_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,15 @@ shakaExtern.ManifestParser.prototype.stop = function() {};
* @exportDoc
*/
shakaExtern.ManifestParser.prototype.update = function() {};


/**
* Tells the parser that the expiration time of an EME session has changed.
* Implementing this is optional.
*
* @param {string} sessionId
* @param {number} expiration
* @exportDoc
*/
shakaExtern.ManifestParser.prototype.onExpirationUpdated = function(
sessionId, expiration) {};
7 changes: 7 additions & 0 deletions externs/shaka/offline.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ shakaExtern.OfflineConfiguration;
* originalManifestUri: string,
* duration: number,
* size: number,
* expiration: number,
* tracks: !Array.<shakaExtern.Track>,
* appMetadata: Object
* }}
Expand All @@ -74,6 +75,9 @@ shakaExtern.OfflineConfiguration;
* The duration of the content, in seconds.
* @property {number} size
* The size of the content, in bytes.
* @property {number} expiration
* The time that the encrypted license expires, in milliseconds. If the media
* is clear or the license never expires, this will equal Infinity.
* @property {!Array.<shakaExtern.Track>} tracks
* The tracks that are stored. This only lists those found in the first
* Period.
Expand All @@ -90,6 +94,7 @@ shakaExtern.StoredContent;
* originalManifestUri: string,
* duration: number,
* size: number,
* expiration: number,
* periods: !Array.<shakaExtern.PeriodDB>,
* sessionIds: !Array.<string>,
* drmInfo: ?shakaExtern.DrmInfo,
Expand All @@ -104,6 +109,8 @@ shakaExtern.StoredContent;
* The total duration of the media, in seconds.
* @property {number} size
* The total size of all stored segments, in bytes.
* @property {number} expiration
* The license expiration, in milliseconds; or Infinity if not applicable.
* @property {!Array.<shakaExtern.PeriodDB>} periods
* The Periods that are stored.
* @property {!Array.<string>} sessionIds
Expand Down
1 change: 1 addition & 0 deletions lib/cast/cast_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ shaka.cast.CastUtils.PlayerGetterMethods = [
'drmInfo',
'getAudioLanguages',
'getConfiguration',
'getExpiration',
'getManifestUri',
'getPlaybackRate',
'getPlayheadTimeAsDate',
Expand Down
10 changes: 10 additions & 0 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,16 @@ shaka.dash.DashParser.prototype.update = function() {
};


/**
* @override
* @exportInterface
*/
shaka.dash.DashParser.prototype.onExpirationUpdated = function(
sessionId, expiration) {
// No-op
};


/**
* Makes a network request for the manifest and parses the resulting data.
*
Expand Down
7 changes: 7 additions & 0 deletions lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ shaka.hls.HlsParser.prototype.update = function() {
};


/** @override */
shaka.hls.HlsParser.prototype.onExpirationUpdated = function(
sessionId, expiration) {
// No-op
};


/**
* Parses the manifest.
*
Expand Down
65 changes: 62 additions & 3 deletions lib/media/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ goog.require('shaka.util.Uint8ArrayUtils');
* @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) {
shaka.media.DrmEngine = function(
networkingEngine, onError, onKeyStatus, onExpirationUpdated) {
/** @private {Array.<string>} */
this.supportedTypes_ = null;

Expand Down Expand Up @@ -89,6 +91,9 @@ shaka.media.DrmEngine = function(networkingEngine, onError, onKeyStatus) {
/** @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 All @@ -105,6 +110,9 @@ shaka.media.DrmEngine = function(networkingEngine, onError, onKeyStatus) {
/** @private {boolean} */
this.initialRequestsSent_ = false;

/** @private {?number} */
this.expirationInterval_ = setInterval(this.pollExpiration_.bind(this), 1000);

// Add a catch to the Promise to avoid console logs about uncaught errors.
this.allSessionsLoaded_.catch(function() {});
};
Expand All @@ -115,6 +123,7 @@ shaka.media.DrmEngine = function(networkingEngine, onError, onKeyStatus) {
* loaded: boolean,
* initData: Uint8Array,
* session: !MediaKeySession,
* oldExpiration: number,
* updatePromise: shaka.util.PublicPromise
* }}
*
Expand All @@ -126,6 +135,9 @@ shaka.media.DrmEngine = function(networkingEngine, onError, onKeyStatus) {
* The init data used to create the session.
* @property {!MediaKeySession} session
* The session object.
* @property {number} oldExpiration
* The expiration of the session on the last check. This is used to fire
* an event when it changes.
* @property {shaka.util.PublicPromise} updatePromise
* An optional Promise that will be resolved/rejected on the next update()
* call. This is used to track the 'license-release' message when calling
Expand Down Expand Up @@ -157,6 +169,11 @@ shaka.media.DrmEngine.prototype.destroy = function() {
async.push(this.video_.setMediaKeys(null).catch(Functional.noop));
}

if (this.expirationInterval_) {
clearInterval(this.expirationInterval_);
this.expirationInterval_ = null;
}

if (this.keyStatusTimer_) {
this.keyStatusTimer_.cancel();
}
Expand All @@ -172,6 +189,7 @@ shaka.media.DrmEngine.prototype.destroy = function() {
this.networkingEngine_ = null; // We don't own it, don't destroy() it.
this.config_ = null;
this.onError_ = null;
this.onExpirationUpdated_ = null;

return Promise.all(async);
};
Expand Down Expand Up @@ -376,6 +394,20 @@ shaka.media.DrmEngine.prototype.getSessionIds = function() {
};


/**
* Returns the next expiration time, or Infinity.
* @return {number}
*/
shaka.media.DrmEngine.prototype.getExpiration = function() {
var expirations = this.activeSessions_.map(function(session) {
var expiration = session.session.expiration;
return isNaN(expiration) ? Infinity : expiration;
});
// This will equal Infinity if there are no entries.
return Math.min.apply(Math, expirations);
};


/**
* Returns the DrmInfo that was used to initialize the current key system.
*
Expand Down Expand Up @@ -864,8 +896,13 @@ shaka.media.DrmEngine.prototype.loadOfflineSession_ = function(sessionId) {
this.eventManager_.listen(session, 'keystatuseschange',
this.onKeyStatusesChange_.bind(this));

var activeSession =
{initData: null, session: session, loaded: false, updatePromise: null};
var activeSession = {
initData: null,
session: session,
loaded: false,
oldExpiration: Infinity,
updatePromise: null
};
this.activeSessions_.push(activeSession);

return session.load(sessionId).then(function(present) {
Expand Down Expand Up @@ -935,6 +972,7 @@ shaka.media.DrmEngine.prototype.createTemporarySession_ =
initData: initData,
session: session,
loaded: false,
oldExpiration: Infinity,
updatePromise: null
});

Expand Down Expand Up @@ -1398,3 +1436,24 @@ shaka.media.DrmEngine.getCommonDrmInfos = function(drms1, drms2) {

return commonDrms;
};


/**
* Called in an interval timer to poll the expiration times of the sessions. We
* don't get an event from EME when the expiration updates, so we poll it so we
* can fire an event when it happens.
* @private
*/
shaka.media.DrmEngine.prototype.pollExpiration_ = function() {
this.activeSessions_.forEach(function(session) {
var old = session.oldExpiration;
var new_ = session.session.expiration;
if (isNaN(new_))
new_ = Infinity;

if (new_ != old) {
this.onExpirationUpdated_(session.session.sessionId, new_);
session.oldExpiration = new_;
}
}.bind(this));
};
47 changes: 44 additions & 3 deletions lib/offline/offline_manifest_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

goog.provide('shaka.offline.OfflineManifestParser');

goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.media.ManifestParser');
goog.require('shaka.media.PresentationTimeline');
goog.require('shaka.offline.OfflineUtils');
Expand All @@ -30,7 +32,10 @@ goog.require('shaka.util.Error');
* @constructor
* @implements {shakaExtern.ManifestParser}
*/
shaka.offline.OfflineManifestParser = function() {};
shaka.offline.OfflineManifestParser = function() {
/** @private {number} */
this.manifestId_ = -1;
};


/** @override */
Expand All @@ -50,6 +55,7 @@ shaka.offline.OfflineManifestParser.prototype.start =
}
var manifestId = Number(parts[1]);
var storageEngine = shaka.offline.OfflineUtils.createStorageEngine();
this.manifestId_ = manifestId;

if (!storageEngine) {
return Promise.reject(new shaka.util.Error(
Expand Down Expand Up @@ -91,14 +97,49 @@ shaka.offline.OfflineManifestParser.prototype.update = function() {
};


/** @override */
shaka.offline.OfflineManifestParser.prototype.onExpirationUpdated = function(
sessionId, expiration) {
var storageEngine = shaka.offline.OfflineUtils.createStorageEngine();
goog.asserts.assert(storageEngine, 'Must support offline storage');

storageEngine.init(shaka.offline.OfflineUtils.DB_SCHEME)
.then(function() {
return storageEngine.get('manifest', this.manifestId_);
}.bind(this))
.then(function(manifest) {
if (!manifest) {
// Manifest was deleted, ignore update.
return;
}
if (manifest.sessionIds.indexOf(sessionId) < 0) {
shaka.log.debug('Ignoring updated expiration for unknown session');
return;
}

if (manifest.expiration == undefined ||
manifest.expiration > expiration) {
shaka.log.debug('Updating expiration for stored content');
manifest.expiration = expiration;
return storageEngine.insert('manifest', manifest);
}
})
.catch(function(error) {
shaka.log.error('Error updating offline manifest expiration', error);
})
.then(function() {
return storageEngine.destroy();
});
};


/**
* Reconstructs a manifest object from the given database manifest.
*
* @param {shakaExtern.ManifestDB} manifest
* @return {shakaExtern.Manifest}
*/
shaka.offline.OfflineManifestParser.reconstructManifest = function(
manifest) {
shaka.offline.OfflineManifestParser.reconstructManifest = function(manifest) {
var timeline = new shaka.media.PresentationTimeline(null, 0);
timeline.setDuration(manifest.duration);
var drmInfos = manifest.drmInfo ? [manifest.drmInfo] : [];
Expand Down
2 changes: 2 additions & 0 deletions lib/offline/offline_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ shaka.offline.OfflineUtils.getStoredContent = function(manifest) {
originalManifestUri: manifest.originalManifestUri,
duration: manifest.duration,
size: manifest.size,
expiration: manifest.expiration == undefined ? Infinity :
manifest.expiration,
tracks: tracks,
appMetadata: manifest.appMetadata
};
Expand Down
9 changes: 5 additions & 4 deletions lib/offline/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,8 @@ 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() {});
drmEngine = new shaka.media.DrmEngine(
netEngine, onError, function() {}, function() {});
drmEngine.configure(this.player_.getConfiguration().drm);
return drmEngine.init(manifest, true /* isOffline */);
})
Expand Down Expand Up @@ -423,8 +423,8 @@ shaka.offline.Storage.prototype.loadInternal = function(
.then(function(data) {
this.checkDestroyed_();
manifest = data;
drmEngine =
new shaka.media.DrmEngine(netEngine, onError, onKeyStatusChange);
drmEngine = new shaka.media.DrmEngine(
netEngine, onError, onKeyStatusChange, function() {});
drmEngine.configure(config.drm);
return drmEngine.init(manifest, true /* isOffline */);
}.bind(this))
Expand Down Expand Up @@ -708,6 +708,7 @@ shaka.offline.Storage.prototype.createOfflineManifest_ = function(
originalManifestUri: originalManifestUri,
duration: this.duration_,
size: 0,
expiration: this.drmEngine_.getExpiration(),
periods: periods,
sessionIds: sessions,
drmInfo: drmInfo,
Expand Down
Loading

0 comments on commit b4d0fa4

Please sign in to comment.