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

Downloading videos using Background Fetch API #169

Merged
merged 9 commits into from
Dec 14, 2021

Conversation

dero
Copy link
Collaborator

@dero dero commented Nov 19, 2021

Short Screencast

kino-background-fetch-api.mp4

Summary

This PR adds support for downloading single videos and streamed videos using Background Fetch API.

We're adding an offline version of MPEG DASH manifests to all streamed videos. This offline manifest only contains one selected video and audio representation (HD H.264 MP4), which allows us to only download this one representation for offline playback.

We've been doing this on-the-fly in the past, but with the introduction of Background Fetch API, it arguably make more sense to move towards using the more explicit separate offline manifests, because it would bring a larger amount of complexity to the codebase if we tried to manipulate DASH manifest data on the fly in the service worker.

  • The DOMParser object is not available in the worker context, meaning that parsing the manifest file would have to be done by an external library like xmldom. (Extra bundle weight, one more thing to maintain and rely on.)
  • We can't instantiate a video element or use its canPlayType method statically from the worker context. That means we can't test which of the representations in the manifest are actually playable on the current device and in the current browser. We would need to build a cache layer in IDB in order to figure that out ahead of time and cache it for later when SW is invoked with the downloaded files.

We can avoid both issues by uploading the offline version of each manifest upfront. Plus this approach allows us to simplify the code quite a bit.

The main downside, I think, is that users will not be able to select their preferred download resolution with static offline manifests in place. Note: This is not a regression, we never had a setting for download quality. But this change does not allow for such a setting should we decide to implement it in the future.

Edit: Thinking about this a bit more, maybe it would be possible to leverage the Media Capabilities API to detect support and gain playback performance estimates for individual resources listed in DASH manifests – the API should be available from worker threads.

We might decide to pick this back up later.

Questions

  • Should the "Download videos in the background" setting be enabled by default?
  • Do we create a new video post for "Background Fetch API"?

Fixes #25

@dero dero requested a review from derekherman November 19, 2021 12:33
@dero dero self-assigned this Nov 19, 2021
Copy link
Member

@beaufortfrancois beaufortfrancois left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's my first pass.
Thanks @dero for working on it!

this.id,
urls,
{
title: videoData.title,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

videoData.title may end up being confusing. See video at http://localhost:5000/multiple-sources/ for instance.
Shall we have something like Downloading "${videoData.title}" video? (Source)

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 7979e32.


// eslint-disable-next-line compat/compat
navigator.serviceWorker.ready.then(async (swReg) => {
this.maybeAbort(swReg);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you'd want to await there before or is this intentional?

Suggested change
this.maybeAbort(swReg);
await this.maybeAbort(swReg);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 7979e32.

src/js/sw/sw.js Outdated
@@ -217,3 +220,53 @@ const fetchHandler = async (event) => {
self.addEventListener('install', precacheAssets);
self.addEventListener('activate', clearOldCaches);
self.addEventListener('fetch', fetchHandler);

/** @type {MessagePort} */
let messageChannelPort;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some quick comment to explain where it comes from may be helpful to readers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 7979e32.

src/js/sw/sw.js Outdated
const bgFetchHandler = async (e) => {
/** @type {BackgroundFetchRegistration} */
const bgFetchRegistration = e.registration;
const bgFetch = new BackgroundFetch();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Shall we move this until we actually need videoId?

const bgFetchHandler = async (e) => {
  /** @type {BackgroundFetchRegistration} */
  const bgFetchRegistration = e.registration;
  const records = await bgFetchRegistration.matchAll();
  const urls = records.map((record) => record.request.url);
  
  if (urls.length === 0)
    return;
  
  const responsePromises = records.map((record) => record.responseReady);
  const responses = await Promise.all(responsePromises);
    
  const bgFetch = new BackgroundFetch();
  bgFetch.fromRegistration(bgFetchRegistration);
...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@beaufortfrancois Good suggestion, thanks! Addressed in 7979e32.

@@ -217,3 +220,53 @@ const fetchHandler = async (event) => {
self.addEventListener('install', precacheAssets);
self.addEventListener('activate', clearOldCaches);
self.addEventListener('fetch', fetchHandler);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we gate all the code added only if (!('BackgroundFetchManager' in self))?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@beaufortfrancois Thank you, addressed in 7979e32.

@beaufortfrancois
Copy link
Member

LGTM. Thanks @dero!

@derekherman derekherman merged commit ba4f9ce into develop Dec 14, 2021
@derekherman derekherman deleted the add/background-fetch branch December 14, 2021 00:55
@Swansoffie02
Copy link

Short Screencast

kino-background-fetch-api.mp4

Summary

This PR adds support for downloading single videos and streamed videos using Background Fetch API.

We're adding an offline version of MPEG DASH manifests to all streamed videos. This offline manifest only contains one selected video and audio representation (HD H.264 MP4), which allows us to only download this one representation for offline playback.

We've been doing this on-the-fly in the past, but with the introduction of Background Fetch API, it arguably make more sense to move towards using the more explicit separate offline manifests, because it would bring a larger amount of complexity to the codebase if we tried to manipulate DASH manifest data on the fly in the service worker.

  • The DOMParser object is not available in the worker context, meaning that parsing the manifest file would have to be done by an external library like xmldom. (Extra bundle weight, one more thing to maintain and rely on.)
  • We can't instantiate a video element or use its canPlayType method statically from the worker context. That means we can't test which of the representations in the manifest are actually playable on the current device and in the current browser. We would need to build a cache layer in IDB in order to figure that out ahead of time and cache it for later when SW is invoked with the downloaded files.

We can avoid both issues by uploading the offline version of each manifest upfront. Plus this approach allows us to simplify the code quite a bit.

The main downside, I think, is that users will not be able to select their preferred download resolution with static offline manifests in place. Note: This is not a regression, we never had a setting for download quality. But this change does not allow for such a setting should we decide to implement it in the future.

Edit: Thinking about this a bit more, maybe it would be possible to leverage the Media Capabilities API to detect support and gain playback performance estimates for individual resources listed in DASH manifests – the API should be available from worker threads.

We might decide to pick this back up later.

Questions

  • Should the "Download videos in the background" setting be enabled by default?
  • Do we create a new video post for "Background Fetch API"?

Fixes #25

@lkraav
Copy link

lkraav commented Dec 30, 2021

Why the revert?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Downloading static video to offline storage via Background Fetch API
5 participants