Расширения медиа-источников

Франсуа Бофор
François Beaufort
Джо Медлей
Joe Medley

Media Source Extensions (MSE) — это JavaScript API, который позволяет вам создавать потоки для воспроизведения из сегментов аудио или видео. Хотя это и не рассматривается в этой статье, понимание MSE необходимо, если вы хотите встроить видео на свой сайт, которые делают следующее:

  • Адаптивная потоковая передача, другими словами, адаптация к возможностям устройства и условиям сети.
  • Адаптивное сращивание, например, вставка рекламы
  • Сдвиг во времени
  • Контроль производительности и размера загрузки
Базовый поток данных MSE
Рисунок 1 : Базовый поток данных MSE

Вы можете представить MSE как цепочку. Как показано на рисунке, между загруженным файлом и элементами мультимедиа находится несколько слоев.

  • Элемент <audio> или <video> для воспроизведения мультимедиа.
  • Экземпляр MediaSource с SourceBuffer для подачи медиа-элемента.
  • Вызов fetch() или XHR для извлечения медиаданных в объекте Response .
  • Вызов Response.arrayBuffer() для подачи MediaSource.SourceBuffer .

На практике цепочка выглядит так:

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

Если вы смогли разобраться в объяснениях, то можете смело прекращать чтение. Если вам нужны более подробные объяснения, то, пожалуйста, продолжайте читать. Я собираюсь пройти по этой цепочке, построив базовый пример MSE. Каждый из шагов сборки будет добавлять код к предыдущему шагу.

Примечание о ясности

Расскажет ли эта статья все, что вам нужно знать о воспроизведении медиа на веб-странице? Нет, она предназначена только для того, чтобы помочь вам понять более сложный код, который вы можете найти в другом месте. Для ясности этот документ упрощает и исключает многие вещи. Мы думаем, что можем обойтись без этого, поскольку мы также рекомендуем использовать библиотеку, такую ​​как Shaka Player от Google . Я буду отмечать повсюду, где я намеренно упрощаю.

Несколько вещей, не охваченных

Вот несколько вещей, которые я не буду описывать (перечислять в произвольном порядке).

  • Элементы управления воспроизведением. Мы получаем их бесплатно благодаря использованию элементов HTML5 <audio> и <video> .
  • Обработка ошибок.

Для использования в производственных условиях

Вот несколько рекомендаций по использованию API, связанных с MSE, в производственных условиях:

  • Перед вызовами этих API обработайте любые события ошибок или исключения API и проверьте HTMLMediaElement.readyState и MediaSource.readyState . Эти значения могут измениться до того, как будут доставлены связанные события.
  • Убедитесь, что предыдущие вызовы appendBuffer() и remove() еще не выполняются, проверив логическое значение SourceBuffer.updating перед обновлением mode SourceBuffer , timestampOffset , appendWindowStart , appendWindowEnd или вызовом appendBuffer() или remove() для SourceBuffer .
  • Для всех экземпляров SourceBuffer , добавленных в ваш MediaSource , убедитесь, что ни одно из их updating значений не является истинным, прежде чем вызывать MediaSource.endOfStream() или обновлять MediaSource.duration .
  • Если значение MediaSource.readyState равно ended , вызовы вроде appendBuffer() и remove() или установка SourceBuffer.mode или SourceBuffer.timestampOffset приведут к тому, что это значение перейдет в open . Это означает, что вы должны быть готовы обрабатывать несколько событий sourceopen .
  • При обработке событий HTMLMediaElement error содержимое MediaError.message может быть полезно для определения основной причины сбоя, особенно для ошибок, которые трудно воспроизвести в тестовых средах.

Присоедините экземпляр MediaSource к медиа-элементу

Как и во многих вещах в веб-разработке в наши дни, вы начинаете с обнаружения функций. Затем получите элемент media, либо элемент <audio> , либо <video> . Наконец, создайте экземпляр MediaSource . Он преобразуется в URL и передается в атрибут source элемента media.

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
Рисунок 1 : Исходный атрибут как объект blob

То, что объект MediaSource может быть передан атрибуту src , может показаться немного странным. Обычно это строки, но они также могут быть blob . Если вы проверите страницу со встроенными медиа и изучите ее элемент media, вы поймете, что я имею в виду.

Готов ли экземпляр MediaSource?

URL.createObjectURL() сам по себе синхронен; однако он обрабатывает вложение асинхронно. Это вызывает небольшую задержку, прежде чем вы сможете что-либо сделать с экземпляром MediaSource . К счастью, есть способы проверить это. Самый простой способ — использовать свойство MediaSource с именем readyState . Свойство readyState описывает связь между экземпляром MediaSource и элементом мультимедиа. Оно может иметь одно из следующих значений:

  • closed — экземпляр MediaSource не прикреплен к элементу мультимедиа.
  • open — экземпляр MediaSource присоединен к медиа-элементу и готов к приему данных или получает данные.
  • ended — экземпляр MediaSource присоединен к медиа-элементу, и все его данные переданы этому элементу.

Запрос этих параметров напрямую может негативно повлиять на производительность. К счастью, MediaSource также запускает события при изменении readyState , в частности sourceopen , sourceclosed , sourceended . Для примера, который я создаю, я собираюсь использовать событие sourceopen для указания того, когда следует извлекать и буферизировать видео.

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() . Я знаю, что это кажется преждевременным, но я могу сделать это в любое время после того, как атрибут src элемента media будет подключен к экземпляру MediaSource . Вызов этого метода не уничтожает никакие объекты. Он позволяет платформе обрабатывать сборку мусора в подходящее время, поэтому я вызываю его немедленно.

Создать SourceBuffer

Теперь пришло время создать SourceBuffer , который является объектом, который фактически выполняет работу по перемещению данных между источниками медиа и элементами медиа. SourceBuffer должен быть специфичным для типа загружаемого вами медиа-файла.

На практике это можно сделать, вызвав addSourceBuffer() с соответствующим значением. Обратите внимание, что в примере ниже строка типа mime содержит тип mime и два кодека. Это строка mime для видеофайла, но она использует отдельные кодеки для видео- и аудиочастей файла.

Версия 1 спецификации MSE позволяет агентам пользователей различаться в вопросе о том, требовать ли и тип MIME, и кодек. Некоторые агенты пользователей не требуют, но разрешают только тип MIME. Некоторые агенты пользователей, например Chrome, требуют кодек для типов MIME, которые не описывают свои кодеки самостоятельно. Вместо того, чтобы пытаться разобраться со всем этим, лучше просто включить оба.

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

Получить медиа-файл

Если вы поищете в интернете примеры MSE, вы найдете множество примеров, которые извлекают медиафайлы с помощью XHR. Чтобы быть более передовым, я собираюсь использовать Fetch API и возвращаемый им Promise . Если вы попытаетесь сделать это в Safari, это не сработает без полифилла fetch() .

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

Плеер производственного качества будет иметь один и тот же файл в нескольких версиях для поддержки разных браузеров. Он может использовать отдельные файлы для аудио и видео, чтобы позволить выбирать аудио на основе языковых настроек.

Реальный код также будет иметь несколько копий медиафайлов с разным разрешением, чтобы он мог адаптироваться к различным возможностям устройства и сетевым условиям. Такое приложение может загружать и воспроизводить видео порциями, используя запросы диапазона или сегменты. Это позволяет адаптироваться к сетевым условиям во время воспроизведения медиа . Вы могли слышать термины DASH или HLS, которые являются двумя методами достижения этого. Полное обсуждение этой темы выходит за рамки этого введения.

Обработать объект ответа

Код выглядит почти готовым, но медиа не воспроизводится. Нам нужно получить медиаданные из объекта Response в SourceBuffer .

Типичный способ передачи данных из объекта ответа в экземпляр MediaSource — получить ArrayBuffer из объекта ответа и передать его в SourceBuffer . Начните с вызова response.arrayBuffer() , который возвращает обещание буферу. В своем коде я передал это обещание второму предложению then() , где я добавляю его в SourceBuffer .

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()

После того, как все ArrayBuffers будут добавлены, и никаких дополнительных медиаданных не ожидается, вызовите MediaSource.endOfStream() . Это изменит MediaSource.readyState на ended и запустит событие sourceended .

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

Окончательная версия

Вот полный пример кода. Надеюсь, вы узнали что-то новое о Media Source Extensions.

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

Обратная связь

,

Франсуа Бофор
François Beaufort
Джо Медлей
Joe Medley

Media Source Extensions (MSE) — это JavaScript API, который позволяет вам создавать потоки для воспроизведения из сегментов аудио или видео. Хотя это и не рассматривается в этой статье, понимание MSE необходимо, если вы хотите встроить видео на свой сайт, которые делают следующее:

  • Адаптивная потоковая передача, другими словами, адаптация к возможностям устройства и условиям сети.
  • Адаптивное сращивание, например, вставка рекламы
  • Сдвиг во времени
  • Контроль производительности и размера загрузки
Базовый поток данных MSE
Рисунок 1 : Базовый поток данных MSE

Вы можете представить MSE как цепочку. Как показано на рисунке, между загруженным файлом и элементами мультимедиа находится несколько слоев.

  • Элемент <audio> или <video> для воспроизведения мультимедиа.
  • Экземпляр MediaSource с SourceBuffer для подачи медиа-элемента.
  • Вызов fetch() или XHR для извлечения медиаданных в объекте Response .
  • Вызов Response.arrayBuffer() для подачи MediaSource.SourceBuffer .

На практике цепочка выглядит так:

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

Если вы смогли разобраться в объяснениях, то можете смело прекращать чтение. Если вам нужны более подробные объяснения, то, пожалуйста, продолжайте читать. Я собираюсь пройти по этой цепочке, построив базовый пример MSE. Каждый из шагов сборки будет добавлять код к предыдущему шагу.

Примечание о ясности

Расскажет ли эта статья все, что вам нужно знать о воспроизведении медиа на веб-странице? Нет, она предназначена только для того, чтобы помочь вам понять более сложный код, который вы можете найти в другом месте. Для ясности этот документ упрощает и исключает многие вещи. Мы думаем, что можем обойтись без этого, поскольку мы также рекомендуем использовать библиотеку, такую ​​как Shaka Player от Google . Я буду отмечать повсюду, где я намеренно упрощаю.

Несколько вещей, не охваченных

Вот несколько вещей, которые я не буду описывать (перечислять в произвольном порядке).

  • Элементы управления воспроизведением. Мы получаем их бесплатно благодаря использованию элементов HTML5 <audio> и <video> .
  • Обработка ошибок.

Для использования в производственных условиях

Вот несколько рекомендаций по использованию API, связанных с MSE, в производственных условиях:

  • Перед вызовами этих API обработайте любые события ошибок или исключения API и проверьте HTMLMediaElement.readyState и MediaSource.readyState . Эти значения могут измениться до того, как будут доставлены связанные события.
  • Убедитесь, что предыдущие вызовы appendBuffer() и remove() еще не выполняются, проверив логическое значение SourceBuffer.updating перед обновлением mode SourceBuffer , timestampOffset , appendWindowStart , appendWindowEnd или вызовом appendBuffer() или remove() для SourceBuffer .
  • Для всех экземпляров SourceBuffer , добавленных в ваш MediaSource , убедитесь, что ни одно из их updating значений не является истинным, прежде чем вызывать MediaSource.endOfStream() или обновлять MediaSource.duration .
  • Если значение MediaSource.readyState равно ended , вызовы вроде appendBuffer() и remove() или установка SourceBuffer.mode или SourceBuffer.timestampOffset приведут к тому, что это значение перейдет в open . Это означает, что вы должны быть готовы обрабатывать несколько событий sourceopen .
  • При обработке событий HTMLMediaElement error содержимое MediaError.message может быть полезно для определения основной причины сбоя, особенно для ошибок, которые трудно воспроизвести в тестовых средах.

Присоедините экземпляр MediaSource к медиа-элементу

Как и во многих вещах в веб-разработке в наши дни, вы начинаете с обнаружения функций. Затем получите элемент media, либо элемент <audio> , либо <video> . Наконец, создайте экземпляр MediaSource . Он преобразуется в URL и передается в атрибут source элемента media.

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
Рисунок 1 : Исходный атрибут как объект blob

То, что объект MediaSource может быть передан атрибуту src , может показаться немного странным. Обычно это строки, но они также могут быть blob . Если вы проверите страницу со встроенными медиа и изучите ее элемент media, вы поймете, что я имею в виду.

Готов ли экземпляр MediaSource?

URL.createObjectURL() сам по себе синхронен; однако он обрабатывает вложение асинхронно. Это вызывает небольшую задержку, прежде чем вы сможете что-либо сделать с экземпляром MediaSource . К счастью, есть способы проверить это. Самый простой способ — использовать свойство MediaSource с именем readyState . Свойство readyState описывает связь между экземпляром MediaSource и элементом мультимедиа. Оно может иметь одно из следующих значений:

  • closed — экземпляр MediaSource не прикреплен к элементу мультимедиа.
  • open — экземпляр MediaSource присоединен к медиа-элементу и готов к приему данных или получает данные.
  • ended — экземпляр MediaSource присоединен к медиа-элементу, и все его данные переданы этому элементу.

Запрос этих параметров напрямую может негативно повлиять на производительность. К счастью, MediaSource также запускает события при изменении readyState , в частности sourceopen , sourceclosed , sourceended . Для примера, который я создаю, я собираюсь использовать событие sourceopen для указания того, когда следует извлекать и буферизировать видео.

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() . Я знаю, что это кажется преждевременным, но я могу сделать это в любое время после того, как атрибут src элемента media будет подключен к экземпляру MediaSource . Вызов этого метода не уничтожает никакие объекты. Он позволяет платформе обрабатывать сборку мусора в подходящее время, поэтому я вызываю его немедленно.

Создать SourceBuffer

Теперь пришло время создать SourceBuffer , который является объектом, который фактически выполняет работу по перемещению данных между источниками медиа и элементами медиа. SourceBuffer должен быть специфичным для типа загружаемого вами медиа-файла.

На практике это можно сделать, вызвав addSourceBuffer() с соответствующим значением. Обратите внимание, что в примере ниже строка типа mime содержит тип mime и два кодека. Это строка mime для видеофайла, но она использует отдельные кодеки для видео- и аудиочастей файла.

Версия 1 спецификации MSE позволяет агентам пользователей различаться в вопросе о том, требовать ли и тип MIME, и кодек. Некоторые агенты пользователей не требуют, но разрешают только тип MIME. Некоторые агенты пользователей, например Chrome, требуют кодек для типов MIME, которые не описывают свои кодеки самостоятельно. Вместо того, чтобы пытаться разобраться со всем этим, лучше просто включить оба.

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

Получить медиа-файл

Если вы поищете в интернете примеры MSE, вы найдете множество примеров, которые извлекают медиафайлы с помощью XHR. Чтобы быть более передовым, я собираюсь использовать Fetch API и возвращаемый им Promise . Если вы попытаетесь сделать это в Safari, это не сработает без полифилла fetch() .

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

Плеер производственного качества будет иметь один и тот же файл в нескольких версиях для поддержки разных браузеров. Он может использовать отдельные файлы для аудио и видео, чтобы позволить выбирать аудио на основе языковых настроек.

Реальный код также будет иметь несколько копий медиафайлов с разным разрешением, чтобы он мог адаптироваться к различным возможностям устройства и сетевым условиям. Такое приложение может загружать и воспроизводить видео порциями, используя запросы диапазона или сегменты. Это позволяет адаптироваться к сетевым условиям во время воспроизведения медиа . Вы могли слышать термины DASH или HLS, которые являются двумя методами достижения этого. Полное обсуждение этой темы выходит за рамки этого введения.

Обработать объект ответа

Код выглядит почти готовым, но медиа не воспроизводится. Нам нужно получить медиаданные из объекта Response в SourceBuffer .

Типичный способ передачи данных из объекта ответа в экземпляр MediaSource — получить ArrayBuffer из объекта ответа и передать его в SourceBuffer . Начните с вызова response.arrayBuffer() , который возвращает обещание буферу. В своем коде я передал это обещание второму предложению then() , где я добавляю его в SourceBuffer .

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()

После того, как все ArrayBuffers будут добавлены, и никаких дополнительных медиаданных не ожидается, вызовите MediaSource.endOfStream() . Это изменит MediaSource.readyState на ended и запустит событие sourceended .

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

Окончательная версия

Вот полный пример кода. Надеюсь, вы узнали что-то новое о Media Source Extensions.

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

Обратная связь