scandoc-ai-components 0.1.16 → 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.
- package/dist/index.js +399 -191
- 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
|
|
11178
|
-
width:
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
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;
|
|
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",
|
|
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.
|
|
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.
|
|
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
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
}
|
|
11406
|
-
|
|
11407
|
-
|
|
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
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
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`;
|
|
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;
|
|
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
|
|