BrowserCodeReader.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  2. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  3. return new (P || (P = Promise))(function (resolve, reject) {
  4. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  5. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  6. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  7. step((generator = generator.apply(thisArg, _arguments || [])).next());
  8. });
  9. };
  10. import ArgumentException from '../core/ArgumentException';
  11. import BinaryBitmap from '../core/BinaryBitmap';
  12. import ChecksumException from '../core/ChecksumException';
  13. import HybridBinarizer from '../core/common/HybridBinarizer';
  14. import FormatException from '../core/FormatException';
  15. import NotFoundException from '../core/NotFoundException';
  16. import { HTMLCanvasElementLuminanceSource } from './HTMLCanvasElementLuminanceSource';
  17. import { VideoInputDevice } from './VideoInputDevice';
  18. /**
  19. * @deprecated Moving to @zxing/browser
  20. *
  21. * Base class for browser code reader.
  22. */
  23. export class BrowserCodeReader {
  24. /**
  25. * Creates an instance of BrowserCodeReader.
  26. * @param {Reader} reader The reader instance to decode the barcode
  27. * @param {number} [timeBetweenScansMillis=500] the time delay between subsequent successful decode tries
  28. *
  29. * @memberOf BrowserCodeReader
  30. */
  31. constructor(reader, timeBetweenScansMillis = 500, _hints) {
  32. this.reader = reader;
  33. this.timeBetweenScansMillis = timeBetweenScansMillis;
  34. this._hints = _hints;
  35. /**
  36. * This will break the loop.
  37. */
  38. this._stopContinuousDecode = false;
  39. /**
  40. * This will break the loop.
  41. */
  42. this._stopAsyncDecode = false;
  43. /**
  44. * Delay time between decode attempts made by the scanner.
  45. */
  46. this._timeBetweenDecodingAttempts = 0;
  47. }
  48. /**
  49. * If navigator is present.
  50. */
  51. get hasNavigator() {
  52. return typeof navigator !== 'undefined';
  53. }
  54. /**
  55. * If mediaDevices under navigator is supported.
  56. */
  57. get isMediaDevicesSuported() {
  58. return this.hasNavigator && !!navigator.mediaDevices;
  59. }
  60. /**
  61. * If enumerateDevices under navigator is supported.
  62. */
  63. get canEnumerateDevices() {
  64. return !!(this.isMediaDevicesSuported && navigator.mediaDevices.enumerateDevices);
  65. }
  66. /** Time between two decoding tries in milli seconds. */
  67. get timeBetweenDecodingAttempts() {
  68. return this._timeBetweenDecodingAttempts;
  69. }
  70. /**
  71. * Change the time span the decoder waits between two decoding tries.
  72. *
  73. * @param {number} millis Time between two decoding tries in milli seconds.
  74. */
  75. set timeBetweenDecodingAttempts(millis) {
  76. this._timeBetweenDecodingAttempts = millis < 0 ? 0 : millis;
  77. }
  78. /**
  79. * Sets the hints.
  80. */
  81. set hints(hints) {
  82. this._hints = hints || null;
  83. }
  84. /**
  85. * Sets the hints.
  86. */
  87. get hints() {
  88. return this._hints;
  89. }
  90. /**
  91. * Lists all the available video input devices.
  92. */
  93. listVideoInputDevices() {
  94. return __awaiter(this, void 0, void 0, function* () {
  95. if (!this.hasNavigator) {
  96. throw new Error("Can't enumerate devices, navigator is not present.");
  97. }
  98. if (!this.canEnumerateDevices) {
  99. throw new Error("Can't enumerate devices, method not supported.");
  100. }
  101. const devices = yield navigator.mediaDevices.enumerateDevices();
  102. const videoDevices = [];
  103. for (const device of devices) {
  104. const kind = device.kind === 'video' ? 'videoinput' : device.kind;
  105. if (kind !== 'videoinput') {
  106. continue;
  107. }
  108. const deviceId = device.deviceId || device.id;
  109. const label = device.label || `Video device ${videoDevices.length + 1}`;
  110. const groupId = device.groupId;
  111. const videoDevice = { deviceId, label, kind, groupId };
  112. videoDevices.push(videoDevice);
  113. }
  114. return videoDevices;
  115. });
  116. }
  117. /**
  118. * Obtain the list of available devices with type 'videoinput'.
  119. *
  120. * @returns {Promise<VideoInputDevice[]>} an array of available video input devices
  121. *
  122. * @memberOf BrowserCodeReader
  123. *
  124. * @deprecated Use `listVideoInputDevices` instead.
  125. */
  126. getVideoInputDevices() {
  127. return __awaiter(this, void 0, void 0, function* () {
  128. const devices = yield this.listVideoInputDevices();
  129. return devices.map(d => new VideoInputDevice(d.deviceId, d.label));
  130. });
  131. }
  132. /**
  133. * Let's you find a device using it's Id.
  134. */
  135. findDeviceById(deviceId) {
  136. return __awaiter(this, void 0, void 0, function* () {
  137. const devices = yield this.listVideoInputDevices();
  138. if (!devices) {
  139. return null;
  140. }
  141. return devices.find(x => x.deviceId === deviceId);
  142. });
  143. }
  144. /**
  145. * Decodes the barcode from the device specified by deviceId while showing the video in the specified video element.
  146. *
  147. * @param deviceId the id of one of the devices obtained after calling getVideoInputDevices. Can be undefined, in this case it will decode from one of the available devices, preffering the main camera (environment facing) if available.
  148. * @param video the video element in page where to show the video while decoding. Can be either an element id or directly an HTMLVideoElement. Can be undefined, in which case no video will be shown.
  149. * @returns The decoding result.
  150. *
  151. * @memberOf BrowserCodeReader
  152. *
  153. * @deprecated Use `decodeOnceFromVideoDevice` instead.
  154. */
  155. decodeFromInputVideoDevice(deviceId, videoSource) {
  156. return __awaiter(this, void 0, void 0, function* () {
  157. return yield this.decodeOnceFromVideoDevice(deviceId, videoSource);
  158. });
  159. }
  160. /**
  161. * In one attempt, tries to decode the barcode from the device specified by deviceId while showing the video in the specified video element.
  162. *
  163. * @param deviceId the id of one of the devices obtained after calling getVideoInputDevices. Can be undefined, in this case it will decode from one of the available devices, preffering the main camera (environment facing) if available.
  164. * @param video the video element in page where to show the video while decoding. Can be either an element id or directly an HTMLVideoElement. Can be undefined, in which case no video will be shown.
  165. * @returns The decoding result.
  166. *
  167. * @memberOf BrowserCodeReader
  168. */
  169. decodeOnceFromVideoDevice(deviceId, videoSource) {
  170. return __awaiter(this, void 0, void 0, function* () {
  171. this.reset();
  172. let videoConstraints;
  173. if (!deviceId) {
  174. videoConstraints = { facingMode: 'environment' };
  175. }
  176. else {
  177. videoConstraints = { deviceId: { exact: deviceId } };
  178. }
  179. const constraints = { video: videoConstraints };
  180. return yield this.decodeOnceFromConstraints(constraints, videoSource);
  181. });
  182. }
  183. /**
  184. * In one attempt, tries to decode the barcode from a stream obtained from the given constraints while showing the video in the specified video element.
  185. *
  186. * @param constraints the media stream constraints to get s valid media stream to decode from
  187. * @param video the video element in page where to show the video while decoding. Can be either an element id or directly an HTMLVideoElement. Can be undefined, in which case no video will be shown.
  188. * @returns The decoding result.
  189. *
  190. * @memberOf BrowserCodeReader
  191. */
  192. decodeOnceFromConstraints(constraints, videoSource) {
  193. return __awaiter(this, void 0, void 0, function* () {
  194. const stream = yield navigator.mediaDevices.getUserMedia(constraints);
  195. return yield this.decodeOnceFromStream(stream, videoSource);
  196. });
  197. }
  198. /**
  199. * In one attempt, tries to decode the barcode from a stream obtained from the given constraints while showing the video in the specified video element.
  200. *
  201. * @param {MediaStream} [constraints] the media stream constraints to get s valid media stream to decode from
  202. * @param {string|HTMLVideoElement} [video] the video element in page where to show the video while decoding. Can be either an element id or directly an HTMLVideoElement. Can be undefined, in which case no video will be shown.
  203. * @returns {Promise<Result>} The decoding result.
  204. *
  205. * @memberOf BrowserCodeReader
  206. */
  207. decodeOnceFromStream(stream, videoSource) {
  208. return __awaiter(this, void 0, void 0, function* () {
  209. this.reset();
  210. const video = yield this.attachStreamToVideo(stream, videoSource);
  211. const result = yield this.decodeOnce(video);
  212. return result;
  213. });
  214. }
  215. /**
  216. * Continuously decodes the barcode from the device specified by device while showing the video in the specified video element.
  217. *
  218. * @param {string|null} [deviceId] the id of one of the devices obtained after calling getVideoInputDevices. Can be undefined, in this case it will decode from one of the available devices, preffering the main camera (environment facing) if available.
  219. * @param {string|HTMLVideoElement|null} [video] the video element in page where to show the video while decoding. Can be either an element id or directly an HTMLVideoElement. Can be undefined, in which case no video will be shown.
  220. * @returns {Promise<void>}
  221. *
  222. * @memberOf BrowserCodeReader
  223. *
  224. * @deprecated Use `decodeFromVideoDevice` instead.
  225. */
  226. decodeFromInputVideoDeviceContinuously(deviceId, videoSource, callbackFn) {
  227. return __awaiter(this, void 0, void 0, function* () {
  228. return yield this.decodeFromVideoDevice(deviceId, videoSource, callbackFn);
  229. });
  230. }
  231. /**
  232. * Continuously tries to decode the barcode from the device specified by device while showing the video in the specified video element.
  233. *
  234. * @param {string|null} [deviceId] the id of one of the devices obtained after calling getVideoInputDevices. Can be undefined, in this case it will decode from one of the available devices, preffering the main camera (environment facing) if available.
  235. * @param {string|HTMLVideoElement|null} [video] the video element in page where to show the video while decoding. Can be either an element id or directly an HTMLVideoElement. Can be undefined, in which case no video will be shown.
  236. * @returns {Promise<void>}
  237. *
  238. * @memberOf BrowserCodeReader
  239. */
  240. decodeFromVideoDevice(deviceId, videoSource, callbackFn) {
  241. return __awaiter(this, void 0, void 0, function* () {
  242. let videoConstraints;
  243. if (!deviceId) {
  244. videoConstraints = { facingMode: 'environment' };
  245. }
  246. else {
  247. videoConstraints = { deviceId: { exact: deviceId } };
  248. }
  249. const constraints = { video: videoConstraints };
  250. return yield this.decodeFromConstraints(constraints, videoSource, callbackFn);
  251. });
  252. }
  253. /**
  254. * Continuously tries to decode the barcode from a stream obtained from the given constraints while showing the video in the specified video element.
  255. *
  256. * @param {MediaStream} [constraints] the media stream constraints to get s valid media stream to decode from
  257. * @param {string|HTMLVideoElement} [video] the video element in page where to show the video while decoding. Can be either an element id or directly an HTMLVideoElement. Can be undefined, in which case no video will be shown.
  258. * @returns {Promise<Result>} The decoding result.
  259. *
  260. * @memberOf BrowserCodeReader
  261. */
  262. decodeFromConstraints(constraints, videoSource, callbackFn) {
  263. return __awaiter(this, void 0, void 0, function* () {
  264. const stream = yield navigator.mediaDevices.getUserMedia(constraints);
  265. return yield this.decodeFromStream(stream, videoSource, callbackFn);
  266. });
  267. }
  268. /**
  269. * In one attempt, tries to decode the barcode from a stream obtained from the given constraints while showing the video in the specified video element.
  270. *
  271. * @param {MediaStream} [constraints] the media stream constraints to get s valid media stream to decode from
  272. * @param {string|HTMLVideoElement} [video] the video element in page where to show the video while decoding. Can be either an element id or directly an HTMLVideoElement. Can be undefined, in which case no video will be shown.
  273. * @returns {Promise<Result>} The decoding result.
  274. *
  275. * @memberOf BrowserCodeReader
  276. */
  277. decodeFromStream(stream, videoSource, callbackFn) {
  278. return __awaiter(this, void 0, void 0, function* () {
  279. this.reset();
  280. const video = yield this.attachStreamToVideo(stream, videoSource);
  281. return yield this.decodeContinuously(video, callbackFn);
  282. });
  283. }
  284. /**
  285. * Breaks the decoding loop.
  286. */
  287. stopAsyncDecode() {
  288. this._stopAsyncDecode = true;
  289. }
  290. /**
  291. * Breaks the decoding loop.
  292. */
  293. stopContinuousDecode() {
  294. this._stopContinuousDecode = true;
  295. }
  296. /**
  297. * Sets the new stream and request a new decoding-with-delay.
  298. *
  299. * @param stream The stream to be shown in the video element.
  300. * @param decodeFn A callback for the decode method.
  301. */
  302. attachStreamToVideo(stream, videoSource) {
  303. return __awaiter(this, void 0, void 0, function* () {
  304. const videoElement = this.prepareVideoElement(videoSource);
  305. this.addVideoSource(videoElement, stream);
  306. this.videoElement = videoElement;
  307. this.stream = stream;
  308. yield this.playVideoOnLoadAsync(videoElement);
  309. return videoElement;
  310. });
  311. }
  312. /**
  313. *
  314. * @param videoElement
  315. */
  316. playVideoOnLoadAsync(videoElement) {
  317. return new Promise((resolve, reject) => this.playVideoOnLoad(videoElement, () => resolve()));
  318. }
  319. /**
  320. * Binds listeners and callbacks to the videoElement.
  321. *
  322. * @param element
  323. * @param callbackFn
  324. */
  325. playVideoOnLoad(element, callbackFn) {
  326. this.videoEndedListener = () => this.stopStreams();
  327. this.videoCanPlayListener = () => this.tryPlayVideo(element);
  328. element.addEventListener('ended', this.videoEndedListener);
  329. element.addEventListener('canplay', this.videoCanPlayListener);
  330. element.addEventListener('playing', callbackFn);
  331. // if canplay was already fired, we won't know when to play, so just give it a try
  332. this.tryPlayVideo(element);
  333. }
  334. /**
  335. * Checks if the given video element is currently playing.
  336. */
  337. isVideoPlaying(video) {
  338. return (video.currentTime > 0 &&
  339. !video.paused &&
  340. !video.ended &&
  341. video.readyState > 2);
  342. }
  343. /**
  344. * Just tries to play the video and logs any errors.
  345. * The play call is only made is the video is not already playing.
  346. */
  347. tryPlayVideo(videoElement) {
  348. return __awaiter(this, void 0, void 0, function* () {
  349. if (this.isVideoPlaying(videoElement)) {
  350. console.warn('Trying to play video that is already playing.');
  351. return;
  352. }
  353. try {
  354. yield videoElement.play();
  355. }
  356. catch (_a) {
  357. console.warn('It was not possible to play the video.');
  358. }
  359. });
  360. }
  361. /**
  362. * Searches and validates a media element.
  363. */
  364. getMediaElement(mediaElementId, type) {
  365. const mediaElement = document.getElementById(mediaElementId);
  366. if (!mediaElement) {
  367. throw new ArgumentException(`element with id '${mediaElementId}' not found`);
  368. }
  369. if (mediaElement.nodeName.toLowerCase() !== type.toLowerCase()) {
  370. throw new ArgumentException(`element with id '${mediaElementId}' must be an ${type} element`);
  371. }
  372. return mediaElement;
  373. }
  374. /**
  375. * Decodes the barcode from an image.
  376. *
  377. * @param {(string|HTMLImageElement)} [source] The image element that can be either an element id or the element itself. Can be undefined in which case the decoding will be done from the imageUrl parameter.
  378. * @param {string} [url]
  379. * @returns {Promise<Result>} The decoding result.
  380. *
  381. * @memberOf BrowserCodeReader
  382. */
  383. decodeFromImage(source, url) {
  384. if (!source && !url) {
  385. throw new ArgumentException('either imageElement with a src set or an url must be provided');
  386. }
  387. if (url && !source) {
  388. return this.decodeFromImageUrl(url);
  389. }
  390. return this.decodeFromImageElement(source);
  391. }
  392. /**
  393. * Decodes the barcode from a video.
  394. *
  395. * @param {(string|HTMLImageElement)} [source] The image element that can be either an element id or the element itself. Can be undefined in which case the decoding will be done from the imageUrl parameter.
  396. * @param {string} [url]
  397. * @returns {Promise<Result>} The decoding result.
  398. *
  399. * @memberOf BrowserCodeReader
  400. */
  401. decodeFromVideo(source, url) {
  402. if (!source && !url) {
  403. throw new ArgumentException('Either an element with a src set or an URL must be provided');
  404. }
  405. if (url && !source) {
  406. return this.decodeFromVideoUrl(url);
  407. }
  408. return this.decodeFromVideoElement(source);
  409. }
  410. /**
  411. * Decodes continuously the barcode from a video.
  412. *
  413. * @param {(string|HTMLImageElement)} [source] The image element that can be either an element id or the element itself. Can be undefined in which case the decoding will be done from the imageUrl parameter.
  414. * @param {string} [url]
  415. * @returns {Promise<Result>} The decoding result.
  416. *
  417. * @memberOf BrowserCodeReader
  418. *
  419. * @experimental
  420. */
  421. decodeFromVideoContinuously(source, url, callbackFn) {
  422. if (undefined === source && undefined === url) {
  423. throw new ArgumentException('Either an element with a src set or an URL must be provided');
  424. }
  425. if (url && !source) {
  426. return this.decodeFromVideoUrlContinuously(url, callbackFn);
  427. }
  428. return this.decodeFromVideoElementContinuously(source, callbackFn);
  429. }
  430. /**
  431. * Decodes something from an image HTML element.
  432. */
  433. decodeFromImageElement(source) {
  434. if (!source) {
  435. throw new ArgumentException('An image element must be provided.');
  436. }
  437. this.reset();
  438. const element = this.prepareImageElement(source);
  439. this.imageElement = element;
  440. let task;
  441. if (this.isImageLoaded(element)) {
  442. task = this.decodeOnce(element, false, true);
  443. }
  444. else {
  445. task = this._decodeOnLoadImage(element);
  446. }
  447. return task;
  448. }
  449. /**
  450. * Decodes something from an image HTML element.
  451. */
  452. decodeFromVideoElement(source) {
  453. const element = this._decodeFromVideoElementSetup(source);
  454. return this._decodeOnLoadVideo(element);
  455. }
  456. /**
  457. * Decodes something from an image HTML element.
  458. */
  459. decodeFromVideoElementContinuously(source, callbackFn) {
  460. const element = this._decodeFromVideoElementSetup(source);
  461. return this._decodeOnLoadVideoContinuously(element, callbackFn);
  462. }
  463. /**
  464. * Sets up the video source so it can be decoded when loaded.
  465. *
  466. * @param source The video source element.
  467. */
  468. _decodeFromVideoElementSetup(source) {
  469. if (!source) {
  470. throw new ArgumentException('A video element must be provided.');
  471. }
  472. this.reset();
  473. const element = this.prepareVideoElement(source);
  474. // defines the video element before starts decoding
  475. this.videoElement = element;
  476. return element;
  477. }
  478. /**
  479. * Decodes an image from a URL.
  480. */
  481. decodeFromImageUrl(url) {
  482. if (!url) {
  483. throw new ArgumentException('An URL must be provided.');
  484. }
  485. this.reset();
  486. const element = this.prepareImageElement();
  487. this.imageElement = element;
  488. const decodeTask = this._decodeOnLoadImage(element);
  489. element.src = url;
  490. return decodeTask;
  491. }
  492. /**
  493. * Decodes an image from a URL.
  494. */
  495. decodeFromVideoUrl(url) {
  496. if (!url) {
  497. throw new ArgumentException('An URL must be provided.');
  498. }
  499. this.reset();
  500. // creates a new element
  501. const element = this.prepareVideoElement();
  502. const decodeTask = this.decodeFromVideoElement(element);
  503. element.src = url;
  504. return decodeTask;
  505. }
  506. /**
  507. * Decodes an image from a URL.
  508. *
  509. * @experimental
  510. */
  511. decodeFromVideoUrlContinuously(url, callbackFn) {
  512. if (!url) {
  513. throw new ArgumentException('An URL must be provided.');
  514. }
  515. this.reset();
  516. // creates a new element
  517. const element = this.prepareVideoElement();
  518. const decodeTask = this.decodeFromVideoElementContinuously(element, callbackFn);
  519. element.src = url;
  520. return decodeTask;
  521. }
  522. _decodeOnLoadImage(element) {
  523. return new Promise((resolve, reject) => {
  524. this.imageLoadedListener = () => this.decodeOnce(element, false, true).then(resolve, reject);
  525. element.addEventListener('load', this.imageLoadedListener);
  526. });
  527. }
  528. _decodeOnLoadVideo(videoElement) {
  529. return __awaiter(this, void 0, void 0, function* () {
  530. // plays the video
  531. yield this.playVideoOnLoadAsync(videoElement);
  532. // starts decoding after played the video
  533. return yield this.decodeOnce(videoElement);
  534. });
  535. }
  536. _decodeOnLoadVideoContinuously(videoElement, callbackFn) {
  537. return __awaiter(this, void 0, void 0, function* () {
  538. // plays the video
  539. yield this.playVideoOnLoadAsync(videoElement);
  540. // starts decoding after played the video
  541. this.decodeContinuously(videoElement, callbackFn);
  542. });
  543. }
  544. isImageLoaded(img) {
  545. // During the onload event, IE correctly identifies any images that
  546. // weren’t downloaded as not complete. Others should too. Gecko-based
  547. // browsers act like NS4 in that they report this incorrectly.
  548. if (!img.complete) {
  549. return false;
  550. }
  551. // However, they do have two very useful properties: naturalWidth and
  552. // naturalHeight. These give the true size of the image. If it failed
  553. // to load, either of these should be zero.
  554. if (img.naturalWidth === 0) {
  555. return false;
  556. }
  557. // No other way of checking: assume it’s ok.
  558. return true;
  559. }
  560. prepareImageElement(imageSource) {
  561. let imageElement;
  562. if (typeof imageSource === 'undefined') {
  563. imageElement = document.createElement('img');
  564. imageElement.width = 200;
  565. imageElement.height = 200;
  566. }
  567. if (typeof imageSource === 'string') {
  568. imageElement = this.getMediaElement(imageSource, 'img');
  569. }
  570. if (imageSource instanceof HTMLImageElement) {
  571. imageElement = imageSource;
  572. }
  573. return imageElement;
  574. }
  575. /**
  576. * Sets a HTMLVideoElement for scanning or creates a new one.
  577. *
  578. * @param videoSource The HTMLVideoElement to be set.
  579. */
  580. prepareVideoElement(videoSource) {
  581. let videoElement;
  582. if (!videoSource && typeof document !== 'undefined') {
  583. videoElement = document.createElement('video');
  584. videoElement.width = 200;
  585. videoElement.height = 200;
  586. }
  587. if (typeof videoSource === 'string') {
  588. videoElement = (this.getMediaElement(videoSource, 'video'));
  589. }
  590. if (videoSource instanceof HTMLVideoElement) {
  591. videoElement = videoSource;
  592. }
  593. // Needed for iOS 11
  594. videoElement.setAttribute('autoplay', 'true');
  595. videoElement.setAttribute('muted', 'true');
  596. videoElement.setAttribute('playsinline', 'true');
  597. return videoElement;
  598. }
  599. /**
  600. * Tries to decode from the video input until it finds some value.
  601. */
  602. decodeOnce(element, retryIfNotFound = true, retryIfChecksumOrFormatError = true) {
  603. this._stopAsyncDecode = false;
  604. const loop = (resolve, reject) => {
  605. if (this._stopAsyncDecode) {
  606. reject(new NotFoundException('Video stream has ended before any code could be detected.'));
  607. this._stopAsyncDecode = undefined;
  608. return;
  609. }
  610. try {
  611. const result = this.decode(element);
  612. resolve(result);
  613. }
  614. catch (e) {
  615. const ifNotFound = retryIfNotFound && e instanceof NotFoundException;
  616. const isChecksumOrFormatError = e instanceof ChecksumException || e instanceof FormatException;
  617. const ifChecksumOrFormat = isChecksumOrFormatError && retryIfChecksumOrFormatError;
  618. if (ifNotFound || ifChecksumOrFormat) {
  619. // trying again
  620. return setTimeout(loop, this._timeBetweenDecodingAttempts, resolve, reject);
  621. }
  622. reject(e);
  623. }
  624. };
  625. return new Promise((resolve, reject) => loop(resolve, reject));
  626. }
  627. /**
  628. * Continuously decodes from video input.
  629. */
  630. decodeContinuously(element, callbackFn) {
  631. this._stopContinuousDecode = false;
  632. const loop = () => {
  633. if (this._stopContinuousDecode) {
  634. this._stopContinuousDecode = undefined;
  635. return;
  636. }
  637. try {
  638. const result = this.decode(element);
  639. callbackFn(result, null);
  640. setTimeout(loop, this.timeBetweenScansMillis);
  641. }
  642. catch (e) {
  643. callbackFn(null, e);
  644. const isChecksumOrFormatError = e instanceof ChecksumException || e instanceof FormatException;
  645. const isNotFound = e instanceof NotFoundException;
  646. if (isChecksumOrFormatError || isNotFound) {
  647. // trying again
  648. setTimeout(loop, this._timeBetweenDecodingAttempts);
  649. }
  650. }
  651. };
  652. loop();
  653. }
  654. /**
  655. * Gets the BinaryBitmap for ya! (and decodes it)
  656. */
  657. decode(element) {
  658. // get binary bitmap for decode function
  659. const binaryBitmap = this.createBinaryBitmap(element);
  660. return this.decodeBitmap(binaryBitmap);
  661. }
  662. /**
  663. * Creates a binaryBitmap based in some image source.
  664. *
  665. * @param mediaElement HTML element containing drawable image source.
  666. */
  667. createBinaryBitmap(mediaElement) {
  668. const ctx = this.getCaptureCanvasContext(mediaElement);
  669. if (mediaElement instanceof HTMLVideoElement) {
  670. this.drawFrameOnCanvas(mediaElement);
  671. }
  672. else {
  673. this.drawImageOnCanvas(mediaElement);
  674. }
  675. const canvas = this.getCaptureCanvas(mediaElement);
  676. const luminanceSource = new HTMLCanvasElementLuminanceSource(canvas);
  677. const hybridBinarizer = new HybridBinarizer(luminanceSource);
  678. return new BinaryBitmap(hybridBinarizer);
  679. }
  680. /**
  681. *
  682. */
  683. getCaptureCanvasContext(mediaElement) {
  684. if (!this.captureCanvasContext) {
  685. const elem = this.getCaptureCanvas(mediaElement);
  686. let ctx;
  687. try {
  688. ctx = elem.getContext('2d', { willReadFrequently: true });
  689. }
  690. catch (e) {
  691. ctx = elem.getContext('2d');
  692. }
  693. this.captureCanvasContext = ctx;
  694. }
  695. return this.captureCanvasContext;
  696. }
  697. /**
  698. *
  699. */
  700. getCaptureCanvas(mediaElement) {
  701. if (!this.captureCanvas) {
  702. const elem = this.createCaptureCanvas(mediaElement);
  703. this.captureCanvas = elem;
  704. }
  705. return this.captureCanvas;
  706. }
  707. /**
  708. * Overwriting this allows you to manipulate the next frame in anyway you want before decode.
  709. */
  710. drawFrameOnCanvas(srcElement, dimensions = {
  711. sx: 0,
  712. sy: 0,
  713. sWidth: srcElement.videoWidth,
  714. sHeight: srcElement.videoHeight,
  715. dx: 0,
  716. dy: 0,
  717. dWidth: srcElement.videoWidth,
  718. dHeight: srcElement.videoHeight,
  719. }, canvasElementContext = this.captureCanvasContext) {
  720. canvasElementContext.drawImage(srcElement, dimensions.sx, dimensions.sy, dimensions.sWidth, dimensions.sHeight, dimensions.dx, dimensions.dy, dimensions.dWidth, dimensions.dHeight);
  721. }
  722. /**
  723. * Ovewriting this allows you to manipulate the snapshot image in anyway you want before decode.
  724. */
  725. drawImageOnCanvas(srcElement, dimensions = {
  726. sx: 0,
  727. sy: 0,
  728. sWidth: srcElement.naturalWidth,
  729. sHeight: srcElement.naturalHeight,
  730. dx: 0,
  731. dy: 0,
  732. dWidth: srcElement.naturalWidth,
  733. dHeight: srcElement.naturalHeight,
  734. }, canvasElementContext = this.captureCanvasContext) {
  735. canvasElementContext.drawImage(srcElement, dimensions.sx, dimensions.sy, dimensions.sWidth, dimensions.sHeight, dimensions.dx, dimensions.dy, dimensions.dWidth, dimensions.dHeight);
  736. }
  737. /**
  738. * Call the encapsulated readers decode
  739. */
  740. decodeBitmap(binaryBitmap) {
  741. return this.reader.decode(binaryBitmap, this._hints);
  742. }
  743. /**
  744. * 🖌 Prepares the canvas for capture and scan frames.
  745. */
  746. createCaptureCanvas(mediaElement) {
  747. if (typeof document === 'undefined') {
  748. this._destroyCaptureCanvas();
  749. return null;
  750. }
  751. const canvasElement = document.createElement('canvas');
  752. let width;
  753. let height;
  754. if (typeof mediaElement !== 'undefined') {
  755. if (mediaElement instanceof HTMLVideoElement) {
  756. width = mediaElement.videoWidth;
  757. height = mediaElement.videoHeight;
  758. }
  759. else if (mediaElement instanceof HTMLImageElement) {
  760. width = mediaElement.naturalWidth || mediaElement.width;
  761. height = mediaElement.naturalHeight || mediaElement.height;
  762. }
  763. }
  764. canvasElement.style.width = width + 'px';
  765. canvasElement.style.height = height + 'px';
  766. canvasElement.width = width;
  767. canvasElement.height = height;
  768. return canvasElement;
  769. }
  770. /**
  771. * Stops the continuous scan and cleans the stream.
  772. */
  773. stopStreams() {
  774. if (this.stream) {
  775. this.stream.getVideoTracks().forEach(t => t.stop());
  776. this.stream = undefined;
  777. }
  778. if (this._stopAsyncDecode === false) {
  779. this.stopAsyncDecode();
  780. }
  781. if (this._stopContinuousDecode === false) {
  782. this.stopContinuousDecode();
  783. }
  784. }
  785. /**
  786. * Resets the code reader to the initial state. Cancels any ongoing barcode scanning from video or camera.
  787. *
  788. * @memberOf BrowserCodeReader
  789. */
  790. reset() {
  791. // stops the camera, preview and scan 🔴
  792. this.stopStreams();
  793. // clean and forget about HTML elements
  794. this._destroyVideoElement();
  795. this._destroyImageElement();
  796. this._destroyCaptureCanvas();
  797. }
  798. _destroyVideoElement() {
  799. if (!this.videoElement) {
  800. return;
  801. }
  802. // first gives freedon to the element 🕊
  803. if (typeof this.videoEndedListener !== 'undefined') {
  804. this.videoElement.removeEventListener('ended', this.videoEndedListener);
  805. }
  806. if (typeof this.videoPlayingEventListener !== 'undefined') {
  807. this.videoElement.removeEventListener('playing', this.videoPlayingEventListener);
  808. }
  809. if (typeof this.videoCanPlayListener !== 'undefined') {
  810. this.videoElement.removeEventListener('loadedmetadata', this.videoCanPlayListener);
  811. }
  812. // then forgets about that element 😢
  813. this.cleanVideoSource(this.videoElement);
  814. this.videoElement = undefined;
  815. }
  816. _destroyImageElement() {
  817. if (!this.imageElement) {
  818. return;
  819. }
  820. // first gives freedon to the element 🕊
  821. if (undefined !== this.imageLoadedListener) {
  822. this.imageElement.removeEventListener('load', this.imageLoadedListener);
  823. }
  824. // then forget about that element 😢
  825. this.imageElement.src = undefined;
  826. this.imageElement.removeAttribute('src');
  827. this.imageElement = undefined;
  828. }
  829. /**
  830. * Cleans canvas references 🖌
  831. */
  832. _destroyCaptureCanvas() {
  833. // then forget about that element 😢
  834. this.captureCanvasContext = undefined;
  835. this.captureCanvas = undefined;
  836. }
  837. /**
  838. * Defines what the videoElement src will be.
  839. *
  840. * @param videoElement
  841. * @param stream
  842. */
  843. addVideoSource(videoElement, stream) {
  844. // Older browsers may not have `srcObject`
  845. try {
  846. // @note Throws Exception if interrupted by a new loaded request
  847. videoElement.srcObject = stream;
  848. }
  849. catch (err) {
  850. // @note Avoid using this in new browsers, as it is going away.
  851. // @ts-ignore
  852. videoElement.src = URL.createObjectURL(stream);
  853. }
  854. }
  855. /**
  856. * Unbinds a HTML video src property.
  857. *
  858. * @param videoElement
  859. */
  860. cleanVideoSource(videoElement) {
  861. try {
  862. videoElement.srcObject = null;
  863. }
  864. catch (err) {
  865. videoElement.src = '';
  866. }
  867. this.videoElement.removeAttribute('src');
  868. }
  869. }