qrcode-reader.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  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. console.log("Initialisation du lecteur de QRCode");
  52. // build the HTML
  53. qrr.buildHTML();
  54. qrr.scriptLoaded = false;
  55. qrr.isOpen = false;
  56. // load the script performing the actual QRCode reading
  57. $.getScript( $.qrCodeReader.jsQRpath, function( data, textStatus, jqxhr ) {
  58. if (jqxhr.status == 200) {
  59. qrr.scriptLoaded = true;
  60. console.log("Script de lecture QRCode chargé avec succès");
  61. } else {
  62. console.error("Erreur lors du chargement du script de lecture QRCode");
  63. }
  64. });
  65. },
  66. // build the HTML interface of the widget
  67. buildHTML: function() {
  68. qrr.bgOverlay = $('<div id="qrr-overlay"></div>');
  69. qrr.container = $('<div id="qrr-container"></div>');
  70. qrr.closeBtn = $('<span id="qrr-close">&times;</span>')
  71. qrr.closeBtn.appendTo(qrr.container);
  72. qrr.closeBtn = $('<button type="button" class="fadeIn third" id="qrr-close">ANNULER</button>')
  73. qrr.closeBtn.appendTo(qrr.container);
  74. qrr.okBtn = $('<a id="qrr-ok">OK</a>');
  75. qrr.loadingMessage = $('<div id="qrr-loading-message">🎥 Unable to access video stream (please make sure you have a webcam enabled)</div>');
  76. qrr.canvas = $('<canvas id="qrr-canvas" class="hidden"></canvas>');
  77. qrr.audio = $('<audio hidden id="qrr-beep" src="' + $.qrCodeReader.beepPath + '" type="audio/mp3"></audio>');
  78. qrr.outputDiv = $('<div id="qrr-output"></div>');
  79. qrr.outputNoData = $('<div id="qrr-nodata">No QR code detected.</div>');
  80. qrr.outputData = $('<div id="qrr-output-data"></div>');
  81. qrr.outputNoData.appendTo(qrr.outputDiv);
  82. qrr.outputData.appendTo(qrr.outputDiv);
  83. qrr.loadingMessage.appendTo(qrr.container);
  84. qrr.canvas.appendTo(qrr.container);
  85. qrr.outputDiv.appendTo(qrr.container);
  86. qrr.audio.appendTo(qrr.container);
  87. qrr.okBtn.appendTo(qrr.container);
  88. qrr.bgOverlay.appendTo(document.body);
  89. qrr.bgOverlay.on("click", qrr.close);
  90. qrr.closeBtn.on("click", qrr.close);
  91. qrr.container.appendTo(document.body);
  92. qrr.video = document.createElement("video");
  93. },
  94. // draw a line
  95. drawLine: function(begin, end, color) {
  96. var canvas = qrr.canvas[0].getContext("2d");
  97. canvas.beginPath();
  98. canvas.moveTo(begin.x, begin.y);
  99. canvas.lineTo(end.x, end.y);
  100. canvas.lineWidth = 4;
  101. canvas.strokeStyle = color;
  102. canvas.stroke();
  103. },
  104. // draw a rectangle around a matched QRCode image
  105. drawBox: function(location, color) {
  106. qrr.drawLine(location.topLeftCorner, location.topRightCorner, color);
  107. qrr.drawLine(location.topRightCorner, location.bottomRightCorner, color);
  108. qrr.drawLine(location.bottomRightCorner, location.bottomLeftCorner, color);
  109. qrr.drawLine(location.bottomLeftCorner, location.topLeftCorner, color);
  110. },
  111. // merge the options with the element data attributes and then save them
  112. setOptions: function (element, options) {
  113. // data-attributes options
  114. var dataOptions = {
  115. multiple: $(element).data("qrr-multiple"),
  116. qrcodeRegexp: new RegExp($(element).data("qrr-qrcode-regexp")),
  117. audioFeedback: $(element).data("qrr-audio-feedback"),
  118. repeatTimeout: $(element).data("qrr-repeat-timeout"),
  119. target: $(element).data("qrr-target"),
  120. skipDuplicates: $(element).data("qrr-skip-duplicates"),
  121. lineColor: $(element).data("qrr-line-color"),
  122. callback: $(element).data("qrr-callback")
  123. }
  124. // argument options override data-attributes options
  125. options = $.extend( {}, dataOptions, options);
  126. // extend defaults with options
  127. var settings = $.extend( {}, $.qrCodeReader.defaults, options);
  128. // save options in the data attributes
  129. $(element).data("qrr", settings);
  130. },
  131. // get the options from the element the reader is attached
  132. getOptions: function (element) {
  133. qrr.settings = $(element).data("qrr");
  134. },
  135. // open the QRCode reader interface
  136. open: function () {
  137. // prevent multiple opening
  138. if (qrr.isOpen) return;
  139. console.log("Ouverture de l'interface du lecteur de QRCode");
  140. // get options for the current called element
  141. qrr.getOptions(this);
  142. // show the widget
  143. qrr.bgOverlay.show();
  144. qrr.container.slideDown();
  145. // initialize codes container
  146. qrr.codes = [];
  147. // initialize interface
  148. qrr.outputNoData.show();
  149. qrr.outputData.empty();
  150. qrr.outputData.hide();
  151. if (qrr.settings.multiple) {
  152. qrr.okBtn.show();
  153. qrr.okBtn.off("click").on("click", qrr.doneReading);
  154. } else {
  155. qrr.okBtn.hide();
  156. }
  157. // close on ESC, doneReading on Enter if multiple
  158. $(document).on('keyup.qrCodeReader', function(e) {
  159. if(e.keyCode === 27) {
  160. qrr.close();
  161. }
  162. if (qrr.settings.multiple && e.keyCode === 13) {
  163. qrr.doneReading();
  164. }
  165. });
  166. qrr.isOpen = true;
  167. if (qrr.scriptLoaded) {
  168. // start the business
  169. qrr.start();
  170. }
  171. },
  172. // get the camera, show video, start searching qrcode in the stream
  173. start: function() {
  174. console.log("Démarrage de la capture vidéo pour la lecture du QRCode");
  175. // Use {facingMode: environment} to attempt to get the front camera on phones
  176. navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) {
  177. qrr.video.srcObject = stream;
  178. qrr.video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
  179. qrr.video.play();
  180. qrr.startReading();
  181. });
  182. },
  183. // start continuously searching qrcode in the video stream
  184. startReading: function() {
  185. qrr.requestID = window.requestAnimationFrame(qrr.read);
  186. },
  187. // done with reading QRcode
  188. doneReading: function() {
  189. var value = qrr.codes[0];
  190. // fill in the target element
  191. if (qrr.settings.target) {
  192. if (qrr.settings.multiple) {
  193. var value = qrr.codes.join("\n");
  194. }
  195. $(qrr.settings.target).val(value);
  196. }
  197. // call a callback
  198. if (qrr.settings.callback) {
  199. try {
  200. if (qrr.settings.multiple) {
  201. qrr.settings.callback(qrr.codes);
  202. } else {
  203. qrr.settings.callback(value);
  204. }
  205. } catch(err) {
  206. console.error(err);
  207. }
  208. }
  209. // close the widget
  210. qrr.close();
  211. },
  212. // search for a QRCode
  213. read: function() {
  214. var codeRead = false;
  215. var canvas = qrr.canvas[0].getContext("2d");
  216. qrr.loadingMessage.text("⌛ Loading video...");
  217. qrr.canvas.off("click.qrCodeReader", qrr.startReading);
  218. if (qrr.video.readyState === qrr.video.HAVE_ENOUGH_DATA) {
  219. console.log("Données vidéo suffisantes pour la lecture");
  220. qrr.loadingMessage.hide();
  221. qrr.canvas.removeClass("hidden");
  222. qrr.canvas[0].height = qrr.video.videoHeight;
  223. qrr.canvas[0].width = qrr.video.videoWidth;
  224. canvas.drawImage(qrr.video, 0, 0, qrr.canvas[0].width, qrr.canvas[0].height);
  225. var imageData = canvas.getImageData(0, 0, qrr.canvas[0].width, qrr.canvas[0].height);
  226. // this performs the actual QRCode reading
  227. var code = jsQR(imageData.data, imageData.width, imageData.height, {
  228. inversionAttempts: "dontInvert",
  229. });
  230. // a QRCode has been found
  231. if (code && qrr.settings.qrcodeRegexp.test(code.data)) {
  232. console.log("QRCode détecté : ", code.data);
  233. // draw lines around the matched QRCode
  234. qrr.drawBox(code.location, qrr.settings.lineColor);
  235. codeRead = true;
  236. qrr.codes.push(code.data);
  237. qrr.outputNoData.hide();
  238. qrr.outputData.show();
  239. // play audio if requested
  240. if (qrr.settings.audioFeedback) {
  241. qrr.audio[0].play();
  242. }
  243. // read multiple codes
  244. if (qrr.settings.multiple) {
  245. // avoid duplicates
  246. if(qrr.settings.skipDuplicates) {
  247. qrr.codes = $.unique(qrr.codes);
  248. }
  249. // show our reading
  250. $('<div class="qrr-input"></div>').text(code.data).appendTo(qrr.outputData);
  251. qrr.outputDiv[0].scrollTop = qrr.outputDiv[0].scrollHeight;
  252. // read again by clicking on the canvas
  253. qrr.canvas.on("click.qrCodeReader", qrr.startReading);
  254. // repeat reading after a timeout
  255. if (qrr.settings.repeatTimeout > 0) {
  256. setTimeout(qrr.startReading, qrr.settings.repeatTimeout);
  257. } else {
  258. qrr.loadingMessage.text("Click on the image to read the next QRCode");
  259. qrr.loadingMessage.show();
  260. }
  261. // single reading
  262. } else {
  263. qrr.doneReading();
  264. }
  265. } else {
  266. console.log("Aucun QRCode détecté lors de cette itération");
  267. }
  268. }
  269. if (!codeRead) {
  270. qrr.startReading();
  271. }
  272. },
  273. close: function() {
  274. console.log("Fermeture de l'interface du lecteur de QRCode");
  275. // cancel the refresh function
  276. if (qrr.requestID) {
  277. window.cancelAnimationFrame(qrr.requestID);
  278. }
  279. // unbind keyboard
  280. $(document).off('keyup.qrCodeReader');
  281. // stop the video
  282. if (qrr.video.srcObject) {
  283. qrr.video.srcObject.getTracks()[0].stop();
  284. }
  285. // hide the GUI
  286. qrr.canvas.addClass("hidden");
  287. qrr.loadingMessage.show();
  288. qrr.bgOverlay.hide();
  289. qrr.container.hide();
  290. qrr.isOpen = false;
  291. }
  292. };
  293. $.fn.qrCodeReader = function ( options ) {
  294. // Instantiate the plugin only once (singletone) in the page:
  295. // when called again (or on a different element), we simply re-set the options
  296. // and display the QrCode reader interface with the right options.
  297. // Options are saved in the data attribute of the bound element.
  298. if(!$.qrCodeReader.instance) {
  299. qrr = new QRCodeReader();
  300. qrr.init();
  301. $.qrCodeReader.instance = qrr;
  302. }
  303. return this.each(function () {
  304. qrr.setOptions(this, options);
  305. $(this).off("click.qrCodeReader").on("click.qrCodeReader", qrr.open);
  306. });
  307. };
  308. }( jQuery, window, document ));