Skip to content

Commit

Permalink
Photobooth code to optimized video playback/capture
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Neil committed May 5, 2016
0 parents commit b838042
Show file tree
Hide file tree
Showing 9 changed files with 335 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.idea
26 changes: 26 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';
var SERVER_PORT = 9001;

module.exports = function (grunt) {

grunt.loadNpmTasks('grunt-serve');

grunt.initConfig({

serve: {
options: {
port: SERVER_PORT
},
path: './app'
}

});

grunt.registerTask('default', [
'serve'
]);


};


17 changes: 17 additions & 0 deletions app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE>
<html>
<head>
<script src="/app/scripts/config.js"></script>
<script src="/app/scripts/variables.js"></script>
<link rel="stylesheet" href="/app/styles.css">
</head>
<body>

<a id="start-btn" href="#capture">Start</a>
<div id="video-container"></div>

<script src="/app/scripts/video-utils.js"></script>
<script src="/app/scripts/main.js"></script>

</body>
</html>
7 changes: 7 additions & 0 deletions app/scripts/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var config = {
dims: [960, 720],
facing: 'user', // user | environment - front or rear facing camera. Defaults to user (front)
countdown: true, // use the countdown
countdown_from: 9, // number of seconds to show in the countdown
shutterspeed: 50 // how long the flash should display
};
40 changes: 40 additions & 0 deletions app/scripts/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
(function(exports){

var v = exports.video;
if( !v ) throw new Error('video is not defined');

var video = v.get("#video-stream", config.dims);
navigator.getUserMedia = v.getUserMedia();

if (!navigator.getUserMedia) {
throw new Error('cannot get user media');
}

navigator.getUserMedia({video: {
frameRate: { ideal: 60, max: 60 } ,
//facingMode: config.facing
}}, v.stream.bind(video), v.error);

function takePicture(){
console.log('aw snap');
v.flash();
var canvas = v.snapshot(video, true);
console.log(canvas);

document.body.appendChild(canvas);
}

function onCountdown(time){
console.log(time);
}

var startBtn = document.getElementById('start-btn');
startBtn.addEventListener('click', function(){
video.paused && video.play();
if( config.countdown ){
v.countdown(config.countdown_from, onCountdown, takePicture);
}

});

})(exports || {});
14 changes: 14 additions & 0 deletions app/scripts/variables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
var exports = {};

/**
* write config to css to keep overlays and shutterspeeds consitently configured in 1 place
*/
document.write(`
<style>
:root {
--photbooth-camera-width: ${config.dims[0]}px;
--photbooth-camera-height: ${config.dims[1]}px;
--photbooth-camera-flash: opacity ${config.shutterspeed}ms ease-in-out
}
</style>
`);
158 changes: 158 additions & 0 deletions app/scripts/video-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
(function(exports) {
"use strict";

var video = {};

/**
* Create object with id
* @param {string} el html element
* @param {string} id id
* @returns {Element}
*/
function createObjId(el, id){
var obj = document.createElement(el);
obj.setAttribute('id', id);
return obj;
}
/**
* Create our video object
* @returns {Element}
*/
function createVideo(){
var obj = createObjId('video', 'video-stream');
obj.autoplay = true;
obj.src = '';
return obj;
}
/**
* Create cowndown element
* @returns {Element}
*/
function createCountdown(){
var c = createObjId('div', 'countdown-container');
c.setAttribute('class', 'hidden');
return c;
}

var container = document.getElementById('video-container');

if( !container )
throw new Error('create html element #video-container to use photobooth');

var countdown = createCountdown();
var countdownDisplay = createObjId('h1', 'countdown-display');
var flash = createObjId('div', 'camera-flash');

// put it all in the dom
container.appendChild(createVideo());
container.appendChild(flash);
countdown.appendChild(countdownDisplay);
container.appendChild(countdown);

/**
* Ask for permission to use camera
* @returns {*}
*/
video.getUserMedia = function () {
return navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia ||
navigator.oGetUserMedia
};

/**
* Get the video element and optionally set dims
* @param selector
* @param dims
* @returns {Element}
*/
video.get = function (selector, dims) {

var video = document.querySelector(selector);
if (dims && dims.length === 2 && typeof dims !== 'string') {
video.width = dims[0];
video.height = dims[1];
}
return video;
};

/**
* Set the video source when the user grants permission
* @param stream
*/
video.stream = function(stream) {
this.src = window.URL.createObjectURL(stream);
};

/**
* Well crap, they clicked deny video access
* @param e
*/
video.error = function(e) {
throw e;
};

/**
* Simulate camera flash
*/
video.flash = function(interval){
flash.className = 'display';
setTimeout(function(){
flash.removeAttribute('class');
}, interval || config.shutterspeed);
};

/**
* Recursive countdown function
* @param {Number} from countdown from this number
* @param {function} change broadcast each time the from changes
* @param {callback} done broadcast when the countdown is over
* @returns {*}
*/
video.countdown = function(from, change, done){

if( from === 0 ){
countdown.className = 'hidden';
change(from);
return done();
}
if( typeof from === 'function'){
change = function(){};
done = from;
from = config.countdown_from;
}
if( !done && typeof change === 'function' ){
done = change;
change = function(){};
}

countdown.removeChild(countdownDisplay);
countdownDisplay = createObjId('h1', 'countdown-display');
countdown.appendChild(countdownDisplay);
setTimeout(function(){
countdownDisplay.className = 'countdown time-'+from;
}, 30);
countdownDisplay.innerHTML = from;
change(from);
countdown.removeAttribute('class');

return setTimeout(function(){
video.countdown(--from, change, done);
}, 1000);
};

video.snapshot = function(v, pause){

var canvas = document.createElement('canvas');
canvas.width = config.dims[0];
canvas.height = config.dims[1];
var context = canvas.getContext('2d');
pause && v.pause();
context.drawImage(v,0,0,config.dims[0], config.dims[1]);
return canvas;
};

exports.video = video;

})(exports || {});
63 changes: 63 additions & 0 deletions app/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
:root{
--photobooth-countdown: transform 800ms ease-out;
}
/**
* holds the video object and all it's assets
*/
#video-container{
display: block;
position: relative;
width: var(--photbooth-camera-width);
height: var(--photbooth-camera-height);
text-align: center;
}
/**
* Contains the countdown
*/
#countdown-container{
display: block;
position: absolute;
top: 50%;
left: 50%;
color: #fff;
font-family: sans-serif;
}
/**
* off state for countdown
*/
#countdown-container.hidden{
display: none;
}
#countdown-display{
margin: 0px;
font-size: 200px;
line-height: 1px;
position: relative;
left: -50px;
transition: var(--photobooth-countdown);
-moz-transition: var(--photobooth-countdown);
-webkit-transition: var(--photobooth-countdown);
}
#countdown-display.countdown{
transform: scaleX(0) scaleY(0);
}
/**
* Camera flash default state to transition from/to
*/
#camera-flash{
opacity: 0;
top: 0;
background: #fff;
position:absolute;
width: var(--photbooth-camera-width);
height: var(--photbooth-camera-height);
transition: var(--photbooth-camera-flash);
-moz-transition: var(--photbooth-camera-flash);
-webkit-transition: var(--photbooth-camera-flash);
}
/**
* Show the camera flash so css can animate it
*/
#camera-flash.display{
opacity: 1;
}
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "photobooth",
"version": "0.1",
"devDependencies": {
"grunt": "^1.0.1",
"grunt-serve": "^0.1.6"
}
}

0 comments on commit b838042

Please sign in to comment.