You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

718 lines
24 KiB

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