Skip to content

Commit

Permalink
feat(offline): Changed store order of operations.
Browse files Browse the repository at this point in the history
This changes the order that things happen when the offline storage
mechanism stores a manifest, so that the manifest is made and the
segments are downloaded in separate steps.
This is in preparation for adding background fetch support.

Issue shaka-project#879

Change-Id: I4451db839b654f6134f06a58c240a9ca98d31a4e
  • Loading branch information
theodab committed Aug 11, 2021
1 parent c07d1a3 commit 5215f53
Show file tree
Hide file tree
Showing 10 changed files with 432 additions and 279 deletions.
1 change: 1 addition & 0 deletions build/types/offline
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# The offline storage system and manifest parser plugin.

+../../lib/offline/download_info.js
+../../lib/offline/download_manager.js
+../../lib/offline/download_progress_estimator.js
+../../lib/offline/indexeddb/base_storage_cell.js
Expand Down
33 changes: 31 additions & 2 deletions externs/shaka/offline.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ shaka.extern.OfflineSupport;
* size: number,
* expiration: number,
* tracks: !Array.<shaka.extern.Track>,
* appMetadata: Object
* appMetadata: Object,
* isIncomplete: boolean
* }}
*
* @property {?string} offlineUri
Expand All @@ -53,6 +54,9 @@ shaka.extern.OfflineSupport;
* The tracks that are stored.
* @property {Object} appMetadata
* The metadata passed to store().
* @property {boolean} isIncomplete
* If true, the content is still downloading. Manifests with this set cannot
* be played yet.
* @exportDoc
*/
shaka.extern.StoredContent;
Expand All @@ -68,7 +72,8 @@ shaka.extern.StoredContent;
* streams: !Array.<shaka.extern.StreamDB>,
* sessionIds: !Array.<string>,
* drmInfo: ?shaka.extern.DrmInfo,
* appMetadata: Object
* appMetadata: Object,
* isIncomplete: (boolean|undefined)
* }}
*
* @property {number} creationTime
Expand All @@ -91,6 +96,8 @@ shaka.extern.StoredContent;
* The DRM info used to initialize EME.
* @property {Object} appMetadata
* A metadata object passed from the application.
* @property {(boolean|undefined)} isIncomplete
* If true, the content is still downloading.
*/
shaka.extern.ManifestDB;

Expand Down Expand Up @@ -195,6 +202,8 @@ shaka.extern.StreamDB;
* appendWindowEnd: number,
* timestampOffset: number,
* tilesLayout: ?string,
* pendingSegmentRefId: (string|undefined),
* pendingInitSegmentRefId: (string|undefined),
* dataKey: number
* }}
*
Expand All @@ -215,6 +224,17 @@ shaka.extern.StreamDB;
* The value is a grid-item-dimension consisting of two positive decimal
* integers in the format: column-x-row ('4x3'). It describes the
* arrangement of Images in a Grid. The minimum valid LAYOUT is '1x1'.
* @property {(string|undefined)} pendingSegmentRefId
* Contains an id that identifies what the segment was, originally. Used to
* coordinate where segments are stored, during the downloading process.
* If this field is non-null, it's assumed that the segment is not fully
* downloaded.
* @property {(string|undefined)} pendingInitSegmentRefId
* Contains an id that identifies what the init segment was, originally.
* Used to coordinate where init segments are stored, during the downloading
* process.
* If this field is non-null, it's assumed that the init segment is not fully
* downloaded.
* @property {number} dataKey
* The key to the data in storage.
*/
Expand Down Expand Up @@ -333,6 +353,15 @@ shaka.extern.StorageCell = class {
*/
addManifests(manifests) {}

/**
* Updates the given manifest, stored at the given key.
*
* @param {number} key
* @param {!shaka.extern.ManifestDB} manifest
* @return {!Promise}
*/
updateManifest(key, manifest) {}

/**
* Replace the expiration time of the manifest stored under |key| with
* |newExpiration|. If no manifest is found under |key| then this should
Expand Down
68 changes: 68 additions & 0 deletions lib/offline/download_info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

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

goog.require('shaka.util.Networking');
goog.requireType('shaka.media.InitSegmentReference');
goog.requireType('shaka.media.SegmentReference');


/**
* An object that represents a single segment, that the storage system will soon
* download, but has not yet started downloading.
*/
shaka.offline.DownloadInfo = class {
/**
* @param {shaka.media.SegmentReference|shaka.media.InitSegmentReference} ref
* @param {number} estimateId
* @param {number} groupId
* @param {boolean} isInitSegment
*/
constructor(ref, estimateId, groupId, isInitSegment) {
/** @type {shaka.media.SegmentReference|shaka.media.InitSegmentReference} */
this.ref = ref;

/** @type {number} */
this.estimateId = estimateId;

/** @type {number} */
this.groupId = groupId;

/** @type {boolean} */
this.isInitSegment = isInitSegment;
}

/**
* Creates an ID that encapsulates all important information in the ref, which
* can then be used to check for equality.
* @param {shaka.media.SegmentReference|shaka.media.InitSegmentReference} ref
* @return {string}
*/
static idForSegmentRef(ref) {
// Escape the URIs using encodeURI, to make sure that a weirdly formed URI
// cannot cause two unrelated refs to be considered equivalent.
return ref.getUris().map((uri) => '{' + encodeURI(uri) + '}').join('') +
':' + ref.startByte + ':' + ref.endByte;
}

/** @return {string} */
getRefId() {
return shaka.offline.DownloadInfo.idForSegmentRef(this.ref);
}

/**
* @param {shaka.extern.PlayerConfiguration} config
* @return {!shaka.extern.Request}
*/
makeSegmentRequest(config) {
return shaka.util.Networking.createSegmentRequest(
this.ref.getUris(),
this.ref.startByte,
this.ref.endByte,
config.streaming.retryParameters);
}
};
18 changes: 13 additions & 5 deletions lib/offline/download_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,25 +102,33 @@ shaka.offline.DownloadManager = class {
return Promise.all(promises);
}

/**
* Adds a byte length to the download estimate.
*
* @param {number} estimatedByteLength
* @return {number} estimateId
*/
addDownloadEstimate(estimatedByteLength) {
return this.estimator_.open(estimatedByteLength);
}

/**
* Add a request to be downloaded as part of a group.
*
* @param {number} groupId
* The group to add this segment to. If the group does not exist, a new
* group will be created.
* @param {shaka.extern.Request} request
* @param {number} estimatedByteLength
* @param {number} estimateId
* @param {boolean} isInitSegment
* @param {function(BufferSource):!Promise} onDownloaded
* The callback for when this request has been downloaded. Downloading for
* |group| will pause until the promise returned by |onDownloaded| resolves.
* @return {!Promise} Resolved when this request is complete.
*/
queue(groupId, request, estimatedByteLength, isInitSegment, onDownloaded) {
queue(groupId, request, estimateId, isInitSegment, onDownloaded) {
this.destroyer_.ensureNotDestroyed();

const id = this.estimator_.open(estimatedByteLength);

const group = this.groups_.get(groupId) || Promise.resolve();

// Add another download to the group.
Expand Down Expand Up @@ -148,7 +156,7 @@ shaka.offline.DownloadManager = class {
}

// Update all our internal stats.
this.estimator_.close(id, response.byteLength);
this.estimator_.close(estimateId, response.byteLength);
this.onProgress_(
this.estimator_.getEstimatedProgress(),
this.estimator_.getTotalDownloaded());
Expand Down
35 changes: 35 additions & 0 deletions lib/offline/indexeddb/base_storage_cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,28 @@ shaka.offline.indexeddb.BaseStorageCell = class {
return this.rejectAdd(this.manifestStore_);
}

/** @override */
updateManifest(key, manifest) {
// By default, reject all updates.
return this.rejectUpdate(this.manifestStore_);
}

/**
* @param {number} key
* @param {!shaka.extern.ManifestDB} manifest
* @return {!Promise}
* @protected
*/
updateManifestImplementation(key, manifest) {
const op = this.connection_.startReadWriteOperation(this.manifestStore_);
const store = op.store();
store.get(key).onsuccess = (e) => {
store.put(manifest, key);
};

return op.promise();
}

/** @override */
updateManifestExpiration(key, newExpiration) {
const op = this.connection_.startReadWriteOperation(this.manifestStore_);
Expand Down Expand Up @@ -145,6 +167,19 @@ shaka.offline.indexeddb.BaseStorageCell = class {
'Cannot add new value to ' + storeName));
}

/**
* @param {string} storeName
* @return {!Promise}
* @protected
*/
rejectUpdate(storeName) {
return Promise.reject(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.STORAGE,
shaka.util.Error.Code.MODIFY_OPERATION_NOT_SUPPORTED,
'Cannot modify values in ' + storeName));
}

/**
* @param {string} storeName
* @param {!Array.<T>} values
Expand Down
5 changes: 5 additions & 0 deletions lib/offline/indexeddb/v5_storage_cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ shaka.offline.indexeddb.V5StorageCell = class
return this.add(this.manifestStore_, manifests);
}

/** @override */
updateManifest(key, manifest) {
return this.updateManifestImplementation(key, manifest);
}

/** @override */
convertManifest(old) {
// JSON serialization turns Infinity into null, so turn it back now.
Expand Down
Loading

0 comments on commit 5215f53

Please sign in to comment.