Media Source Extensions (MSE), ses veya video segmentlerinden oynatma için akış oluşturmanıza olanak tanıyan bir JavaScript API'sidir. Bu makalede ele alınmasa da sitenize aşağıdaki gibi işlemler yapan videolar yerleştirmek istiyorsanız MSE'yi anlamanız gerekir:
- Uyarlanabilir akış (cihaz özelliklerine ve ağ koşullarına uyum sağlamanın başka bir yolu)
- Reklam ekleme gibi uyarlanabilir birleştirme
- Zaman kaydırma
- Performans ve indirme boyutunu kontrol etme
MSE'yi bir zincir olarak düşünebilirsiniz. Şekilde gösterildiği gibi, indirilen dosya ile medya öğeleri arasında birkaç katman vardır.
- Medyayı oynatmak için bir
<audio>
veya<video>
öğesi. - Medya öğesini beslemek için
SourceBuffer
içeren birMediaSource
örneği. - Bir
Response
nesnesinde medya verilerini almak içinfetch()
veya XHR çağrısı. MediaSource.SourceBuffer
beslemek içinResponse.arrayBuffer()
çağrısı.
Pratikte zincir şu şekilde görünür:
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function (response) {
return response.arrayBuffer();
})
.then(function (arrayBuffer) {
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}
Şimdiye kadarki açıklamaları anlayabildiyseniz okumayı bırakabilirsiniz. Daha ayrıntılı bir açıklama istiyorsanız lütfen okumaya devam edin. Temel bir MSE örneği oluşturarak bu zinciri adım adım açıklayacağım. Derleme adımlarının her biri, bir önceki adıma kod ekler.
Anlaşılırlık hakkında bir not
Bu makalede, web sayfasında medya oynatma hakkında bilmeniz gereken her şey açıklanacak mı? Hayır, yalnızca başka yerlerde bulabileceğiniz daha karmaşık kodları anlamanıza yardımcı olmak için tasarlanmıştır. Anlaşılırlık için bu dokümanda birçok şey basitleştirilmiş ve hariç tutulmuştur. Google'ın Shaka Player gibi bir kitaplık kullanmanızı da önerdiğimiz için bu durumdan sorunsuzca kurtulabileceğimizi düşünüyoruz. Nerede kasıtlı olarak basitleştirdiğimi belirteceğim.
Bu lisansın kapsamında olmayanlar
Belirli bir sırayla belirtmeyeceğim birkaç konuyu aşağıda bulabilirsiniz.
- Oynatma kontrolleri. HTML5
<audio>
ve<video>
öğeleri sayesinde bunları ücretsiz olarak ediniyoruz. - Hata işleme.
Üretim ortamlarında kullanım için
MSE ile ilgili API'lerin üretimde kullanılmasıyla ilgili olarak önerdiğimiz bazı noktalar şunlardır:
- Bu API'lere çağrıda bulunmadan önce tüm hata etkinliklerini veya API istisnalarını ele alın ve
HTMLMediaElement.readyState
ileMediaSource.readyState
değerlerini kontrol edin. Bu değerler, ilişkili etkinlikler yayınlanmadan önce değişebilir. SourceBuffer
'ınmode
,timestampOffset
,appendWindowStart
,appendWindowEnd
özelliklerini güncellemeden veyaSourceBuffer
'daappendBuffer()
ya daremove()
'ı çağırmadan önceSourceBuffer.updating
boole değerini kontrol ederek öncekiappendBuffer()
veremove()
çağrılarının devam etmediğinden emin olun.MediaSource
'unuza eklenen tümSourceBuffer
örnekleri içinMediaSource.endOfStream()
çağırmadan veyaMediaSource.duration
'i güncellemeden önceupdating
değerlerinin hiçbirinin doğru olmadığından emin olun.MediaSource.readyState
değeriended
iseappendBuffer()
veremove()
gibi çağrılar veyaSourceBuffer.mode
ya daSourceBuffer.timestampOffset
ayarlaması bu değerinopen
değerine geçiş yapmasına neden olur. Bu nedenle, birden fazlasourceopen
etkinliğini yönetmeye hazır olmalısınız.HTMLMediaElement error
etkinlikleri işlenirken, özellikle test ortamlarında yeniden oluşturulması zor olan hatalar içinMediaError.message
içeriği, hatanın temel nedenini belirlemek amacıyla yararlı olabilir.
Bir MediaSource örneğini medya öğesine ekleme
Bugünlerde web geliştirmede yapılan pek çok şeyde olduğu gibi, önce özellik
algılama ile başlıyorsunuz. Ardından bir medya öğesi (<audio>
veya <video>
öğesi) alın.
Son olarak MediaSource
örneği oluşturun. Bu değer bir URL'ye dönüştürülür ve medya öğesinin
kaynak özelliğine iletilir.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
// Is the MediaSource instance ready?
} else {
console.log('The Media Source Extensions API is not supported.');
}
Bir MediaSource
nesnesinin src
özelliğine iletilebileceği biraz tuhaf görünebilir. Bunlar genellikle dizedir ancak blob da olabilirler.
Yerleşik medyası olan bir sayfayı inceler ve medya öğesini incelerseniz ne demek istediğimi anlarsınız.
MediaSource örneği hazır mı?
URL.createObjectURL()
eşzamanlı olsa da eki eşzamansız olarak işler. Bu durum, MediaSource
örneğiyle herhangi bir işlem yapmadan önce kısa bir gecikmeye neden olur. Neyse ki bunu test etmenin yolları var.
En basit yöntem, readyState
adlı bir MediaSource
mülkü kullanmaktır. readyState
mülkü, bir MediaSource
örneği ile medya öğesi arasındaki ilişkiyi tanımlar. Aşağıdaki değerlerden biri olabilir:
closed
:MediaSource
örneği bir medya öğesine eklenmemiş.open
:MediaSource
örneği bir medya öğesine ekli ve veri almaya hazır veya veri alıyor.ended
:MediaSource
örneği bir medya öğesine eklenmiştir ve tüm verileri bu öğeye iletilmiştir.
Bu seçenekleri doğrudan sorgulamak performansı olumsuz yönde etkileyebilir. Neyse ki MediaSource
, readyState
değiştiğinde (özellikle sourceopen
, sourceclosed
, sourceended
) de etkinlik tetikler. Oluşturduğum örnekte, videoyu ne zaman getirip arabelleğe alacağımı belirtmek için sourceopen
etkinliğini kullanacağım.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
<strong>mediaSource.addEventListener('sourceopen', sourceOpen);</strong>
} else {
console.log("The Media Source Extensions API is not supported.")
}
<strong>function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
// Create a SourceBuffer and get the media file.
}</strong>
revokeObjectURL()
işlevini de çağırdığımı fark edin. Bunun erken olduğunun farkındayım ancak medya öğesinin src
özelliği bir MediaSource
örneğine bağlandıktan sonra bunu istediğim zaman yapabilirim. Bu yöntem çağrıldığında hiçbir nesne kaldırılmaz. Platformun uygun bir zamanda çöp toplama işlemini gerçekleştirmesine izin verir. Bu nedenle, hemen çağırıyorum.
SourceBuffer Oluşturma
Şimdi sıra, medya kaynakları ile medya öğeleri arasındaki verileri kapatma işini yapan nesne olan SourceBuffer
'ı oluşturmaya geldi. SourceBuffer
, yüklediğiniz medya dosyasının türüne özel olmalıdır.
Uygulamada bunu, addSourceBuffer()
işlevini uygun değerle çağırarak yapabilirsiniz. Aşağıdaki örnekte mime türü dizesinin bir mime türü ve iki codec içerdiğine dikkat edin. Bu, bir video dosyası için mime dizesidir ancak dosyanın video ve ses bölümleri için ayrı codec'ler kullanır.
MSE spesifikasyonunun 1. sürümü, kullanıcı aracılarının hem mime türü hem de codec gerektirip gerektirmeyeceği konusunda farklı olmasına olanak tanır. Bazı kullanıcı aracıları, mime türüne izin verir ancak codec'e izin vermez. Bazı kullanıcı aracıları (ör. Chrome), kendi codec'lerini tanımlamayan mime türleri için codec gerektirir. Tüm bunları ayırt etmek yerine her ikisini de eklemek daha iyidir.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
<strong>
var mime = 'video/webm; codecs="opus, vp09.00.10.08"'; // e.target refers to
the mediaSource instance. // Store it in a variable so it can be used in a
closure. var mediaSource = e.target; var sourceBuffer =
mediaSource.addSourceBuffer(mime); // Fetch and process the video.
</strong>;
}
Medya dosyasını alma
MSE örnekleri için internette arama yaparsanız XHR kullanarak medya dosyalarını alan birçok olduğunu görürsünüz. Daha da ileri gitmek için Fetch API'yi ve döndürdüğü Promise'i kullanacağım. Bunu Safari'de yapmaya çalışıyorsanız fetch()
polyfill olmadan çalışmaz.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
<strong>
fetch(videoUrl) .then(function(response){' '}
{
// Process the response object.
}
);
</strong>;
}
Üretim kalitesindeki bir oynatıcı, aynı dosyanın farklı tarayıcıları desteklemek için birden fazla sürümde olmasını sağlayabilir. Ses ve video için ayrı dosyalar kullanarak sesin dil ayarlarına göre seçilmesine izin verebilir.
Gerçek dünyadaki kodda, farklı cihaz özelliklerine ve ağ koşullarına uyum sağlayabilmesi için medya dosyalarının farklı çözünürlüklerde birden fazla kopyası bulunur. Bu tür bir uygulama, aralık istekleri veya segmentler kullanarak videoları parçalar halinde yükleyip oynatabilir. Bu, medya oynatılırken ağ koşullarına uyum sağlamaya olanak tanır. DASH veya HLS terimlerini duymuş olabilirsiniz. Bu iki yöntem, bu işlemi gerçekleştirmenin yollarından biridir. Bu girişte bu konunun kapsamlı olarak açıklanması girişin kapsamı dışındadır.
Yanıt nesnesini işleme
Kod neredeyse tamamlanmış görünüyor ancak medya oynatılmıyor. Medya verilerini Response
nesnesinden SourceBuffer
öğesine almamız gerekiyor.
Yanıt nesnesinden MediaSource
örneğine veri aktarmanın tipik yolu, yanıt nesnesinden bir ArrayBuffer
almak ve bunu SourceBuffer
'ye aktarmaktır. response.arrayBuffer()
öğesini çağırarak başlayın. Bu çağrı, tampon için
bir söz döndürür. Kodumda bu promise'i, SourceBuffer
'a eklediğim ikinci bir then()
yan tümcesine ilettim.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function(response) {
<strong>return response.arrayBuffer();</strong>
})
<strong>.then(function(arrayBuffer) {
sourceBuffer.appendBuffer(arrayBuffer);
});</strong>
}
endOfStream() işlevini çağırın.
Tüm ArrayBuffers
eklendikten ve başka medya verisi beklenmedikten sonra MediaSource.endOfStream()
işlevini çağırın. Bu işlemle MediaSource.readyState
, ended
olarak değişir ve sourceended
etkinliği tetiklenir.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function(response) {
return response.arrayBuffer();
})
.then(function(arrayBuffer) {
<strong>sourceBuffer.addEventListener('updateend', function(e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});</strong>
sourceBuffer.appendBuffer(arrayBuffer);
});
}
Nihai sürüm
Kod örneğinin tamamını aşağıda bulabilirsiniz. Medya Kaynağı Uzantıları hakkında bilgi edindiğinizi umuyoruz.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function (response) {
return response.arrayBuffer();
})
.then(function (arrayBuffer) {
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}