Skip to content

Commit

Permalink
feat: Add downloadSizeCallback before storing offline (#3049)
Browse files Browse the repository at this point in the history
When downloading content for storage, it's useful to know how much storage space
will be needed before storing. This is extremely important for mobile devices
that have limited space. See shaka-project/shaka-player-embedded#167.

Use the info in the manifest and make HEAD requests to determine the
size of the content we're downloading.

Add a downloadSizeCallback that will be called with the size once we know it.
The callback determine if the content can be downloaded due to its estimated
size.

Closes #3049 .
  • Loading branch information
Álvaro Velad Galván authored Feb 1, 2021
1 parent 7137286 commit 54b8f6e
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 0 deletions.
5 changes: 5 additions & 0 deletions externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,7 @@ shaka.extern.AbrConfiguration;
* @typedef {{
* trackSelectionCallback:
* function(shaka.extern.TrackList):!Promise<shaka.extern.TrackList>,
* downloadSizeCallback: function(number):!Promise<boolean>,
* progressCallback: function(shaka.extern.StoredContent,number),
* usePersistentLicense: boolean
* }}
Expand All @@ -902,6 +903,10 @@ shaka.extern.AbrConfiguration;
* Called inside <code>store()</code> to determine which tracks to save from a
* manifest. It is passed an array of Tracks from the manifest and it should
* return an array of the tracks to store.
* @property {function(number):!Promise<boolean>} downloadSizeCallback
* Called inside <code>store()</code> to determine if the content can be
* downloaded due to its estimated size. The estimated size of the download is
* passed and it must return if the download is allowed or not.
* @property {function(shaka.extern.StoredContent,number)} progressCallback
* Called inside <code>store()</code> to give progress info back to the app.
* It is given the current manifest being stored and the progress of it being
Expand Down
23 changes: 23 additions & 0 deletions lib/offline/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,29 @@ shaka.offline.Storage = class {
// Let the application choose which tracks to store.
const chosenTracks =
await config.offline.trackSelectionCallback(allTracks);
const duration = manifest.presentationTimeline.getDuration();
let sizeEstimate = 0;
for (const track of chosenTracks) {
const trackSize = track.bandwidth * duration / 8;
sizeEstimate += trackSize;
}
try {
const allowedDownload =
await config.offline.downloadSizeCallback(sizeEstimate);
if (!allowedDownload) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.STORAGE,
shaka.util.Error.Code.STORAGE_LIMIT_REACHED);
}
} catch (e) {
shaka.log.warning(
'downloadSizeCallback has produced an unexpected error', e);
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.STORAGE,
shaka.util.Error.Code.DOWNLOAD_SIZE_CALLBACK_ERROR);
}

/** @type {!Set.<number>} */
const variantIds = new Set();
Expand Down
11 changes: 11 additions & 0 deletions lib/util/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,17 @@ shaka.util.Error.Code = {
*/
'MISSING_STORAGE_CELL': 9013,

/**
* The storage limit defined in <code>downloadSizeCallback</code> has been
* reached.
*/
'STORAGE_LIMIT_REACHED': 9014,

/**
* <code>downloadSizeCallback</code> has produced an unexpected error.
*/
'DOWNLOAD_SIZE_CALLBACK_ERROR': 9015,

/**
* CS IMA SDK, required for ad insertion, has not been included on the page.
*/
Expand Down
10 changes: 10 additions & 0 deletions lib/util/player_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ shaka.util.PlayerConfiguration = class {
// eslint-disable-next-line require-await
trackSelectionCallback: async (tracks) => tracks,

downloadSizeCallback: async (sizeEstimate) => {
if (navigator.storage && navigator.storage.estimate) {
const estimate = await navigator.storage.estimate();
// Limit to 95% of quota.
return estimate.usage + sizeEstimate < estimate.quota * 0.95;
} else {
return true;
}
},

// Need some operation in the callback or else closure may remove calls
// to the function as it would be a no-op. The operation can't just be a
// log message, because those are stripped in the compiled build.
Expand Down

0 comments on commit 54b8f6e

Please sign in to comment.