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.