From 85fe4438b6cc5f2b1cd807bfd2a103b96d88198a Mon Sep 17 00:00:00 2001 From: Jacob Trimble Date: Tue, 28 Jun 2016 11:36:24 -0700 Subject: [PATCH] Various fixes for offline playback. * Remove warnings for incorrect argument counts in configure. * Duration incorrect for multi-Period. * Multi-Period does not always work with multi-codec. * Progress meter visible from start and after done. * Offline buttons enable while storing if asset is switched. b/29777213 Change-Id: I934bec0e6b5be2d69a908629b187459a6289f7a7 --- demo/demo.css | 4 +++ demo/index.html | 2 +- demo/offline_section.js | 63 ++++++++++++++++++++++++++--------------- lib/offline/storage.js | 47 ++++++++++++++++++++++++++---- lib/player.js | 8 +----- 5 files changed, 88 insertions(+), 36 deletions(-) diff --git a/demo/demo.css b/demo/demo.css index 7192dc3c8..62ec118c6 100644 --- a/demo/demo.css +++ b/demo/demo.css @@ -182,6 +182,10 @@ summary { cursor: pointer; } +#progressDiv { + display: none; +} + #logSection { display: none; } diff --git a/demo/index.html b/demo/index.html index f1a22b5f3..04d7eedb6 100644 --- a/demo/index.html +++ b/demo/index.html @@ -109,7 +109,7 @@ Offline -
+
0%
diff --git a/demo/offline_section.js b/demo/offline_section.js index 5a20d2da8..106920ca0 100644 --- a/demo/offline_section.js +++ b/demo/offline_section.js @@ -24,9 +24,21 @@ var shakaDemo = shakaDemo || {}; shakaDemo.offlineOptGroup_ = null; -/** @private */ -shakaDemo.updateButtons_ = function() { +/** @private {boolean} */ +shakaDemo.offlineOperationInProgress_ = false; + + +/** + * @param {boolean} canHide True to hide the progress value if there isn't an + * operation going. + * @private + */ +shakaDemo.updateButtons_ = function(canHide) { var assetList = document.getElementById('assetList'); + var inProgress = shakaDemo.offlineOperationInProgress_; + + document.getElementById('progressDiv').style.display = + canHide && !inProgress ? 'none' : 'block'; var option = assetList.options[assetList.selectedIndex]; var storedContent = option.storedContent; @@ -39,14 +51,23 @@ shakaDemo.updateButtons_ = function() { }); var storeBtn = document.getElementById('storeOffline'); - storeBtn.disabled = (!supportsDrm || storedContent != null); - storeBtn.title = !supportsDrm ? - 'This browser does not support persistent licenses' : - (storeBtn.disabled ? 'Selected asset is already stored offline' : ''); + storeBtn.disabled = (inProgress || !supportsDrm || storedContent != null); + if (inProgress) + storeBtn.title = 'There is already an operation in progress'; + else if (!supportsDrm) + storeBtn.title = 'This browser does not support persistent licenses'; + else if (storeBtn.disabled) + storeBtn.title = 'Selected asset is already stored offline'; + else + storeBtn.title = ''; var deleteBtn = document.getElementById('deleteOffline'); - deleteBtn.disabled = (storedContent == null); - deleteBtn.title = - deleteBtn.disabled ? 'Selected asset is not stored offline' : ''; + deleteBtn.disabled = (inProgress || storedContent == null); + if (inProgress) + deleteBtn.title = 'There is already an operation in progress'; + else if (deleteBtn.disabled) + deleteBtn.title = 'Selected asset is not stored offline'; + else + deleteBtn.title = ''; }; @@ -57,8 +78,8 @@ shakaDemo.setupOffline_ = function() { document.getElementById('deleteOffline') .addEventListener('click', shakaDemo.deleteAsset_); document.getElementById('assetList') - .addEventListener('change', shakaDemo.updateButtons_); - shakaDemo.updateButtons_(); + .addEventListener('change', shakaDemo.updateButtons_.bind(null, true)); + shakaDemo.updateButtons_(true); }; @@ -109,24 +130,21 @@ shakaDemo.setupOfflineAssets_ = function() { /** @private */ shakaDemo.storeAsset_ = function() { shakaDemo.closeError(); + shakaDemo.offlineOperationInProgress_ = true; + shakaDemo.updateButtons_(false); var assetList = document.getElementById('assetList'); var progress = document.getElementById('progress'); - var storeBtn = document.getElementById('storeOffline'); - var deleteBtn = document.getElementById('deleteOffline'); var option = assetList.options[assetList.selectedIndex]; var asset = shakaDemo.preparePlayer_(option.asset); progress.textContent = '0'; - storeBtn.disabled = true; - deleteBtn.disabled = true; var metadata = {name: asset.name || asset.manifestUri}; var storage = new shaka.offline.Storage(shakaDemo.player_); storage.configure(/** @type {shakaExtern.OfflineConfiguration} */ ({ progressCallback: function(data, percent) { - var progress = document.getElementById('progress'); progress.textContent = (percent * 100).toFixed(2); } })); @@ -137,7 +155,8 @@ shakaDemo.storeAsset_ = function() { var error = /** @type {!shaka.util.Error} */(reason); shakaDemo.onError_(error); }).then(function() { - shakaDemo.updateButtons_(); + shakaDemo.offlineOperationInProgress_ = false; + shakaDemo.updateButtons_(false); return storage.destroy(); }); }; @@ -146,15 +165,12 @@ shakaDemo.storeAsset_ = function() { /** @private */ shakaDemo.deleteAsset_ = function() { shakaDemo.closeError(); + shakaDemo.offlineOperationInProgress_ = true; + shakaDemo.updateButtons_(false); var assetList = document.getElementById('assetList'); - var storeBtn = document.getElementById('storeOffline'); - var deleteBtn = document.getElementById('deleteOffline'); var option = assetList.options[assetList.selectedIndex]; - storeBtn.disabled = true; - deleteBtn.disabled = true; - var storage = new shaka.offline.Storage(shakaDemo.player_); storage.configure(/** @type {shakaExtern.OfflineConfiguration} */ ({ progressCallback: function(data, percent) { @@ -169,7 +185,8 @@ shakaDemo.deleteAsset_ = function() { var error = /** @type {!shaka.util.Error} */(reason); shakaDemo.onError_(error); }).then(function() { - shakaDemo.updateButtons_(); + shakaDemo.offlineOperationInProgress_ = false; + shakaDemo.updateButtons_(false); return storage.destroy(); }); }; diff --git a/lib/offline/storage.js b/lib/offline/storage.js index aaffca1ea..0cc2636ab 100644 --- a/lib/offline/storage.js +++ b/lib/offline/storage.js @@ -62,6 +62,9 @@ shaka.offline.Storage = function(player) { /** @private {boolean} */ this.storeInProgress_ = false; + /** @private {Array.} */ + this.firstPeriodTracks_ = null; + /** * The IDs of the segments that have been stored for an in-progress store(). * This is used to cleanup in destroy(). @@ -514,7 +517,7 @@ shaka.offline.Storage.prototype.defaultTrackSelect_ = function(tracks) { shaka.offline.Storage.prototype.defaultConfig_ = function() { return { trackSelectionCallback: this.defaultTrackSelect_.bind(this), - progressCallback: function() {} + progressCallback: new Function('storedContent', 'percent', '') }; }; @@ -537,8 +540,33 @@ shaka.offline.Storage.prototype.initIfNeeded_ = function() { * @private */ shaka.offline.Storage.prototype.filterPeriod_ = function(period) { + function getFirstStreamOfType(period, tracks, contentType) { + var tracksOfType = + tracks.filter(function(track) { return track.type == contentType; }); + if (tracksOfType.length == 0) + return null; + var data = + shaka.util.StreamUtils.findStreamForTrack(period, tracksOfType[0]); + goog.asserts.assert( + data, 'Could not find stream with id ' + tracksOfType[0].id); + return data.stream; + } + var StreamUtils = shaka.util.StreamUtils; - StreamUtils.filterPeriod(this.drmEngine_, /* activeStreams */ {}, period); + var activeStreams = {}; + if (this.firstPeriodTracks_) { + // Use the first stream of each content type as the "active stream". This + // is then used to filter out the streams that are not compatible with it. + // This ensures that in multi-Period content, all Periods have streams + // with compatible MIME types. + activeStreams = { + 'video': getFirstStreamOfType( + this.manifest_.periods[0], this.firstPeriodTracks_, 'video'), + 'audio': getFirstStreamOfType( + this.manifest_.periods[0], this.firstPeriodTracks_, 'audio') + }; + } + StreamUtils.filterPeriod(this.drmEngine_, activeStreams, period); StreamUtils.applyRestrictions( period, this.player_.getConfiguration().restrictions); }; @@ -556,6 +584,7 @@ shaka.offline.Storage.prototype.cleanup_ = function() { this.drmEngine_ = null; this.manifest_ = null; this.storeInProgress_ = false; + this.firstPeriodTracks_ = null; this.inProgressSegmentIds_ = []; this.manifestId_ = -1; return ret; @@ -631,13 +660,19 @@ shaka.offline.Storage.prototype.createOfflineManifest_ = function( shaka.offline.Storage.prototype.createPeriod_ = function(period) { var allTracks = shaka.util.StreamUtils.getTracks(period, null); var tracks = this.config_.trackSelectionCallback(allTracks); + if (this.firstPeriodTracks_ == null) { + this.firstPeriodTracks_ = tracks; + // Now that the first tracks are chosen, filter again. This ensures all + // Periods have compatible content types. + this.manifest_.periods.forEach(this.filterPeriod_.bind(this)); + } // TODO(modmaker): Issue a warning if multiple tracks of the same variety // are selected (type, kind, and language). var streams = tracks.map(function(track) { var data = shaka.util.StreamUtils.findStreamForTrack(period, track); goog.asserts.assert(data, 'Could not find track with id ' + track.id); - return this.createStream_(data.streamSet, data.stream); + return this.createStream_(period, data.streamSet, data.stream); }.bind(this)); return { @@ -651,12 +686,14 @@ shaka.offline.Storage.prototype.createPeriod_ = function(period) { * Converts a manifest stream to a database stream. This will search the * segment index and add all the segments to the download manager. * + * @param {shakaExtern.Period} period * @param {shakaExtern.StreamSet} streamSet * @param {shakaExtern.Stream} stream * @return {shakaExtern.StreamDB} * @private */ -shaka.offline.Storage.prototype.createStream_ = function(streamSet, stream) { +shaka.offline.Storage.prototype.createStream_ = function( + period, streamSet, stream) { /** @type {!Array.} */ var segmentsDb = []; var startTime = this.manifest_.presentationTimeline.getEarliestStart(); @@ -686,7 +723,7 @@ shaka.offline.Storage.prototype.createStream_ = function(streamSet, stream) { uri: 'offline:' + this.manifestId_ + '/' + stream.id + '/' + id }); - endTime = ref.endTime; + endTime = ref.endTime + period.startTime; ref = stream.getSegmentReference(++i); } diff --git a/lib/player.js b/lib/player.js index 58f82b637..2972c6eaf 100644 --- a/lib/player.js +++ b/lib/player.js @@ -1167,13 +1167,7 @@ shaka.Player.prototype.defaultConfig_ = function() { manifest: { retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(), dash: { - customScheme: function(node) { - // Reference node to keep closure from removing it. - // If the argument is removed, it breaks our function length check - // in mergeConfigObjects_(). - // TODO: Find a better solution if possible. - if (node) return null; - }, + customScheme: new Function('node', ''), clockSyncUri: '' } },