【问题标题】:Is it possible to cache an entire HTML5 video using the Service Worker API for offline use?是否可以使用 Service Worker API 缓存整个 HTML5 视频以供离线使用?
【发布时间】:2017-01-29 01:54:55
【问题描述】:

我有一个缓存所有静态资源的离线应用程序。目前,仅缓存前 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>

【问题讨论】:

  • 也许您可以将视频转换为 blob 并保存 blob。 this好像有一些相关的技巧
  • 缓存 API 服务工作者使用stores request/response pairs。所以我怀疑是否有任何文件类型限制。 Quote:...a storage mechanism for Request / Response object pairs that are cached...
  • 事实上,我已经对其进行了测试,并且效果很好,正如预期的那样。 You can check it here
  • @UmurKaragöz 谢谢,我也这样做了,而且效果很好。 (我刚刚发布了一个包含代码的答案)

标签: javascript html browser-cache service-worker


【解决方案1】:

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

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

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

const videoRequest = fetch('/path/to/video.mp4').then(response => response.blob());
videoRequest.then(blob => {
  ...
});
  1. 使用IndexedDB API 来存储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)
        })
      );
    })
  );
});

资源:

【讨论】:

  • 嗨@Raphael Rafatpanah,我正在尝试为多个视频执行此操作,有没有办法实现这一点,基本上我想在 indexeddb 中保存总共 5 个视频并能够列出视频在浏览器中,以便我可以在离线时单击并播放它们。
  • 只要设备有足够的磁盘空间,它可能是可行的。您可能会在 iOS 上遇到问题,因为我认为与 Chrome 相比,存储限制更严格。下面是一个帮助您入门的示例:github.com/persianturtle/offline-first-example
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-08
  • 1970-01-01
  • 1970-01-01
  • 2012-11-12
  • 1970-01-01
  • 2019-03-14
  • 1970-01-01
相关资源
最近更新 更多