Kaynakları etkin bir şekilde önceden yükleyerek medya oynatma hızınızı artırma.
Oynatma işleminin daha hızlı başlaması, videonuzu izleyen veya sesinizi dinleyen kullanıcı sayısını artırır. 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 oynatma hızınızı artırmak için kullanabileceğiniz teknikleri inceleyeceğiz.
Medya dosyalarını önceden yüklemeyle ilgili üç yöntemi, avantaj ve dezavantajlarıyla birlikte açıklayacağım.
Çok iyi... | Ama... | |
---|---|---|
Videoyu ö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 yoksayabilir. |
Kaynak getirme işlemi, HTML dokümanı tamamen yüklenip ayrıştırıldığında başlar. | ||
Medya Kaynağı Uzantıları (MSE), MSE'ye medya sağlamaktan uygulama sorumlu olduğu için medya öğelerindeki preload özelliğini yoksayar.
|
||
Bağlantıyı önceden yükleme |
Tarayıcıyı, dokümanın onload etkinliğini engellemeden video kaynağı isteği göndermeye zorlar.
|
HTTP aralığı istekleri uyumlu değildir. |
MSE ve dosya segmentleriyle uyumludur. | Tam kaynaklar getirilirken yalnızca küçük medya dosyaları (<5 MB) için kullanılmalıdır. | |
Manuel arabelleğe alma | Tam kontrol | Karmaşık hatalarla ilgili işlemler web sitesinin sorumluluğundadır. |
Video ön yükleme özelliği
Video kaynağı bir web sunucusunda barındırılan benzersiz bir dosyaysa önceden ne kadar bilgi veya içerik yükleneceği konusunda tarayıcıya ipucu vermek için video preload
özelliğini kullanabilirsiniz. Bu, Medya Kaynağı Uzantıları (MSE)'nın preload
ile uyumlu olmadığı anlamına gelir.
Kaynak getirme işlemi yalnızca ilk HTML dokümanı tamamen yüklendiğinde ve ayrıştırıldığında başlar (ör.DOMContentLoaded
etkinliği tetiklenir). Bununla birlikte, kaynak gerçekten getirildiğinde çok farklı bir load
etkinliği tetiklenir.
preload
özelliğinin metadata
olarak ayarlanması, kullanıcının videoya ihtiyaç duymasının beklenmediğini ancak meta verilerinin (boyutlar, parça listesi, süre vb.) getirilmesinin istendiğini gösterir. 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çin duraklamaya gerek kalmadan oynatmanın tamamlanabilmesi için yeterli veriyi önbelleğe alabileceğini gösterir.
<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>
Ancak bazı noktalara dikkat edilmelidir. Bu yalnızca bir ipucu olduğundan tarayıcı, preload
özelliğini tamamen yoksayabilir. Şu anda Chrome'da uygulanan bazı kurallar aşağıda verilmiştir:
- Veri Tasarrufu etkinleştirildiğinde Chrome,
preload
değerininone
olarak zorlar. - Android 4.3'te Chrome, Android hatası nedeniyle
preload
değerininone
olarak zorlar. - Chrome, hücresel bağlantıda (2G, 3G ve 4G)
preload
değerinimetadata
olarak zorlar.
İpuçları
Web siteniz aynı alanda birçok 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 sayede, aynı alan adıyla maksimum HTTP bağlantısı sayısına (HTTP 1.1 spesifikasyonuna göre 6) ulaşarak kaynakların yüklenmesini askıya alabilirsiniz. Videolar temel kullanıcı deneyiminizin bir parçası değilse bunun sayfa hızını da iyileştirebileceğini unutmayın.
Bağlantıyı önceden yükleme
Diğer makalelerde açıklandığı gibi, bağlantı önceden yükleme, load
etkinliğini engellemeden ve sayfa indirilirken tarayıcıyı bir kaynak için istekte bulunmaya zorlamanıza olanak tanıyan açıklayıcı 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 referans verilene kadar etkin değildir.
Önceden yükleme, mevcut gezinmeye odaklandığı ve kaynakları türüne (komut dosyası, stil, yazı tipi, video, ses vb.) göre öncelikli olarak getirdiği için ön getirmeden farklıdır. Mevcut oturumlar için tarayıcı önbelleğini ısıtmak amacıyla kullanılmalıdır.
Videonun tamamını önceden yükleme
Web sitenizde bir videonun tamamını ön yüklemek için aşağıdaki adımları uygulayın. Böylece, JavaScript'iniz video içeriğini getirmeyi istediğinde kaynak tarayıcı tarafından önbelleğe alınmış olabileceğinden önbellekten okunur. Ön yükleme isteği henüz tamamlanmadıysa 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>
Önden yüklenen kaynak, örnekteki bir video öğesi tarafından kullanılacağından as
ön yükleme bağlantısı değeri video
olur. Ses öğesi olsaydı as="audio"
olurdu.
İlk segmenti önceden yükleme
Aşağıdaki örnekte, bir videonun ilk segmentinin <link
rel="preload">
ile nasıl önceden yükleneceği ve Medya Kaynağı Uzantıları ile nasıl kullanılacağı gösterilmektedir. MSE JavaScript API'yi bilmiyorsanız MSE ile ilgili temel bilgiler başlıklı makaleyi inceleyin.
Basitlik 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 desteklenip desteklenmediğini algılayabilirsiniz:
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 ve hizmet işçilerini incelemeden önce, MSE ile bir videonun nasıl manuel olarak arabelleğe alınacağını görelim. Aşağıdaki örnekte, web sunucunuzun HTTP Range
isteklerini desteklediği varsayılmaktadır ancak bu, dosya segmentlerine oldukça benzer olacaktır. Google'ın Shaka Player, JW Player ve Video.js gibi bazı orta katman kitaplıklarının bu işlemi sizin için yapacak şekilde tasarlandığını 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ünü ele aldığınız için ön yükleme yaparken cihazın pil seviyesini, "Veri Tasarrufu Modu" kullanıcı tercihini ve ağ bilgilerini göz önünde bulundurmanızı öneririz.
Pil farkındalığı
Videoyu önceden yüklemeyi düşünmeden önce kullanıcıların cihazlarının pil seviyesini göz önünde bulundurun. Bu sayede, güç seviyesi düşük olduğunda pil ömrü korunur.
Cihazın pili azaldığında önceden yükleme özelliğini 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ştiren kullanıcılara hızlı ve hafif uygulamalar sunmak için Save-Data
istemci ipucu istek üstbilgisini kullanın. Uygulamanız bu istek üstbilgisini tanımlayarak maliyet ve performans açısından kısıtlanmış kullanıcılara özelleştirilmiş ve optimize edilmiş bir kullanıcı deneyimi sunabilir.
Daha fazla bilgi edinmek için Veri Tasarrufu ile Hızlı ve Hafif Uygulamalar Sunma başlıklı makaleyi inceleyin.
Ağ bilgilerine dayalı akıllı yükleme
Önceden yüklemeden önce navigator.connection.type
'ü kontrol edebilirsiniz. Bu değer cellular
olarak ayarlandığında, ön yüklemeyi önleyebilir ve kullanıcılara mobil ağ operatörlerinin bant genişliği için ücret alabileceğini bildirebilir, yalnızca önceden önbelleğe alınmış içeriğin otomatik oynatılmasını 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 Ağ Bilgileri örneğine göz atın.
Birden fazla ilk segmenti önbelleğe alma
Peki kullanıcının hangi medyayı seçeceğini bilmeden bazı medya içeriklerini tahmini olarak önceden yüklemek istersem ne olur? Kullanıcı 10 video içeren bir web sayfasındaysa her birinden bir segment dosyası almak için muhtemelen yeterli belleğimiz vardır ancak kesinlikle 10 gizli <video>
öğesi ve 10 MediaSource
nesnesi oluşturup bu verileri beslemeye başlamamalıyız.
Aşağıdaki iki bölümden oluşan örnekte, güçlü ve kullanımı kolay Cache API'yi kullanarak videonun ilk birkaç segmentini nasıl önbelleğe alabileceğinizi görebilirsiniz. Benzer bir şeyin IndexedDB ile de elde edilebileceğini unutmayın. Cache API'ye window
nesnesinden de erişilebildiğinden henüz hizmet işçilerini kullanmıyoruz.
Getirme ve önbelleğe alma
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
istekleri kullanacak olsaydım Cache API'nin Range
yanıtlarını henüz desteklemediği için bir Response
nesnesini manuel olarak yeniden oluşturmam gerektiğini unutmayın. networkResponse.arrayBuffer()
çağrısının, yanıtın tüm içeriğini bir kerede oluşturma belleğine getirdiğini unutmayın. Bu nedenle küçük aralıklar kullanmanız önerilir.
Referans olması amacıyla, HTTP Range isteklerini video ön önbelleğe kaydetmek için yukarıdaki örneğin bir bölümünü 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ı bir oynatma düğmesini tıkladığında, oynatma mümkünse hemen başlaması için önbelleğe alma API'sinde bulunan videonun ilk segmentini getiririz. Aksi takdirde, ağdan alırız. Tarayıcıların ve kullanıcıların önbelleği temizlemeye karar verebileceğini unutmayın.
Daha önce de görüldüğü gibi, videonun ilk segmentini video öğesine beslemek 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 video dosyasının tamamını getirip Cache API'ye kaydettiyseniz ne olur? Tarayıcı bir HTTP Range
isteği gönderdiğinde, Cache API Range
yanıtlarını henüz desteklemediğinden videonun tamamını oluşturma belleğine getirmek istemezsiniz.
Bu isteklerin nasıl yakalanacağını ve bir hizmet çalışanından özelleştirilmiş bir Range
yanıtın nasıl döndürüleceğini 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ı belirtmek önemlidir. response.blob()
, dosyanın yalnızca bir adını sağlarken response.arrayBuffer()
dosyanın tamamını oluşturucu belleğine getirir.
Özel X-From-Cache
HTTP üstbilgim, bu isteğin önbellekten mi yoksa ağdan mı geldiğini öğrenmek için kullanılabilir. ShakaPlayer gibi bir oynatıcı 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 Örnek Medya Uygulaması'na ve özellikle de ranged-response.js dosyasına göz atın.