scandoc-ai-components 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +399 -191
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11174,14 +11174,16 @@ class ExtractorVideo {
11174
11174
  static VALIDATION_BATCH_SIZE = 1;
11175
11175
  static VALIDATION_IMG_WIDTH = 384;
11176
11176
  static VALIDATION_IMG_HEIGHT = 384;
11177
- static VIDEO_SETTINGS = Object.freeze({
11178
- width: {
11179
- ideal: 1920
11180
- },
11181
- height: {
11182
- ideal: 1080
11183
- }
11184
- });
11177
+ static VIDEO_CANDIDATE_SETTINGS = Object.freeze([{
11178
+ width: 1920,
11179
+ height: 1080
11180
+ }, {
11181
+ width: 1280,
11182
+ height: 960
11183
+ }, {
11184
+ width: 1280,
11185
+ height: 720
11186
+ }]);
11185
11187
  constructor(onExtractedResults) {
11186
11188
  this.candidateImages = [];
11187
11189
  this.extractionImages = {};
@@ -11195,8 +11197,6 @@ class ExtractorVideo {
11195
11197
  this.waitingForSecondSide = false;
11196
11198
  this.hasShownTurnMessage = false;
11197
11199
  this.turnMessageTimer = null;
11198
-
11199
- // initialize through setter so we see the first set too:
11200
11200
  this.onExtractedResults = onExtractedResults;
11201
11201
  }
11202
11202
  reset() {
@@ -11294,13 +11294,11 @@ class ExtractorVideo {
11294
11294
  this.hasShownTurnMessage = true;
11295
11295
  this.scanStartTime = Date.now();
11296
11296
  this.showMessage("Turn to the other side", true);
11297
-
11298
- // Clear lock after 3 seconds
11299
11297
  clearTimeout(this.turnMessageTimer);
11300
11298
  this.turnMessageTimer = setTimeout(() => {
11301
11299
  this.hasShownTurnMessage = false;
11302
11300
  this.waitingForSecondSide = false;
11303
- this.lastMessage = null; // allow same message again later
11301
+ this.lastMessage = null;
11304
11302
  }, 3000);
11305
11303
  } else if (Object.keys(this.extractionImages).length === 2) {
11306
11304
  this.waitingForSecondSide = false;
@@ -11367,7 +11365,7 @@ class ExtractorVideo {
11367
11365
  this.turnMessageTimer = null;
11368
11366
  this.stopVideo();
11369
11367
  if (isExtractionOk) {
11370
- this.showMessage("Success - data extracted", true);
11368
+ this.showMessage("Success - data extracted", "success");
11371
11369
  const shouldStop = this.onExtractedResults({
11372
11370
  success: true,
11373
11371
  code: "001",
@@ -11375,7 +11373,7 @@ class ExtractorVideo {
11375
11373
  data: extractionData
11376
11374
  });
11377
11375
  if (shouldStop === true) {
11378
- setTimeout(() => this.startVideo(), ExtractorVideo.FREQUENCY_MS);
11376
+ setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
11379
11377
  }
11380
11378
  } else {
11381
11379
  this.onExtractedResults({
@@ -11383,8 +11381,378 @@ class ExtractorVideo {
11383
11381
  code: "005",
11384
11382
  info: "Document validation passed but extraction failed."
11385
11383
  });
11386
- setTimeout(() => this.startVideo(), ExtractorVideo.FREQUENCY_MS);
11384
+ setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
11385
+ }
11386
+ }
11387
+ getSupportedConstraintsSafe() {
11388
+ if (!navigator.mediaDevices?.getSupportedConstraints) return {};
11389
+ try {
11390
+ return navigator.mediaDevices.getSupportedConstraints();
11391
+ } catch (_) {
11392
+ return {};
11393
+ }
11394
+ }
11395
+ getAspectRatio(settings) {
11396
+ if (typeof settings?.aspectRatio === "number" && settings.aspectRatio > 0) {
11397
+ return settings.aspectRatio;
11398
+ }
11399
+ if (settings?.width && settings?.height) {
11400
+ return settings.width / settings.height;
11401
+ }
11402
+ return null;
11403
+ }
11404
+ isLandscape(settings) {
11405
+ return (settings?.width || 0) > (settings?.height || 0);
11406
+ }
11407
+ isFrontLabel(label) {
11408
+ const l = (label || "").toLowerCase();
11409
+ return l.includes("front") || l.includes("selfie") || l.includes("user facing") || l.includes("facing front");
11410
+ }
11411
+ isRearLikeLabel(label) {
11412
+ const l = (label || "").toLowerCase();
11413
+ return l.includes("rear") || l.includes("back") || l.includes("environment") || l.includes("facing back") || l.includes("facing rear");
11414
+ }
11415
+ scoreTrack({
11416
+ settings,
11417
+ label
11418
+ }) {
11419
+ const width = settings?.width || 0;
11420
+ const height = settings?.height || 0;
11421
+ const pixels = width * height;
11422
+ const aspectRatio = this.getAspectRatio(settings) || 0;
11423
+ let score = pixels;
11424
+ if (width > height) score += 1_000_000_000;
11425
+ if (aspectRatio >= 1.3) score += 100_000_000;
11426
+ if (this.isRearLikeLabel(label)) score += 10_000_000;
11427
+ if (this.isFrontLabel(label)) score -= 10_000_000_000;
11428
+ return score;
11429
+ }
11430
+ buildVideoConstraints({
11431
+ width,
11432
+ height,
11433
+ mode,
11434
+ deviceId
11435
+ }) {
11436
+ const supported = this.getSupportedConstraintsSafe();
11437
+ const video = {};
11438
+ if (deviceId) {
11439
+ video.deviceId = {
11440
+ exact: deviceId
11441
+ };
11442
+ } else if (supported.facingMode) {
11443
+ if (typeof mode === "string") {
11444
+ video.facingMode = {
11445
+ ideal: mode
11446
+ };
11447
+ } else if (mode && typeof mode === "object") {
11448
+ video.facingMode = mode;
11449
+ } else {
11450
+ video.facingMode = {
11451
+ ideal: "environment"
11452
+ };
11453
+ }
11454
+ }
11455
+ if (supported.width) {
11456
+ video.width = {
11457
+ ideal: width
11458
+ };
11459
+ }
11460
+ if (supported.height) {
11461
+ video.height = {
11462
+ ideal: height
11463
+ };
11464
+ }
11465
+ if (supported.aspectRatio) {
11466
+ video.aspectRatio = {
11467
+ ideal: width / height
11468
+ };
11469
+ }
11470
+ return {
11471
+ video,
11472
+ audio: false
11473
+ };
11474
+ }
11475
+ async pauseBetweenCameraOps(ms = 180) {
11476
+ await new Promise(resolve => setTimeout(resolve, ms));
11477
+ }
11478
+ async stopMediaStream(stream) {
11479
+ if (!stream) return;
11480
+ try {
11481
+ stream.getTracks().forEach(t => t.stop());
11482
+ } catch (_) {}
11483
+ await this.pauseBetweenCameraOps();
11484
+ }
11485
+ async tryEnableDocumentFocus(stream) {
11486
+ const track = stream?.getVideoTracks?.()[0];
11487
+ if (!track) {
11488
+ return {
11489
+ ok: false,
11490
+ reason: "No video track found"
11491
+ };
11492
+ }
11493
+ if (!track.getCapabilities || !track.applyConstraints) {
11494
+ return {
11495
+ ok: false,
11496
+ reason: "Focus APIs unavailable on this browser/device",
11497
+ settingsBefore: track.getSettings ? track.getSettings() : null
11498
+ };
11499
+ }
11500
+ const caps = track.getCapabilities();
11501
+ const result = {
11502
+ ok: true,
11503
+ capabilities: caps,
11504
+ attempts: [],
11505
+ settingsBefore: track.getSettings ? track.getSettings() : null
11506
+ };
11507
+ try {
11508
+ let applied = false;
11509
+ if (Array.isArray(caps.focusMode) && caps.focusMode.includes("continuous")) {
11510
+ await track.applyConstraints({
11511
+ advanced: [{
11512
+ focusMode: "continuous"
11513
+ }]
11514
+ });
11515
+ result.attempts.push("Applied focusMode=continuous");
11516
+ applied = true;
11517
+ }
11518
+ if (caps.focusDistance) {
11519
+ const min = caps.focusDistance.min;
11520
+ const max = caps.focusDistance.max;
11521
+ const docDistance = min + (max - min) * 0.30;
11522
+ const advanced = [];
11523
+ if (Array.isArray(caps.focusMode) && caps.focusMode.includes("manual")) {
11524
+ advanced.push({
11525
+ focusMode: "manual"
11526
+ });
11527
+ }
11528
+ advanced.push({
11529
+ focusDistance: docDistance
11530
+ });
11531
+ await track.applyConstraints({
11532
+ advanced
11533
+ });
11534
+ result.attempts.push(`Applied focusDistance=${docDistance}`);
11535
+ applied = true;
11536
+ }
11537
+ if (!applied) {
11538
+ result.ok = false;
11539
+ result.reason = "No supported focus controls were exposed";
11540
+ }
11541
+ result.settingsAfter = track.getSettings ? track.getSettings() : null;
11542
+ return result;
11543
+ } catch (err) {
11544
+ result.ok = false;
11545
+ result.error = err.message || String(err);
11546
+ result.settingsAfter = track.getSettings ? track.getSettings() : null;
11547
+ return result;
11548
+ }
11549
+ }
11550
+ async openCandidateStream({
11551
+ mode,
11552
+ deviceId,
11553
+ width,
11554
+ height
11555
+ }) {
11556
+ return navigator.mediaDevices.getUserMedia(this.buildVideoConstraints({
11557
+ width,
11558
+ height,
11559
+ mode,
11560
+ deviceId
11561
+ }));
11562
+ }
11563
+ async testDeviceAtPreferredResolutions(device, mode) {
11564
+ const attempts = [];
11565
+ for (const size of ExtractorVideo.VIDEO_CANDIDATE_SETTINGS) {
11566
+ let stream = null;
11567
+ try {
11568
+ stream = await this.openCandidateStream({
11569
+ mode,
11570
+ deviceId: device?.deviceId,
11571
+ width: size.width,
11572
+ height: size.height
11573
+ });
11574
+ const track = stream.getVideoTracks()[0];
11575
+ const settings = track.getSettings();
11576
+ const label = track.label || device?.label || "";
11577
+ const aspectRatio = this.getAspectRatio(settings);
11578
+ const result = {
11579
+ stream,
11580
+ label,
11581
+ settings,
11582
+ aspectRatio,
11583
+ landscape: this.isLandscape(settings),
11584
+ score: this.scoreTrack({
11585
+ settings,
11586
+ label
11587
+ }),
11588
+ requested: size
11589
+ };
11590
+ attempts.push({
11591
+ ok: true,
11592
+ requested: size,
11593
+ returnedWidth: settings.width,
11594
+ returnedHeight: settings.height,
11595
+ aspectRatio,
11596
+ label
11597
+ });
11598
+ return {
11599
+ result,
11600
+ attempts
11601
+ };
11602
+ } catch (err) {
11603
+ attempts.push({
11604
+ ok: false,
11605
+ requested: size,
11606
+ error: err.message || String(err)
11607
+ });
11608
+ await this.stopMediaStream(stream);
11609
+ }
11610
+ }
11611
+ return {
11612
+ result: null,
11613
+ attempts
11614
+ };
11615
+ }
11616
+ async pickBestPhoneCamera(mode = "environment") {
11617
+ const debug = {
11618
+ mode,
11619
+ tested: []
11620
+ };
11621
+
11622
+ // First try a warmup stream to unlock labels
11623
+ let warmupStream = null;
11624
+ let warmupResult = null;
11625
+ try {
11626
+ const warmupSize = ExtractorVideo.VIDEO_CANDIDATE_SETTINGS[2]; // 1280x720
11627
+ warmupStream = await this.openCandidateStream({
11628
+ mode,
11629
+ width: warmupSize.width,
11630
+ height: warmupSize.height
11631
+ });
11632
+ const track = warmupStream.getVideoTracks()[0];
11633
+ const settings = track.getSettings();
11634
+ const label = track.label || "";
11635
+ warmupResult = {
11636
+ stream: warmupStream,
11637
+ label,
11638
+ settings,
11639
+ aspectRatio: this.getAspectRatio(settings),
11640
+ landscape: this.isLandscape(settings),
11641
+ score: this.scoreTrack({
11642
+ settings,
11643
+ label
11644
+ }),
11645
+ requested: warmupSize
11646
+ };
11647
+ } catch (_) {
11648
+ warmupStream = null;
11649
+ }
11650
+ let devices = [];
11651
+ try {
11652
+ devices = await navigator.mediaDevices.enumerateDevices();
11653
+ } catch (_) {
11654
+ devices = [];
11655
+ }
11656
+ const videoInputs = devices.filter(d => d.kind === "videoinput");
11657
+ const candidateDevices = mode === "environment" ? videoInputs.filter(d => !this.isFrontLabel(d.label)) : videoInputs;
11658
+ if (warmupStream) {
11659
+ await this.stopMediaStream(warmupStream);
11660
+ warmupStream = null;
11661
+ }
11662
+ const usableResults = [];
11663
+ for (let i = 0; i < candidateDevices.length; i += 1) {
11664
+ const device = candidateDevices[i];
11665
+ const {
11666
+ result,
11667
+ attempts
11668
+ } = await this.testDeviceAtPreferredResolutions(device, mode);
11669
+ debug.tested.push({
11670
+ deviceId: device.deviceId,
11671
+ label: device.label,
11672
+ attempts,
11673
+ final: result ? {
11674
+ label: result.label,
11675
+ settings: result.settings,
11676
+ aspectRatio: result.aspectRatio,
11677
+ landscape: result.landscape,
11678
+ score: result.score,
11679
+ requested: result.requested
11680
+ } : null
11681
+ });
11682
+ if (result) {
11683
+ const frontRejected = mode === "environment" && this.isFrontLabel(result.label);
11684
+ const geometryOk = result.landscape;
11685
+ if (!frontRejected && geometryOk) {
11686
+ usableResults.push(result);
11687
+ } else {
11688
+ await this.stopMediaStream(result.stream);
11689
+ }
11690
+ }
11691
+ }
11692
+ if (usableResults.length > 0) {
11693
+ usableResults.sort((a, b) => b.score - a.score);
11694
+ const chosen = usableResults[0];
11695
+ for (const candidate of usableResults) {
11696
+ if (candidate !== chosen) {
11697
+ await this.stopMediaStream(candidate.stream);
11698
+ }
11699
+ }
11700
+ debug.chosen = {
11701
+ label: chosen.label,
11702
+ settings: chosen.settings,
11703
+ aspectRatio: chosen.aspectRatio,
11704
+ requested: chosen.requested,
11705
+ source: "device-test"
11706
+ };
11707
+ return {
11708
+ chosen,
11709
+ debug
11710
+ };
11387
11711
  }
11712
+
11713
+ // Final fallback: simple facingMode-based opens in descending priority
11714
+ for (const size of ExtractorVideo.VIDEO_CANDIDATE_SETTINGS) {
11715
+ try {
11716
+ const stream = await this.openCandidateStream({
11717
+ mode,
11718
+ width: size.width,
11719
+ height: size.height
11720
+ });
11721
+ const track = stream.getVideoTracks()[0];
11722
+ const settings = track.getSettings();
11723
+ const label = track.label || "";
11724
+ const aspectRatio = this.getAspectRatio(settings);
11725
+ const landscape = this.isLandscape(settings);
11726
+ if (mode === "environment" && !landscape) {
11727
+ await this.stopMediaStream(stream);
11728
+ continue;
11729
+ }
11730
+ const chosen = {
11731
+ stream,
11732
+ label,
11733
+ settings,
11734
+ aspectRatio,
11735
+ landscape,
11736
+ score: this.scoreTrack({
11737
+ settings,
11738
+ label
11739
+ }),
11740
+ requested: size
11741
+ };
11742
+ debug.chosen = {
11743
+ label,
11744
+ settings,
11745
+ aspectRatio,
11746
+ requested: size,
11747
+ source: "fallback-facingMode"
11748
+ };
11749
+ return {
11750
+ chosen,
11751
+ debug
11752
+ };
11753
+ } catch (_) {}
11754
+ }
11755
+ throw new Error("Could not start a suitable camera.");
11388
11756
  }
11389
11757
  async startVideo() {
11390
11758
  try {
@@ -11399,40 +11767,20 @@ class ExtractorVideo {
11399
11767
  this.scanStartTime = Date.now();
11400
11768
  const cfgValues = (0,_config__WEBPACK_IMPORTED_MODULE_1__.getScanDocAIConfigValues)();
11401
11769
  const mode = cfgValues.VIDEO_FACING_MODE ?? "environment";
11402
- let stream;
11403
- try {
11404
- stream = await navigator.mediaDevices.getUserMedia(buildDocumentScanConstraints(mode));
11405
- } catch (e) {
11406
- console.warn("Preferred camera constraints failed, falling back:", e);
11407
- stream = await navigator.mediaDevices.getUserMedia({
11408
- video: true,
11409
- audio: false
11410
- });
11411
- }
11412
- this.video.srcObject = stream;
11413
- try {
11414
- await this.video.play();
11415
- } catch (e) {
11770
+ const {
11771
+ chosen,
11772
+ debug
11773
+ } = await this.pickBestPhoneCamera(mode);
11774
+ this.video.srcObject = chosen.stream;
11775
+ await this.video.play().catch(e => {
11416
11776
  console.warn(`Error on video play: ${e}`);
11417
- }
11418
-
11419
- // Try to improve document focus after stream is active
11420
- try {
11421
- const focusResult = await tryEnableDocumentFocus(stream);
11422
- console.log("Focus result:", focusResult);
11423
- } catch (focusError) {
11424
- console.warn("Could not apply focus settings:", focusError);
11425
- }
11426
-
11427
- // Avoid re-binding listeners every start
11428
- if (!this._boundLoadedDataHandler) {
11429
- this._boundLoadedDataHandler = () => this.adjustOverlayPosition();
11430
- this.video.addEventListener("loadeddata", this._boundLoadedDataHandler);
11431
- }
11432
- if (!this._boundResizeHandler) {
11433
- this._boundResizeHandler = () => this.adjustOverlayPosition();
11434
- window.addEventListener("resize", this._boundResizeHandler);
11435
- }
11777
+ });
11778
+ this.focusDebug = await this.tryEnableDocumentFocus(chosen.stream);
11779
+ this.cameraDebug = debug;
11780
+ this.video.addEventListener("loadeddata", () => {
11781
+ this.adjustOverlayPosition();
11782
+ });
11783
+ window.addEventListener("resize", () => this.adjustOverlayPosition());
11436
11784
  setTimeout(() => this.adjustOverlayPosition(), 500);
11437
11785
  this.scanStartTime = Date.now();
11438
11786
  setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
@@ -11482,7 +11830,7 @@ class ExtractorVideo {
11482
11830
  const centerX = offsetX + scaleWidth / 2;
11483
11831
  const centerY = offsetY + scaleHeight / 2;
11484
11832
  dot.style.display = "block";
11485
- dot.style.left = `${centerX - 8}px`; // center minus half dot size
11833
+ dot.style.left = `${centerX - 8}px`;
11486
11834
  dot.style.top = `${centerY - 8}px`;
11487
11835
  }
11488
11836
  getHTML() {
@@ -11491,7 +11839,6 @@ class ExtractorVideo {
11491
11839
  const messageColor = cfgValues.VIDEO_COLORS?.messageColor;
11492
11840
  const isMobile = window.innerWidth < 768 || window.innerHeight > window.innerWidth && window.innerWidth < 1024;
11493
11841
  if (isMobile) {
11494
- // Mobile version with overlay feedback
11495
11842
  return `
11496
11843
  <div class="mobileVideoArea">
11497
11844
  <video id="ScanDocAIVideoElement" class="mobileVideo" autoplay muted playsinline></video>
@@ -11603,7 +11950,7 @@ class ExtractorVideo {
11603
11950
  z-index: 4;
11604
11951
  animation: flicker 1s infinite;
11605
11952
  pointer-events: none;
11606
- display: none; /* Hidden until video is ready */
11953
+ display: none;
11607
11954
  }
11608
11955
 
11609
11956
  @keyframes flicker {
@@ -11657,8 +12004,6 @@ function getExtractionVideo(tokenOrCallback, maybeCallback) {
11657
12004
  EXTRACTION_VIDEO = new ExtractorVideo(cb);
11658
12005
  return EXTRACTION_VIDEO;
11659
12006
  }
11660
-
11661
- // If called again, update callback too
11662
12007
  if (typeof cb === "function") {
11663
12008
  EXTRACTION_VIDEO.onExtractedResults = cb;
11664
12009
  } else if (cb !== undefined) {
@@ -11666,143 +12011,6 @@ function getExtractionVideo(tokenOrCallback, maybeCallback) {
11666
12011
  }
11667
12012
  return EXTRACTION_VIDEO;
11668
12013
  }
11669
- function getSupportedConstraintsSafe() {
11670
- try {
11671
- return navigator.mediaDevices?.getSupportedConstraints?.() || {};
11672
- } catch (_) {
11673
- return {};
11674
- }
11675
- }
11676
- function buildDocumentScanConstraints(mode = "environment") {
11677
- const supported = getSupportedConstraintsSafe();
11678
- const video = {};
11679
- if (supported.facingMode) {
11680
- video.facingMode = typeof mode === "string" ? {
11681
- ideal: mode
11682
- } : mode || {
11683
- ideal: "environment"
11684
- };
11685
- }
11686
- if (supported.width) {
11687
- video.width = {
11688
- ideal: 1920
11689
- };
11690
- }
11691
- if (supported.height) {
11692
- video.height = {
11693
- ideal: 1080
11694
- };
11695
- }
11696
- if (supported.aspectRatio) {
11697
- video.aspectRatio = {
11698
- ideal: 16 / 9
11699
- };
11700
- }
11701
- return {
11702
- video,
11703
- audio: false
11704
- };
11705
- }
11706
- async function tryEnableDocumentFocus(stream) {
11707
- const track = stream?.getVideoTracks?.()[0];
11708
- if (!track) {
11709
- return {
11710
- ok: false,
11711
- reason: "No video track found"
11712
- };
11713
- }
11714
- if (!track.getCapabilities || !track.applyConstraints) {
11715
- return {
11716
- ok: false,
11717
- reason: "Focus APIs unavailable on this browser/device",
11718
- settingsBefore: track.getSettings ? track.getSettings() : null
11719
- };
11720
- }
11721
- const caps = track.getCapabilities();
11722
- const result = {
11723
- ok: true,
11724
- capabilities: caps,
11725
- attempts: [],
11726
- settingsBefore: track.getSettings ? track.getSettings() : null
11727
- };
11728
- try {
11729
- let applied = false;
11730
- if (Array.isArray(caps.focusMode) && caps.focusMode.includes("continuous")) {
11731
- await track.applyConstraints({
11732
- advanced: [{
11733
- focusMode: "continuous"
11734
- }]
11735
- });
11736
- result.attempts.push("Applied focusMode=continuous");
11737
- applied = true;
11738
- }
11739
- if (caps.focusDistance) {
11740
- const min = caps.focusDistance.min ?? 0;
11741
- const max = caps.focusDistance.max ?? 0;
11742
- const docDistance = min + (max - min) * 0.3;
11743
- const advanced = [];
11744
- if (Array.isArray(caps.focusMode) && caps.focusMode.includes("manual")) {
11745
- advanced.push({
11746
- focusMode: "manual"
11747
- });
11748
- }
11749
- advanced.push({
11750
- focusDistance: docDistance
11751
- });
11752
- await track.applyConstraints({
11753
- advanced
11754
- });
11755
- result.attempts.push(`Applied focusDistance=${docDistance}`);
11756
- applied = true;
11757
- }
11758
- if (!applied) {
11759
- result.ok = false;
11760
- result.reason = "No supported focus controls were exposed";
11761
- }
11762
- result.settingsAfter = track.getSettings ? track.getSettings() : null;
11763
- return result;
11764
- } catch (err) {
11765
- result.ok = false;
11766
- result.error = err.message || String(err);
11767
- result.settingsAfter = track.getSettings ? track.getSettings() : null;
11768
- return result;
11769
- }
11770
- }
11771
- async function checkAutofocusSupport(environmentCameras) {
11772
- try {
11773
- const camerasWithAutofocus = [];
11774
- for (const camera of environmentCameras) {
11775
- // console.log(camera.deviceId);
11776
- const constraints = {
11777
- video: {
11778
- deviceId: {
11779
- exact: camera.deviceId
11780
- }
11781
- }
11782
- };
11783
-
11784
- // Try to access camera stream with autofocus enabled
11785
- const stream = await navigator.mediaDevices.getUserMedia(constraints);
11786
- const tracks = stream.getVideoTracks();
11787
- const track = tracks[0];
11788
- if (track) {
11789
- // Check if autofocus is supported
11790
- const capabilities = track.getCapabilities();
11791
- // console.log(capabilities);
11792
- if (capabilities.focusMode && capabilities.focusMode.includes("continuous")) {
11793
- camerasWithAutofocus.push(camera);
11794
- }
11795
- }
11796
-
11797
- // Cleanup
11798
- stream.getTracks().forEach(track => track.stop());
11799
- }
11800
- return camerasWithAutofocus;
11801
- } catch (error) {
11802
- console.log("Error checking autofocus support:", error);
11803
- return [];
11804
- }
11805
- }
11806
12014
 
11807
12015
  /***/ }),
11808
12016
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "scandoc-ai-components",
3
3
  "author": "ScanDoc-AI",
4
- "version": "0.1.15",
4
+ "version": "0.1.17",
5
5
  "private": false,
6
6
  "description": "Pure JavaScript package for integrating ScanDoc-AI services.",
7
7
  "keywords": [