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: ''
}
},