视频是一种难以管理的素材资源;流式传输需要大量带宽,并且缓存并不简单。如果视频循环播放(例如在信息亭显示屏上),这些问题会更加严重。例如,如果某公司有数百部设备每天 24 小时循环播放 30 个视频,则可能会很快使网络不堪重负。通过从缓存中提供视频,而不是通过流式传输提供视频,您只需支付一次下载费用,后续播放速度更快,并且可以离线播放。为此,您可以利用浏览器的存储功能,其中 Cache Storage API 和 IndexedDB 最适合存储视频文件。虽然这两种 API 都是不错的选择,但我们将重点介绍 Cache 存储 API,因为它与热门的服务工作器库 Workbox 集成在一起。
缓存来自服务工作线程的视频
由于下载和缓存视频等大型资源可能是一项非常耗时且占用大量处理器的任务,因此您应该在后台(主线程之外)执行此操作。Service worker 特别适合用于分流缓存任务。它们充当网页和网络之间的代理,可以拦截请求并对网络响应应用额外的逻辑,例如缓存策略。
缓存策略有很多种,每种策略都旨在帮助解决不同的使用情形。例如,如需在缓存中提供文件(如果可用),否则回退到网络,您可以编写以下代码。
self.addEventListener('fetch', function (event) { event.respondWith( caches.match(event.request).then(function (response) { return response || fetch(event.request); }), ); });
针对需要不同缓存策略的不同资源类型或网址管理此过程可能非常繁琐且容易出错。Workbox 提供了一组工具,包括路由辅助程序和缓存策略,让您能够以更具声明性和可重用性的方式编写 service worker 代码。
之前的策略称为“先缓存”。如需使用 Workbox 编写相同的内容,您需要添加以下内容:
registerRoute( ({ request }) => request.destination === 'video', new CacheFirst() );
Workbox 为其他缓存策略和常见的 Service Worker 任务提供了类似的配方,包括与 Webpack 和 Rollup 等构建工具的集成。
设置好 Workbox 后,您需要选择何时缓存视频。这里有两种方法:在页面加载时急切地加载,或在请求视频时延迟加载。
急切方法
预缓存是一种在 service worker 安装期间将文件保存到缓存中的技术,这样一来,文件在 service worker 启动后即可使用。Workbox 可以自动为构建过程中可访问的文件设置预缓存。
您可以在服务工作线程中使用以下 Workbox 代码来预缓存文件:
import { addPlugins, precacheAndRoute } from 'workbox-precaching'; import { RangeRequestsPlugin } from 'workbox-range-requests'; addPlugins([new RangeRequestsPlugin()]); precacheAndRoute(self.__WB_MANIFEST);
import(s) - 从相应的 Workbox 模块加载所需的绑定。由于 Service Worker 尚未普遍支持 ESModules,因此您需要将由 Workbox 提供支持的 Service Worker 传递给打包程序,才能在生产环境中正常运行。RangeRequestsPlugin- 使具有Range标头的请求能够通过缓存的响应来满足。这是必需的,因为浏览器通常会为媒体内容使用Range标头。addPlugins- 允许您向每个 Workbox 请求添加 Workbox 插件。precacheAndRoute- 向预缓存列表添加条目,并创建用于处理相应提取请求的路由。__WB_MANIFEST- 一个占位符,Workbox CLI(或构建工具插件)会将其替换为预缓存清单。
将您的服务工作线程传递到 Workbox CLI 或您选择的构建工具中,并配置预缓存的生成方式;workbox-config.js 文件(如下所示)会告知 CLI 应如何呈现您的服务工作线程:
module.exports = { globDirectory: '.', globPatterns: ['**/*.{html,mp4}'], maximumFileSizeToCacheInBytes: 5000000, swSrc: 'sw.js', swDest: 'sw.js', };
globDirectory- 开始搜索预缓存文件的根文件夹globPatterns- 应预缓存的文件模式(“glob”)。maximumFileSizeToCacheInBytes- 可预缓存的文件大小上限(以字节为单位)。swSrc- 将用于生成服务工作线程的文件的位置。swDest- 生成的服务工作线程的目标位置(可以与源文件相同,但请确保每次运行都存在self.__WB_MANIFEST)。
当构建流程运行时,系统会生成新的 Service Worker 版本,并将 self.__WB_MANIFEST 替换为文件列表,每个文件都有一个哈希值来表示其修订版本:
precacheAndRoute([ { revision: '524ac4b453c83f76eb9caeec11854ca5', url: 'ny.mp4', }, ]);
每次运行 build 流程时,系统都会使用当前的一组匹配文件及其当前修订版本哈希重新写入此列表。这样可确保每当添加、移除或更改文件时,服务工作线程都会在下次安装时更新缓存。
延迟方法
如果您在构建时无法获取所有视频,或者只想在需要时缓存视频,则应采用延迟加载方法。这种方法要求缓存和提供服务分开进行;因为在视频播放期间,仅从网络中提取部分内容,所以无法在流式传输时缓存文件。
缓存文件
可以使用 Cache.open() 创建缓存,然后使用 Cache.add() 或 Cache.addAll() 将文件添加到缓存中。如果您的应用收到要缓存的视频的 JSON 列表,则可以按如下方式将其添加到视频缓存中:
// Open video cache const cache = await caches.open('video-cache'); // Fetch list of videos const videos = await (await fetch('/video-list.json')).json(); // Add videos to cache await cache.addAll(videos);
这种方法的优势在于,您可以独立于 service worker 生命周期来控制缓存步骤,甚至可以从其他 web worker 进行控制。缺点是存储管理部分由开发者负责:您需要编写自己的算法来跟踪文件更改、跟踪浏览器中当前缓存的文件,并管理文件更新,以确保只有更改的文件得到更新。
提供缓存的视频文件
然后,可以使用服务工作线程运行时缓存策略(例如 cache first)来提供之前缓存的视频文件:
import { registerRoute } from 'workbox-routing'; import { CacheFirst } from 'workbox-strategies'; import { CacheableResponsePlugin } from 'workbox-cacheable-response'; import { RangeRequestsPlugin } from 'workbox-range-requests'; registerRoute( ({ request }) => request.destination === 'video', new CacheFirst({ cacheName: 'video-cache', plugins: [ new CacheableResponsePlugin({ statuses: [200], }), new RangeRequestsPlugin(), ], }), );
import(s) - 从相应的 workbox 模块加载所需的绑定。registerRoute- 将请求路由到提供响应的函数(缓存策略和插件)。CacheFirst- 一种缓存策略,如果缓存中存在请求,则从缓存中满足请求,否则从网络中提取请求并更新缓存。CacheableResponsePlugin- 用于指示响应可缓存时需要存在的标头。请务必仅包含缓存视频的路由的 200 状态,以避免在视频流式传输时缓存部分内容响应 (206)。RangeRequestsPlugin- 插件,可让包含Range标头的请求通过缓存的响应来完成。这是必需的,因为浏览器通常会为媒体内容使用Range标头。
对于需要进行大量流式传输的应用,优化视频加载是一项重要任务。通过利用浏览器的 Cache Storage API 和 Workbox,您可以轻松完成这项原本很困难的任务,从而节省用户的带宽、降低服务器负载、实现更快的视频播放速度,并让视频即使在离线状态下也能播放。