qrcode-reader.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. ;(function( $, window, document, undefined ) {
  2. "use strict";
  3. // cross browser request animation frame
  4. if ( !window.requestAnimationFrame ) {
  5. window.requestAnimationFrame = ( function() {
  6. return window.webkitRequestAnimationFrame ||
  7. window.mozRequestAnimationFrame ||
  8. window.oRequestAnimationFrame ||
  9. window.msRequestAnimationFrame ||
  10. function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) {
  11. window.setTimeout( callback, 1000 / 60 );
  12. };
  13. } )();
  14. }
  15. var qrr, // our qrcode reader singletone instance
  16. QRCodeReader = function() {};
  17. $.qrCodeReader = {
  18. jsQRpath: "../libs/qrcode-reader/js/jsQR.js",
  19. beepPath: "../libs/qrcode-reader/audio/beep.mp3",
  20. instance: null,
  21. defaults: {
  22. // single read or multiple readings/
  23. multiple: false,
  24. // only triggers for QRCodes matching the regexp
  25. qrcodeRegexp: /./,
  26. // play "Beep!" sound when reading qrcode successfully
  27. audioFeedback: true,
  28. // in case of multiple readings, after a successful reading,
  29. // wait for repeatTimeout milliseconds before trying for the next lookup.
  30. // Set to 0 to disable automatic re-tries: in such case user will have to
  31. // click on the webcam canvas to trigger a new reading tentative
  32. repeatTimeout: 1500,
  33. // target input element to fill in with the readings in case of successful reading
  34. // (newline separated in case of multiple readings).
  35. // Such element can be specified as jQuery object or as string identifier, e.g. "#target-input"
  36. target: null,
  37. // in case of multiple readings, skip duplicate readings
  38. skipDuplicates: true,
  39. // color of the lines highlighting the QRCode in the image when found
  40. lineColor: "#FF3B58",
  41. // In case of multiple readings, function to call when pressing the OK button (or Enter),
  42. // in such case read QRCodes are passed as an array.
  43. // In case of single reading, call immediately after the successful reading
  44. // (in the latter case the QRCode is passed as a single string value)
  45. callback: function(code) {}
  46. }
  47. };
  48. QRCodeReader.prototype = {
  49. constructor: QRCodeReader,
  50. init: function () {
  51. // build the HTML
  52. qrr.buildHTML();
  53. qrr.scriptLoaded = false;
  54. qrr.isOpen = false;
  55. // load the script performing the actual QRCode reading
  56. $.getScript( $.qrCodeReader.jsQRpath, function( data, textStatus, jqxhr ) {
  57. if ( jqxhr.status == 200) {
  58. qrr.scriptLoaded = true;
  59. } else {
  60. console.error("Erreur lors du chargement du script d'analyse QRCode");
  61. };
  62. });
  63. },
  64. // build the HTML interface of the widget
  65. buildHTML: function() {
  66. qrr.bgOverlay = $('<div id="qrr-overlay"></div>');
  67. qrr.container = $('<div id="qrr-container"></div>');
  68. qrr.closeBtn = $('<button type="button" class="fadeIn third" id="qrr-close">ANNULER</button>')
  69. qrr.closeBtn.appendTo(qrr.container);
  70. qrr.okBtn = $('<a id="qrr-ok">OK</a>');
  71. qrr.loadingMessage = $('<div id="qrr-loading-message">🎥 Impossible d\'accéder au flux vidéo (veuillez vous assurer que vous avez activé une webcam et/ou que vous avez accordé les droits à votre navigateur)</div>');
  72. qrr.canvas = $('<canvas id="qrr-canvas" class="hidden"></canvas>');
  73. qrr.audio = $('<audio hidden id="qrr-beep" src="' + $.qrCodeReader.beepPath + '" type="audio/mp3"></audio>');
  74. qrr.outputDiv = $('<div id="qrr-output"></div>');
  75. qrr.outputNoData = $('<div id="qrr-nodata">Aucun QRCode détecté</div>');
  76. qrr.outputData = $('<div id="qrr-output-data"></div>');
  77. qrr.outputNoData.appendTo(qrr.outputDiv);
  78. qrr.outputData.appendTo(qrr.outputDiv);
  79. qrr.loadingMessage.appendTo(qrr.container);
  80. qrr.canvas.appendTo(qrr.container);
  81. qrr.outputDiv.appendTo(qrr.container);
  82. qrr.audio.appendTo(qrr.container);
  83. qrr.okBtn.appendTo(qrr.container);
  84. qrr.bgOverlay.appendTo(document.body);
  85. qrr.bgOverlay.on("click", qrr.close);
  86. qrr.closeBtn.on("click", qrr.close);
  87. qrr.container.appendTo(document.body);
  88. qrr.video = document.createElement("video");
  89. },
  90. // draw a line
  91. drawLine: function(begin, end, color) {
  92. var canvas = qrr.canvas[0].getContext("2d");
  93. canvas.beginPath();
  94. canvas.moveTo(begin.x, begin.y);
  95. canvas.lineTo(end.x, end.y);
  96. canvas.lineWidth = 4;
  97. canvas.strokeStyle = color;
  98. canvas.stroke();
  99. },
  100. // draw a rectangle around a matched QRCode image
  101. drawBox: function(location, color) {
  102. qrr.drawLine(location.topLeftCorner, location.topRightCorner, color);
  103. qrr.drawLine(location.topRightCorner, location.bottomRightCorner, color);
  104. qrr.drawLine(location.bottomRightCorner, location.bottomLeftCorner, color);
  105. qrr.drawLine(location.bottomLeftCorner, location.topLeftCorner, color);
  106. },
  107. // merge the options with the element data attributes and then save them
  108. setOptions: function (element, options) {
  109. // data-attributes options
  110. var dataOptions = {
  111. multiple: $(element).data("qrr-multiple"),
  112. qrcodeRegexp: new RegExp($(element).data("qrr-qrcode-regexp")),
  113. audioFeedback: $(element).data("qrr-audio-feedback"),
  114. repeatTimeout: $(element).data("qrr-repeat-timeout"),
  115. target: $(element).data("qrr-target"),
  116. skipDuplicates: $(element).data("qrr-skip-duplicates"),
  117. lineColor: $(element).data("qrr-line-color"),
  118. callback: $(element).data("qrr-callback")
  119. }
  120. // argument options override data-attributes options
  121. options = $.extend( {}, dataOptions, options);
  122. // extend defaults with options
  123. var settings = $.extend( {}, $.qrCodeReader.defaults, options);
  124. // save options in the data attributes
  125. $(element).data("qrr", settings);
  126. },
  127. // get the options from the element the reader is attached
  128. getOptions: function (element) {
  129. qrr.settings = $(element).data("qrr");
  130. },
  131. // open the QRCode reader interface
  132. open: function () {
  133. // prevent multiple opening
  134. if (qrr.isOpen) return;
  135. // get options for the current called element
  136. qrr.getOptions(this);
  137. // show the widget
  138. qrr.bgOverlay.show();
  139. qrr.container.slideDown();
  140. // initialize codes container
  141. qrr.codes = [];
  142. // initialize interface
  143. qrr.outputNoData.show();
  144. qrr.outputData.empty();
  145. qrr.outputData.hide();
  146. if (qrr.settings.multiple) {
  147. qrr.okBtn.show();
  148. qrr.okBtn.off("click").on("click", qrr.doneReading);
  149. } else {
  150. qrr.okBtn.hide();
  151. }
  152. // close on ESC, doneReading on Enter if multiple
  153. $(document).on('keyup.qrCodeReader', function(e) {
  154. if(e.keyCode === 27) {
  155. qrr.close();
  156. }
  157. if (qrr.settings.multiple && e.keyCode === 13) {
  158. qrr.doneReading();
  159. }
  160. });
  161. qrr.isOpen = true;
  162. if (qrr.scriptLoaded) {
  163. // start the business
  164. qrr.start();
  165. }
  166. },
  167. // get the camera, show video, start searching qrcode in the stream
  168. start: function() {
  169. // Use {facingMode: environment} to attempt to get the front camera on phones
  170. navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) {
  171. qrr.video.srcObject = stream;
  172. qrr.video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
  173. qrr.video.play();
  174. qrr.startReading();
  175. });
  176. },
  177. // start continuously searching qrcode in the video stream
  178. startReading: function() {
  179. qrr.requestID = window.requestAnimationFrame(qrr.read);
  180. },
  181. // done with reading QRcode
  182. doneReading: function() {
  183. var value = qrr.codes[0];
  184. // fill in the target element
  185. if (qrr.settings.target) {
  186. if (qrr.settings.multiple) {
  187. var value = qrr.codes.join("\n");
  188. }
  189. $(qrr.settings.target).val(value);
  190. }
  191. // call a callback
  192. if (qrr.settings.callback) {
  193. try {
  194. if (qrr.settings.multiple) {
  195. qrr.settings.callback(qrr.codes);
  196. } else {
  197. qrr.settings.callback(value);
  198. }
  199. } catch(err) {
  200. console.error(err);
  201. }
  202. }
  203. // close the widget
  204. qrr.close();
  205. },
  206. // search for a QRCode
  207. read: function() {
  208. var codeRead = false;
  209. var canvas = qrr.canvas[0].getContext("2d");
  210. qrr.loadingMessage.text("⌛ Chargement de la vidéo...");
  211. qrr.canvas.off("click.qrCodeReader", qrr.startReading);
  212. if (qrr.video.readyState === qrr.video.HAVE_ENOUGH_DATA) {
  213. qrr.loadingMessage.hide();
  214. qrr.canvas.removeClass("hidden");
  215. qrr.canvas[0].height = qrr.video.videoHeight;
  216. qrr.canvas[0].width = qrr.video.videoWidth;
  217. canvas.drawImage(qrr.video, 0, 0, qrr.canvas[0].width, qrr.canvas[0].height);
  218. var imageData = canvas.getImageData(0, 0, qrr.canvas[0].width, qrr.canvas[0].height);
  219. // this performs the actual QRCode reading
  220. var code = jsQR(imageData.data, imageData.width, imageData.height, {
  221. inversionAttempts: "dontInvert",
  222. });
  223. // a QRCode has been found
  224. if (code && qrr.settings.qrcodeRegexp.test(code.data)) {
  225. // draw lines around the matched QRCode
  226. qrr.drawBox(code.location, qrr.settings.lineColor);
  227. codeRead = true;
  228. qrr.codes.push(code.data);
  229. qrr.outputNoData.hide();
  230. qrr.outputData.show();
  231. // play audio if requested
  232. if (qrr.settings.audioFeedback) {
  233. qrr.audio[0].play();
  234. }
  235. // read multiple codes
  236. if (qrr.settings.multiple) {
  237. // avoid duplicates
  238. if(qrr.settings.skipDuplicates) {
  239. qrr.codes = $.unique(qrr.codes);
  240. }
  241. // show our reading
  242. $('<div class="qrr-input"></div>').text(code.data).appendTo(qrr.outputData);
  243. qrr.outputDiv[0].scrollTop = qrr.outputDiv[0].scrollHeight;
  244. // read again by clicking on the canvas
  245. qrr.canvas.on("click.qrCodeReader", qrr.startReading);
  246. // repeat reading after a timeout
  247. if (qrr.settings.repeatTimeout > 0) {
  248. setTimeout(qrr.startReading, qrr.settings.repeatTimeout);
  249. } else {
  250. qrr.loadingMessage.text("Cliquez sur l'image pour lire le prochain QRCode");
  251. qrr.loadingMessage.show();
  252. }
  253. // single reading
  254. } else {
  255. qrr.doneReading();
  256. }
  257. }
  258. }
  259. if (!codeRead) {
  260. qrr.startReading();
  261. }
  262. },
  263. close: function() {
  264. // cancel the refresh function
  265. if (qrr.requestID) {
  266. window.cancelAnimationFrame(qrr.requestID);
  267. }
  268. // unbind keyboard
  269. $(document).off('keyup.qrCodeReader');
  270. // stop the video
  271. if (qrr.video.srcObject) {
  272. qrr.video.srcObject.getTracks()[0].stop();
  273. }
  274. // hide the GUI
  275. qrr.canvas.addClass("hidden");
  276. qrr.loadingMessage.show();
  277. qrr.bgOverlay.hide();
  278. qrr.container.hide();
  279. qrr.isOpen = false;
  280. }
  281. };
  282. $.fn.qrCodeReader = function ( options ) {
  283. // Instantiate the plugin only once (singletone) in the page:
  284. // when called again (or on a different element), we simply re-set the options
  285. // and display the QrCode reader interface with the right options.
  286. // Options are saved in the data attribute of the bound element.
  287. if(!$.qrCodeReader.instance) {
  288. qrr = new QRCodeReader();
  289. qrr.init();
  290. $.qrCodeReader.instance = qrr;
  291. }
  292. return this.each(function () {
  293. qrr.setOptions(this, options);
  294. $(this).off("click.qrCodeReader").on("click.qrCodeReader", qrr.open);
  295. });
  296. };
  297. }( jQuery, window, document ));