预载视频,可快速播放
作者:François Beaufort | 译:Vicky·Ye
原文地址:
https://developers.google.com/web/fundamentals/media/fast-playback-with-video-preload
在以往的项目中,只要有视频的存在,那么就会是个让人费神的项目。且不说对它的适配兼容问题,只说它的加载问题就能说上半天了。本文作者从视频预加载的各种方法入手,讨论了如何让视频播放速度更快的解决办法。
众所周知,如果你的视频可以更快的播放意味着会有更多的人观看。在本文中,让我们一起通过一些预加载技术来加速视频播放。
注意: 除非另有说明,否则本文也适用于 audio 元素。
视频地址:
https://developers.google.com/web/fundamentals/media/fast-playback-with-video-preload
致谢:版权所有 Blender Foundation | www.blender.org 。
TL; DR
这很棒... | 但... | |
---|---|---|
视频 preload 属性 | 易用于 Web 服务器上托管的唯一文件。 | 浏览器可能完全忽略该属性。 |
HTML 文档完全加载和解析后,资源才开始获取。 | ||
当应用程序使用 MSE 扩展媒体时,MSE会忽略媒体元素上的 preload 属性。 | ||
Link preload | 强制浏览器发出视频资源请求,但不会阻止文档的 onload 事件。 | HTTP Range请求不兼容。 |
兼容 MSE 和文档片断。 | 获取完整资源时,只能是小型媒体文件(< 5MB)。 | |
手动缓冲 | 完全控制 | 复杂的错误需要网页来处理。 |
视频预加载(preload)属性
如果网页中只有一个视频文件,您可能会使用 video 标签的 preload
属性来提示浏览器预加载的信息或内容量。但这意味着 Media Source Extensions(MSE)与 preload
将不兼容。
资源的获取将仅在HTML文档初始加载和解析完成后启动(例如, DOMContentLoaded
事件的触发),而 window.onload
事件则完全不同,它在的资源都加载完成后才被触发。
将 preload
属性设置为 metadata
表示用户不想马上加载视频,但是需要预先获取其元数据(尺寸,轨道列表,时长等)。 请注意,从 Chrome 64 开始, preload
的默认值是 metadata
(以前是 auto
)。
<video id="video" preload="metadata" src="file.mp4" controls></video>
<script>
video.addEventListener('loadedmetadata', function() {
if (video.buffered.length === 0) return;
var bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
console.log(bufferedSeconds + ' seconds of video are ready to play!');
});
</script>
将 preload
属性设置为 auto
表示浏览器将缓存整个视频,无需暂停缓冲,可以支持完整播放。
<video id="video" preload="auto" src="file.mp4" controls></video>
<script>
video.addEventListener('loadedmetadata', function() {
if (video.buffered.length === 0) return;
var bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
console.log(bufferedSeconds + ' seconds of video are ready to play!');
});
</script>
由于 preload
属性只是一个提示,浏览器可能会完全忽略 preload
属性。写到这,请注意以下Chrome中的一些应用规则:
• 启用 Data Saver后 ,Chrome 会强制设置 preload
值为 none
。
• 在 Android 4.3中,由于 Android 的 bug,Chrome 会强制设置 preload
值为 none
。
• 在蜂窝连接(2G,3G和4G)时,Chrome 会强制设置 preload
值为 metadata
。
提示
如果您的网站在同一个域中包含多个视频资源,我建议您将 preload
值设置为 metadata
或定义 poster
属性并将 preload
设置为 none
。 这样,可以避免在同一域名中 HTTP 连接数达到最大时导致资源加载挂起(根据 HTTP 1.1规范6)。 请注意,如果视频不属于您的核心用户体验,这样做也会提高网页加载速度。
Link preload
正如其他文章所述 ,link preload 是一种声明性资源获取,允许您强制浏览器在不阻止 window.onload
事件和页面渲染的情况下发出资源请求。 通过 <linkrel="preload">
预加载的资源在DOM、JavaScript或CSS没有明确引用之前,被存储在本地浏览器中。
预加载 preload 与预读取 prefetch 的不同之处在于它侧重于当前页面的资源预加载,并根据它们的优先级(脚本,样式,字体,视频,音频等)获取资源。它通常用于为当前会话预热浏览器缓存。
预加载完整视频
以下示例讲述了如何在您的网站上预加载完整视频,以便当您的 JavaScript 请求获取视频内容时,它会从缓存中读取,因为视频资源可能已被浏览器缓存。 如果预加载请求尚未完成,则将进行常规网络获取。
<link rel="preload" as="video" href="https://cdn.com/small-file.mp4">
<video id="video" controls></video>
<script>
// Later on, after some condition has been met, set video source to the
// preloaded video URL.
video.src = 'https://cdn.com/small-file.mp4';
video.play().then(_ => {
// If preloaded video URL was already cached, playback started immediately.
});
</script>
注意: 我建议仅将其用于小型媒体文件(<5MB)。
由于link预加载的 as
属性值为 video
,所以预加载资源将由例子中的视频元素使用。如果它是一个音频元素,它将是 as="audio"
。
预加载第一个片段
下面的示例显示了如何用 <linkrel="preload">
来预加载视频的第一段内容,并将其与 Media Source Extensions 一起使用。 如果您不熟悉 MSE Javascript API ,请参阅 MSE 基础知识。
为简单起见,我们假设整个视频已被拆分为若干较小的文件,如“file1.webm”,“file2.webm”,“file_3.webm”等。
<link rel="preload" as="fetch" href="https://cdn.com/file_1.webm">
<video id="video" controls></video>
<script>
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });
function sourceOpen() {
URL.revokeObjectURL(video.src);
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');
// If video is preloaded already, fetch will return immediately a response
// from the browser cache (memory cache). Otherwise, it will perform a
// regular network fetch.
fetch('https://cdn.com/file_1.webm')
.then(response => response.arrayBuffer())
.then(data => {
// Append the data into the new sourceBuffer.
sourceBuffer.appendBuffer(data);
// TODO: Fetch file_2.webm when user starts playing video.
})
.catch(error => {
// TODO: Show "Video is not available" message to user.
});
}
</script>
警告: 对于跨域问题,请确保正确设置了CORS请求头。 由于我们无法使用 fetch(videoFileUrl, { mode: 'no-cors' }) 检索未知响应所创建的缓存数组,因此我们无法将其提供给视频或音频元素。
支持
由于 link preload 尚未在每个浏览器中得到支持。您可以使用下面的代码检测其可用性,以调整您的展现效果。
function preloadFullVideoSupported() {
const link = document.createElement('link');
link.as = 'video';
return (link.as === 'video');
}
function preloadFirstSegmentSupported() {
const link = document.createElement('link');
link.as = 'fetch';
return (link.as === 'fetch');
}
手动缓冲
在我们深入了解 Cache API 和 Service Worker 之前,让我们看看如何使用 MSE 手动缓冲视频。 下面的例子模拟了支持 HTTP Range 请求的 Web 服务器,但这种方法与缓存文件片段非常相似。 请注意,一些插件库如 Google 的 Shaka Player ,JW Player 和 Video.js 都可以为您处理此问题。
<video id="video" controls></video>
<script>
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });
function sourceOpen() {
URL.revokeObjectURL(video.src);
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');
// Fetch beginning of the video by setting the Range HTTP request header.
fetch('file.webm', { headers: { range: 'bytes=0-567139' } })
.then(response => response.arrayBuffer())
.then(data => {
sourceBuffer.appendBuffer(data);
sourceBuffer.addEventListener('updateend', updateEnd, { once: true });
});
}
function updateEnd() {
// Video is now ready to play!
var bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
console.log(bufferedSeconds + ' seconds of video are ready to play!');
// Fetch the next segment of video when user starts playing the video.
video.addEventListener('playing', fetchNextSegment, { once: true });
}
function fetchNextSegment() {
fetch('file.webm', { headers: { range: 'bytes=567140-1196488' } })
.then(response => response.arrayBuffer())
.then(data => {
const sourceBuffer = mediaSource.sourceBuffers[0];
sourceBuffer.appendBuffer(data);
// TODO: Fetch further segment and append it.
});
}
</script>
注意事项
由于您现在采用手动控制缓冲整个媒体,我建议您在预加载时考虑下使用设备的电池电量、用户的“ Data-Saver 模式”首选项和网络信息等因素。
电池意识
在考虑预加载视频之前,请考虑用户设备的电池电量。 这将在电量较低时保持电池寿命。
当设备电池电量快耗尽时,禁用预加载或预加载分辨率较低的视频。
if ('getBattery' in navigator) {
navigator.getBattery()
.then(battery => {
// If battery is charging or battery level is high enough
if (battery.charging || battery.level > 0.15) {
// TODO: Preload the first segment of a video.
}
});
}
检测“Data-Saver”
使用 Save-Data
客户端提示请求头为在浏览器中启动“流量节省”模式的用户提供快速轻便的应用程序。通过识别此请求头,您的应用程序可以通过自定义限制成本和限制性能的方法为用户提供更好的用户体验。
通过阅读 使用 Save-Data 提供快速和轻量级应用程序 全文,了解更多信息 。
基于网络信息的智能加载
您可以在预加载之前检查 navigator.connection.type
。当它设置为 cellular
时,您可以阻止预加载并提示用户他们的移动网络运营商可能正在收费,并且只自动回放以前缓存的内容。
if ('connection' in navigator) {
if (navigator.connection.type == 'cellular') {
// TODO: Prompt user before preloading video
} else {
// TODO: Preload the first segment of a video.
}
}
查看 网络信息示例 了解如何对网络更改做出反应。
预缓存多个第一片段
如果我们想在不知道用户最终将选择哪一个视频进行播放的情况下,预先加载一些视频,那该如何操作呢?假设用户在浏览一个具有10个视频的网页,我们有足够的内存来缓存每个视频文件,但我们肯定不会去创建10个隐藏的 video 标签和10个 MediaSource
对象以及它们的数据。
下面的两个部分示例向您展示了如何使用功能强大且易用的 Cache API 来预缓存多个视频的第一个片段。需要注意的是,使用 IndexedDB 也可以实现类似的功能。这里我们没有使用 Service Workers,是因为 Cache API 也可以从 Window 对象中访问。
Fetch和Cache
const videoFileUrls = [
'bat_video_file_1.webm',
'cow_video_file_1.webm',
'dog_video_file_1.webm',
'fox_video_file_1.webm',
];
// Let's create a video pre-cache and store all first segments of videos inside.
window.caches.open('video-pre-cache')
.then(cache => Promise.all(videoFileUrls.map(videoFileUrl => fetchAndCache(videoFileUrl, cache))));
function fetchAndCache(videoFileUrl, cache) {
// Check first if video is in the cache.
return cache.match(videoFileUrl)
.then(cacheResponse => {
// Let's return cached response if video is already in the cache.
if (cacheResponse) {
return cacheResponse;
}
// Otherwise, fetch the video from the network.
return fetch(videoFileUrl)
.then(networkResponse => {
// Add the response to the cache and return network response in parallel.
cache.put(videoFileUrl, networkResponse.clone());
return networkResponse;
});
});
}
请注意,如果我要使用 HTTP Range 请求,则必须手动重新创建 Response
对象,因为 Cache API 尚不支持 Range 请求。 还要注意的是,在调用 networkResponse.arrayBuffer()
时会立即响应,并将获取到的全部内容存入渲染器,这也是为什么建议您使用 Range 的原因。
以下代码中我修改了上面例子的部分代码,将 HTTP Range 请求的视频保存到预缓存中,供大家作为参考。
...
return fetch(videoFileUrl, { headers: { range: 'bytes=0-567139' } })
.then(networkResponse => networkResponse.arrayBuffer())
.then(data => {
const response = new Response(data);
// Add the response to the cache and return network response in parallel.
cache.put(videoFileUrl, response.clone());
return response;
});
播放视频
在 Cache API 中我们缓存了视频的第一片段,当用户点击播放按钮时,它可以立即开始播放。否则,我们需要从网络中获取它。需要注意的是,浏览器和用户可能会清除缓存 。
如前所述,我们使用 MSE 将视频的第一片段传给 video 元素。
function onPlayButtonClick(videoFileUrl) {
video.load(); // Used to be able to play video later.
window.caches.open('video-pre-cache')
.then(cache => fetchAndCache(videoFileUrl, cache)) // Defined above.
.then(response => response.arrayBuffer())
.then(data => {
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });
function sourceOpen() {
URL.revokeObjectURL(video.src);
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');
sourceBuffer.appendBuffer(data);
video.play().then(_ => {
// TODO: Fetch the rest of the video when user starts playing video.
});
}
});
}
警告: 对于跨域问题,请确保正确设置了 CORS 请求头。 由于我们无法使用 fetch(videoFileUrl, { mode: 'no-cors' }) 检索未知响应所创建的缓存数组,因此我们无法将其提供给视频或音频元素。
使用Service Worker创建Range请求
现在,如果您已获取整个视频文件并将其保存在 Cache API中。 当浏览器发送 HTTP Range 请求时,您肯定不希望将整个视频存入渲染器内存,因为 Cache API 尚不支持 Range 请求。
那么,让我演示下如何拦截这些请求并从 service worker 返回自定义的 Range 请求头。
addEventListener('fetch', event => {
event.respondWith(loadFromCacheOrFetch(event.request));
});
function loadFromCacheOrFetch(request) {
// Search through all available caches for this request.
return caches.match(request)
.then(response => {
// Fetch from network if it's not already in the cache.
if (!response) {
return fetch(request);
// Note that we may want to add the response to the cache and return
// network response in parallel as well.
}
// Browser sends a HTTP Range request. Let's provide one reconstructed
// manually from the cache.
if (request.headers.has('range')) {
return response.blob()
.then(data => {
// Get start position from Range request header.
const pos = Number(/^bytes\=(\d+)\-/g.exec(request.headers.get('range'))[1]);
const options = {
status: 206,
statusText: 'Partial Content',
headers: response.headers
}
const slicedResponse = new Response(data.slice(pos), options);
slicedResponse.setHeaders('Content-Range': 'bytes ' + pos + '-' +
(data.size - 1) + '/' + data.size);
slicedResponse.setHeaders('X-From-Cache': 'true');
return slicedResponse;
});
}
return response;
}
}
重点注意下例子中我用 response.blob()
重新创建了这个切片响应,是因为这可以让我操作( 在Chrome中 )通过 response.arrayBuffer()
对象存入内存的文件。
我自定义的 X-From-Cache
HTTP 响应头可用于判断此请求是来自缓存还是来自网络。也可以用于像 ShakaPlayer 等播放器用它来忽略响应时间作为网络速度的指标。
视频地址:
https://developers.google.com/web/fundamentals/media/fast-playback-with-video-preload
这里有一个官方媒体应用程序的视频例子 ,特别是它的ranged-response.js文件,讲解了如何处理Range请求的完整解决方案。
索引列表
1、作者弗朗索瓦· 博福特 :
https://developers.google.com/web/resources/contributors/beaufortfrancois
2、视频演示:http://www.blender.org/
3、预加载的信息或内容量:
https://developers.google.com/web/fundamentals/media/video#preload
4、Media Source Extensions(MSE):
https://developers.google.com/web/fundamentals/media/mse/basics
5、Chrome 64:
https://developers.google.com/web/updates/2017/12/chrome-63-64-media-updates#media-preload-defaults-metadata
6、流量节省程序Data Saver:
https://support.google.com/chrome/answer/2392284
7、Android bug(安卓4.3系统错误):
https://bugs.chromium.org/p/chromium/issues/detail?id=612909
8、Link preload:
https://developers.google.com/web/updates/2016/03/link-rel-preload
9、Cache API:
https://developer.mozilla.org/en-US/docs/Web/API/Cache
10、Delivering Fast and Light Applications with Save-Data:
https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/save-data/
11、Network Information API Sample:https://googlechrome.github.io/samples/network-information/
12、Sample Media App :
https://github.com/GoogleChromeLabs/sample-media-pwa
13、ranged-response.js :
https://github.com/GoogleChromeLabs/sample-media-pwa/blob/master/src/client/scripts/ranged-response.js
上一篇: 日本本土 IP:运行原神的神奇工具
下一篇: 社会距离--个人空间(1)
推荐阅读
-
【摩尔线程+Colossal-AI强强联手】MusaBert登上CLUE榜单TOP10:技术细节揭秘 - 技术实力:摩尔线程凭借"软硬兼备"的技术底蕴,让MusaBert得以从底层优化到顶层。其内置多功能GPU配备AI加速和并行计算模块,提供了全面的AI与科学计算支持,为AI推理和低资源条件下的大模型训练等场景带来了高效、经济且环保的算力。 - 算法层面亮点:依托Colossal-AI AI大模型开发系统,MusaBert在训练过程中展现出了卓越的并行性能与易用性,特别在预处理阶段对DataLoader进行了优化,适应低资源环境高效处理海量数据。同时,通过精细的建模优化、领域内数据增强以及Adan优化器等手段,挖掘和展示了预训练语言模型出色的语义理解潜力。基于MusaBert,摩尔线程自主研发的MusaSim通过对比学习方法微调,结合百万对标注数据,MusaSim在多个任务如语义相似度、意图识别和情绪分析中均表现出色。 - 数据资源丰富:MusaBert除了自家高质量语义相似数据外,还融合了悟道开源200GB数据、CLUE社区80GB数据,以及浪潮公司提供的1TB高质量数据,保证模型即便在较小规模下仍具备良好性能。 当前,MusaBert已成功应用于摩尔线程的智能客服与数字人项目,并广泛服务于语义相似度、情绪识别、阅读理解与声韵识别等领域。为了降低大模型开发和应用难度,MusaBert及其相关高质量模型代码已在Colossal-AI仓库开源,可快速训练优质中文BERT模型。同时,通过摩尔线程与潞晨科技的深度合作,仅需一张多功能GPU单卡便能高效训练MusaBert或更大规模的GPT2模型,显著降低预训练成本,进一步推动双方在低资源大模型训练领域的共享目标。 MusaBert荣登CLUE榜单TOP10,象征着摩尔线程与潞晨科技联合研发团队在中文预训练研究领域的领先地位。展望未来,双方将携手探索更大规模的自然语言模型研究,充分运用上游数据资源,产出更为强大的模型并开源。持续强化在摩尔线程多功能GPU上的大模型训练能力,特别是在消费级显卡等低资源环境下,致力于降低使用大模型训练的门槛与成本,推动人工智能更加普惠。而潞晨科技作为重要合作伙伴,将继续发挥关键作用。
-
预载视频,可快速播放
-
硬件:华硕 AC68U 路由器设置 有线网桥(AP)模式 - 我阅读了 APP(普通视频播放器,我使用的是 OplayerHD Lite)的文档说明,WiFi 文件传输必须要求 IP 地址的前三位数字相同。所以在目前的连接情况下,无法传输。 当然,我可以用 iPad 连接电信公司光猫的 WiFi 信号,但在卧室内,这个信号非常弱。如果我想快速传输,就必须把 iPad 放在电信公司光猫附近,然后自己到卧室的电脑上操作。这也不是什么好体验!
-
人工智能目标分割功能可实现快速视频抠像,无需绿色屏幕
-
如何在投放视频广告时快速兼容第三方播放器?
-
实现在聊天或视频通话中抓拍图片并标记的功能需求:快速截图并为图片添加标记,如画框、线条(可删除)
-
如何快速提升网页视频在浏览器中的播放速度:通过直接改动代码来实现
-
网页视频快速播放功能
-
快速提升网页视频播放速度的几种方法
-
带你体验神奇新工具:Chimee - 一款可自定义的H5视频播放器解决方案