是否可以使用 Service Worker API 缓存整个 HTML5 视频以供离线使用?

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

我有一个缓存所有静态资源的离线应用程序。目前,仅缓存视频资产的前 15 秒。

下面显示了 installfetch 事件侦听器的基本实现。

服务工作者:

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);
    });
  );
});

并且在index.html

<video controls preload>
  <source src="/videos/one.mp4" type="video/mp4">
</video>

我使用以下步骤在第一页加载时完成了离线视频,而没有先观看整个视频。

  1. 注册一个服务工作者并缓存所有请求。对于这种情况,静态资产只是 '/'。如果您检查服务工作者的 fetch 事件,您会看到后续请求也被缓存。
  2. 使用 fetch API 请求视频作为 blob

使用 fetch 以 blob 形式请求视频的示例

const videoRequest = fetch('/path/to/video.mp4').then(response => response.blob());
videoRequest.then(blob => {
  ...
});
  1. 使用IndexedDBAPI存储blob。 (使用 IndexedDB 而不是 LocalStorage 以避免在存储时阻塞主线程。)

就是这样!现在,当处于离线模式时,service worker 将拦截请求并从缓存中为 htmlblob 提供服务。

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>

服务工作者

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)
        })
      );
    })
  );
});

资源: