audio-plus-canvas-recording.html 9.58 KB
<title>Audio + Canvas2D/HTML recording using RecordRTC!</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="author" content="Muaz Khan">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style>
body {
    overflow-x: hidden;
    background: rgb(233, 233, 233);
}

#elementToShare {
    background: rgb(233, 233, 233);
    font-size: 2em;
    height: 100%;
    left: 0;
    padding: 0 1em;
    position: absolute;
    top: 0;
    width: 100%;
}

input,
textarea {
    border: 1px solid red;
    font-size: 1em;
    outline: none;
    padding: .3em .8em;
}

button,
input[type=button] {
    -moz-border-radius: 3px;
    -moz-transition: none;
    -webkit-transition: none;
    background: #0370ea;
    background: -moz-linear-gradient(top, #008dfd 0, #0370ea 100%);
    background: -webkit-linear-gradient(top, #008dfd 0, #0370ea 100%);
    border: 1px solid #076bd2;
    border-radius: 3px;
    color: #fff;
    display: inline-block;
    font-family: inherit;
    font-size: .8em;
    font-size: 1.5em;
    line-height: 1.3;
    padding: 5px 12px;
    text-align: center;
    text-shadow: 1px 1px 1px #076bd2;
}

button:hover,
input[type=button]:hover {
    background: rgb(9, 147, 240);
}

button:active,
input[type=button]:active {
    background: rgb(10, 118, 190);
}

button[disabled],
input[type=button][disabled] {
    background: none;
    border: 1px solid rgb(187, 181, 181);
    color: gray;
    text-shadow: none;
}

a {
    color: #2844FA;
    cursor: pointer;
    text-decoration: none;
}

a:hover,
a:focus {
    color: #1B29A4;
}

a:active {
    color: #000;
}
</style>
<div id="elementToShare" contenteditable style="margin-top:3px;">
    <h2>
        Audio + Canvas2D/HTML recording <br />using <a href="https://www.webrtc-experiment.com/RecordRTC/" contenteditable="false">RecordRTC</a> and <a href="https://www.webrtc-experiment.com/ffmpeg/" contenteditable="false">ffmpeg</a>!
    </h2>
    <h3>Content is edit-able.</h3>
    <input type="text" value="Type Text!" />
    <br />
    <textarea>resize textarea</textarea>
    <br />
    <br />
    <ol>
        <li>This demo uses <a href="https://www.webrtc-experiment.com/Pre-recorded-Media-Streaming/" contenteditable="false">HTML2Canvas Library</a> along with <a href="https://www.webrtc-experiment.com/RecordRTC/" contenteditable="false">RecordRTC</a> to record/encode HTML snapshots (webp images) in WebM container.</li>
        <li>It also uses <a href="https://www.webrtc-experiment.com/RecordRTC/" contenteditable="false">RecordRTC</a> to record audio in WAV container.</li>
        <li>Then it uses <a href="https://www.webrtc-experiment.com/ffmpeg/" contenteditable="false">ffmpeg-asm.js</a> to transcode and merge both WAV/WebM in single MP4 container.</li>
    </ol>
</div>
<div style="position: fixed;top;0;left: 20%;right: 20%;text-align: center;">
    <button id="start" contenteditable="false">Start Audio+Canvas Recording</button>
    <button id="stop" disabled contenteditable="false">Stop</button>
</div>
<script src="https://cdn.webrtc-experiment.com/screenshot.js">
</script>
<script src="https://cdn.webrtc-experiment.com/RecordRTC.js">
</script>
<script>
var elementToShare = document.getElementById('elementToShare');
var canvas2d = document.createElement('canvas');
var context = canvas2d.getContext('2d');
canvas2d.width = elementToShare.clientWidth;
canvas2d.height = elementToShare.clientHeight;
canvas2d.style.top = 0;
canvas2d.style.left = 0;
canvas2d.style.zIndex = -1;
(document.body || document.documentElement).appendChild(canvas2d);
var isRecordingStarted = false;
var isStoppedRecording = false;
(function looper() {
    if (!isRecordingStarted) {
        return setTimeout(looper, 500);
    }
    html2canvas(elementToShare, {
        grabMouse: true,
        onrendered: function(canvas) {
            context.clearRect(0, 0, canvas2d.width, canvas2d.height);
            context.drawImage(canvas, 0, 0, canvas2d.width, canvas2d.height);
            if (isStoppedRecording) {
                return;
            }
            setTimeout(looper, 1);
        }
    });
})();
var canvasRecorder = RecordRTC(canvas2d, {
    type: 'canvas'
});
var recordAudio;
document.getElementById('start').onclick = function() {
    this.disabled = true;
    navigator.getUserMedia({
        audio: true
    }, function(stream) {
        var audio = document.createElement('audio');
        audio.muted = true;
        audio.volume = 0;
        audio.src = URL.createObjectURL(stream);
        recordAudio = RecordRTC(stream, {
            type: 'audio'
        });
        isStoppedRecording = false;
        isRecordingStarted = true;
        canvasRecorder.startRecording();
        recordAudio.startRecording();
        document.getElementById('stop').disabled = false;
    }, function(error) {
        log(JSON.stringify(error));
    });
};
document.getElementById('stop').onclick = function() {
    this.disabled = true;
    recordAudio.stopRecording(function() {
        isStoppedRecording = true;
        canvasRecorder.stopRecording(function() {
            convertStreams(canvasRecorder.getBlob(), recordAudio.getBlob());
            log('<a href="' + workerPath + '" download="ffmpeg-asm.js">ffmpeg-asm.js</a> file download started. It is about 18MB in size; please be patient!');
        });
    });
    document.getElementById('start').disabled = false;
};
// var workerPath = location.href.replace(location.href.split('/').pop(), '') + 'ffmpeg_asm.js';
var workerPath = 'https://archive.org/download/ffmpeg_asm/ffmpeg_asm.js';

function processInWebWorker() {
    var blob = URL.createObjectURL(new Blob(['importScripts("' + workerPath + '");var now = Date.now;function print(text) {postMessage({"type" : "stdout","data" : text});};onmessage = function(event) {var message = event.data;if (message.type === "command") {var Module = {print: print,printErr: print,files: message.files || [],arguments: message.arguments || [],TOTAL_MEMORY: 268435456};postMessage({"type" : "start","data" : Module.arguments.join(" ")});postMessage({"type" : "stdout","data" : "Received command: " +Module.arguments.join(" ") +((Module.TOTAL_MEMORY) ? ".  Processing with " + Module.TOTAL_MEMORY + " bits." : "")});var time = now();var result = ffmpeg_run(Module);var totalTime = now() - time;postMessage({"type" : "stdout","data" : "Finished processing (took " + totalTime + "ms)"});postMessage({"type" : "done","data" : result,"time" : totalTime});}};postMessage({"type" : "ready"});'], {
        type: 'application/javascript'
    }));
    var worker = new Worker(blob);
    URL.revokeObjectURL(blob);
    return worker;
}
var worker;

function convertStreams(videoBlob, audioBlob) {
    var vab;
    var aab;
    var buffersReady;
    var workerReady;
    var posted = false;
    var fileReader1 = new FileReader();
    fileReader1.onload = function() {
        vab = this.result;
        if (aab) buffersReady = true;
        if (buffersReady && workerReady && !posted) postMessage();
    };
    var fileReader2 = new FileReader();
    fileReader2.onload = function() {
        aab = this.result;
        if (vab) buffersReady = true;
        if (buffersReady && workerReady && !posted) postMessage();
    };
    fileReader1.readAsArrayBuffer(videoBlob);
    fileReader2.readAsArrayBuffer(audioBlob);
    if (!worker) {
        worker = processInWebWorker();
    }
    worker.onmessage = function(event) {
        var message = event.data;
        if (message.type == "ready") {
            log('<a href="' + workerPath + '" download="ffmpeg-asm.js">ffmpeg-asm.js</a> file has been loaded.');
            workerReady = true;
            if (buffersReady)
                postMessage();
        } else if (message.type == "stdout") {
            log(message.data);
        } else if (message.type == "start") {
            log('<a href="' + workerPath + '" download="ffmpeg-asm.js">ffmpeg-asm.js</a> file received ffmpeg command.');
        } else if (message.type == "done") {
            log(JSON.stringify(message));
            var result = message.data[0];
            log(JSON.stringify(result));
            var blob = new Blob([result.data], {
                type: 'video/mp4'
            });
            log(JSON.stringify(blob));
            PostBlob(blob);
        }
    };
    var postMessage = function() {
        posted = true;
        worker.postMessage({
            type: 'command',
            arguments: [
                '-i', 'video.webm',
                '-i', 'audio.wav',
                '-c:v', 'mpeg4',
                '-c:a', 'vorbis',
                '-b:v', '6400k',
                '-b:a', '4800k',
                '-strict', 'experimental', 'output.mp4'
            ],
            files: [{
                data: new Uint8Array(vab),
                name: 'video.webm'
            }, {
                data: new Uint8Array(aab),
                name: "audio.wav"
            }]
        });
    };
}
var h2 = document.querySelector('h2');

function PostBlob(blob) {
    h2.innerHTML = '<a href="' + URL.createObjectURL(blob) + '" target="_blank" download="Recorded Audio+Canvas File.mp4">Download Recorded Audio+Canvas file in MP4 container and play in VLC player!</a>';
    h2.setAttribute('contenteditable', 'false');
}

function log(message) {
    h2.innerHTML = message;
    console.log(message);
}
</script>
<a href="https://www.webrtc-experiment.com/ffmpeg/" style="border-bottom: 1px solid red; color: red; font-size: 1.2em; position: absolute; right: 0; text-decoration: none; top: 0;">←Try other Ffmpeg.js and RecordRTC demos</a>
<script src="https://archive.org/download/ffmpeg_asm/ffmpeg_asm.js" async>
</script>