Medya Kaynağı Uzantıları

François Beaufort
François Beaufort
Ali Porsuk
Ali Polat

Medya Kaynağı Uzantıları (MSE), ses veya video segmentlerinden oynatılacak akışlar oluşturmanızı sağlayan bir JavaScript API'sidir. Bu makalede ele alınmamış olsa da, sitenize aşağıdakiler gibi işlemler yapan videoları yerleştirmek istiyorsanız MSE'yi anlamanız gerekir:

  • Uyarlanabilir akış, cihazın özelliklerine ve ağ koşullarına uyarlamanın başka bir yoludur.
  • Uyarlanabilir birleştirme (ör. reklam ekleme)
  • Zaman kaydırma
  • Performans ve indirme boyutu denetimi
Temel MSE veri akışı
Şekil 1: Temel MSE veri akışı

MSE'yi bir zincir olarak düşünebilirsiniz. Şekilde gösterildiği gibi, indirilen dosya ile medya öğeleri arasında birkaç katman bulunur.

  • Medyayı oynatmak için <audio> veya <video> öğesi.
  • Medya öğesini beslemek için SourceBuffer içeren bir MediaSource örneği.
  • Bir Response nesnesindeki medya verilerini almak için fetch() veya XHR çağrısı.
  • MediaSource.SourceBuffer feed'i için Response.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);
    });
}

Şu ana kadar yapılanlar açıklamalardan anlaşılacaksa okumaktan çekinmeyin. Daha ayrıntılı bir açıklama için okumaya devam edin. Temel bir MSE örneği oluşturarak bu zincirin üzerinden geçeceğim. Derleme adımlarının her biri, bir önceki adıma kod ekler.

Netlik hakkında bir not

Bu makale bir web sayfasında medya oynatma hakkında bilmeniz gereken her şeyi açıklıyor mu? Hayır, yalnızca başka bir yerde bulabileceğiniz daha karmaşık kodları anlamanıza yardımcı olmayı amaçlamaktadır. Netlik adına, bu belgede pek çok konuyu basitleştirmemekte ve kapsamamaktadır. Ayrıca Google'ın Shaka Player gibi bir kitaplık kullanmanızı önerdiğimiz için bu sorunun üstesinden gelebiliriz. Sadeleştirme yaptığım kısımların devamında da bunu not edeceğim.

Kapsam dışında olan bazı konular

Burada, belirli bir sıra olmaksızın, değinmeyeceğim birkaç konu var.

  • Oynatma kontrolleri. Bunları HTML5 <audio> ve <video> öğelerini kullanarak ücretsiz alırız.
  • Hata işleme.

Üretim ortamlarında kullanım için

MSE ile ilgili API'lerin üretim kullanımı için önerdiğim bazı özellikler şunlardır:

  • Bu API'lerde çağrı yapmadan önce tüm hata etkinliklerini veya API istisnalarını işleme alıp HTMLMediaElement.readyState ve MediaSource.readyState öğelerini kontrol edin. Bu değerler, ilişkili etkinlikler yayınlanmadan önce değişebilir.
  • SourceBuffer'in mode, timestampOffset, appendWindowStart, appendWindowEnd özelliğini güncellemeden ya da SourceBuffer üzerinde appendBuffer() veya remove() yöntemini çağırmadan önce SourceBuffer.updating boole değerini kontrol ederek önceki appendBuffer() ve remove() çağrılarının hâlâ devam etmediğinden emin olun.
  • MediaSource cihazınıza eklediğiniz tüm SourceBuffer örnekleri için MediaSource.endOfStream() çağrısı yapmadan veya MediaSource.duration özelliğini güncellemeden önce updating değerlerinin hiçbirinin doğru olmadığından emin olun.
  • MediaSource.readyState değeri ended ise appendBuffer() ve remove() gibi çağrılar ya da SourceBuffer.mode veya SourceBuffer.timestampOffset değeri ayarlamak, bu değerin open değerine geçmesine neden olur. Bu nedenle birden fazla sourceopen etkinliğini yönetmek için hazırlıklı olmanız gerekir.
  • HTMLMediaElement error etkinlikleri işlenirken, MediaError.message içeriği, özellikle test ortamlarında yeniden oluşturulması zor olan hatalar için hatanın temel nedenini belirlemek açısından faydalı olabilir.

Medya öğesine MediaSource örneği ekleme

Günümüzde web geliştirmedeki pek çok şeyde olduğu gibi, bu işe özellik algılama ile başlıyorsunuz. Ardından, <audio> veya <video> öğesi olmak üzere bir medya öğesi alın. Son olarak MediaSource örneği oluşturun. Bir URL'ye dönüştürülür ve medya öğesinin kaynak özelliğine aktarılır.

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.');
}
Blob olarak kaynak özelliği
Şekil 1: Blob olarak bir kaynak özelliği

src özelliğine bir MediaSource nesnesinin geçirilebilmesi biraz tuhaf görünebilir. Genellikle dizedirler, ancak bloblar da olabilirler. Yerleştirilmiş medya içeren bir sayfayı inceler ve medya öğesini incelerseniz ne demek olduğunu anlayabilirsiniz.

MediaSource örneği hazır mı?

URL.createObjectURL() kendisi eşzamanlıdır ancak eki eşzamansız olarak işler. Bu işlem, MediaSource örneğiyle herhangi bir işlem yapabilmeniz için kısa bir gecikmeye neden olur. Neyse ki bunu test etmenin yolları var. En basit yol, readyState adlı bir MediaSource mülkü kullanmaktır. readyState özelliği, MediaSource örneği ile medya öğesi arasındaki ilişkiyi tanımlar. Aşağıdaki değerlerden birine sahip olabilir:

  • closed - MediaSource örneği bir medya öğesine bağlı değil.
  • open - MediaSource örneği bir medya öğesine bağlıdır ve veri almaya hazır veya veri alıyor.
  • ended - MediaSource örneği bir medya öğesine ekli ve tüm verileri bu öğeye geçirildi.

Bu seçenekleri doğrudan sorgulamak performansı olumsuz yönde etkileyebilir. Neyse ki MediaSource, readyState değiştiğinde (özellikle sourceopen, sourceclosed, sourceended) etkinlikleri de tetikler. Oluşturduğum örnekte, videonun ne zaman getirilip arabelleğe alınacağını bildirmek 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>

Ayrıca revokeObjectURL() dediğime dikkat edin. Bunun erken göründüğünün farkındayım, ama medya öğesinin src özelliği bir MediaSource örneğine bağlandıktan sonra istediğim zaman bunu yapabilirim. Bu yöntemi çağırmak hiçbir nesneyi yok etmez. Platformun çöp toplama işlemini uygun zamanda gerçekleştirmesine izin verir. Bu nedenle hemen çağrıyorum.

SourceBuffer oluşturma

Şimdi sıra, medya kaynakları ile medya öğeleri arasında veri aktarımını yapan nesne olan SourceBuffer'yi oluşturmaya geldi. SourceBuffer öğesi, yüklediğiniz medya dosyasının türüne özel olmalıdır.

Pratikte, addSourceBuffer() öğesini uygun değerle çağırarak bunu 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 gerektirmeme konusunda farklılık göstermesine olanak tanır. Bazı kullanıcı aracıları gerekli değildir ancak yalnızca MIME türüne izin verir. Örneğin Chrome gibi bazı kullanıcı aracıları, codec'lerini kendi kendilerine tanımlamayan MIME türleri için bir codec'e ihtiyaç duyar. Tüm bunları çözümlemeye çalışmak yerine, her ikisini birden dahil etmek 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ı alın

MSE örnekleri için internette arama yaparsanız XHR kullanarak medya dosyalarını alan birçok şey bulabilirsiniz. Daha ileri teknoloji olması için Fetch API'sini ve döndürdüğü Promise'i kullanacağım. Bunu Safari'de yapmaya çalışıyorsanız fetch() çoklu dolgusu 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 kalitesi oynatıcısı, farklı tarayıcıları desteklemek için aynı dosyanın birden fazla sürümde olmasını sağlar. Dil ayarlarına göre sesin seçilmesine olanak tanımak için ses ve video için ayrı dosyalar kullanabilir.

Ayrıca gerçek dünya kodunda, medya dosyalarının farklı çözünürlüklerde birden fazla kopyası bulunur. Bu sayede, farklı cihaz özellikleri ve ağ koşullarına uyum sağlayabilir. Bu tür bir uygulama, aralık isteklerini veya segmentleri kullanarak videoları parçalar halinde yükleyip oynatabilir. Bu, medya oynatılırken ağ koşullarına uyum sağlar. DASH veya HLS terimlerini duymuş olabilirsiniz. Bu terimler, bunu yapmanın iki yöntemidir. Bu konuyu tam olarak ele almak bu tanıtımın kapsamı dışındadır.

Yanıt nesnesini işle

Kod neredeyse bitti ancak medya oynatılamıyor. Medya verilerini Response nesnesinden SourceBuffer'a almamız gerekir.

Yanıt nesnesinden MediaSource örneğine veri iletmenin tipik yolu, yanıt nesnesinden bir ArrayBuffer almak ve bunu SourceBuffer'a iletmektir. Arabelleğe bir söz veren response.arrayBuffer() yöntemini çağırarak başlayın. Kodumda, bu sözü SourceBuffer öğesine eklediğim ikinci bir then() maddesine 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() yöntemini çağırın

Tüm ArrayBuffers eklendikten sonra ve başka medya verisi beklendikten sonra MediaSource.endOfStream() numaralı telefonu arayın. Bu işlem, MediaSource.readyState değerini ended olarak değiştirir ve sourceended etkinliğini tetikler.

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);
    });
}

Son sürüm

Tam kod örneğini burada bulabilirsiniz. Medya Kaynağı Uzantıları hakkında bilgi edindiğinizi umuyorum.

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);
    });
}

Geri bildirim