A Pen by Michael Ward on CodePen.
Forked from anonymous/canvas-images-to-video-recording-example.markdown
Created
October 21, 2017 08:47
-
-
Save michaelfward/347705d4cd3e5c620cd099b76006c607 to your computer and use it in GitHub Desktop.
Canvas Images to Video Recording Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div> | |
<h1 style="display:inline"> Recording Tests </h1> | |
<small> Just playing with Web APIs</small> | |
</div> | |
<div> | |
<input type="file" multiple> | |
<button onclick="canvasRenderer.render();">Render</button> | |
<button onclick="canvasRenderer.stopRender()"> Stop Rendering </button> | |
<button onclick="canvasRenderer.record()">Start Recording</button> | |
<button onclick="stopRecording();">Stop Recording</button> | |
<button onclick="canvasRenderer.render();">Next</button> | |
<span></span> | |
</div> | |
<canvas id="target" height="600" width="800"></canvas> | |
<video controls></video> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Recorder { | |
constructor(stream){ | |
this.__stream = stream; | |
this.__recorder = new MediaRecorder(this.__stream); | |
this.__recorder.ondataavailable = (data) => this.__onDataAvailable(data); | |
this.__recorder.onstart = () => this.__onStartRecording(); | |
this.__recorder.onstop = () => this.__onStopRecording(); | |
this.__subscriptions = []; | |
} | |
start(){ | |
this.__recorder.start(); | |
} | |
async stop(){ | |
return new Promise(function(resolve, reject){ | |
this.subscribe( (data) => { | |
resolve(data); | |
}); | |
this.__recorder.stop(); | |
}.bind(this)); | |
} | |
subscribe(handler){ | |
this.__subscriptions.push(handler); | |
} | |
__onDataAvailable(dataBlob){ | |
this.__subscriptions.forEach( (subscriber, listPosition) => { | |
subscriber(dataBlob) | |
}) | |
} | |
__onStartRecording(){ | |
this.recording = true; | |
} | |
__onStopRecording(){ | |
this.recording = false; | |
} | |
} | |
class RenderingTarget { | |
constructor (target){ | |
this.target = target; | |
this.renderingContext = this.target.getContext('2d'); | |
this.__renderData = []; | |
this.__pos = -1; | |
this.__img = new Image(); | |
this.__stream = this.target.captureStream(10); | |
this.__recorder = new Recorder(this.__stream); | |
} | |
addRenderableData(data){ | |
if (data instanceof Array){ | |
this.__renderData.push.apply(this.__renderData, data); | |
} else { | |
this.__renderData.push(data); | |
} | |
} | |
__render(){ | |
if (this.__renderData.length <= this.__pos){ | |
return; | |
} else { | |
this.__pos += 1; | |
this.__img.src = this.__renderData[this.__pos]; | |
this.__img.addEventListener('load', e => { | |
this.renderingContext.drawImage(this.__img, 0, 0); | |
}) | |
} | |
} | |
__cleanPreviousRenders(){ | |
clearInterval(this.__intervalID); | |
} | |
render(){ | |
this.__cleanPreviousRenders(); | |
this.__intervalID = setInterval(() => { | |
this.__render(); | |
}, 250) | |
} | |
stopRender(){ | |
this.__cleanPreviousRenders(); | |
} | |
get recording(){ | |
return this.__recorder.recording; | |
} | |
record(){ | |
this.__recorder.start(); | |
} | |
async stopRecording(){ | |
return await this.__recorder.stop(); | |
} | |
} | |
async function readFileData(fileReader, file){ | |
return new Promise( (resolve, reject) => { | |
fileReader.readAsDataURL(file); | |
fileReader.onload = () => resolve(fileReader.result); | |
fileReader.onerror = () => reject(''); | |
}) | |
} | |
const loopOverFiles = async (fileList, fileReader, resultList) => { | |
let f; | |
for (let i = 0; i < fileList.length; i++){ | |
f = await readFileData(fileReader, fileList[i]); | |
resultList.push(f); | |
} | |
return new Promise( (resolve, reject) => { | |
resolve(resultList); | |
}) | |
} | |
const extractFileData = (uploadEvent) => { | |
const fileInput = uploadEvent.target, | |
files = fileInput.files; | |
const fileReader = new FileReader(); | |
fileDataArray = []; | |
loopOverFiles(files, fileReader, fileDataArray).then( (data) => { | |
canvasRenderer.addRenderableData(data); | |
}) | |
} | |
const stopRecording = () => { | |
let videoElement = document.querySelector('video'); | |
canvasRenderer.stopRecording().then( data => { | |
let reader = new FileReader(); | |
reader.onload = () => { | |
videoElement.src = reader.result; | |
} | |
reader.readAsDataURL(data.data); | |
}) | |
} | |
document.querySelector('input').addEventListener('change', extractFileData) | |
canvasRenderer = new RenderingTarget(document.querySelector('canvas')); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/pako/1.0.6/pako.min.js"></script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment