webcam.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. // WebcamJS v1.0.16
  2. // Webcam library for capturing JPEG/PNG images in JavaScript
  3. // Attempts getUserMedia, falls back to Flash
  4. // Author: Joseph Huckaby: http://github.com/jhuckaby
  5. // Based on JPEGCam: http://code.google.com/p/jpegcam/
  6. // Copyright (c) 2012 - 2016 Joseph Huckaby
  7. // Licensed under the MIT License
  8. (function(window) {
  9. var _userMedia;
  10. // declare error types
  11. // inheritance pattern here:
  12. // https://stackoverflow.com/questions/783818/how-do-i-create-a-custom-error-in-javascript
  13. function FlashError() {
  14. var temp = Error.apply(this, arguments);
  15. temp.name = this.name = "FlashError";
  16. this.stack = temp.stack;
  17. this.message = temp.message;
  18. }
  19. function WebcamError() {
  20. var temp = Error.apply(this, arguments);
  21. temp.name = this.name = "WebcamError";
  22. this.stack = temp.stack;
  23. this.message = temp.message;
  24. }
  25. IntermediateInheritor = function() {};
  26. IntermediateInheritor.prototype = Error.prototype;
  27. FlashError.prototype = new IntermediateInheritor();
  28. WebcamError.prototype = new IntermediateInheritor();
  29. var Webcam = {
  30. version: '1.0.16',
  31. // globals
  32. protocol: location.protocol.match(/https/i) ? 'https' : 'http',
  33. loaded: false, // true when webcam movie finishes loading
  34. live: false, // true when webcam is initialized and ready to snap
  35. userMedia: true, // true when getUserMedia is supported natively
  36. params: {
  37. width: 0,
  38. height: 0,
  39. dest_width: 0, // size of captured image
  40. dest_height: 0, // these default to width/height
  41. image_format: 'jpeg', // image format (may be jpeg or png)
  42. jpeg_quality: 90, // jpeg image quality from 0 (worst) to 100 (best)
  43. enable_flash: true, // enable flash fallback,
  44. force_flash: false, // force flash mode,
  45. flip_horiz: false, // flip image horiz (mirror mode)
  46. fps: 30, // camera frames per second
  47. upload_name: 'webcam', // name of file in upload post data
  48. constraints: null, // custom user media constraints,
  49. swfURL: '', // URI to webcam.swf movie (defaults to the js location)
  50. flashNotDetectedText: 'ERROR: No Adobe Flash Player detected. Webcam.js relies on Flash for browsers that do not support getUserMedia (like yours).',
  51. noInterfaceFoundText: 'No supported webcam interface found.',
  52. unfreeze_snap: true // Whether to unfreeze the camera after snap (defaults to true)
  53. },
  54. errors: {
  55. FlashError: FlashError,
  56. WebcamError: WebcamError
  57. },
  58. hooks: {}, // callback hook functions
  59. init: function() {
  60. // initialize, check for getUserMedia support
  61. var self = this;
  62. // Setup getUserMedia, with polyfill for older browsers
  63. // Adapted from: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
  64. this.mediaDevices = (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) ?
  65. navigator.mediaDevices : ((navigator.mozGetUserMedia || navigator.webkitGetUserMedia) ? {
  66. getUserMedia: function(c) {
  67. return new Promise(function(y, n) {
  68. (navigator.mozGetUserMedia ||
  69. navigator.webkitGetUserMedia).call(navigator, c, y, n);
  70. });
  71. }
  72. } : null);
  73. window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
  74. this.userMedia = this.userMedia && !!this.mediaDevices && !!window.URL;
  75. // Older versions of firefox (< 21) apparently claim support but user media does not actually work
  76. if (navigator.userAgent.match(/Firefox\D+(\d+)/)) {
  77. if (parseInt(RegExp.$1, 10) < 21) this.userMedia = null;
  78. }
  79. // Make sure media stream is closed when navigating away from page
  80. if (this.userMedia) {
  81. window.addEventListener( 'beforeunload', function(event) {
  82. self.reset();
  83. } );
  84. }
  85. },
  86. attach: function(elem) {
  87. // create webcam preview and attach to DOM element
  88. // pass in actual DOM reference, ID, or CSS selector
  89. if (typeof(elem) == 'string') {
  90. elem = document.getElementById(elem) || document.querySelector(elem);
  91. }
  92. if (!elem) {
  93. return this.dispatch('error', new WebcamError("Could not locate DOM element to attach to."));
  94. }
  95. this.container = elem;
  96. elem.innerHTML = ''; // start with empty element
  97. // insert "peg" so we can insert our preview canvas adjacent to it later on
  98. var peg = document.createElement('div');
  99. elem.appendChild( peg );
  100. this.peg = peg;
  101. // set width/height if not already set
  102. if (!this.params.width) this.params.width = elem.offsetWidth;
  103. if (!this.params.height) this.params.height = elem.offsetHeight;
  104. // make sure we have a nonzero width and height at this point
  105. if (!this.params.width || !this.params.height) {
  106. return this.dispatch('error', new WebcamError("No width and/or height for webcam. Please call set() first, or attach to a visible element."));
  107. }
  108. // set defaults for dest_width / dest_height if not set
  109. if (!this.params.dest_width) this.params.dest_width = this.params.width;
  110. if (!this.params.dest_height) this.params.dest_height = this.params.height;
  111. this.userMedia = _userMedia === undefined ? this.userMedia : _userMedia;
  112. // if force_flash is set, disable userMedia
  113. if (this.params.force_flash) {
  114. _userMedia = this.userMedia;
  115. this.userMedia = null;
  116. }
  117. // check for default fps
  118. if (typeof this.params.fps !== "number") this.params.fps = 30;
  119. // adjust scale if dest_width or dest_height is different
  120. var scaleX = this.params.width / this.params.dest_width;
  121. var scaleY = this.params.height / this.params.dest_height;
  122. if (this.userMedia) {
  123. // setup webcam video container
  124. var video = document.createElement('video');
  125. video.setAttribute('autoplay', 'autoplay');
  126. video.style.width = '' + this.params.dest_width + 'px';
  127. video.style.height = '' + this.params.dest_height + 'px';
  128. if ((scaleX != 1.0) || (scaleY != 1.0)) {
  129. elem.style.overflow = 'hidden';
  130. video.style.webkitTransformOrigin = '0px 0px';
  131. video.style.mozTransformOrigin = '0px 0px';
  132. video.style.msTransformOrigin = '0px 0px';
  133. video.style.oTransformOrigin = '0px 0px';
  134. video.style.transformOrigin = '0px 0px';
  135. video.style.webkitTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
  136. video.style.mozTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
  137. video.style.msTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
  138. video.style.oTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
  139. video.style.transform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
  140. }
  141. // add video element to dom
  142. elem.appendChild( video );
  143. this.video = video;
  144. // ask user for access to their camera
  145. var self = this;
  146. this.mediaDevices.getUserMedia({
  147. "audio": false,
  148. "video": this.params.constraints || {
  149. mandatory: {
  150. minWidth: this.params.dest_width,
  151. minHeight: this.params.dest_height
  152. }
  153. }
  154. })
  155. .then( function(stream) {
  156. // got access, attach stream to video
  157. video.onloadedmetadata = function(e) {
  158. self.stream = stream;
  159. self.loaded = true;
  160. self.live = true;
  161. self.dispatch('load');
  162. self.dispatch('live');
  163. self.flip();
  164. };
  165. video.src = window.URL.createObjectURL( stream ) || stream;
  166. })
  167. .catch( function(err) {
  168. // JH 2016-07-31 Instead of dispatching error, now falling back to Flash if userMedia fails (thx @john2014)
  169. // JH 2016-08-07 But only if flash is actually installed -- if not, dispatch error here and now.
  170. if (self.params.enable_flash && self.detectFlash()) {
  171. setTimeout( function() { self.params.force_flash = 1; self.attach(elem); }, 1 );
  172. }
  173. else {
  174. self.dispatch('error', err);
  175. }
  176. });
  177. }
  178. else if (this.params.enable_flash && this.detectFlash()) {
  179. // flash fallback
  180. window.Webcam = Webcam; // needed for flash-to-js interface
  181. var div = document.createElement('div');
  182. div.innerHTML = this.getSWFHTML();
  183. elem.appendChild( div );
  184. }
  185. else {
  186. this.dispatch('error', new WebcamError( this.params.noInterfaceFoundText ));
  187. }
  188. // setup final crop for live preview
  189. if (this.params.crop_width && this.params.crop_height) {
  190. var scaled_crop_width = Math.floor( this.params.crop_width * scaleX );
  191. var scaled_crop_height = Math.floor( this.params.crop_height * scaleY );
  192. elem.style.width = '' + scaled_crop_width + 'px';
  193. elem.style.height = '' + scaled_crop_height + 'px';
  194. elem.style.overflow = 'hidden';
  195. elem.scrollLeft = Math.floor( (this.params.width / 2) - (scaled_crop_width / 2) );
  196. elem.scrollTop = Math.floor( (this.params.height / 2) - (scaled_crop_height / 2) );
  197. }
  198. else {
  199. // no crop, set size to desired
  200. elem.style.width = '' + this.params.width + 'px';
  201. elem.style.height = '' + this.params.height + 'px';
  202. }
  203. },
  204. reset: function() {
  205. // shutdown camera, reset to potentially attach again
  206. if (this.preview_active) this.unfreeze();
  207. // attempt to fix issue #64
  208. this.unflip();
  209. if (this.userMedia) {
  210. if (this.stream) {
  211. if (this.stream.getVideoTracks) {
  212. // get video track to call stop on it
  213. var tracks = this.stream.getVideoTracks();
  214. if (tracks && tracks[0] && tracks[0].stop) tracks[0].stop();
  215. }
  216. else if (this.stream.stop) {
  217. // deprecated, may be removed in future
  218. this.stream.stop();
  219. }
  220. }
  221. delete this.stream;
  222. delete this.video;
  223. }
  224. if (this.userMedia !== true) {
  225. // call for turn off camera in flash
  226. var movie = this.getMovie();
  227. if (movie && movie._releaseCamera) movie._releaseCamera();
  228. }
  229. if (this.container) {
  230. this.container.innerHTML = '';
  231. delete this.container;
  232. }
  233. this.loaded = false;
  234. this.live = false;
  235. },
  236. set: function() {
  237. // set one or more params
  238. // variable argument list: 1 param = hash, 2 params = key, value
  239. if (arguments.length == 1) {
  240. for (var key in arguments[0]) {
  241. this.params[key] = arguments[0][key];
  242. }
  243. }
  244. else {
  245. this.params[ arguments[0] ] = arguments[1];
  246. }
  247. },
  248. on: function(name, callback) {
  249. // set callback hook
  250. name = name.replace(/^on/i, '').toLowerCase();
  251. if (!this.hooks[name]) this.hooks[name] = [];
  252. this.hooks[name].push( callback );
  253. },
  254. off: function(name, callback) {
  255. // remove callback hook
  256. name = name.replace(/^on/i, '').toLowerCase();
  257. if (this.hooks[name]) {
  258. if (callback) {
  259. // remove one selected callback from list
  260. var idx = this.hooks[name].indexOf(callback);
  261. if (idx > -1) this.hooks[name].splice(idx, 1);
  262. }
  263. else {
  264. // no callback specified, so clear all
  265. this.hooks[name] = [];
  266. }
  267. }
  268. },
  269. dispatch: function() {
  270. // fire hook callback, passing optional value to it
  271. var name = arguments[0].replace(/^on/i, '').toLowerCase();
  272. var args = Array.prototype.slice.call(arguments, 1);
  273. if (this.hooks[name] && this.hooks[name].length) {
  274. for (var idx = 0, len = this.hooks[name].length; idx < len; idx++) {
  275. var hook = this.hooks[name][idx];
  276. if (typeof(hook) == 'function') {
  277. // callback is function reference, call directly
  278. hook.apply(this, args);
  279. }
  280. else if ((typeof(hook) == 'object') && (hook.length == 2)) {
  281. // callback is PHP-style object instance method
  282. hook[0][hook[1]].apply(hook[0], args);
  283. }
  284. else if (window[hook]) {
  285. // callback is global function name
  286. window[ hook ].apply(window, args);
  287. }
  288. } // loop
  289. return true;
  290. }
  291. else if (name == 'error') {
  292. if ((args[0] instanceof FlashError) || (args[0] instanceof WebcamError)) {
  293. message = args[0].message;
  294. } else {
  295. message = "Could not access webcam: " + args[0].name + ": " +
  296. args[0].message + " " + args[0].toString();
  297. }
  298. // default error handler if no custom one specified
  299. alert("Webcam.js Error: " + message);
  300. }
  301. return false; // no hook defined
  302. },
  303. setSWFLocation: function(value) {
  304. // for backward compatibility.
  305. this.set('swfURL', value);
  306. },
  307. detectFlash: function() {
  308. // return true if browser supports flash, false otherwise
  309. // Code snippet borrowed from: https://github.com/swfobject/swfobject
  310. var SHOCKWAVE_FLASH = "Shockwave Flash",
  311. SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
  312. FLASH_MIME_TYPE = "application/x-shockwave-flash",
  313. win = window,
  314. nav = navigator,
  315. hasFlash = false;
  316. if (typeof nav.plugins !== "undefined" && typeof nav.plugins[SHOCKWAVE_FLASH] === "object") {
  317. var desc = nav.plugins[SHOCKWAVE_FLASH].description;
  318. if (desc && (typeof nav.mimeTypes !== "undefined" && nav.mimeTypes[FLASH_MIME_TYPE] && nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) {
  319. hasFlash = true;
  320. }
  321. }
  322. else if (typeof win.ActiveXObject !== "undefined") {
  323. try {
  324. var ax = new ActiveXObject(SHOCKWAVE_FLASH_AX);
  325. if (ax) {
  326. var ver = ax.GetVariable("$version");
  327. if (ver) hasFlash = true;
  328. }
  329. }
  330. catch (e) {;}
  331. }
  332. return hasFlash;
  333. },
  334. getSWFHTML: function() {
  335. // Return HTML for embedding flash based webcam capture movie
  336. var html = '',
  337. swfURL = this.params.swfURL;
  338. // make sure we aren't running locally (flash doesn't work)
  339. if (location.protocol.match(/file/)) {
  340. this.dispatch('error', new FlashError("Flash does not work from local disk. Please run from a web server."));
  341. return '<h3 style="color:red">ERROR: the Webcam.js Flash fallback does not work from local disk. Please run it from a web server.</h3>';
  342. }
  343. // make sure we have flash
  344. if (!this.detectFlash()) {
  345. this.dispatch('error', new FlashError("Adobe Flash Player not found. Please install from get.adobe.com/flashplayer and try again."));
  346. return '<h3 style="color:red">' + this.params.flashNotDetectedText + '</h3>';
  347. }
  348. // set default swfURL if not explicitly set
  349. if (!swfURL) {
  350. // find our script tag, and use that base URL
  351. var base_url = '';
  352. var scpts = document.getElementsByTagName('script');
  353. for (var idx = 0, len = scpts.length; idx < len; idx++) {
  354. var src = scpts[idx].getAttribute('src');
  355. if (src && src.match(/\/webcam(\.min)?\.js/)) {
  356. base_url = src.replace(/\/webcam(\.min)?\.js.*$/, '');
  357. idx = len;
  358. }
  359. }
  360. if (base_url) swfURL = base_url + '/webcam.swf';
  361. else swfURL = 'webcam.swf';
  362. }
  363. // if this is the user's first visit, set flashvar so flash privacy settings panel is shown first
  364. if (window.localStorage && !localStorage.getItem('visited')) {
  365. this.params.new_user = 1;
  366. localStorage.setItem('visited', 1);
  367. }
  368. // construct flashvars string
  369. var flashvars = '';
  370. for (var key in this.params) {
  371. if (flashvars) flashvars += '&';
  372. flashvars += key + '=' + escape(this.params[key]);
  373. }
  374. // construct object/embed tag
  375. html += '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" type="application/x-shockwave-flash" codebase="'+this.protocol+'://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="'+this.params.width+'" height="'+this.params.height+'" id="webcam_movie_obj" align="middle"><param name="wmode" value="opaque" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="'+swfURL+'" /><param name="loop" value="false" /><param name="menu" value="false" /><param name="quality" value="best" /><param name="bgcolor" value="#ffffff" /><param name="flashvars" value="'+flashvars+'"/><embed id="webcam_movie_embed" src="'+swfURL+'" wmode="opaque" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="'+this.params.width+'" height="'+this.params.height+'" name="webcam_movie_embed" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+flashvars+'"></embed></object>';
  376. return html;
  377. },
  378. getMovie: function() {
  379. // get reference to movie object/embed in DOM
  380. if (!this.loaded) return this.dispatch('error', new FlashError("Flash Movie is not loaded yet"));
  381. var movie = document.getElementById('webcam_movie_obj');
  382. if (!movie || !movie._snap) movie = document.getElementById('webcam_movie_embed');
  383. if (!movie) this.dispatch('error', new FlashError("Cannot locate Flash movie in DOM"));
  384. return movie;
  385. },
  386. freeze: function() {
  387. // show preview, freeze camera
  388. var self = this;
  389. var params = this.params;
  390. // kill preview if already active
  391. if (this.preview_active) this.unfreeze();
  392. // determine scale factor
  393. var scaleX = this.params.width / this.params.dest_width;
  394. var scaleY = this.params.height / this.params.dest_height;
  395. // must unflip container as preview canvas will be pre-flipped
  396. this.unflip();
  397. // calc final size of image
  398. var final_width = params.crop_width || params.dest_width;
  399. var final_height = params.crop_height || params.dest_height;
  400. // create canvas for holding preview
  401. var preview_canvas = document.createElement('canvas');
  402. preview_canvas.width = final_width;
  403. preview_canvas.height = final_height;
  404. var preview_context = preview_canvas.getContext('2d');
  405. // save for later use
  406. this.preview_canvas = preview_canvas;
  407. this.preview_context = preview_context;
  408. // scale for preview size
  409. if ((scaleX != 1.0) || (scaleY != 1.0)) {
  410. preview_canvas.style.webkitTransformOrigin = '0px 0px';
  411. preview_canvas.style.mozTransformOrigin = '0px 0px';
  412. preview_canvas.style.msTransformOrigin = '0px 0px';
  413. preview_canvas.style.oTransformOrigin = '0px 0px';
  414. preview_canvas.style.transformOrigin = '0px 0px';
  415. preview_canvas.style.webkitTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
  416. preview_canvas.style.mozTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
  417. preview_canvas.style.msTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
  418. preview_canvas.style.oTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
  419. preview_canvas.style.transform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
  420. }
  421. // take snapshot, but fire our own callback
  422. this.snap( function() {
  423. // add preview image to dom, adjust for crop
  424. preview_canvas.style.position = 'relative';
  425. preview_canvas.style.left = '' + self.container.scrollLeft + 'px';
  426. preview_canvas.style.top = '' + self.container.scrollTop + 'px';
  427. self.container.insertBefore( preview_canvas, self.peg );
  428. self.container.style.overflow = 'hidden';
  429. // set flag for user capture (use preview)
  430. self.preview_active = true;
  431. }, preview_canvas );
  432. },
  433. unfreeze: function() {
  434. // cancel preview and resume live video feed
  435. if (this.preview_active) {
  436. // remove preview canvas
  437. this.container.removeChild( this.preview_canvas );
  438. delete this.preview_context;
  439. delete this.preview_canvas;
  440. // unflag
  441. this.preview_active = false;
  442. // re-flip if we unflipped before
  443. this.flip();
  444. }
  445. },
  446. flip: function() {
  447. // flip container horiz (mirror mode) if desired
  448. if (this.params.flip_horiz) {
  449. var sty = this.container.style;
  450. sty.webkitTransform = 'scaleX(-1)';
  451. sty.mozTransform = 'scaleX(-1)';
  452. sty.msTransform = 'scaleX(-1)';
  453. sty.oTransform = 'scaleX(-1)';
  454. sty.transform = 'scaleX(-1)';
  455. sty.filter = 'FlipH';
  456. sty.msFilter = 'FlipH';
  457. }
  458. },
  459. unflip: function() {
  460. // unflip container horiz (mirror mode) if desired
  461. if (this.params.flip_horiz) {
  462. var sty = this.container.style;
  463. sty.webkitTransform = 'scaleX(1)';
  464. sty.mozTransform = 'scaleX(1)';
  465. sty.msTransform = 'scaleX(1)';
  466. sty.oTransform = 'scaleX(1)';
  467. sty.transform = 'scaleX(1)';
  468. sty.filter = '';
  469. sty.msFilter = '';
  470. }
  471. },
  472. savePreview: function(user_callback, user_canvas) {
  473. // save preview freeze and fire user callback
  474. var params = this.params;
  475. var canvas = this.preview_canvas;
  476. var context = this.preview_context;
  477. // render to user canvas if desired
  478. if (user_canvas) {
  479. var user_context = user_canvas.getContext('2d');
  480. user_context.drawImage( canvas, 0, 0 );
  481. }
  482. // fire user callback if desired
  483. user_callback(
  484. user_canvas ? null : canvas.toDataURL('image/' + params.image_format, params.jpeg_quality / 100 ),
  485. canvas,
  486. context
  487. );
  488. // remove preview
  489. if (this.params.unfreeze_snap) this.unfreeze();
  490. },
  491. snap: function(user_callback, user_canvas) {
  492. // take snapshot and return image data uri
  493. var self = this;
  494. var params = this.params;
  495. if (!this.loaded) return this.dispatch('error', new WebcamError("Webcam is not loaded yet"));
  496. // if (!this.live) return this.dispatch('error', new WebcamError("Webcam is not live yet"));
  497. if (!user_callback) return this.dispatch('error', new WebcamError("Please provide a callback function or canvas to snap()"));
  498. // if we have an active preview freeze, use that
  499. if (this.preview_active) {
  500. this.savePreview( user_callback, user_canvas );
  501. return null;
  502. }
  503. // create offscreen canvas element to hold pixels
  504. var canvas = document.createElement('canvas');
  505. canvas.width = this.params.dest_width;
  506. canvas.height = this.params.dest_height;
  507. var context = canvas.getContext('2d');
  508. // flip canvas horizontally if desired
  509. if (this.params.flip_horiz) {
  510. context.translate( params.dest_width, 0 );
  511. context.scale( -1, 1 );
  512. }
  513. // create inline function, called after image load (flash) or immediately (native)
  514. var func = function() {
  515. // render image if needed (flash)
  516. if (this.src && this.width && this.height) {
  517. context.drawImage(this, 0, 0, params.dest_width, params.dest_height);
  518. }
  519. // crop if desired
  520. if (params.crop_width && params.crop_height) {
  521. var crop_canvas = document.createElement('canvas');
  522. crop_canvas.width = params.crop_width;
  523. crop_canvas.height = params.crop_height;
  524. var crop_context = crop_canvas.getContext('2d');
  525. crop_context.drawImage( canvas,
  526. Math.floor( (params.dest_width / 2) - (params.crop_width / 2) ),
  527. Math.floor( (params.dest_height / 2) - (params.crop_height / 2) ),
  528. params.crop_width,
  529. params.crop_height,
  530. 0,
  531. 0,
  532. params.crop_width,
  533. params.crop_height
  534. );
  535. // swap canvases
  536. context = crop_context;
  537. canvas = crop_canvas;
  538. }
  539. // render to user canvas if desired
  540. if (user_canvas) {
  541. var user_context = user_canvas.getContext('2d');
  542. user_context.drawImage( canvas, 0, 0 );
  543. }
  544. // fire user callback if desired
  545. user_callback(
  546. user_canvas ? null : canvas.toDataURL('image/' + params.image_format, params.jpeg_quality / 100 ),
  547. canvas,
  548. context
  549. );
  550. };
  551. // grab image frame from userMedia or flash movie
  552. if (this.userMedia) {
  553. // native implementation
  554. context.drawImage(this.video, 0, 0, this.params.dest_width, this.params.dest_height);
  555. // fire callback right away
  556. func();
  557. }
  558. else {
  559. // flash fallback
  560. var raw_data = this.getMovie()._snap();
  561. // render to image, fire callback when complete
  562. var img = new Image();
  563. img.onload = func;
  564. img.src = 'data:image/'+this.params.image_format+';base64,' + raw_data;
  565. }
  566. return null;
  567. },
  568. configure: function(panel) {
  569. // open flash configuration panel -- specify tab name:
  570. // "camera", "privacy", "default", "localStorage", "microphone", "settingsManager"
  571. if (!panel) panel = "camera";
  572. this.getMovie()._configure(panel);
  573. },
  574. flashNotify: function(type, msg) {
  575. // receive notification from flash about event
  576. switch (type) {
  577. case 'flashLoadComplete':
  578. // movie loaded successfully
  579. this.loaded = true;
  580. this.dispatch('load');
  581. break;
  582. case 'cameraLive':
  583. // camera is live and ready to snap
  584. this.live = true;
  585. this.dispatch('live');
  586. break;
  587. case 'error':
  588. // Flash error
  589. this.dispatch('error', new FlashError(msg));
  590. break;
  591. default:
  592. // catch-all event, just in case
  593. // console.log("webcam flash_notify: " + type + ": " + msg);
  594. break;
  595. }
  596. },
  597. b64ToUint6: function(nChr) {
  598. // convert base64 encoded character to 6-bit integer
  599. // from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
  600. return nChr > 64 && nChr < 91 ? nChr - 65
  601. : nChr > 96 && nChr < 123 ? nChr - 71
  602. : nChr > 47 && nChr < 58 ? nChr + 4
  603. : nChr === 43 ? 62 : nChr === 47 ? 63 : 0;
  604. },
  605. base64DecToArr: function(sBase64, nBlocksSize) {
  606. // convert base64 encoded string to Uintarray
  607. // from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
  608. var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
  609. nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2,
  610. taBytes = new Uint8Array(nOutLen);
  611. for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
  612. nMod4 = nInIdx & 3;
  613. nUint24 |= this.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
  614. if (nMod4 === 3 || nInLen - nInIdx === 1) {
  615. for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
  616. taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
  617. }
  618. nUint24 = 0;
  619. }
  620. }
  621. return taBytes;
  622. },
  623. upload: function(image_data_uri, target_url, callback) {
  624. // submit image data to server using binary AJAX
  625. var form_elem_name = this.params.upload_name || 'webcam';
  626. // detect image format from within image_data_uri
  627. var image_fmt = '';
  628. if (image_data_uri.match(/^data\:image\/(\w+)/))
  629. image_fmt = RegExp.$1;
  630. else
  631. throw "Cannot locate image format in Data URI";
  632. // extract raw base64 data from Data URI
  633. var raw_image_data = image_data_uri.replace(/^data\:image\/\w+\;base64\,/, '');
  634. // contruct use AJAX object
  635. var http = new XMLHttpRequest();
  636. http.open("POST", target_url, true);
  637. // setup progress events
  638. if (http.upload && http.upload.addEventListener) {
  639. http.upload.addEventListener( 'progress', function(e) {
  640. if (e.lengthComputable) {
  641. var progress = e.loaded / e.total;
  642. Webcam.dispatch('uploadProgress', progress, e);
  643. }
  644. }, false );
  645. }
  646. // completion handler
  647. var self = this;
  648. http.onload = function() {
  649. if (callback) callback.apply( self, [http.status, http.responseText, http.statusText] );
  650. Webcam.dispatch('uploadComplete', http.status, http.responseText, http.statusText);
  651. };
  652. // create a blob and decode our base64 to binary
  653. var blob = new Blob( [ this.base64DecToArr(raw_image_data) ], {type: 'image/'+image_fmt} );
  654. // stuff into a form, so servers can easily receive it as a standard file upload
  655. var form = new FormData();
  656. form.append( form_elem_name, blob, form_elem_name+"."+image_fmt.replace(/e/, '') );
  657. // send data to server
  658. http.send(form);
  659. }
  660. };
  661. Webcam.init();
  662. if (typeof define === 'function' && define.amd) {
  663. define( function() { return Webcam; } );
  664. }
  665. else if (typeof module === 'object' && module.exports) {
  666. module.exports = Webcam;
  667. }
  668. else {
  669. window.Webcam = Webcam;
  670. }
  671. }(window));