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

22

我有一个离线应用程序,可以缓存所有静态资源。目前,只有视频资产的前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
也许你可以将视频转换为 Blob 并保存 Blob。这里有一些相关技术:https://simpl.info/video/offline/ - danyamachine
缓存API服务工作者使用存储请求/响应对。因此,我怀疑是否存在任何文件类型限制。引用:...一个存储机制,用于缓存请求/响应对象对... - Umur Karagöz
1
事实上,我已经进行了测试,它的表现非常出色,正如预期的那样。您可以在此处检查 - Umur Karagöz
@UmurKaragöz 谢谢,我也试过了,它确实很好用。(我刚刚发布了一个包含代码的答案) - Raphael Rafatpanah
1个回答

22

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

  1. 注册服务工作器并缓存所有请求。 对于这种情况,静态资源只是'/'。如果检查服务工作者的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>

服务工作者(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)
        })
      );
    })
  );
});

资源:


嗨@Raphael Rafatpanah,我正在尝试为多个视频完成此操作,有没有实现这个目标的方法?基本上,我想在indexeddb中保存总共5个视频,并能够在浏览器中列出这些视频,以便我可以在离线时单击并播放它们。 - BLESSING
只要设备有足够的磁盘空间,这可能是可行的。在iOS上可能会遇到问题,因为我相信与Chrome相比,存储限制更严格。这是一个示例,可以帮助你入门:https://github.com/persianturtle/offline-first-example - Raphael Rafatpanah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接