-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Photobooth code to optimized video playback/capture
- Loading branch information
Michael Neil
committed
May 5, 2016
0 parents
commit b838042
Showing
9 changed files
with
335 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
.idea |
This file contains 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
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' | ||
]); | ||
|
||
|
||
}; | ||
|
||
|
This file contains 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
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> |
This file contains 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
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 | ||
}; |
This file contains 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
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 || {}); |
This file contains 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
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> | ||
`); |
This file contains 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
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 || {}); |
This file contains 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
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; | ||
} |
This file contains 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
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" | ||
} | ||
} |