Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query download size before storing offline #2900

Closed
TheModMaker opened this issue Oct 12, 2020 · 11 comments · Fixed by #3049
Closed

Query download size before storing offline #2900

TheModMaker opened this issue Oct 12, 2020 · 11 comments · Fixed by #3049
Labels
status: archived Archived and locked; will not be updated type: enhancement New feature or request
Milestone

Comments

@TheModMaker
Copy link
Contributor

Have you read the FAQ and checked for duplicate open issues?
Yes

Is your feature request related to a problem? Please describe.
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.

Describe the solution you'd like
We could use the info in the manifest and make HEAD requests to determine the size of the content we're downloading. This would only include the segments themselves, not the manifest since it's small and we can't determine how large that will be in IndexedDB.

We would have an extra callback (like the track-selection callback) that will be called with the size once we know it. The callback can return a boolean for whether we want to continue or cancel. The method could be asynchronous to allow using the Storage API:

storage.configure('offline.downloadSizeCallback', async (amount) => {
  const estimate = await navigator.storage.estimate();
  // Limit to 75% of quota.
  return (estimate.quota - estimate.usage) * 0.75 > amount;
});

Describe alternatives you've considered

Additional context

@joeyparrish
Copy link
Member

I really like this idea. The async callback would also allow integration with an app's UI to present a prompt to the user.

There are a few wrinkles, though, to be worked out first:

  1. Because the HEAD requests for thousands of segments could increase latency quite a lot, I would suggest that the pre-measurement of storage requirements should simply be avoided if that callback is not configured. However, our config system does not allow callbacks to default to null, because of the template-based runtime type checking we do on configs. This could probably be worked around somehow.
  2. What order should we call these callbacks? You almost certainly want to avoid HEAD requests on all segments of all tracks, so you should probably select tracks before querying the size. But an app may want to select tracks based on the storage requirements. Maybe you don't have room for 1080p, but you do for 720p or 480p.

In fact, the more I think about this latter issue, the more I think it would be ideal for applications to have some way to get size metadata from their catalog directly, so that they can make size-based decisions without hammering their servers with HEAD requests. A two-hour film with 10-second segments (which is on the high end of segment size, but common enough for VOD) would be 720 segments x 2 streams (video + audio) = 1440 segments.

Do app developers really want us to make 1440 HEAD requests before we start downloading content?

@TheModMaker
Copy link
Contributor Author

One thing we could do is have a flag to select between making HEAD requests and estimating it based on the manifest. We could estimate the size based on the bandwidth. This would allow apps to have a ballpark estimate without requests, or to have us make a bunch of requests if they need a more exact answer.

@joeyparrish
Copy link
Member

You make a good point. If we are clear in the API that the size in the callback is only an estimate, we could simply always use average bandwidth x time to compute that size estimate. I feel as though that should be accurate enough for storage decisions, but also extremely efficient in terms of time and server resources. What do you think?

@OmarPedraza
Copy link

Hi @TheModMaker and @joeyparrish,
Being the one who opened shaka-project/shaka-player-embedded#167, maybe I can help a little bit here.

I don't know about what other apps do but we launch two HEAD request (one for video data and other one for audio) for each content after track selection. Then, since we're allowing to create a queue, we store that content size for all elements in the queue and check that adding a new one is going to fit in the device.

Given that and taking into account that we use to add a margin (100 MB right now) to content size, I think an estimation should be enough for checking if the download fits in the device and can continue or not.

@joeyparrish
Copy link
Member

So now we have three competing ideas:

  1. HEAD requests for all segments, accurate, but very slow and increases load on server
  2. HEAD requests for one video and one audio segment, faster, less accurate
  3. Use average bandwidth from manifest, instant, as accurate as the manifest itself (which varies)

I feel like option 2 would skew badly if we used the first segments of each, since the opening may involve many black or nearly-black frames in a row, which would compress really well. Same for audio, which may involve a lot of silence early on.

If the manifest is relatively accurate, option 3 could actually be the best choice. Apps could make decisions with some safety margin, and we could document recommendations for that that app developers could start with if they like.

This would also allow us to put download size estimates on the tracks themselves so that apps could use that info in selecting tracks.

@OmarPedraza, what do you think?

@OmarPedraza
Copy link

OmarPedraza commented Oct 14, 2020

Hi @joeyparrish,
We tested option 3 by getting the content duration from SMIL file and using the different bandwidths available in the MPD and we get very accurate results.

The content we used for this test has a 2623204 ms duration and selected track bandwidths were 97690 bps for audio and 1225627 bps for video. Combining those number we obtain the following estimations:

Audio: 256260798.76 bits -> 32032599.845 bytes
Video: 3215069648.908 bits -> 401883706.1135 bytes

Then we proceed to download the content using an iOS simulator resulting in files with the following sizes:

Audio: 32033338 bytes
Video: 401883923 bytes

As you can see, it seems like these calculation is very accurate so I think that option 3 should be the winner.

@joeyparrish
Copy link
Member

Okay, sounds like a plan. Thanks for your input!

So, we would add a size estimate to the Track API, for use in selecting tracks:

track.sizeEstimate = track.bandwidth * duration / 8;

Repeating @TheModMaker's API proposal, we would add downloadSizeCallback to decide whether or not to start downloading:

storage.configure('offline.downloadSizeCallback', async (sizeEstimate) => {
  const estimate = await navigator.storage.estimate();
  // Limit to 75% of quota.
  return (estimate.quota - estimate.usage) * 0.75 > sizeEstimate;
});

Where downloadSizeCallback is invoked after trackSelectionCallback and the sizeEstimate parameter is the sum of sizeEstimate values from each selected track.

Should the sample downloadSizeCallback above be used as the default callback, with some fallback for browsers without navigator.storage?

@avelad
Copy link
Collaborator

avelad commented Oct 15, 2020

@joeyparrish
Copy link
Member

The fallback to prefixed methods is really helpful, but the final fallback in that article just returns NaN, so we would still need some behavior in the callback for when a storage estimate is unavailable.

Should we allow by default in those cases? Or reject by default?

@joeyparrish
Copy link
Member

If we fall back to "allow", that will preserve prior behavior on older platforms.

However, if we fall back to "reject", we will force app developers using those platforms to write their own callback. This may prompt them to look for and leverage platform-specific APIs in that callback, or it may cause them to mistakenly assume offline storage is no longer supported on that device. :-/

Thoughts?

@avelad
Copy link
Collaborator

avelad commented Oct 15, 2020

By default, we should allow downloading in case the apis are not available.

TheModMaker pushed a commit that referenced this issue Jan 20, 2021
@joeyparrish joeyparrish modified the milestones: Backlog, v3.1 Feb 11, 2021
@shaka-project shaka-project locked and limited conversation to collaborators Apr 2, 2021
@shaka-bot shaka-bot added the status: archived Archived and locked; will not be updated label Apr 15, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
status: archived Archived and locked; will not be updated type: enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants