1

I am recording shorts videos of my webcam about 1 sec thanks to MediaRecorder. When the video is recorded, I push it into a table. At the end, when I have some videos pushed into the table, I try to read each video one after one (like only one big video). But I have some kind of black frames between each video. It is as if the transition between the video is not perfect and reading is choppy. Someone could tell me the reason why it occurs ?

Here is some part of the code I use to record the small videos:

                navigator.mediaDevices.getUserMedia().then(function(media_stream) {
                    var recorder = new MediaRecorder(media_stream);

                    recorder.start();

                    recorder.onstart = function(event) {
                        setTimeout(function(){ recorder.stop(); }, 1000);
                    };

                    recorder.ondataavailable = event => {
                        tableau_rondelle.push(event.data);
                    };

                    recorder.onstop = function(event) {
                        recorder.start();
                    };
                })
                .catch(function(error) {});

And here is some part of the code I use to read the big video:

                            document.getElementById("video_output").onended = function(event) {
                                document.getElementById("video_output").src = URL.createObjectURL(tableau_rondelle.shift());
                            };

                            document.getElementById("video_output").src = URL.createObjectURL(tableau_rondelle.shift());

Thanks.

Bernard
  • 181
  • 2
  • 7
  • The decoder is being reinitlized between videos. A custom format will likely require a custom player that will not reset the decoder. – szatmary Oct 23 '17 at 14:30
  • at Szatmary : Thank you for the help ! Ok, so are you telling me to change the format of my recording? Currently I am recording the video using mimeType: "video/webm". Do I have to change this information? – Bernard Oct 24 '17 at 17:53
  • You need to tell the player that this is a fragmented format. Like HLS or DASH. – szatmary Oct 24 '17 at 19:24
  • I'm completely new with html 5 adaptive streaming. Let's say I want to set up an adaptive streaming media source on a server, to be consumed inside an HTML5 media element. MPEG-DASH should be used. But is that what I want here? I want from client A to record short videos, and then to sand that videos through websocket to client B. Then, from client B the goal is to playback that files one after one with no black frames between each video (i.e. with no resetting of the decoder). I don't want to save file on the server and then stream it to client. Do you think MPEG-DASH is still the solution? – Bernard Oct 24 '17 at 22:04
  • Stack overflow is not the correct place for this discussion. You should do some research and decide for yourself what is the best solution for your project, and discussions should happen on another forum. Stack overflow is best used as one question, one answer per page. Good Luck. – szatmary Oct 24 '17 at 22:07
  • Ok you are right. I really thank you for the help you bring me. I will look closer to MPEG-DASH that sounds well for me. You seem to have great skills about video and codec. May be you can bring me your point of view on the first question I asked here few weeks ago [link](https://stackoverflow.com/questions/46627364/mediarecorder-how-to-play-chunk-blob-of-video-while-recording). Any answers for the moment. But again, thank you :) – Bernard Oct 24 '17 at 22:17

2 Answers2

3

The black frames have nothing to do with the fact you used a MediaRecorder to produce the videos.
It's only caused by the asynchronicity of the whole process of onend event and loading a video:

// We will first load all the video's data in order to avoid HTTP request timings
console.log('generating all blobURIs');
const base_url = "https://dl.dropboxusercontent.com/s/";
const urls = [
  'ihleenn2498tg0a/BigChunksBunny0.mp4',
  'hyeredbcn60feei/BigChunksBunny1.mp4',
  'sjd141zuyc6giaa/BigChunksBunny2.mp4',
  'i9upd28ege6di7s/BigChunksBunny3.mp4',
  'nf43s8jnzk0rmkt/BigChunksBunny4.mp4',
  'mewgtcucsq3lrgq/BigChunksBunny5.mp4',
  '4qan8epsratlkxo/BigChunksBunny6.mp4',
  '28a6i3646ruh5dt/BigChunksBunny7.mp4',
  'prwo7uyqbjbfrc5/BigChunksBunny8.mp4',
  'n2ak9x3zuww8yf4/BigChunksBunny9.mp4',
  '12ic7m1cgmjrygc/BigChunksBunny10.mp4'
].map(url => fetch(base_url + url)
  .then(response => response.blob())
  .then(blob => URL.createObjectURL(blob))
);
Promise.all(urls)
  .then(urls => {
    console.log('generated all blobURIs');
    var current = -1;

    var timings = {
      timeupdate: null,
      end: null,
      play: null
    };
    vid.onended = playNext;
    vid.ontimeupdate = ontimeupdate;
    vid.onplaying = logTimings;
    playNext();

    function playNext() {
      timings.end = performance.now(); // save the current time

      if (++current >= urls.length) return;
      vid.src = urls[current];
      vid.play();
    }

    function ontimeupdate() {
      // timeupdate fires just before onend
      if (this.currentTime >= vid.duration) {
        timings.timeupdate = performance.now();
      }
    }

    function logTimings() {
      if (!timings.timeupdate) return;
      timings.play = performance.now();
      console.log('took ' + (timings.end - timings.timeupdate) + 'ms to fire onend event');
      console.log('took ' + (timings.play - timings.end) + 'ms to start the video');
      console.log('black frame lasted ' + (timings.play - timings.timeupdate) + 'ms');
    }
  });
<video id="vid" autoplay></video>

So how to circumvent this? Well it's not that easy, since we can't know for sure how long it will take to start the video.

One sure thing is that preloading all your videos in their own video player will help you.
An other thing that might help is to trigger the playing of the next video before the last frame of the previous one get displayed, and hence, in the timeupdate event.

console.log('preloading all videos');
const base_url = "https://dl.dropboxusercontent.com/s/";
const vids = [
  'ihleenn2498tg0a/BigChunksBunny0.mp4',
  'hyeredbcn60feei/BigChunksBunny1.mp4',
  'sjd141zuyc6giaa/BigChunksBunny2.mp4',
  'i9upd28ege6di7s/BigChunksBunny3.mp4',
  'nf43s8jnzk0rmkt/BigChunksBunny4.mp4',
  'mewgtcucsq3lrgq/BigChunksBunny5.mp4',
  '4qan8epsratlkxo/BigChunksBunny6.mp4',
  '28a6i3646ruh5dt/BigChunksBunny7.mp4',
  'prwo7uyqbjbfrc5/BigChunksBunny8.mp4',
  'n2ak9x3zuww8yf4/BigChunksBunny9.mp4',
  '12ic7m1cgmjrygc/BigChunksBunny10.mp4'
].map(url => fetch(base_url + url)
  .then(response => response.blob())
  .then(blob => URL.createObjectURL(blob))
  // convert all these urls directly to video players, preloaded
  .then(blobURI => {
    const vid = document.createElement('video');
    vid.src = blobURI;
    return vid.play()
      .then(() => {
        vid.pause();
        vid.currentTime = 0;
        return vid;
      });
  })
);

Promise.all(vids)
  .then(vids => {
    console.log('preloaded all videos');
    let current = -1;
    let vid = vids[0];
    vids.forEach(vid => {
      vid.onended = onend;
      vid.ontimeupdate = ontimeupdate;
    });
    document.body.appendChild(vid);
    playNext();

    function playNext() {
      if (++current >= vids.length) return;
      let next = vids[current];
      vid.replaceWith(next);
      vid = next;
      vid.play();
    }

    function onend() {
      if (!chck.checked) {
        playNext();
      }
    }

    function ontimeupdate() {
      // timeupdate fires just before onend
      if (chck.checked) {
        if (this._ended) return;
        let buffer_time = 400 / 1000; // this is completely arbitrary...
        if (this.currentTime >= this.duration - buffer_time) {
          this._ended = true;
          playNext();
        }
      }
    }
  });
<label>update in timeupdate<input type="checkbox" id="chck"></label><br>

But since your own problem was with recorded video from the MediaRecorder API, and that what you want is to play the whole sequence, then simply use multiple MediaRecorders in parallel: One for every segment, and one for the full video.

Note that you can pause and resume the recording as you wish.

navigator.mediaDevices.getUserMedia({video: true}).then(stream => {
  window.stream = stream;
  vid.srcObject = stream;
  vid.play();
  recordSegments(stream);
  recordFull(stream);
  });
const segments = [];
function recordSegments(stream){
  let int = setInterval(()=>{
    if(segments.length >= 10){
      clearInterval(int);
      stream.getTracks().forEach(t=>t.stop());
      return;
      }
    const chunks = [];
    const rec = new MediaRecorder(stream);
    rec.ondataavailable = e => chunks.push(e.data);
    rec.onstop = e => segments.push(new Blob(chunks));
    rec.start();
    setTimeout(()=>rec.stop(), 1000);
  }, 1000);
}

function recordFull(stream){
  const chunks = [];
  const rec = new MediaRecorder(stream);
  rec.ondataavailable = e => chunks.push(e.data);
  rec.onstop = e => exportAll(new Blob(chunks));
  rec.start();
  setTimeout(()=>rec.stop(), 10000);
  }

function exportAll(full){
  vid.remove();
  segments.unshift(full);
  segments.forEach(blob=>{
    const vid = document.createElement('video');
    vid.src = URL.createObjectURL(blob);
    vid.controls = true;
    document.body.appendChild(vid);
  });
}
<video id="vid"></video>

And as a fiddle since StackSnippets may block gUM requests.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • You said _It's only caused by the asynchronicity of the whole process of onend event and loading a video. One sure thing is that preloading all your videos in their own video player will help you_. So I try to use 2 video player superimposed. While first video is playing on player1, player2 is hidden and second video is paused. When player1 is ended, player2 becomes visible and is playing. Player1 is then hidden and paused, etc. It allow me to show each video without interruption, or black frames. The illusion is perfect. Thank you to help me to better understand my problem and to solve it. – Bernard Oct 25 '17 at 22:57
  • @Bernard, yes if you don't have sound, multiple players night do very well. – Kaiido Oct 26 '17 at 00:31
0

Try using recorder.pause and recorder.resume to tackle this problem.