Skip to content

Instantly share code, notes, and snippets.

@shenningsgard
Created April 10, 2015 00:16
Show Gist options
  • Save shenningsgard/40663ce1d43ea8ed3efa to your computer and use it in GitHub Desktop.
Save shenningsgard/40663ce1d43ea8ed3efa to your computer and use it in GitHub Desktop.
My second attempt to record/playback text entry within an Ace editor and getUserMedia().
$(document).ready(function(){
ace.config.set('basePath', '../js/lib/ace');
var editor = ace.edit("editor");
editor.$blockScrolling = Infinity;
editor.setTheme("ace/theme/monokai");
editor.getSession().setMode("ace/mode/html");
editor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: false
});
var keystrokes = [];
var playbackEvents = [];
function recordKeystroke(e) {
var keyEvent = {
'data': e.data,
'timestamp': Date.now()
};
keystrokes.push(keyEvent);
console.log(e, keyEvent);
}
function captureState() {
var keyEvent = {
'data': {
'action': 'insertText',
'text': editor.getValue()
},
'timestamp': Date.now()
};
keystrokes.push(keyEvent);
}
function record() {
captureState();
editor.on("change", recordKeystroke);
}
window.record = record;
function stop() {
editor.off("change", recordKeystroke);
editor.setReadOnly(false);
if (playbackEvents[0]) {
for (i = 0; i < playbackEvents.length; i++) {
clearTimeout(playbackEvents[i]);
}
}
}
window.stop = stop;
function play() {
var startTime = keystrokes[0].timestamp,
i = 0;
console.log(playbackEvents);
editor.setValue("");
editor.setReadOnly(true);
editor.clearSelection();
for (i = 0; i < keystrokes.length; i++) {
createEvent(startTime, i);
}
}
window.play = play;
function createEvent(starttime, i) {
var k = keystrokes[i],
dT = 1;
var evt = setTimeout(function(){
editor.clearSelection();
switch (k.data.action) {
case 'insertText':
if (k.data.range) {
editor.moveCursorTo(k.data.range.start.row, k.data.range.start.column);
} else {
editor.moveCursorTo(0, 0);
}
editor.insert(k.data.text);
break;
case 'removeText':
editor.getSession().remove(k.data.range);
break;
case 'removeLines':
editor.getSession().remove(k.data.range);
break;
default:
console.log('unknown action: ' + k.data.action);
}
if (i == keystrokes.length - 1) {
editor.setReadOnly(false);
}
}, dT * (keystrokes[i].timestamp - starttime));
playbackEvents.push(evt);
}
function reset(){
stop();
keystrokes = [];
playbackEvents = [];
}
window.reset = reset;
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
<link href="https://html5-demos.appspot.com/static/common.css" rel="stylesheet" type="text/css" media="all">
<title>Record a getUserMedia() Session</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<link href="https;//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap-theme.min.css" rel="stylesheet">
<script type="text/javascript" src="http://www.stevehenningsgard.com/projects/js/lib/ace/ace.js"></script>
<script type="text/javascript" src="http://www.stevehenningsgard.com/projects/js/lib/ace/ext-language_tools.js"></script>
<script type="text/javascript" src="http://www.stevehenningsgard.com/projects/js/lib/ace/ext-keybinding_menu.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6">
<video class="recordClass" autoplay></video>
</div>
<div class="col-md-6" id="video-preview">
<video class="playbackClass"></video>
<span id="elapsed-time"></span>
</div>
</div>
<div class="row">
<div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-default btn-primary" id="camera-me">
<span class="glyphicon glyphicon-camera"></span>
1. Turn on camera
</button>
<button type="button" class="btn btn-default btn-danger" id="record-me">
<span class="glyphicon glyphicon-facetime-video"></span>
2. Record
</button>
<button type="button" class="btn btn-default btn-default" id="stop-me">
<span class="glyphicon glyphicon-stop"></span>
3. Stop
</button>
<button type="button" class="btn btn-default btn-success" id="play-me">
<span class="glyphicon glyphicon-play"></span>
4. Play
</button>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="editor" style="width:100%; height:300px;"></div>
</div>
</div>
</div>
<script src="https://html5-demos.appspot.com/static/getusermedia/whammy.min.js"></script>
<script>
(function(exports) {
exports.URL = exports.URL || exports.webkitURL;
exports.requestAnimationFrame = exports.requestAnimationFrame ||
exports.webkitRequestAnimationFrame || exports.mozRequestAnimationFrame ||
exports.msRequestAnimationFrame || exports.oRequestAnimationFrame;
exports.cancelAnimationFrame = exports.cancelAnimationFrame ||
exports.webkitCancelAnimationFrame || exports.mozCancelAnimationFrame ||
exports.msCancelAnimationFrame || exports.oCancelAnimationFrame;
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
var ORIGINAL_DOC_TITLE = document.title;
var video = document.querySelector('.recordClass');
var playbackVideo = document.querySelector('.playbackClass');
var canvas = document.createElement('canvas'); // offscreen canvas.
var rafId = null;
var startTime = null;
var endTime = null;
var frames = [];
function toggleActivateRecordButton() {
var b = $('#record-me');
b.textContent = b.disabled ? 'Record' : 'Recording...';
b.disabled = !b.disabled;
}
function turnOnCamera(e) {
//e.target.disabled = true;
$('#record-me').disabled = false;
video.controls = true;
var finishVideoSetup_ = function() {
// Note: video.onloadedmetadata doesn't fire in Chrome when using getUserMedia so
// we have to use setTimeout. See crbug.com/110938.
setTimeout(function() {
video.width = 320;//video.clientWidth;
video.height = 240;// video.clientHeight;
// Canvas is 1/2 for performance. Otherwise, getImageData() readback is
// awful 100ms+ as 640x480.
canvas.width = video.width;
canvas.height = video.height;
}, 1000);
};
navigator.getUserMedia({video: true, audio: false}, function(stream) {
video.src = window.URL.createObjectURL(stream);
finishVideoSetup_();
}, function(e) {
alert('Fine, you get a movie instead of your beautiful face ;)');
//video.src = 'Chrome_ImF.mp4';
finishVideoSetup_();
});
};
function recordVideo() {
var elapsedTime = $('#elasped-time');
var ctx = canvas.getContext('2d');
var CANVAS_HEIGHT = canvas.height;
var CANVAS_WIDTH = canvas.width;
record();
frames = []; // clear existing frames;
startTime = Date.now();
toggleActivateRecordButton();
$('#stop-me').disabled = false;
function drawVideoFrame_(time) {
rafId = requestAnimationFrame(drawVideoFrame_);
ctx.drawImage(video, 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
document.title = 'Recording...' + Math.round((Date.now() - startTime) / 1000) + 's';
var url = canvas.toDataURL('image/webp', 1);
frames.push(url);
}
rafId = requestAnimationFrame(drawVideoFrame_);
}
function stopVideo() {
cancelAnimationFrame(rafId);
endTime = Date.now();
$('#stop-me').disabled = true;
document.title = ORIGINAL_DOC_TITLE;
stop();
toggleActivateRecordButton();
console.log('frames captured: ' + frames.length + ' => ' +
((endTime - startTime) / 1000) + 's video');
var fps = frames.length / ( (endTime - startTime) / 1000 );
console.log("fps: " + fps);
embedVideoPreview(fps);
}
function playVideo() {
play();
playbackVideo.play();
}
function embedVideoPreview(fps) {
var url = null;
var video = playbackVideo;
video.autoplay = false;
video.controls = true;
video.loop = false;
video.style.width = canvas.width + 'px';
video.style.height = canvas.height + 'px';
window.URL.revokeObjectURL(video.src);
if (!url) {
var webmBlob = Whammy.fromImageArray(frames, ( fps || (1000 / 55) ));
url = window.URL.createObjectURL(webmBlob);
}
video.src = url;
}
function initEvents() {
$('#camera-me').on('click', turnOnCamera);
$('#record-me').on('click', recordVideo);
$('#stop-me').on('click', stopVideo);
$('#play-me').on('click', playVideo);
}
initEvents();
exports.$ = $;
})(window);
</script>
<script src="http://www.stevehenningsgard.com/projects/coderecorder/app.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment