Skip to content

Commit

Permalink
Deleting offline content now deletes persistent session.
Browse files Browse the repository at this point in the history
Closes #171

Change-Id: Ia0ff2c9892f53c2ceeddb2b589e2e2c886aea54a
  • Loading branch information
TheModMaker authored and joeyparrish committed Oct 7, 2015
1 parent 73ba5cc commit 7d43821
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 13 deletions.
1 change: 1 addition & 0 deletions integration_tests.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<!-- Spec files: -->
<script src="spec/live_integration.js"></script>
<script src="spec/player_integration.js"></script>
<script src="spec/offline_integration.js"></script>

</head>
<body>
Expand Down
29 changes: 28 additions & 1 deletion lib/media/eme_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ shaka.media.EmeManager.prototype.destroy = function() {
this.eventManager_.destroy();
this.eventManager_ = null;

if (this.allSessionsReadyTimer_) {
window.clearTimeout(this.allSessionsReadyTimer_);
this.allSessionsReadyTimer_ = null;
}

this.videoSource_ = null; // not owned by us, do not destroy
this.video_ = null;
};
Expand Down Expand Up @@ -188,6 +193,19 @@ shaka.media.EmeManager.prototype.allSessionsReady = function(timeoutMs) {
};


/**
* Deletes all sessions from persistent storage.
*
* @return {!Promise}
*/
shaka.media.EmeManager.prototype.deleteSessions = function() {
return Promise.all(this.sessions_.map(function(a) {
shaka.log.debug('Removing session', a);
return a.remove();
}));
};


/**
* Choose unencrypted streams for each type if possible. Store chosen streams
* into chosenStreams.
Expand Down Expand Up @@ -565,7 +583,16 @@ shaka.media.EmeManager.prototype.loadSessions_ = function() {
var p = session.load(persistentSessionIds[i]);
this.sessions_.push(session);

p.catch(shaka.util.TypedBind(this,
p.then(shaka.util.TypedBind(this,
function(arg) {
// Assume that the load does not require a message. This allows
// offline sessions to use allSessionsReady.
this.numUpdates_++;
if (this.numUpdates_ >= this.sessions_.length) {
this.allSessionsPresumedReady_.resolve();
}
})
).catch(shaka.util.TypedBind(this,
/** @param {*} error */
function(error) {
var event = shaka.util.FakeEvent.createErrorEvent(error);
Expand Down
103 changes: 93 additions & 10 deletions lib/player/offline_video_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ shaka.player.OfflineVideoSource = function(groupId, estimator, abrManager) {

/** @private {shaka.util.FailoverUri.NetworkCallback} */
this.networkCallback_ = null;

/** @private {shaka.player.DrmInfo.Config} */
this.overrideConfig_ = null;
};
goog.inherits(shaka.player.OfflineVideoSource, shaka.player.StreamVideoSource);
if (shaka.features.Offline) {
Expand Down Expand Up @@ -409,7 +412,7 @@ shaka.player.OfflineVideoSource.prototype.insertGroup_ =
shaka.player.OfflineVideoSource.prototype.load = function() {
shaka.asserts.assert(this.groupId_ >= 0);
var contentDatabase = new shaka.util.ContentDatabaseReader();
var duration, keySystem;
var duration, config;

return contentDatabase.setUpDatabase().then(shaka.util.TypedBind(this,
function() {
Expand All @@ -422,7 +425,15 @@ shaka.player.OfflineVideoSource.prototype.load = function() {
var async = [];
this.sessionIds_ = group['session_ids'];
duration = group['duration'];
keySystem = group['key_system'];
config = {
'keySystem': group['key_system'],
'distinctiveIdentifierRequired': group['distinctive_identifier'],
'persistentStorageRequired': true,
'audioRobustness': group['audio_robustness'],
'videoRobustness': group['video_robustness'],
'withCredentials': group['with_credentials'],
'licenseServerUrl': group['license_server']
};
for (var i = 0; i < group['stream_ids'].length; ++i) {
var streamId = group['stream_ids'][i];
async.push(contentDatabase.retrieveStreamIndex(streamId));
Expand All @@ -433,7 +444,7 @@ shaka.player.OfflineVideoSource.prototype.load = function() {
/** @param {!Array.<shaka.util.ContentDatabase.StreamIndex>} indexes */
function(indexes) {
this.manifestInfo =
this.reconstructManifestInfo_(indexes, duration, keySystem);
this.reconstructManifestInfo_(indexes, duration, config);

var baseClassLoad = shaka.player.StreamVideoSource.prototype.load;
return baseClassLoad.call(this);
Expand All @@ -458,12 +469,13 @@ shaka.player.OfflineVideoSource.prototype.load = function() {
* @param {!Array.<shaka.util.ContentDatabase.StreamIndex>} indexes The indexes
* of the streams in this manifest.
* @param {number} duration The max stream's entire duration in the group.
* @param {string} keySystem The group's DRM key system.
* @param {shaka.player.DrmInfo.Config} config The config info loaded from
* storage.
* @return {!shaka.media.ManifestInfo}
* @private
*/
shaka.player.OfflineVideoSource.prototype.reconstructManifestInfo_ =
function(indexes, duration, keySystem) {
function(indexes, duration, config) {
var manifestInfo = new shaka.media.ManifestInfo();
manifestInfo.minBufferTime = 5;
// TODO(story 1890046): Support multiple periods.
Expand Down Expand Up @@ -493,8 +505,21 @@ shaka.player.OfflineVideoSource.prototype.reconstructManifestInfo_ =
streamInfo.codecs = storedStreamInfo['codecs'];
streamInfo.allowedByKeySystem = true;

var drmInfo = shaka.player.DrmInfo.createFromConfig(
{'keySystem': keySystem, 'licenseServerUrl': ''});
if (this.overrideConfig_) {
if (this.overrideConfig_['licenseServerUrl'] != null) {
config['licenseServerUrl'] = this.overrideConfig_['licenseServerUrl'];
}
if (this.overrideConfig_['withCredentials'] != null) {
config['withCredentials'] = this.overrideConfig_['withCredentials'];
}
config['licensePostProcessor'] =
this.overrideConfig_['licensePostProcessor'];
config['licensePreProcessor'] =
this.overrideConfig_['licensePreProcessor'];
config['serverCertificate'] = this.overrideConfig_['serverCertificate'];
}

var drmInfo = shaka.player.DrmInfo.createFromConfig(config);
var streamSetInfo = new shaka.media.StreamSetInfo();
streamSetInfo.streamInfos.push(streamInfo);
streamSetInfo.drmInfos.push(drmInfo);
Expand All @@ -508,15 +533,31 @@ shaka.player.OfflineVideoSource.prototype.reconstructManifestInfo_ =


/**
* Deletes a group of streams from storage.
* Deletes a group of streams from storage. This destroys the VideoSource.
*
* @param {shaka.player.DrmInfo.Config=} opt_config Optional config to override
* the values stored. Can only change |licenseServerUrl|, |withCredentials|,
* |serverCertificate|, |licensePreProcessor|, and |licensePostProcessor|.
* @return {!Promise}
* @export
*/
shaka.player.OfflineVideoSource.prototype.deleteGroup = function() {
shaka.player.OfflineVideoSource.prototype.deleteGroup = function(opt_config) {
shaka.asserts.assert(this.groupId_ >= 0);
var contentDatabase = new shaka.util.ContentDatabaseWriter(null, null);

return contentDatabase.setUpDatabase().then(shaka.util.TypedBind(this,
if (opt_config) {
this.overrideConfig_ = {
'licenseServerUrl': opt_config['licenseServerUrl'],
'withCredentials': opt_config['withCredentials'],
'serverCertificate': opt_config['serverCertificate'],
'licensePreProcessor': opt_config['licensePreProcessor'],
'licensePostProcessor': opt_config['licensePostProcessor']
};
}

return this.deletePersistentSessions_().then(function() {
return contentDatabase.setUpDatabase();
}).then(shaka.util.TypedBind(this,
function() {
return contentDatabase.deleteGroup(
/** @type {number} */(this.groupId_));
Expand Down Expand Up @@ -545,3 +586,45 @@ shaka.player.OfflineVideoSource.prototype.getSessionIds = function() {
shaka.player.OfflineVideoSource.prototype.isOffline = function() {
return true;
};


/**
* Deletes any persistent sessions associated with the |groupId_|.
*
* @return {!Promise}
* @private
*/
shaka.player.OfflineVideoSource.prototype.deletePersistentSessions_ =
function() {
var fakeVideoElement = /** @type {!HTMLVideoElement} */ (
document.createElement('video'));
fakeVideoElement.src = window.URL.createObjectURL(this.mediaSource);

var emeManager = new shaka.media.EmeManager(null, fakeVideoElement, this);
if (this.config_['licenseRequestTimeout'] != null) {
emeManager.setLicenseRequestTimeout(
Number(this.config_['licenseRequestTimeout']));
}

return this.load().then(function() {
return emeManager.initialize();
}).then(shaka.util.TypedBind(this, function() {
return emeManager.allSessionsReady(this.timeoutMs);
})).then(function() {
return emeManager.deleteSessions();
}).then(shaka.util.TypedBind(this,
function() {
emeManager.destroy();
this.destroy();
return Promise.resolve();
})
).catch(shaka.util.TypedBind(this,
/** @param {*} e */
function(e) {
emeManager.destroy();
this.destroy();
return Promise.reject(e);
})
);
};

7 changes: 6 additions & 1 deletion lib/util/content_database.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ shaka.util.ContentDatabase.CONTENT_STORE_ = 'content_store';
* stream_ids: !Array.<number>,
* session_ids: !Array.<string>,
* duration: ?number,
* key_system: string
* key_system: string,
* license_server: string,
* with_credentials: boolean,
* distinctive_identifier: boolean,
* audio_robustness: string,
* video_robustness: string
* }}
*/
shaka.util.ContentDatabase.GroupInformation;
Expand Down
6 changes: 6 additions & 0 deletions lib/util/content_database_reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ shaka.util.ContentDatabaseReader.prototype.retrieveGroup = function(groupId) {
function(index) {
groupInfo['duration'] = index['duration'];
groupInfo['key_system'] = index['key_system'];
groupInfo['license_server'] = index['license_server'];
groupInfo['with_credentials'] = index['with_credentials'];
groupInfo['distinctive_identifier'] =
index['distinctive_identifier'];
groupInfo['audio_robustness'] = index['audio_robustness'];
groupInfo['video_robustness'] = index['video_robustness'];
return Promise.resolve(groupInfo);
});
} else {
Expand Down
7 changes: 6 additions & 1 deletion lib/util/content_database_writer.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,12 @@ shaka.util.ContentDatabaseWriter.prototype.insertGroup = function(
'stream_ids': streamIds,
'session_ids': sessionIds,
'duration': duration,
'key_system': drmInfo.keySystem
'key_system': drmInfo.keySystem,
'license_server': drmInfo.licenseServerUrl,
'with_credentials': drmInfo.withCredentials,
'distinctive_identifier': drmInfo.distinctiveIdentifierRequired,
'audio_robustness': drmInfo.audioRobustness,
'video_robustness': drmInfo.videoRobustness
};
var request = this.getGroupStore().put(groupInfo);

Expand Down
89 changes: 89 additions & 0 deletions spec/offline_integration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* @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.require('shaka.media.EmeManager');
goog.require('shaka.media.SimpleAbrManager');
goog.require('shaka.player.OfflineVideoSource');
goog.require('shaka.util.ContentDatabaseReader');
goog.require('shaka.util.EWMABandwidthEstimator');
goog.require('shaka.util.PublicPromise');

describe('OfflineVideoSource', function() {

// This tests whether the OfflineVideoSource deletes the persistent sessions
// when it deletes the offline content. This test requires access to
// persistent licenses, which requires a specially configured license server.
// Also, offline only works on devices that support persistent licenses. So
// this test is disabled by default.
//
// To run this test, manually store some protected content in a group BEFORE
// running the tests and then set the groupId below to the where it is stored.
var groupId = -1;
if (groupId >= 0) {
it('deletes persistent sessions', function(done) {
var contentDatabase = new shaka.util.ContentDatabaseReader();
var sessions = [];

var offlineSource = new shaka.player.OfflineVideoSource(
groupId, null, null);

var fakeVideoElement = /** @type {!HTMLVideoElement} */ (
document.createElement('video'));
fakeVideoElement.src = window.URL.createObjectURL(
offlineSource.mediaSource);
var emeManager = new shaka.media.EmeManager(
null, fakeVideoElement, offlineSource);

contentDatabase.setUpDatabase().then(function() {
return contentDatabase.retrieveGroup(groupId);
}).then(function(groupInfo) {
// Find the IDs of the sessions.
sessions = groupInfo['session_ids'];
// Require protected content.
expect(sessions.length).toBeGreaterThan(0);
// Load the source so we can get a valid EmeManager.
return offlineSource.load();
}).then(function() {
// Ensure the EmeManager does not load the sessions.
offlineSource.sessionIds_ = [];
return emeManager.initialize();
}).then(function() {
// Create a fresh source to delete the content.
offlineSource = new shaka.player.OfflineVideoSource(
groupId, null, null);
return offlineSource.deleteGroup();
}).then(function() {
// Try to load the sessions, it will fail.
var async = [];
for (var i = 0; i < sessions.length; i++) {
var p = emeManager.createSession_().load(sessions[i]).then(
function(arg) {
// This means an empty session, it's not valid.
expect(arg).toBe(false);
});
async.push(p);
}
return Promise.all(async);
}).then(function() {
done();
}).catch(function(e) {
fail(e);
});
});
}
});

0 comments on commit 7d43821

Please sign in to comment.