Воспроизведение блоков MediaRecorder в MediaSource HTML5 - видео заморожено

У меня есть этот простой код для получения фрагментов видеопотока и воспроизведения их в MediaSource. Я вижу видео, но иногда оно останавливается. Это может работать в течение нескольких секунд или нескольких минут. Но, наконец, это останавливается в какой-то момент. chrome: // media-internals / не показывает ошибок.

Что здесь не так?

    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
var mediaSource = new MediaSource();
var constraints = {
    "audio": true,
    "video": {
        "mandatory": {
            "minWidth": 320, "maxWidth": 320,
            "minHeight": 240, "maxHeight": 240
        }, "optional": []
    }
};
window.mediaSource = mediaSource;
var sourceBuffer;
var video = document.querySelector('#video');
window.video = video;
video.src = window.URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', function (e) {
    console.log("sourceopen");
    sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
    window.sourceBuffer = sourceBuffer;
}, false);
mediaSource.addEventListener('error', function (e) {
    console.log("error", e)
}, false);
var stack = [];

video.play();
navigator.getUserMedia(constraints, function (stream) {
    console.log("stream", stream);
    mediaRecorder = new MediaRecorder(stream);
    mediaRecorder.ondataavailable = function (e) {
        var reader = new FileReader();
        reader.addEventListener("loadend", function () {
            var arr = new Uint8Array(reader.result);
            sourceBuffer.appendBuffer(arr);
        });
        reader.readAsArrayBuffer(e.data);
    };
    mediaRecorder.start(100);
}, function (e) {
    console.log(e)
});

Вот JSFIDDLE, который собирается попытаться это сделать:https://jsfiddle.net/stivyakovenko/fkt89cLu/6/ Я использую Chrome в качестве основной цели.

 Stepan Yakovenko09 июн. 2016 г., 01:55
@ Steve, я подтверждаю, что открытие этого jsfiddle делает мой localhost более стабильным :))
 Steve11 июн. 2016 г., 06:07
Да, ваш подход звучит более масштабируемо
 Steve08 июн. 2016 г., 23:08
Ваш пример работает в моем браузере UserAgent: «Mozilla / 5.0 (Windows NT 10.0; WOW64) AppleWebKit / 537.36 (KHTML, как Gecko) Chrome / 50.0.2661.102 Safari / 537.36»
 Stepan Yakovenko10 июн. 2016 г., 03:13
@ Стив, правильно
 Steve09 июн. 2016 г., 02:55
да, но это все еще замораживает это для меня через 20 минут или около того
 Steve09 июн. 2016 г., 16:51
Я бы использовал что-то вроде этого для общения в режиме реального времени через веб-сокеты, и я предполагаю, что его пример не включает веб-сокеты для простоты.
 Stepan Yakovenko12 июн. 2016 г., 07:37
ну, команда chrome предполагает, что файл webm не работает, насколько я могу понять детали ошибки.
 Stepan Yakovenko08 июн. 2016 г., 23:10
Это просто вопрос времени. Если подождать достаточно (1-2 минуты, он замерзнет). Мой хром точно такой же.
 Stepan Yakovenko08 июн. 2016 г., 23:18
Я думаю, это совпадение. Сделайте достаточно попыток, и моя тоже замерзнет, ​​я верю /
 Steve12 июн. 2016 г., 04:47
Замораживание при попытке выполнить ffmpeg было вызвано переполнением буфера spawn stderr узла и его устранением. Теперь единственной проблемой остается то, что поток не выглядит доступным для поиска (пользователь не может получать видео при входе в комнату, когда поток уже запущен), и по какой-то причине chrome не воспроизводит поток webm, только mp4.
 Ernest10 июн. 2016 г., 10:15
Что ты имеешь в виду под "труба и выход"? Отправляете ли вы видео фрагменты обратно через сокеты / канал каждому клиенту / зрителю? Лично я думал о создании потока с конкатенированными фрагментами видео (например, файл .m3u8), но я застрял с командой ffmpeg ...
 Steve10 июн. 2016 г., 04:19
Я провел несколько экспериментов, используяMediaSource , socket.io-поток и ffmpeg для передачи и фрагментирования надлежащим образом фрагментированного видео, мне удалось сохранить некоторую хорошую задержку, но после 6 + минутной отметки она зависает. Однако при использовании fs.createReadStream для уже преобразованного видео оно не останавливается.
 Ernest09 июн. 2016 г., 09:52
Есть ли у вас особая причина для отображения того, что вы записываете? (я имею в виду, вы можете просто отобразить поток и одновременно записать его отдельно)
 Steve08 июн. 2016 г., 23:13
Я оставил его на 15 с лишним минут, это было нормально, и я заметил, что когда я запускаю ваш и запускаю свой код, мой код также работаетссылка на сайт , но когда я закрываю ваш, он замерзает

Ответы на вопрос(4)

Решение Вопроса

Похоже, это ошибка в Chrome ...

https://bugs.chromium.org/p/chromium/issues/detail?id=606000

 CpnCrunch15 февр. 2017 г., 02:23
Кажется, сейчас исправлено (я - репортер об ошибке).
 Stepan Yakovenko15 февр. 2017 г., 13:34
ну, мой jsfiddle все еще не работает
 CpnCrunch16 февр. 2017 г., 01:45
См. Комментарий 81 в отчете об ошибке для рабочего примера.
 CpnCrunch16 февр. 2017 г., 01:43
Измените addSourceBuffer, чтобы использовать «opus, vp9», так как это, вероятно, то, что использует Chrome (вы можете указать его при настройке MediaRecorder).
 Stepan Yakovenko23 мар. 2018 г., 16:40
@CpnCrunch комментарий 81 удален
 CpnCrunch16 февр. 2017 г., 01:45
Вам также нужно проверить на наличие sourceBuffer.updating, и если это правда, вам нужно подождать, прежде чем добавлять новые данные в sourceBuffer.

но у меня вообще нет видео. Ваш jsfiddle не работает для меня на Chrome или Firefox (проверено на Ubuntu 14.04 и Windows 7).

После небольшого исследования (в основном потоковой передачи файла после его записи) я обнаружил, что файл не фрагментирован должным образом для воспроизведения MSE. @ Steve: Мне было бы интересно узнать, как вы делали фрагментацию с помощью ffmpeg.

У меня также есть похожий вопрос:Отобразить живое видео getUserMediaStream с расширениями медиапотока (MSE) , с описанием ошибки от chrome: // media-internals.

 Stepan Yakovenko25 июн. 2016 г., 20:34
@Vasilie, команда Chrome приняла это за ошибку, см. Другой ответ.
 Stepan Yakovenko25 июн. 2016 г., 09:45
Vasile, если jsfiddle не работает, может, тебе стоит сообщить об этом как об ошибке разработчикам chrome?
 Ionut Campean25 июн. 2016 г., 12:28
Ну, тот факт, что это не работает в Firefox, заставляет меня думать, что, возможно, это не ошибка. Я посмотрю на спецификацию mse и посмотрю, что я могу придумать.
 CpnCrunch14 февр. 2017 г., 23:03
Это я сообщил об ошибке Chrome, и я могу подтвердить, что она отлично работает в Firefox. Обратите внимание, что в приведенном выше коде есть ошибка: он не обрабатывает случай, когда sourcebuffer занят, поэтому appendBuffer может завершиться ошибкой.

ве ondataavailable. Похоже, такого рода вещи не работают с mediaSource. Это может вообще не работать в моем chrome 66.

Вот способ, который работает как «видеочат» или «прямой эфир» с MediaRecorder без ffmpeg:

Вы можете отправить эти данные по частям на ваш сервер с помощью ajax.Сервер может вернуть «весь файл webm» вашему браузеру Chrome за один длительный ответ. И сервер может вернуть больше данных в этом ответе, как только сервер получит данные от клиента.

И этот вид работы работает только с HTML:

Вы можете использовать список больших двоичных объектов, чтобы собрать все большие двоичные объекты из ondataavailable.затем установите video.src снова и снова.

Вот jsfiddle, который работает:

const constraints = {video: true};

const video1 = document.querySelector('.real1');
const video2 = document.querySelector('.real2');

var blobList = [];

var gCurrentTime = 0;
function playNew(){
	gCurrentTime = video2.currentTime;
	var thisBlob = new Blob(blobList,{type:"video/webm"});
	var url = URL.createObjectURL(thisBlob);
	video2.src = url;
	video2.currentTime = gCurrentTime;
	video2.play();
}
video2.onended = playNew;

var isFirst = true;
function handleSuccess(stream) {
  video1.srcObject = stream;
  var mediaRecorder = new MediaRecorder(stream,{mimeType:"video/webm"});
  mediaRecorder.ondataavailable = function(e){
	blobList.push(e.data);
	if (isFirst){
		playNew();
		isFirst = false;
	}
  }
  mediaRecorder.start(1000);
}

function handleError(error) {
  console.error('Reeeejected!', error);
}
navigator.mediaDevices.getUserMedia(constraints).
  then(handleSuccess).catch(handleError);
<video class="real1" autoplay controls></video>
<video class="real2" controls></video>

https://jsfiddle.net/4akkadht/1/

Решение только для html (второе) будет мигать снова и снова и будет иметь огромную задержку. Решение длительного нажатия на сервере (первое) не будет мигать с задержкой в ​​пять секунд.

но он завис в Firefox

  const main = async(function* main(){
  const logging = true;
  let tasks = Promise.resolve(void 0);

  const devices = yield navigator.mediaDevices.enumerateDevices();
  console.table(devices);

  const stream = yield navigator.mediaDevices.getUserMedia({video: true, audio: true});
  if(logging){
    stream.addEventListener("active", (ev)=>{ console.log(ev.type); });
    stream.addEventListener("inactive", (ev)=>{ console.log(ev.type); });
    stream.addEventListener("addtrack", (ev)=>{ console.log(ev.type); });
    stream.addEventListener("removetrack", (ev)=>{ console.log(ev.type); });
  }

  const rec = new MediaRecorder(stream, {mimeType: 'video/webm; codecs="opus,vp8"'});
  if(logging){
    rec.addEventListener("dataavailable", (ev)=>{ console.log(ev.type); });
    rec.addEventListener("pause", (ev)=>{ console.log(ev.type); });
    rec.addEventListener("resume", (ev)=>{ console.log(ev.type); });
    rec.addEventListener("start", (ev)=>{ console.log(ev.type); });
    rec.addEventListener("stop", (ev)=>{ console.log(ev.type); });
    rec.addEventListener("error", (ev)=>{ console.error(ev.type, ev); });
  }

  const ms = new MediaSource();
  if(logging){
    ms.addEventListener('sourceopen', (ev)=>{ console.log(ev.type); });
    ms.addEventListener('sourceended', (ev)=>{ console.log(ev.type); });
    ms.addEventListener('sourceclose', (ev)=>{ console.log(ev.type); });
    ms.sourceBuffers.addEventListener('addsourcebuffer', (ev)=>{ console.log(ev.type); });
    ms.sourceBuffers.addEventListener('removesourcebuffer', (ev)=>{ console.log(ev.type); });
  }

  const video = document.createElement("video");
  if(logging){
    video.addEventListener('loadstart', (ev)=>{ console.log(ev.type); });
    video.addEventListener('progress', (ev)=>{ console.log(ev.type); });
    video.addEventListener('loadedmetadata', (ev)=>{ console.log(ev.type); });
    video.addEventListener('loadeddata', (ev)=>{ console.log(ev.type); });
    video.addEventListener('canplay', (ev)=>{ console.log(ev.type); });
    video.addEventListener('canplaythrough', (ev)=>{ console.log(ev.type); });
    video.addEventListener('playing', (ev)=>{ console.log(ev.type); });
    video.addEventListener('waiting', (ev)=>{ console.log(ev.type); });
    video.addEventListener('seeking', (ev)=>{ console.log(ev.type); });
    video.addEventListener('seeked', (ev)=>{ console.log(ev.type); });
    video.addEventListener('ended', (ev)=>{ console.log(ev.type); });
    video.addEventListener('emptied', (ev)=>{ console.log(ev.type); });
    video.addEventListener('stalled', (ev)=>{ console.log(ev.type); });
    video.addEventListener('timeupdate', (ev)=>{ console.log(ev.type); }); // annoying
    video.addEventListener('durationchange', (ev)=>{ console.log(ev.type); });
    video.addEventListener('ratechange', (ev)=>{ console.log(ev.type); });
    video.addEventListener('play', (ev)=>{ console.log(ev.type); });
    video.addEventListener('pause', (ev)=>{ console.log(ev.type); });
    video.addEventListener('error', (ev)=>{ console.warn(ev.type, ev); });
  }
  //video.srcObject = ms;
  video.src = URL.createObjectURL(ms);
  video.volume = 0;
  video.controls = true;
  video.autoplay = true;
  document.body.appendChild(video);

  yield new Promise((resolve, reject)=>{
    ms.addEventListener('sourceopen', ()=> resolve(), {once: true});
  });

  const sb = ms.addSourceBuffer(rec.mimeType);
  if(logging){
    sb.addEventListener('updatestart', (ev)=>{ console.log(ev.type); }); // annoying
    sb.addEventListener('update', (ev)=>{ console.log(ev.type); }); // annoying
    sb.addEventListener('updateend', (ev)=>{ console.log(ev.type); }); // annoying
    sb.addEventListener('error', (ev)=>{ console.error(ev.type, ev); });
    sb.addEventListener('abort', (ev)=>{ console.log(ev.type); });
    }

  const stop = async(function* stop(){
    console.info("stopping");
    if(sb.updating){ sb.abort(); }
    if(ms.readyState === "open"){ ms.endOfStream(); }
    rec.stop();
    stream.getTracks().map((track)=>{ track.stop(); });
    yield video.pause();
    console.info("end");
  });

  const button = document.createElement("button");
  button.innerHTML = "stop";
  button.addEventListener("click", ()=>{
    document.body.removeChild(button);
    tasks = tasks.then(stop);
  }, {once: true});
  document.body.appendChild(button);

  let i = 0;
  rec.ondataavailable = ({data})=>{
    tasks = tasks.then(async(function*(){
        console.group(""+i);

      try{
        if(logging){ console.log("dataavailable", "size:", data.size); }

        if(data.size === 0){
          console.warn("empty recorder data");
          throw new Error("empty recorder data");
        }

        const buf = yield readAsArrayBuffer(data);

        sb.appendBuffer(buf);
        yield new Promise((resolve, reject)=>{
          sb.addEventListener('updateend', ()=> resolve(), {once: true});
          sb.addEventListener("error", (err)=> reject(ev), {once: true});
        });

                if(logging){
          console.log("timestampOffset", sb.timestampOffset);
          console.log("appendWindowStart", sb.appendWindowStart);
          console.log("appendWindowEnd", sb.appendWindowEnd);
          for(let i=0; i<sb.buffered.length; i++){
            console.log("buffered", i, sb.buffered.start(i), sb.buffered.end(i));
          }
          for(let i=0; i<video.seekable.length; i++){
            console.log("seekable", i, video.seekable.start(i), video.seekable.end(i));
          }
          console.log("webkitAudioDecodedByteCount", video.webkitAudioDecodedByteCount);
          console.log("webkitVideoDecodedByteCount", video.webkitVideoDecodedByteCount);
          console.log("webkitDecodedFrameCount", video.webkitDecodedFrameCount);
          console.log("webkitDroppedFrameCount", video.webkitDroppedFrameCount);
        }

        if (video.buffered.length > 1) {
          console.warn("MSE buffered has a gap!");
          throw new Error("MSE buffered has a gap!");
        }
      }catch(err){
          console.error(err);
        yield stop();
        console.groupEnd(""+i); i++;
        return Promise.reject(err);
      }

      console.groupEnd(""+i);
      i++;
    }));
  };

  rec.start(1000);
  console.info("start");
});



function sleep(ms){
  return new Promise(resolve =>
    setTimeout((()=>resolve(ms)), ms));
}


function readAsArrayBuffer(blob) {
  return new Promise((resolve, reject)=>{
    const reader = new FileReader();
    reader.addEventListener("loadend", ()=> resolve(reader.result), {once: true});
    reader.addEventListener("error", (err)=> reject(err.error), {once: true});
    reader.readAsArrayBuffer(blob);
  });
}


function async(generatorFunc){
  return function (arg) {
    const generator = generatorFunc(arg);
    return next(null);
    function next(arg) {
      const result = generator.next(arg);
      if(result.done){ return result.value; }
      else if(result.value instanceof Promise){ return result.value.then(next); }
      else{ return Promise.resolve(result.value); }
    }
  }
}

console.clear();
main().catch(console.error);

https://jsfiddle.net/nthyfgvs/

Ваш ответ на вопрос