Kaynakları etkin bir şekilde önceden yükleyerek medya oynatmanızı hızlandırma
Oynatma işleminin daha hızlı başlaması, videonuzu veya sesinizi daha fazla kişinin izlemesi anlamına gelir. Bu bilinen bir gerçektir. Bu makalede, kullanım alanınıza bağlı olarak kaynakları etkin bir şekilde önceden yükleyerek ses ve video oynatmanızı hızlandırmak için kullanabileceğiniz teknikleri inceleyeceğiz.
Medya dosyalarını önceden yüklemenin üç yöntemini açıklayacağım. Bunlar, bunların artıları ve eksilerinden başlıyor.
Harika... | Ama... | |
---|---|---|
Video önceden yükleme özelliği | Web sunucusunda barındırılan benzersiz bir dosya için kullanımı kolaydır. | Tarayıcılar bu özelliği tamamen yok sayabilir. |
Kaynak getirme, HTML belgesi tamamen yüklendiğinde ve ayrıştırıldığında başlar. | ||
Uygulama, MSE'ye medya sağlamaktan sorumlu olduğundan Medya Kaynağı Uzantıları (MSE), medya öğelerinde preload özelliğini yoksayar.
|
||
Bağlantı önceden yükleme |
Tarayıcıyı, dokümanın onload etkinliğini engellemeden video kaynağı isteğinde bulunmaya zorlar.
|
HTTP Aralığı istekleri uyumlu değildir. |
MSE ve dosya segmentleriyle uyumludur. | Tüm kaynaklar getirilirken yalnızca küçük medya dosyaları (<5 MB) için kullanılmalıdır. | |
Manuel arabelleğe alma | Tam denetim | Karmaşık hataları ele alma, web sitesinin sorumluluğundadır. |
Video önceden yükleme özelliği
Video kaynağı, web sunucusunda barındırılan benzersiz bir dosyaysa tarayıcıya ne kadar bilgi veya içeriğin önceden yükleneceği konusunda ipucu vermek için video preload
özelliğini kullanabilirsiniz. Bu, Medya Kaynağı Uzantıları (MSE) öğesinin preload
ile uyumlu olmadığı anlamına gelir.
Kaynak getirme, yalnızca ilk HTML belgesi tamamen yüklendiğinde ve ayrıştırıldığında başlar (ör. DOMContentLoaded
etkinliği tetiklendiğinde), kaynak gerçekten getirildiğinde ise çok farklı load
etkinliği tetiklenir.
preload
özelliğinin metadata
olarak ayarlanması, kullanıcının videoya ihtiyaç duymasının beklenmediğini ancak meta verilerin (boyutlar, parça listesi, süre vb.) alınmasının istendiğini belirtir. Chrome 64'ten itibaren preload
için varsayılan değerin metadata
olduğunu unutmayın. (Daha önce auto
idi).
<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
özelliğinin auto
olarak ayarlanması, tarayıcının daha fazla arabelleğe alma işlemi için durdurma gerektirmeden oynatmanın tamamlanmasını sağlayacak kadar veriyi önbelleğe alabileceğini belirtir.
<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>
Yine de dikkat edilmesi gereken bazı noktalar var. Bu sadece bir ipucu olduğundan tarayıcı preload
özelliğini tamamen yok sayabilir. Bu metnin yazıldığı sırada Chrome'da uygulanan
bazı kurallar aşağıda belirtilmiştir:
- Veri Tasarrufu etkinleştirildiğinde Chrome
preload
değerininone
yapmaya zorlar. - Android 4.3'te Chrome, bir Android Hatası nedeniyle
preload
değerininone
olmaya zorlar. - Hücresel bağlantıda (2G, 3G ve 4G) Chrome,
preload
değerinimetadata
yapmaya zorlar.
İpuçları
Web siteniz aynı alanda çok sayıda video kaynağı içeriyorsa preload
değerini metadata
olarak ayarlamanızı veya poster
özelliğini tanımlayıp preload
değerini none
olarak ayarlamanızı öneririz. Bu şekilde, aynı alan için maksimum sayıda HTTP bağlantısına (HTTP 1.1 spesifikasyonuna göre 6) ulaşmaktan kaçınmış olursunuz. Bu da kaynakların yüklenmesini geciktirebilir. Videolar temel kullanıcı deneyiminizin bir parçası değilse bu işlemin sayfa hızını da artırabileceğini unutmayın.
Bağlantı önceden yükleme
Diğer makalelerde bahsedildiği gibi, link preload (bağlantı önceden yükleme) özelliği, tarayıcının load
etkinliğini engellemeden ve sayfa indirilirken bir kaynak için istekte bulunmasını zorunlu kılmanıza olanak tanıyan bildirim temelli bir getirme işlemidir. <link rel="preload">
aracılığıyla yüklenen kaynaklar tarayıcıda yerel olarak depolanır ve DOM, JavaScript veya CSS'de açıkça başvuruda bulunulana kadar etkili bir şekilde etkisizdir.
Önceden yükleme, geçerli gezinmeye odaklanması ve kaynakları türlerine (komut dosyası, stil, yazı tipi, video, ses vb.) göre öncelikli olarak getirmesi açısından önceden getirme işleminden farklıdır. Tarayıcı önbelleğini geçerli oturumlar için ısıtmak üzere kullanılmalıdır.
Tam videoyu önceden yükle
JavaScript'iniz video içeriği getirmeyi istediğinde, kaynak tarayıcı tarafından önbelleğe alınmış olabileceğinden video önbellekten okunur. Bunun için web sitenizdeki tam uzunluktaki bir videoyu nasıl önceden yükleyeceğinizi buradan öğrenebilirsiniz. Önceden yükleme isteği henüz tamamlanmamışsa normal bir ağ getirme işlemi gerçekleşir.
<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>
Önceden yüklenmiş kaynak örnekteki bir video öğesi tarafından kullanılacağından as
önceden yükleme bağlantısı değeri video
olur. Bu bir ses öğesi olsaydı as="audio"
olurdu.
İlk segmenti önceden yükle
Aşağıdaki örnekte, bir videonun ilk segmentinin <link
rel="preload">
ile nasıl önceden yükleneceği ve bunun Medya Kaynağı Uzantıları ile nasıl kullanılacağı gösterilmektedir. MSE JavaScript API hakkında bilginiz yoksa MSE ile ilgili temel bilgiler bölümünü inceleyin.
Kolaylık olması açısından, videonun tamamının file_1.webm
, file_2.webm
, file_3.webm
gibi daha küçük
dosyalara bölündüğünü varsayalım.
<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>
Destek
Aşağıdaki snippet'leri kullanarak <link rel=preload>
için çeşitli as
türlerinin desteklendiğini tespit edebilirsiniz:
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');
}
Manuel arabelleğe alma
Cache API'yi ve hizmet çalışanlarını incelemeden önce, bir videonun MSE ile manuel olarak nasıl arabelleğe alınacağına bakalım. Aşağıdaki örnekte, web sunucunuzun HTTP Range
isteklerini desteklediği varsayılmıştır, ancak bu durum dosya segmentleriyle oldukça benzerdir. Google Shaka Oynatıcısı, JW Player ve Video.js gibi bazı ara katman yazılımı kitaplıklarının bunu sizin yerinize yönetecek şekilde oluşturulduğunu unutmayın.
<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>
Dikkat edilmesi gereken noktalar
Artık medya arabelleğe alma deneyiminin tamamının kontrolü sizde olduğundan, önceden yüklemeyi düşünürken cihazın pil düzeyini, "Veri Tasarrufu Modu" kullanıcı tercihini ve ağ bilgilerini göz önünde bulundurmanızı öneririm.
Pil farkındalığı
Bir videoyu önceden yüklemeyi düşünmeden önce, kullanıcıların cihazlarının pil düzeyini göz önünde bulundurun. Bu, güç seviyesi düşük olduğunda pil ömrünü korur.
Cihazın pili azaldığında önceden yüklemeyi devre dışı bırakın veya en azından daha düşük çözünürlüklü bir videoyu önceden yükleyin.
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.
}
});
}
"Veri Tasarrufu"nu algılama
Tarayıcılarında "veri tasarrufu" modunu etkinleştirmiş kullanıcılara hızlı ve basit uygulamalar sunmak için Save-Data
istemci ipucu istek başlığını kullanın. Uygulamanız, bu istek başlığını tanımlayarak maliyet ve performans kısıtlamalı kullanıcılara optimize edilmiş bir kullanıcı deneyimi sunabilir ve bu deneyimi sunabilir.
Daha fazla bilgi için Save-Data ile Hızlı ve Hafif Uygulamalar Sunma bölümüne bakın.
Ağ bilgilerine dayalı akıllı yükleme
Önceden yükleme işleminden önce navigator.connection.type
başlıklı makaleyi incelemenizi öneririz. cellular
olarak ayarlandığında önceden yüklemeyi engelleyebilir, kullanıcılara mobil ağ operatörlerinin bant genişliği için ücret alıyor olabileceği konusunda öneride bulunabilir ve yalnızca önceden önbelleğe alınan içeriği otomatik olarak oynatmayı başlatabilirsiniz.
if ('connection' in navigator) {
if (navigator.connection.type == 'cellular') {
// TODO: Prompt user before preloading video
} else {
// TODO: Preload the first segment of a video.
}
}
Ağ değişikliklerine nasıl tepki vereceğinizi öğrenmek için de Ağ Bilgileri örneğine göz atın.
Birden çok ilk segmenti önceden önbelleğe al
Kullanıcının en sonunda hangi medyayı seçeceğini bilmeden bazı medya içeriklerini tahmine dayalı olarak
önceden yüklemek istersem ne olur? Kullanıcı 10 video içeren bir web sayfasındaysa muhtemelen her birinden bir segment dosyası getirmek için yeterli belleğimiz vardır, ancak 10 gizli <video>
öğesi ve 10 MediaSource
nesnesi oluşturup bu verileri beslemeye kesinlikle başlamamamız gerekir.
Aşağıdaki iki bölümden oluşan örnekte, güçlü ve kullanımı kolay Cache API'yi kullanarak videonun birden çok ilk segmentini nasıl önceden önbelleğe alacağınız gösterilmektedir. Benzer bir işlemin IndexedDB ile de elde edilebileceğini unutmayın. Cache API'ye window
nesnesinden de erişilebildiği için henüz Service Worker'ları kullanmıyoruz.
Getir ve önbelleğe al
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
isteklerini kullanacak olsaydım Cache API henüz Range
yanıtlarını desteklemediğinden bir Response
nesnesini manuel olarak yeniden oluşturmam gerekecekti. networkResponse.arrayBuffer()
çağrısının, yanıtın tüm içeriğini bir defada oluşturucu belleğine getirdiğini unutmayın. Bu nedenle, küçük aralıklar kullanmak isteyebilirsiniz.
Referans olması açısından, yukarıdaki örneğin bir kısmını HTTP Aralığı isteklerini video önbelleğine kaydedecek şekilde değiştirdim.
...
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;
});
Videoyu oynat
Kullanıcılar oynat düğmesini tıkladığında Cache API'de bulunan ilk video segmentini getiririz. Böylece oynatma kullanılabiliyorsa hemen başlar. Aksi takdirde, verileri ağdan alırız. Tarayıcıların ve kullanıcıların Önbelleği temizlemeye karar verebileceğini unutmayın.
Daha önce görüldüğü gibi, videonun bu ilk segmentini video öğesine aktarmak için MSE'yi kullanırız.
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.
});
}
});
}
Hizmet çalışanı ile aralık yanıtları oluşturma
Peki bir video dosyasının tamamını getirip Cache API'ye kaydettiyseniz ne olur? Tarayıcı HTTP Range
isteği gönderdiğinde, Cache API henüz Range
yanıtlarını desteklemediğinden videonun tamamını oluşturucu belleğine aktarmak kesinlikle istemezsiniz.
Şimdi bu isteklere nasıl müdahale edeceğimi ve bir Service Worker'dan özelleştirilmiş Range
yanıtı döndüreceğimi göstereyim.
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;
}
}
Bu dilimlenmiş yanıtı yeniden oluşturmak için response.blob()
kullandığımı unutmama gerek. Bu şekilde dosya için bir herkese açık kullanıcı adı verirken response.arrayBuffer()
dosyanın tamamını oluşturucu belleğine getiriyor.
Özel X-From-Cache
HTTP üst bilgim, bu isteğin önbellekten mi yoksa ağdan mı geldiğini öğrenmek için kullanılabilir. ShakaPlayer gibi oyuncular tarafından ağ hızının göstergesi olarak yanıt süresini yoksaymak için kullanılabilir.
Range
isteklerinin nasıl ele alınacağına dair eksiksiz bir çözüm için resmi Sample Media App'e, özellikle de ranged-response.js dosyasına göz atın.