/**
* @project js-webcam-screenshot <http://github-com/mlocati/js-webcam-screenshot>
* @author Michele Locati <mlocati@gmail.com>
* @license
* in English: do what you want with this code, but don't sue me for problems
* in Legal: MIT license (http://opensource.org/licenses/MIT)
*
* This code is based on https://github.com/codepo8/interaction-cam
* by Christian Heilmann
*/
/* jshint unused:vars, undef:true, browser:true */
/* global jQuery */
(function(w, $) {
'use strict';
var
getUserMedia = w.navigator.getUserMedia || w.navigator.webkitGetUserMedia || w.navigator.mozGetUserMedia || w.navigator.msGetUserMedia || null,
isSupported = (getUserMedia && w.FormData) ? true : false;
if(isSupported) {
w.navigator._webcamScreenshotgetUserMedia = getUserMedia;
}
/** The namespace of js-webcam-screenshot.
* @global
* @namespace
*/
var WebcamScreenshot = {
/** The current version of WebcamScreenshot.
* @static
* @constant
* @default
* @type {string}
*/
VERSION: '0.1.0',
/** The default name of the image field to be posted.
* @static
* @default
* @type {string}
*/
DEFAULT_POST_FIELDNAME: 'image',
/** Returns <code>true</code> if the browser is supported, <code>false</code> otherwise.
* @return {boolean}
*/
getIsSpported: function() {
return isSupported;
},
/** Possible code values passed to the [<code>callback</code>]{@link WebcamScreenshot.goBeforePostCallback} of the [<code>go()</code> method]{@link WebcamScreenshot.go}.
* @static
* @readonly
* @enum {number}
*/
RC: {
/** All good. */
OK: 0,
/** User cancelled. */
USER_CANCELLED: 1,
/** Browser is not supported. */
UNSUPPORTED_BROWSER: 2,
/** Error accessing the webcam. */
CANT_ACCESS_WEBCAM: 3,
/** Error thrown by the callback called just before posting data. */
SAVE_CALLBACKERROR: 4,
/** Error while posting the image. */
SAVE_FAILED: 5
},
/** Take a shot with the webcam.
* @param {WebcamScreenshot.goOptions} [options] - Options.
* @param {WebcamScreenshot.goCallback} [callback] - A function that will be called at the end.
* @static
*/
go: function(options, callback) {
if(!callback) {
callback = function() {};
}
if(!isSupported) {
callback(WebcamScreenshot.RC.UNSUPPORTED_BROWSER, 'Browser is not supported.');
return;
}
options = $.extend(true, {parent: w.document.body, width: 300, cancelText: 'Cancel', takeText: 'Take it', dialogFramework: '', dialogTitle: 'Screenshot from webcam'}, options || {});
switch(options.dialogFramework) {
case 'bs2':
case 'bs3':
case 'jQueryUI':
break;
default:
options.dialogFramework = '';
break;
}
var
video = w.document.createElement('video'),
$shadow = null,
$dialog = null,
height = null,
currentStream = null,
resize = function() {
if($shadow) {
$shadow.css({width: $(w).width(), height: $(w).height()});
}
if($dialog && (!options.dialogFramework)) {
$dialog.css('left', ($(w).width() - options.width) / 2 + 'px');
}
},
dispose = function() {
$(w).off('resize', resize);
if($shadow) {
$shadow.remove();
$shadow = null;
}
if($dialog) {
switch(options.dialogFramework) {
case 'bs2':
case 'bs3':
$dialog.modal('hide').remove();
break;
case 'jQueryUI':
$dialog.dialog('destroy');
break;
default:
$dialog.remove();
break;
}
$dialog = null;
}
if(currentStream) {
try {
currentStream.stop();
}
catch(e) {
}
currentStream = null;
}
}
;
var doCancel = function() {
dispose();
callback(WebcamScreenshot.RC.USER_CANCELLED, 'User cancelled.');
};
var doTake = function() {
var i;
if(options.canvas) {
var canvasList = [];
if(options.canvas instanceof $) {
canvasList = options.canvas;
}
else if (options.canvas instanceof Array) {
canvasList = options.canvas;
}
else {
canvasList = [options.canvas];
}
for(i = 0; i < canvasList.length; i++) {
canvasList[i].setAttribute('width', options.width);
canvasList[i].setAttribute('height', height);
canvasList[i].getContext('2d').drawImage(video, 0, 0, options.width, height);
}
}
if(options.postTo) {
var canvas = w.document.createElement('canvas'), formData = new w.FormData();
canvas.setAttribute('width', options.width);
canvas.setAttribute('height', height);
canvas.getContext('2d').drawImage(video, 0, 0, options.width, height);
WebcamScreenshot.canvasToFormData(formData, options.postFieldname || WebcamScreenshot.DEFAULT_POST_FIELDNAME, canvas, options.postImageFormat);
if(options.onBeforePost) {
try {
options.onBeforePost(formData);
}
catch(err) {
dispose();
callback(WebcamScreenshot.RC.SAVE_CALLBACKERROR, err.message || err.name || err.toString());
return;
}
}
var ajaxData = {
url: options.postTo,
data: formData,
cache: false,
contentType: false,
processData: false,
type: 'POST'
};
if(options.postReturnDataType) {
ajaxData.dataType = options.postReturnDataType;
}
$.ajax(ajaxData)
.fail(function(jqXHR, textStatus, errorThrown) {
w.alert(errorThrown);
})
.success(function(data) {
dispose();
callback(WebcamScreenshot.RC.OK, data);
})
;
return;
}
dispose();
callback(WebcamScreenshot.RC.OK);
};
$shadow = $('<div class="webcamscreenshot-shadow" />');
$(w).on('resize', resize);
resize();
$(options.parent).append($shadow);
switch(options.dialogFramework) {
case 'bs2':
$dialog = $([
'<div class="modal hide" role="dialog">',
'<div class="modal-header">',
'<button type="button" class="close">×</button>',
'<h3></h3>',
'</div>',
'<div class="modal-body"></div>',
'<div class="modal-footer"></div>',
'</div>',
''].join(''));
$dialog.find('button.close').on('click', function() { doCancel(); });
$dialog.find('.modal-header h3').text(options.dialogTitle);
$dialog.find('.modal-body').append(video);
$dialog.find('.modal-footer')
.append($('<button class="btn" />')
.html(options.cancelText)
.on('click', function() {
doCancel();
})
)
.append($('<button class="btn btn-primary" />')
.html(options.takeText)
.on('click', function() {
doTake();
})
)
;
$(options.parent).append($dialog);
break;
case 'bs3':
$dialog = $([
'<div class="modal">',
'<div class="modal-dialog">',
'<div class="modal-content">',
'<div class="modal-header">',
'<button type="button" class="close">×</button>',
'<h4 class="modal-title"></h4>',
'</div>',
'<div class="modal-body"></div>',
'<div class="modal-footer"></div>',
'</div>',
'</div>',
'</div>',
''].join(''));
$dialog.find('button.close').on('click', function() { doCancel(); });
$dialog.find('.modal-title').text(options.dialogTitle);
$dialog.find('.modal-body').append(video);
$dialog.find('.modal-footer')
.append($('<button type="button" class="btn btn-default" />')
.html(options.cancelText)
.on('click', function() {
doCancel();
})
)
.append($('<button type="button" class="btn btn-primary" />')
.html(options.takeText)
.on('click', function() {
doTake();
})
)
;
$(options.parent).append($dialog);
break;
case 'jQueryUI':
$dialog = $('<div />');
$dialog.append(video);
$dialog.dialog({
appendTo: options.parent,
autoOpen: false,
closeText: options.cancelText,
modal: true,
resizable: false,
title: options.dialogTitle,
width: options.width + 40,
close: function() {
dispose();
},
buttons: [
{
text: options.cancelText,
click: function() {
doCancel();
}
},
{
text: options.takeText,
click: function() {
doTake();
}
}
]
});
break;
default:
$dialog = $('<div class="webcamscreenshot-dialog" />');
$(options.parent).append($dialog);
$dialog
.append(video)
.append($('<div class="webcamscreenshot-buttons" />')
.append($('<button />')
.html(options.takeText)
.on('click', function() {
doTake();
})
)
.append($('<button />')
.html(options.cancelText)
.on('click', function() {
doCancel();
})
)
)
;
break;
}
video.addEventListener(
'canplay',
function(e) {
height = Math.ceil(options.width * video.videoHeight / video.videoWidth);
video.setAttribute('width', options.width);
video.setAttribute('height', height);
},
false
);
w.navigator._webcamScreenshotgetUserMedia(
{
video: true,
audio: false
},
function(stream) {
currentStream = stream;
if(w.navigator.mozGetUserMedia) {
video.mozSrcObject = currentStream;
}
else {
var url = w.URL || w.webkitURL;
video.src = (url && url.createObjectURL) ? url.createObjectURL(currentStream) : currentStream;
}
video.play();
switch(options.dialogFramework) {
case 'bs2':
case 'bs3':
$shadow.remove();
$shadow = null;
$dialog.modal('show');
break;
case 'jQueryUI':
$shadow.remove();
$shadow = null;
$dialog.dialog('open');
break;
}
},
function(err) {
dispose();
callback(WebcamScreenshot.RC.CANT_ACCESS_WEBCAM, err.message || err.name || err.toString());
}
);
},
/** Helper function to add the image represented in a <code><canvas></code> to a <code>FormData</code> instance for posting.
* @param {FormData} formData - The <code>FormData</code> instance to add the image to.
* @param {string} fieldName - The name of the field that will contain the image data.
* @param {HTMLCanvasElement} canvas - A <code><canvas></code> DOM element.
* @param {string} [imageFormat='png'] The format of the image (accepted values: <code>'jpg'</code>, <code>'png'</code>).
*/
canvasToFormData: function(formData, fieldName, canvas, imageFormat) {
var mimeType, filename;
switch(imageFormat) {
case 'jpg':
mimeType = 'image/jpeg';
filename = 'image.jpg';
break;
case 'png':
/* falls through */
default:
mimeType = 'image/png';
filename = 'image.png';
break;
}
var done = function(blob) {
formData.append(fieldName, blob, filename);
};
var i, L;
for(L = ['getAsFile', 'getAsBlob', 'webkitGetAsFile', 'webkitGetAsBlob', 'mozGetAsFile', 'mozGetAsBlob', 'msGetAsFile', 'msGetAsBlob'], i = 0; i < L.length; i++) {
if(canvas[L[i]]) {
done(canvas[L[i]](filename));
return;
}
}
var chunks = canvas.toDataURL(mimeType).split(',');
var binaryString;
if (chunks[0].indexOf('base64') >= 0) {
binaryString = w.atob(chunks[1]);
}
else {
binaryString = w.unescape(chunks[1]);
}
var length = binaryString.length;
if(w.Blob) {
var array = new Array(length);
for(i = 0; i < length; i++) {
array[i] = binaryString.charCodeAt(i);
}
done(new w.Blob([new w.Uint8Array(array)], {type: mimeType}));
}
else {
var arrayBuffer = new w.ArrayBuffer(length), byteArray = new w.Uint8Array(arrayBuffer);
for (i = 0; i < length; i++) {
byteArray[i] = binaryString.charCodeAt(i);
}
var bb = new w.BlobBuilder();
bb.append(arrayBuffer);
done(bb.getBlob(mimeType));
}
}
};
/** The options for [<code>go()</code> method]{@link WebcamScreenshot.go}
* @typedef {Object} WebcamScreenshot.goOptions
* @property {number} [width=300] - The width of the image to be taken (in pixels).
* @property {string} [cancelText='Cancel'] - The text (html is accepted) to show for the "Cancel" button.
* @property {string} [takeText='Take it'] - The text (html is accepted) to show for the "Take it" button.
* @property {HTMLElement} [parent=window.document.body] - The DOM node to which we'll append our DOM nodes.
* @property {HTMLCanvasElement|HTMLCanvasElement[]|jQuery} [canvas=null] - The <canvas> DOM node to which we'll render the taken shot (it can also be a jQuery object or an array of DOM nodes).
* @property {string} [postTo] - If specified, we'll post the taken shot to this URL (as a form POST).
* @property {string} [postFieldname] - If postTo, this will be the name of the field containing the image (defaults to WebcamScreenshot.DEFAULT_POST_FIELDNAME).
* @property {string} [postImageFormat='png'] - The file format of the data to send (accepted values: <code>'jpg'</code>, <code>'png'</code>).
* @property {string} [postReturnDataType] - The type of data that you're expecting back from the server. For possible values see thee description of the <code>dataType</code> options of [<code>jQuery.ajax()</code>]{@link http://api.jquery.com/jquery.ajax/}.
* @property {WebcamScreenshot.goBeforePostCallback} [onBeforePost] - A function that will be called before posting the image.
* @property {string} [dialogFramework] - The optional framework to use to show the dialog. Allowed values:
* <ul>
* <li><code>'bs2'</code> for Bootstrap 2</li>
* <li><code>'bs3'</code> for Bootstrap 3</li>
* <li><code>'jQueryUI'</code> for jQuery UI</li>
* </ul>
* @property {string} [dialogTitle='Screenshot from webcam'] - The title of the dialog (used only if <code>dialogFramework</code> is specified).
*/
/** This callback is called just before POSTing the image.
* @callback WebcamScreenshot.goBeforePostCallback
* @param {FormData} formData - The <code>FormData</code> instance that's going to be posted. Useful to add other values to the POST (example: <code>formData.append('myfield', 'myvalue');</code>).
*/
/** Callback called after the [<code>go()</code> method]{@link WebcamScreenshot.go} ends.
* @callback WebcamScreenshot.goCallback
* @param {WebcamScreenshot.RC} code - One of the <code>WebcamScreenshot.RC</code> values.
* @param {(string|Object)} [result] - This value may be:
* <ul>
* <li>If <code>code</code> is not <code>WebcamScreenshot.RC.OK</code> → <code>result</code> will contain error description ({string}).</li>
* <li>If <code>code</code> is <code>WebcamScreenshot.RC.OK</code>:
* <ul>
* <li>If the <code>postTo</code> option has been specified → <code>result</code> will contain the response from the server.</li>
* <li>If the <code>postTo</code> optionw wasn't set → <code>result</code> will be <code>undefined</code>.</li>
* </ul>
* </li>
* </ul>
*/
w.WebcamScreenshot = WebcamScreenshot;
})(window, jQuery);