如何主動預先載入資源,加快媒體播放速度。
加快播放速度意味著有更多人在觀看影片或聆聽 音訊。這是已知的錯誤。本文將介紹 可主動提高音訊和影片播放速度的技巧 根據您的用途預先載入資源。
我會說明預先載入媒體檔案的三種方法,首先從專業人員開始 和缺點
太棒了... | 不過... | |
---|---|---|
影片預先載入屬性 | 用於網路伺服器託管的專屬檔案。 | 瀏覽器可能會完全忽略這個屬性。 |
資源擷取作業會在 HTML 文件完全載入後開始, 剖析。 | ||
媒體來源擴充功能 (MSE) 會忽略媒體元素的 preload 屬性,因為應用程式是由
向 MSE 提供媒體內容
|
||
預先載入連結 |
強制瀏覽器在不封鎖影片的情況下發出影片資源請求
文件的 onload 事件。
|
HTTP 範圍要求不相容。 |
與 MSE 和檔案區隔相容。 | 擷取完整資源時,僅適用於小型媒體檔案 (小於 5 MB)。 | |
手動緩衝處理 | 完全控制 | 複雜錯誤處理是網站負責的網站。 |
影片預先載入屬性
如果影片來源是網路伺服器代管的專屬檔案,建議你
使用影片 preload
屬性向瀏覽器提供提示,方法為
預先載入的資訊或內容。換句話說,媒體來源額外資訊
(MSE) 與 preload
不相容。
只有在初始 HTML 文件發生後,系統才會開始擷取資源
已完全載入並剖析 (例如已觸發 DOMContentLoaded
事件)
而截然不同的 load
事件會在資源觸發時
上次擷取到的內容
將 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;
const 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;
const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
console.log(`${bufferedSeconds} seconds of video are ready to play.`);
});
</script>
不過,您還是需要注意一些事項。這只是提示
會忽略 preload
屬性。截至本文撰寫時,
已套用至 Chrome:
- 啟用 Data Saver 時,Chrome 會強制
preload
值:none
。 - 在 Android 4.3 中,Chrome 會強制
preload
值設為none
,因為 Android 錯誤。 - 使用行動網路連線 (2G、3G 和 4G) 時,Chrome 會強制
preload
的值為metadata
。
提示
如果您的網站包含許多位於相同網域的影片資源,
建議您將 preload
值設為 metadata
或定義 poster
屬性並將 preload
設為 none
。如此一來
連至相同網域的 HTTP 連線數量上限 (根據
HTTP 1.1 規格),可停止載入資源。請注意,這也可能
加快網頁速度 (如果影片不屬於核心使用者體驗)。
預先載入連結
如其他文章已介紹,連結預先載入是一種宣告式擷取作業,
可讓您強制瀏覽器在沒有資源的情況下
也會封鎖 load
事件及網頁下載期間。資源
透過 <link rel="preload">
載入的物件會儲存在本機瀏覽器中
在 DOM、JavaScript、
或 CSS
預先載入與預先擷取不同之處,在於前者著重於目前的導覽, 根據類型 (指令碼、樣式、字型、 影片、音訊等)。應用於為目前的 工作階段。
預先載入完整影片
以下說明如何在網站上預先載入完整影片, 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>
由於預先載入的資源會由
在本例中,as
預先載入連結值為 video
。如果是音訊
元素,應該是 as="audio"
。
預先載入第一個片段
以下範例說明如何使用 <link
rel="preload">
預先載入影片的第一個片段,並搭配媒體來源擴充功能使用。如果還不熟悉
MSE JavaScript API,請參閱 MSE 基本概念。
為簡單起見,我們假設整部影片已經分割為
file_1.webm
、file_2.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>
支援
您可以使用as
<link rel=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 和服務工作站之前,先來看看
如何使用 MSE 手動緩衝影片緩衝處理。以下範例假設您的網站
伺服器支援 HTTP Range
但與 file
區隔請注意,部分中介軟體程式庫,例如 Google 的 Shaka 程式庫
播放器、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!
const 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>
注意事項
由於您現在已擁有完整的媒體緩衝體驗,建議您 考量裝置的電池電量、「節省數據模式」使用者偏好設定和 網路資訊。
電池感知
將使用者的電池電量納入考量裝置再考慮 關於預先載入影片的資訊這樣可以延長電池電量時 偏低。
停用預先載入功能,或至少預先載入解析度較低的影片 裝置電力耗盡。
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.
}
});
}
偵測「節省數據用量」
使用 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 Worker,因為
Cache API 也可透過 window
物件存取。
擷取並快取
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()
會擷取整個內容
傳回至轉譯器記憶體,這也是為什麼建議您使用
小數值
為了方便參考,我已修改上述範例的部分以儲存 HTTP 範圍 要求傳送至影片友善快取
...
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;
});
播放影片
使用者按下播放按鈕時,我們會擷取影片的第一個片段 可在適用情況下立即開始播放。 否則我們會直接從網路擷取請注意,瀏覽器 而使用者可以選擇清除快取。
如先前所述,我們使用 MSE 將該影片的第一個片段輸入至影片 元素。
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.
});
}
});
}
使用 Service Worker 建立範圍回應
現在,如果您擷取整個影片檔案
Cache API?當瀏覽器傳送 HTTP Range
要求時,您當然不會
需要將整個影片匯入轉譯器記憶體,因為 Cache API 沒有
目前支援 Range
回應。
接下來,我會示範如何攔截這些要求,並傳回自訂的 Range
收到來自 Service Worker 的回應。
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()
重新建立此切割線
只是留下檔案控制代碼
response.arrayBuffer()
會將整個檔案匯入轉譯器記憶體。
我可以透過我的自訂 X-From-Cache
HTTP 標頭判斷這項要求是否
來自快取或網路包括
ShakaPlayer,以便忽略回應時間,
每個網路速度
請參閱官方的 Sample Media 應用程式,特別是
ranged-response.js 檔案,適用於處理 Range
的完整解決方案
要求。