qrcode-reader.js 12 KB

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