Medya Kaynağı Uzantıları

François Beaufort
François Beaufort
Joe Medley
Joe Medley

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
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 vardır.

  • Medyayı oynatmak için bir <audio> veya <video> öğesi.
  • Medya öğesini beslemek için SourceBuffer içeren bir MediaSource örneği.
  • Bir Response nesnesinde medya verilerini almak için fetch() veya XHR çağrısı.
  • MediaSource.SourceBuffer feed'ine Response.arrayBuffer() tarafından yapılan bir arama.

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ı anladıysanız okumayı bırakabilirsiniz. Daha ayrıntılı bir açıklama için 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 ö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çıklanıyor mu? 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> öğelerini kullanarak bu bilgileri ücretsiz olarak elde ederiz.
  • 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ı yapmadan önce tüm hata etkinliklerini veya API istisnalarını ele alın ve HTMLMediaElement.readyState ile MediaSource.readyState değerlerini kontrol edin. Bu değerler, ilişkili etkinlikler yayınlanmadan önce değişebilir.
  • SourceBuffer'ın mode, timestampOffset, appendWindowStart, appendWindowEnd özelliklerini güncellemeden veya SourceBuffer'da appendBuffer() ya da remove()'ı çağırmadan önce SourceBuffer.updating boole değerini kontrol ederek önceki appendBuffer() ve remove() çağrılarının devam etmediğinden emin olun.
  • MediaSource'unuza eklenen tüm SourceBuffer örnekleri için MediaSource.endOfStream() çağrısını yapmadan veya MediaSource.duration öğesini 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 veya SourceBuffer.mode ya da SourceBuffer.timestampOffset ayarlaması bu değerin open değerine geçiş yapmasına neden olur. Bu nedenle, birden fazla sourceopen 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çin MediaError.message içeriği, hatanın temel nedenini belirlemek amacıyla yararlı olabilir.

Bir medya öğesine MediaSource örneği ekleme

Günümüzde web geliştirmedeki birçok şeyde olduğu gibi, özellik algılamayla başlarsınız. Ardından bir medya öğesi (<audio> veya <video> öğesi) alın. Son olarak MediaSource örneği oluşturun. 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.');
}
Blob olarak bir kaynak özellik
Şekil 1: Blob olarak bir kaynak özelliği

Bir MediaSource nesnesinin src özelliğine iletilebileceği biraz tuhaf görünebilir. Bunlar genellikle dizelerdir ancak blob da olabilirler. Yerleşik medya içeren bir sayfayı inceleyip medya öğesini incelerseniz ne demek istediğimi anlayacaksı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 biraz gecikme yaşanmasına 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 eklenmiştir ve veri almaya hazırdır veya veri almaktadır.
  • 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şturacağım örnekte, videoyu ne zaman getireceğimi ve 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 toplamasını sağlamaktadır. Bu nedenle hemen çağırıyorum.

SourceBuffer Oluşturma

Artık SourceBuffer nesnesini oluşturmanın zamanı geldi. Bu nesne, verileri medya kaynakları ile medya öğeleri arasında aktarma işini yapan nesnedir. 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 örnek bulursunuz. 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 kalitesinde bir oynatıcı, farklı tarayıcıları desteklemek için aynı dosyanın birden fazla sürümüne sahiptir. Ses ve video için ayrı dosyalar kullanarak sesin dil ayarlarına göre seçilmesine izin verebilir.

Gerçek dünyadaki kod, farklı cihaz özelliklerine ve ağ koşullarına uyum sağlayabilmek için medya dosyalarının farklı çözünürlüklerde birden fazla kopyasına da sahip olur. 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 konunun ayrıntılı bir şekilde ele alınması bu girişin kapsamı dışındadır.

Yanıt nesnesini işleme

Kod neredeyse tamamlanmış görünüyor ancak medya oynatılmıyor. Response nesnesinden SourceBuffer nesnesine medya verileri 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. Tampona bir söz döndüren response.arrayBuffer() işlevini çağırarak başlayın. 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ı burada 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);
    });
}

Geri bildirim