Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to cache an entire HTML5 video using the Service Worker API for offline use?

Tags:

I have an offline app that caches all static resources. Currently, only the first 15 seconds of video assets are cached.

Below shows basic implementations of the install and fetch event listeners.

Service Worker:

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open('v1').then(cache => {
      return cache.addAll([
        '/',
        '/videos/one.mp4',
        '/videos/two.mp4'
      ]);
    })
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      if (response) return response;
      return fetch(event.request);
    });
  );
});

And in index.html

<video controls preload>
  <source src="/videos/one.mp4" type="video/mp4">
</video>
like image 230
Raphael Rafatpanah Avatar asked Jan 29 '17 01:01

Raphael Rafatpanah


People also ask

Can service workers access cache?

Using a Service worker you can easily set an app up to use cached assets first, thus providing a default experience even when offline, before then getting more data from the network (commonly known as Offline First).

Can HTML be cached?

Implementing user personalization in scripts allows caching of the page's HTML. Then the scripts can modify the page after loading asynchronously. Beyond using JavaScript for personalization, The Gap is caching HTML.

How do I cache images with a service worker?

I'd recommend following the approach outlined in this "Service Worker Caching Strategies Based on Request Types" article, and use request. destination inside of your fetch handler to figure out which requests are going to be used for images. self. addEventListener('fetch', (event) => { if (event.

What is service worker cache?

A service worker intercepts network-type HTTP requests and uses a caching strategy to determine what resources should be returned to the browser.


1 Answers

I used the following steps to accomplish offline video on the first page load without first watching the entire video(s).

  1. Register a service worker and cache all requests. Static assets are just '/' for this case. If you inspect the service worker's fetch event, you'll see that subsequent requests are also cached.
  2. Use the fetch API to request a video as a blob.

Example using fetch to request a video as a blob

const videoRequest = fetch('/path/to/video.mp4').then(response => response.blob());
videoRequest.then(blob => {
  ...
});
  1. Use the IndexedDB API to store the blob. (Use IndexedDB instead of LocalStorage to avoid blocking the main thread while storing.)

That's it! Now when in offline mode, the service worker will intercept requests and serve both the html and blob from cache.

index.html

<!DOCTYPE html>
<html>
<head>
  <title>Test</title>
</head>
<body>

  <h1>Service Worker Test</h1>

  <p>Try reloading the page without an Internet connection.</p>

  <video controls></video>

  <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker.register('/service-worker.js').then(registration => {
          console.log('ServiceWorker registration successful with scope: ', registration.scope);
        }).catch(error => {
          console.log('ServiceWorker registration failed: ', error);
        });
      });
    } else {
      alert('serviceWorker is not in navigator');
    }
  </script>

  <script>
    const videos = {
      one: document.querySelector('video')
    };

    const videoRequest = fetch('/path/to/video.mp4').then(response => response.blob());
    videoRequest.then(blob => {
      const request = indexedDB.open('databaseNameHere', 1);

      request.onsuccess = event => {
        const db = event.target.result;

        const transaction = db.transaction(['videos']);
        const objectStore = transaction.objectStore('videos');

        const test = objectStore.get('test');

        test.onerror = event => {
          console.log('error');
        };

        test.onsuccess = event => {
          videos.one.src = window.URL.createObjectURL(test.result.blob);
        };
      }

      request.onupgradeneeded = event => {
        const db = event.target.result;
        const objectStore = db.createObjectStore('videos', { keyPath: 'name' });

        objectStore.transaction.oncomplete = event => {
          const videoObjectStore = db.transaction('videos', 'readwrite').objectStore('videos');
          videoObjectStore.add({name: 'test', blob: blob});
        };
      }
    });
  </script>
</body>
</html>

Service Worker

const latest = {
  cache: 'some-cache-name/v1'
};

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(latest.cache).then(cache => {
      return cache.addAll([
        '/'
      ]);
    })
  );
});

self.addEventListener('fetch', event => {
  // exclude requests that start with chrome-extension://
  if (event.request.url.startsWith('chrome-extension://')) return;
  event.respondWith(
    caches.open(latest.cache).then(cache => {
      return cache.match(event.request).then(response => {
        var fetchPromise = fetch(event.request).then(networkResponse => {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        })
        return response || fetchPromise;
      })
    })
  );
});

self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.filter(cacheName => {
          if (cacheName === latest.cache) {
            return false;
          }

          return true;
        }).map(cacheName => {
          return caches.delete(cacheName)
        })
      );
    })
  );
});

Resources:

  • Excellent course on Service Workers & IndexedDB
  • Using fetch for requestType blob
  • IndexedDB
  • Example using IndexedDB to store a blob
like image 128
Raphael Rafatpanah Avatar answered Oct 10 '22 03:10

Raphael Rafatpanah