The situation
I need to do the following:
Get the video from a
<video>
and play inside a<canvas>
Record the stream from the canvas as a Blob
That’s it. The first part is okay.
For the second part, I managed to record a Blob. The problem is that the Blob is empty.
The view
<video id="video" controls="true" src="http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv"></video> <canvas id="myCanvas" width="532" height="300"></canvas>
The code
// Init console.log(MediaRecorder.isTypeSupported('video/webm')) // true const canvas = document.querySelector("canvas") const ctx = canvas.getContext("2d") const video = document.querySelector("video") // Start the video in the player video.play() // On play event - draw the video in the canvas video.addEventListener('play', () => { function step() { ctx.drawImage(video, 0, 0, canvas.width, canvas.height) requestAnimationFrame(step) } requestAnimationFrame(step); // Init stream and recorder const stream = canvas.captureStream() const recorder = new MediaRecorder(stream, { mimeType: 'video/webm', }); // Get the blob data when is available let allChunks = []; recorder.ondataavailable = function(e) { console.log({e}) // img1 allChunks.push(e.data); } // Start to record recorder.start() // Stop the recorder after 5s and check the result setTimeout(() => { recorder.stop() const fullBlob = new Blob(allChunks, { 'type' : 'video/webm' }); const downloadUrl = window.URL.createObjectURL(fullBlob) console.log({fullBlob}) // img2 }, 5000); })
The result
This the console.log
of the ondataavailable
event:
This is the console.log
of the Blob:
The fiddle
Here is the JSFiddle. You can check the results in the console:
https://jsfiddle.net/1b7v2pen/
Browsers behavior
This behavior (Blob data size: 0) it happens on Chrome and Opera.
On Firefox it behaves slightly different.
It records a very small video Blob (725 bytes). The video length is 5 seconds as it should be, but it’s just a black screen.
The question
What is the proper way to the record a stream from a canvas?
Is there something wrong in the code?
Why did the Blob come out empty?
Advertisement
Answer
MediaRecorder.stop()
is kind of an asynchronous method.
In the stop algorithm, there is a call to requestData, which itself will queue a task to fire an event dataavailable with the currently available data since the last such event.
This means that synchronously after you called MediaRecorder#stop() the last data grabbed will not be part of your allChunks
Array yet. They will become not so long after (normally in the same event loop).
So, when you are about to save recordings made from a MediaRecorder, be sure to always build the final Blob from the MediaRecorder’s onstop
event, which will signal that the MediaRecorder is actually ended, did fire its last dataavailable event, and that everything is all good.
And one thing I missed at first, is that you are requesting a cross-domain video. Doing so, without the correct cross-origin request, will make your canvas (and MediaElement) tainted, so your MediaStream will be muted.
Since the video you are trying to request is from wikimedia, you can simply request it as a cross-origin resource, but for other resources, you’ll have to be sure the server is configured to allow these requests.
const canvas = document.querySelector("canvas") const ctx = canvas.getContext("2d") const video = document.querySelector("video") // Start the video in the player video.play() // On play event - draw the video in the canvas video.addEventListener('play', () => { function step() { ctx.drawImage(video, 0, 0, canvas.width, canvas.height) requestAnimationFrame(step) } requestAnimationFrame(step); // Init stream and recorder const stream = canvas.captureStream() const recorder = new MediaRecorder(stream, { mimeType: 'video/webm', }); // Get the blob data when is available let allChunks = []; recorder.ondataavailable = function(e) { allChunks.push(e.data); } recorder.onstop = (e) => { const fullBlob = new Blob(allChunks, { 'type' : 'video/webm' }); const downloadUrl = window.URL.createObjectURL(fullBlob) console.log({fullBlob}) console.log({downloadUrl}) } // Start to record recorder.start() // Stop the recorder after 5s and check the result setTimeout(() => { recorder.stop() }, 5000); })
<!--add the 'crossorigin' attribute to your video --> <video id="video" controls="true" src="https://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv" crossorigin="anonymous"></video> <canvas id="myCanvas" width="532" height="300"></canvas>
Also, I can’t refrain to note that if you don’t do any special drawings from your canvas, you might want to save the video source directly, or at least, record the <video>’s captureStream MediaStream directly.