Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save michaelfward/347705d4cd3e5c620cd099b76006c607 to your computer and use it in GitHub Desktop.
Save michaelfward/347705d4cd3e5c620cd099b76006c607 to your computer and use it in GitHub Desktop.
Canvas Images to Video Recording Example
<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>
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'));
<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