scandoc-ai-components 0.1.16 → 0.1.18
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 +545 -197
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11170,18 +11170,21 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
11170
11170
|
|
|
11171
11171
|
const DOCUMENT_DETECTOR = new _ai_detect_document__WEBPACK_IMPORTED_MODULE_0__["default"]();
|
|
11172
11172
|
class ExtractorVideo {
|
|
11173
|
+
static DEBUG = true;
|
|
11173
11174
|
static FREQUENCY_MS = 10;
|
|
11174
11175
|
static VALIDATION_BATCH_SIZE = 1;
|
|
11175
11176
|
static VALIDATION_IMG_WIDTH = 384;
|
|
11176
11177
|
static VALIDATION_IMG_HEIGHT = 384;
|
|
11177
|
-
static
|
|
11178
|
-
width:
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11178
|
+
static VIDEO_CANDIDATE_SETTINGS = Object.freeze([{
|
|
11179
|
+
width: 1920,
|
|
11180
|
+
height: 1080
|
|
11181
|
+
}, {
|
|
11182
|
+
width: 1280,
|
|
11183
|
+
height: 960
|
|
11184
|
+
}, {
|
|
11185
|
+
width: 1280,
|
|
11186
|
+
height: 720
|
|
11187
|
+
}]);
|
|
11185
11188
|
constructor(onExtractedResults) {
|
|
11186
11189
|
this.candidateImages = [];
|
|
11187
11190
|
this.extractionImages = {};
|
|
@@ -11195,10 +11198,48 @@ class ExtractorVideo {
|
|
|
11195
11198
|
this.waitingForSecondSide = false;
|
|
11196
11199
|
this.hasShownTurnMessage = false;
|
|
11197
11200
|
this.turnMessageTimer = null;
|
|
11198
|
-
|
|
11199
|
-
// initialize through setter so we see the first set too:
|
|
11201
|
+
this.debugLog = [];
|
|
11200
11202
|
this.onExtractedResults = onExtractedResults;
|
|
11201
11203
|
}
|
|
11204
|
+
logDebug(event, data = {}) {
|
|
11205
|
+
if (!ExtractorVideo.DEBUG) return;
|
|
11206
|
+
const entry = {
|
|
11207
|
+
ts: new Date().toISOString(),
|
|
11208
|
+
event,
|
|
11209
|
+
data
|
|
11210
|
+
};
|
|
11211
|
+
this.debugLog.push(entry);
|
|
11212
|
+
try {
|
|
11213
|
+
console.log("[ScanDocAI]", event, data);
|
|
11214
|
+
} catch (_) {}
|
|
11215
|
+
}
|
|
11216
|
+
getDebugDump() {
|
|
11217
|
+
return {
|
|
11218
|
+
userAgent: navigator.userAgent,
|
|
11219
|
+
secureContext: window.isSecureContext,
|
|
11220
|
+
location: window.location.href,
|
|
11221
|
+
videoPresent: !!this.video,
|
|
11222
|
+
videoReadyState: this.video?.readyState,
|
|
11223
|
+
videoNetworkState: this.video?.networkState,
|
|
11224
|
+
videoPaused: this.video?.paused,
|
|
11225
|
+
videoMuted: this.video?.muted,
|
|
11226
|
+
videoAutoplay: this.video?.autoplay,
|
|
11227
|
+
videoPlaysInline: this.video?.playsInline,
|
|
11228
|
+
videoDimensions: {
|
|
11229
|
+
videoWidth: this.video?.videoWidth || 0,
|
|
11230
|
+
videoHeight: this.video?.videoHeight || 0,
|
|
11231
|
+
clientWidth: this.video?.clientWidth || 0,
|
|
11232
|
+
clientHeight: this.video?.clientHeight || 0
|
|
11233
|
+
},
|
|
11234
|
+
streamTracks: this.video?.srcObject ? this.video.srcObject.getTracks().map(t => ({
|
|
11235
|
+
kind: t.kind,
|
|
11236
|
+
label: t.label,
|
|
11237
|
+
readyState: t.readyState,
|
|
11238
|
+
enabled: t.enabled
|
|
11239
|
+
})) : [],
|
|
11240
|
+
log: this.debugLog
|
|
11241
|
+
};
|
|
11242
|
+
}
|
|
11202
11243
|
reset() {
|
|
11203
11244
|
this.stopVideo();
|
|
11204
11245
|
this.candidateImages = [];
|
|
@@ -11215,12 +11256,16 @@ class ExtractorVideo {
|
|
|
11215
11256
|
this.hasShownTurnMessage = false;
|
|
11216
11257
|
clearTimeout(this.turnMessageTimer);
|
|
11217
11258
|
this.turnMessageTimer = null;
|
|
11259
|
+
this.debugLog = [];
|
|
11218
11260
|
}
|
|
11219
11261
|
async analyzeVideoStream() {
|
|
11220
11262
|
if (!this.isRunning) return;
|
|
11221
11263
|
const now = Date.now();
|
|
11222
11264
|
const cfgValues = (0,_config__WEBPACK_IMPORTED_MODULE_1__.getScanDocAIConfigValues)();
|
|
11223
11265
|
if (this.scanStartTime && now - this.scanStartTime > cfgValues.MAX_SCAN_DURATION_MS) {
|
|
11266
|
+
this.logDebug("scan_timeout", {
|
|
11267
|
+
elapsedMs: now - this.scanStartTime
|
|
11268
|
+
});
|
|
11224
11269
|
this.stopVideo();
|
|
11225
11270
|
this.onExtractedResults({
|
|
11226
11271
|
success: false,
|
|
@@ -11231,7 +11276,16 @@ class ExtractorVideo {
|
|
|
11231
11276
|
}
|
|
11232
11277
|
const canvas = document.createElement("canvas");
|
|
11233
11278
|
const video = document.getElementById("ScanDocAIVideoElement");
|
|
11234
|
-
if (video
|
|
11279
|
+
if (!video) {
|
|
11280
|
+
this.logDebug("analyze_no_video_element");
|
|
11281
|
+
setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
|
|
11282
|
+
return;
|
|
11283
|
+
}
|
|
11284
|
+
if (video.videoWidth < video.videoHeight) {
|
|
11285
|
+
this.logDebug("portrait_video_detected", {
|
|
11286
|
+
videoWidth: video.videoWidth,
|
|
11287
|
+
videoHeight: video.videoHeight
|
|
11288
|
+
});
|
|
11235
11289
|
this.showMessage("Please rotate your device to landscape mode.");
|
|
11236
11290
|
this.candidateImages = [];
|
|
11237
11291
|
setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
|
|
@@ -11250,6 +11304,9 @@ class ExtractorVideo {
|
|
|
11250
11304
|
const [isValidationOk, response] = await (0,_requests_validation__WEBPACK_IMPORTED_MODULE_3__["default"])(images.map(e => e.validationImg), this.pastBlurValues, {});
|
|
11251
11305
|
if (!isValidationOk) {
|
|
11252
11306
|
const msg = Array.isArray(response) ? response.join(", ") : String(response || "");
|
|
11307
|
+
this.logDebug("validation_failed", {
|
|
11308
|
+
response: msg
|
|
11309
|
+
});
|
|
11253
11310
|
if (msg.toLowerCase().includes("token expired/invalid")) {
|
|
11254
11311
|
this.stopVideo();
|
|
11255
11312
|
this.showMessage("Token expired/invalid. Please provide a new token.", true);
|
|
@@ -11264,6 +11321,7 @@ class ExtractorVideo {
|
|
|
11264
11321
|
return;
|
|
11265
11322
|
}
|
|
11266
11323
|
if (response?.InfoCode === "1006" && response?.TransactionID) {
|
|
11324
|
+
this.logDebug("unsupported_document", response);
|
|
11267
11325
|
const frontImage = this.candidateImages[this.candidateImages.length - 1].fullImg;
|
|
11268
11326
|
await (0,_requests_error_logging__WEBPACK_IMPORTED_MODULE_4__["default"])(frontImage, "", response.TransactionID, response.OS, response.Device, response.Browser);
|
|
11269
11327
|
this.showMessage("Document not supported.", true);
|
|
@@ -11294,13 +11352,11 @@ class ExtractorVideo {
|
|
|
11294
11352
|
this.hasShownTurnMessage = true;
|
|
11295
11353
|
this.scanStartTime = Date.now();
|
|
11296
11354
|
this.showMessage("Turn to the other side", true);
|
|
11297
|
-
|
|
11298
|
-
// Clear lock after 3 seconds
|
|
11299
11355
|
clearTimeout(this.turnMessageTimer);
|
|
11300
11356
|
this.turnMessageTimer = setTimeout(() => {
|
|
11301
11357
|
this.hasShownTurnMessage = false;
|
|
11302
11358
|
this.waitingForSecondSide = false;
|
|
11303
|
-
this.lastMessage = null;
|
|
11359
|
+
this.lastMessage = null;
|
|
11304
11360
|
}, 3000);
|
|
11305
11361
|
} else if (Object.keys(this.extractionImages).length === 2) {
|
|
11306
11362
|
this.waitingForSecondSide = false;
|
|
@@ -11347,6 +11403,10 @@ class ExtractorVideo {
|
|
|
11347
11403
|
return;
|
|
11348
11404
|
}
|
|
11349
11405
|
this.candidateImages = [];
|
|
11406
|
+
}).catch(err => {
|
|
11407
|
+
this.logDebug("document_detector_error", {
|
|
11408
|
+
message: err?.message || String(err)
|
|
11409
|
+
});
|
|
11350
11410
|
}).finally(() => {
|
|
11351
11411
|
setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
|
|
11352
11412
|
});
|
|
@@ -11367,7 +11427,7 @@ class ExtractorVideo {
|
|
|
11367
11427
|
this.turnMessageTimer = null;
|
|
11368
11428
|
this.stopVideo();
|
|
11369
11429
|
if (isExtractionOk) {
|
|
11370
|
-
this.showMessage("Success - data extracted",
|
|
11430
|
+
this.showMessage("Success - data extracted", "success");
|
|
11371
11431
|
const shouldStop = this.onExtractedResults({
|
|
11372
11432
|
success: true,
|
|
11373
11433
|
code: "001",
|
|
@@ -11375,7 +11435,7 @@ class ExtractorVideo {
|
|
|
11375
11435
|
data: extractionData
|
|
11376
11436
|
});
|
|
11377
11437
|
if (shouldStop === true) {
|
|
11378
|
-
setTimeout(() => this.
|
|
11438
|
+
setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
|
|
11379
11439
|
}
|
|
11380
11440
|
} else {
|
|
11381
11441
|
this.onExtractedResults({
|
|
@@ -11383,11 +11443,428 @@ class ExtractorVideo {
|
|
|
11383
11443
|
code: "005",
|
|
11384
11444
|
info: "Document validation passed but extraction failed."
|
|
11385
11445
|
});
|
|
11386
|
-
setTimeout(() => this.
|
|
11446
|
+
setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
|
|
11447
|
+
}
|
|
11448
|
+
}
|
|
11449
|
+
getSupportedConstraintsSafe() {
|
|
11450
|
+
if (!navigator.mediaDevices?.getSupportedConstraints) return {};
|
|
11451
|
+
try {
|
|
11452
|
+
return navigator.mediaDevices.getSupportedConstraints();
|
|
11453
|
+
} catch (_) {
|
|
11454
|
+
return {};
|
|
11455
|
+
}
|
|
11456
|
+
}
|
|
11457
|
+
getAspectRatio(settings) {
|
|
11458
|
+
if (typeof settings?.aspectRatio === "number" && settings.aspectRatio > 0) {
|
|
11459
|
+
return settings.aspectRatio;
|
|
11460
|
+
}
|
|
11461
|
+
if (settings?.width && settings?.height) {
|
|
11462
|
+
return settings.width / settings.height;
|
|
11463
|
+
}
|
|
11464
|
+
return null;
|
|
11465
|
+
}
|
|
11466
|
+
isLandscape(settings) {
|
|
11467
|
+
return (settings?.width || 0) > (settings?.height || 0);
|
|
11468
|
+
}
|
|
11469
|
+
isFrontLabel(label) {
|
|
11470
|
+
const l = (label || "").toLowerCase();
|
|
11471
|
+
return l.includes("front") || l.includes("selfie") || l.includes("user facing") || l.includes("facing front");
|
|
11472
|
+
}
|
|
11473
|
+
isRearLikeLabel(label) {
|
|
11474
|
+
const l = (label || "").toLowerCase();
|
|
11475
|
+
return l.includes("rear") || l.includes("back") || l.includes("environment") || l.includes("facing back") || l.includes("facing rear");
|
|
11476
|
+
}
|
|
11477
|
+
scoreTrack({
|
|
11478
|
+
settings,
|
|
11479
|
+
label
|
|
11480
|
+
}) {
|
|
11481
|
+
const width = settings?.width || 0;
|
|
11482
|
+
const height = settings?.height || 0;
|
|
11483
|
+
const pixels = width * height;
|
|
11484
|
+
const aspectRatio = this.getAspectRatio(settings) || 0;
|
|
11485
|
+
let score = pixels;
|
|
11486
|
+
if (width > height) score += 1_000_000_000;
|
|
11487
|
+
if (aspectRatio >= 1.3) score += 100_000_000;
|
|
11488
|
+
if (this.isRearLikeLabel(label)) score += 10_000_000;
|
|
11489
|
+
if (this.isFrontLabel(label)) score -= 10_000_000_000;
|
|
11490
|
+
return score;
|
|
11491
|
+
}
|
|
11492
|
+
buildVideoConstraints({
|
|
11493
|
+
width,
|
|
11494
|
+
height,
|
|
11495
|
+
mode,
|
|
11496
|
+
deviceId
|
|
11497
|
+
}) {
|
|
11498
|
+
const supported = this.getSupportedConstraintsSafe();
|
|
11499
|
+
const video = {};
|
|
11500
|
+
if (deviceId) {
|
|
11501
|
+
video.deviceId = {
|
|
11502
|
+
exact: deviceId
|
|
11503
|
+
};
|
|
11504
|
+
} else if (supported.facingMode) {
|
|
11505
|
+
if (typeof mode === "string") {
|
|
11506
|
+
video.facingMode = {
|
|
11507
|
+
ideal: mode
|
|
11508
|
+
};
|
|
11509
|
+
} else if (mode && typeof mode === "object") {
|
|
11510
|
+
video.facingMode = mode;
|
|
11511
|
+
} else {
|
|
11512
|
+
video.facingMode = {
|
|
11513
|
+
ideal: "environment"
|
|
11514
|
+
};
|
|
11515
|
+
}
|
|
11516
|
+
}
|
|
11517
|
+
if (supported.width) video.width = {
|
|
11518
|
+
ideal: width
|
|
11519
|
+
};
|
|
11520
|
+
if (supported.height) video.height = {
|
|
11521
|
+
ideal: height
|
|
11522
|
+
};
|
|
11523
|
+
if (supported.aspectRatio) video.aspectRatio = {
|
|
11524
|
+
ideal: width / height
|
|
11525
|
+
};
|
|
11526
|
+
return {
|
|
11527
|
+
video,
|
|
11528
|
+
audio: false
|
|
11529
|
+
};
|
|
11530
|
+
}
|
|
11531
|
+
async pauseBetweenCameraOps(ms = 180) {
|
|
11532
|
+
await new Promise(resolve => setTimeout(resolve, ms));
|
|
11533
|
+
}
|
|
11534
|
+
async stopMediaStream(stream) {
|
|
11535
|
+
if (!stream) return;
|
|
11536
|
+
try {
|
|
11537
|
+
stream.getTracks().forEach(t => t.stop());
|
|
11538
|
+
} catch (_) {}
|
|
11539
|
+
await this.pauseBetweenCameraOps();
|
|
11540
|
+
}
|
|
11541
|
+
async tryEnableDocumentFocus(stream) {
|
|
11542
|
+
const track = stream?.getVideoTracks?.()[0];
|
|
11543
|
+
if (!track) {
|
|
11544
|
+
this.logDebug("focus_no_track");
|
|
11545
|
+
return {
|
|
11546
|
+
ok: false,
|
|
11547
|
+
reason: "No video track found"
|
|
11548
|
+
};
|
|
11549
|
+
}
|
|
11550
|
+
if (!track.getCapabilities || !track.applyConstraints) {
|
|
11551
|
+
this.logDebug("focus_api_unavailable");
|
|
11552
|
+
return {
|
|
11553
|
+
ok: false,
|
|
11554
|
+
reason: "Focus APIs unavailable on this browser/device",
|
|
11555
|
+
settingsBefore: track.getSettings ? track.getSettings() : null
|
|
11556
|
+
};
|
|
11557
|
+
}
|
|
11558
|
+
const caps = track.getCapabilities();
|
|
11559
|
+
const result = {
|
|
11560
|
+
ok: true,
|
|
11561
|
+
capabilities: caps,
|
|
11562
|
+
attempts: [],
|
|
11563
|
+
settingsBefore: track.getSettings ? track.getSettings() : null
|
|
11564
|
+
};
|
|
11565
|
+
try {
|
|
11566
|
+
let applied = false;
|
|
11567
|
+
if (Array.isArray(caps.focusMode) && caps.focusMode.includes("continuous")) {
|
|
11568
|
+
await track.applyConstraints({
|
|
11569
|
+
advanced: [{
|
|
11570
|
+
focusMode: "continuous"
|
|
11571
|
+
}]
|
|
11572
|
+
});
|
|
11573
|
+
result.attempts.push("Applied focusMode=continuous");
|
|
11574
|
+
applied = true;
|
|
11575
|
+
}
|
|
11576
|
+
if (caps.focusDistance) {
|
|
11577
|
+
const min = caps.focusDistance.min;
|
|
11578
|
+
const max = caps.focusDistance.max;
|
|
11579
|
+
const docDistance = min + (max - min) * 0.30;
|
|
11580
|
+
const advanced = [];
|
|
11581
|
+
if (Array.isArray(caps.focusMode) && caps.focusMode.includes("manual")) {
|
|
11582
|
+
advanced.push({
|
|
11583
|
+
focusMode: "manual"
|
|
11584
|
+
});
|
|
11585
|
+
}
|
|
11586
|
+
advanced.push({
|
|
11587
|
+
focusDistance: docDistance
|
|
11588
|
+
});
|
|
11589
|
+
await track.applyConstraints({
|
|
11590
|
+
advanced
|
|
11591
|
+
});
|
|
11592
|
+
result.attempts.push(`Applied focusDistance=${docDistance}`);
|
|
11593
|
+
applied = true;
|
|
11594
|
+
}
|
|
11595
|
+
if (!applied) {
|
|
11596
|
+
result.ok = false;
|
|
11597
|
+
result.reason = "No supported focus controls were exposed";
|
|
11598
|
+
}
|
|
11599
|
+
result.settingsAfter = track.getSettings ? track.getSettings() : null;
|
|
11600
|
+
this.logDebug("focus_result", result);
|
|
11601
|
+
return result;
|
|
11602
|
+
} catch (err) {
|
|
11603
|
+
result.ok = false;
|
|
11604
|
+
result.error = err.message || String(err);
|
|
11605
|
+
result.settingsAfter = track.getSettings ? track.getSettings() : null;
|
|
11606
|
+
this.logDebug("focus_error", result);
|
|
11607
|
+
return result;
|
|
11608
|
+
}
|
|
11609
|
+
}
|
|
11610
|
+
async openCandidateStream({
|
|
11611
|
+
mode,
|
|
11612
|
+
deviceId,
|
|
11613
|
+
width,
|
|
11614
|
+
height
|
|
11615
|
+
}) {
|
|
11616
|
+
const constraints = this.buildVideoConstraints({
|
|
11617
|
+
width,
|
|
11618
|
+
height,
|
|
11619
|
+
mode,
|
|
11620
|
+
deviceId
|
|
11621
|
+
});
|
|
11622
|
+
this.logDebug("getUserMedia_attempt", {
|
|
11623
|
+
constraints
|
|
11624
|
+
});
|
|
11625
|
+
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
11626
|
+
this.logDebug("getUserMedia_success", {
|
|
11627
|
+
deviceId,
|
|
11628
|
+
width,
|
|
11629
|
+
height,
|
|
11630
|
+
trackLabels: stream.getTracks().map(t => t.label)
|
|
11631
|
+
});
|
|
11632
|
+
return stream;
|
|
11633
|
+
}
|
|
11634
|
+
async testDeviceAtPreferredResolutions(device, mode) {
|
|
11635
|
+
const attempts = [];
|
|
11636
|
+
for (const size of ExtractorVideo.VIDEO_CANDIDATE_SETTINGS) {
|
|
11637
|
+
let stream = null;
|
|
11638
|
+
try {
|
|
11639
|
+
stream = await this.openCandidateStream({
|
|
11640
|
+
mode,
|
|
11641
|
+
deviceId: device?.deviceId,
|
|
11642
|
+
width: size.width,
|
|
11643
|
+
height: size.height
|
|
11644
|
+
});
|
|
11645
|
+
const track = stream.getVideoTracks()[0];
|
|
11646
|
+
const settings = track.getSettings();
|
|
11647
|
+
const label = track.label || device?.label || "";
|
|
11648
|
+
const aspectRatio = this.getAspectRatio(settings);
|
|
11649
|
+
const result = {
|
|
11650
|
+
stream,
|
|
11651
|
+
label,
|
|
11652
|
+
settings,
|
|
11653
|
+
aspectRatio,
|
|
11654
|
+
landscape: this.isLandscape(settings),
|
|
11655
|
+
score: this.scoreTrack({
|
|
11656
|
+
settings,
|
|
11657
|
+
label
|
|
11658
|
+
}),
|
|
11659
|
+
requested: size
|
|
11660
|
+
};
|
|
11661
|
+
attempts.push({
|
|
11662
|
+
ok: true,
|
|
11663
|
+
requested: size,
|
|
11664
|
+
returnedWidth: settings.width,
|
|
11665
|
+
returnedHeight: settings.height,
|
|
11666
|
+
aspectRatio,
|
|
11667
|
+
label
|
|
11668
|
+
});
|
|
11669
|
+
this.logDebug("device_test_success", {
|
|
11670
|
+
deviceLabel: device?.label,
|
|
11671
|
+
deviceId: device?.deviceId,
|
|
11672
|
+
requested: size,
|
|
11673
|
+
settings,
|
|
11674
|
+
label
|
|
11675
|
+
});
|
|
11676
|
+
return {
|
|
11677
|
+
result,
|
|
11678
|
+
attempts
|
|
11679
|
+
};
|
|
11680
|
+
} catch (err) {
|
|
11681
|
+
attempts.push({
|
|
11682
|
+
ok: false,
|
|
11683
|
+
requested: size,
|
|
11684
|
+
error: err.message || String(err)
|
|
11685
|
+
});
|
|
11686
|
+
this.logDebug("device_test_error", {
|
|
11687
|
+
deviceLabel: device?.label,
|
|
11688
|
+
deviceId: device?.deviceId,
|
|
11689
|
+
requested: size,
|
|
11690
|
+
error: err?.message || String(err),
|
|
11691
|
+
name: err?.name
|
|
11692
|
+
});
|
|
11693
|
+
await this.stopMediaStream(stream);
|
|
11694
|
+
}
|
|
11695
|
+
}
|
|
11696
|
+
return {
|
|
11697
|
+
result: null,
|
|
11698
|
+
attempts
|
|
11699
|
+
};
|
|
11700
|
+
}
|
|
11701
|
+
async pickBestPhoneCamera(mode = "environment") {
|
|
11702
|
+
const debug = {
|
|
11703
|
+
mode,
|
|
11704
|
+
tested: []
|
|
11705
|
+
};
|
|
11706
|
+
let warmupStream = null;
|
|
11707
|
+
let warmupResult = null;
|
|
11708
|
+
try {
|
|
11709
|
+
const warmupSize = ExtractorVideo.VIDEO_CANDIDATE_SETTINGS[2];
|
|
11710
|
+
warmupStream = await this.openCandidateStream({
|
|
11711
|
+
mode,
|
|
11712
|
+
width: warmupSize.width,
|
|
11713
|
+
height: warmupSize.height
|
|
11714
|
+
});
|
|
11715
|
+
const track = warmupStream.getVideoTracks()[0];
|
|
11716
|
+
const settings = track.getSettings();
|
|
11717
|
+
const label = track.label || "";
|
|
11718
|
+
warmupResult = {
|
|
11719
|
+
stream: warmupStream,
|
|
11720
|
+
label,
|
|
11721
|
+
settings,
|
|
11722
|
+
aspectRatio: this.getAspectRatio(settings),
|
|
11723
|
+
landscape: this.isLandscape(settings),
|
|
11724
|
+
score: this.scoreTrack({
|
|
11725
|
+
settings,
|
|
11726
|
+
label
|
|
11727
|
+
}),
|
|
11728
|
+
requested: warmupSize
|
|
11729
|
+
};
|
|
11730
|
+
this.logDebug("warmup_success", {
|
|
11731
|
+
label,
|
|
11732
|
+
settings
|
|
11733
|
+
});
|
|
11734
|
+
} catch (err) {
|
|
11735
|
+
this.logDebug("warmup_error", {
|
|
11736
|
+
name: err?.name,
|
|
11737
|
+
message: err?.message || String(err)
|
|
11738
|
+
});
|
|
11739
|
+
warmupStream = null;
|
|
11740
|
+
}
|
|
11741
|
+
let devices = [];
|
|
11742
|
+
try {
|
|
11743
|
+
devices = await navigator.mediaDevices.enumerateDevices();
|
|
11744
|
+
this.logDebug("enumerate_devices", {
|
|
11745
|
+
devices: devices.map(d => ({
|
|
11746
|
+
kind: d.kind,
|
|
11747
|
+
label: d.label,
|
|
11748
|
+
deviceId: d.deviceId
|
|
11749
|
+
}))
|
|
11750
|
+
});
|
|
11751
|
+
} catch (err) {
|
|
11752
|
+
this.logDebug("enumerate_devices_error", {
|
|
11753
|
+
name: err?.name,
|
|
11754
|
+
message: err?.message || String(err)
|
|
11755
|
+
});
|
|
11756
|
+
devices = [];
|
|
11757
|
+
}
|
|
11758
|
+
const videoInputs = devices.filter(d => d.kind === "videoinput");
|
|
11759
|
+
const candidateDevices = mode === "environment" ? videoInputs.filter(d => !this.isFrontLabel(d.label)) : videoInputs;
|
|
11760
|
+
if (warmupStream) {
|
|
11761
|
+
await this.stopMediaStream(warmupStream);
|
|
11762
|
+
warmupStream = null;
|
|
11763
|
+
}
|
|
11764
|
+
const usableResults = [];
|
|
11765
|
+
for (let i = 0; i < candidateDevices.length; i += 1) {
|
|
11766
|
+
const device = candidateDevices[i];
|
|
11767
|
+
const {
|
|
11768
|
+
result,
|
|
11769
|
+
attempts
|
|
11770
|
+
} = await this.testDeviceAtPreferredResolutions(device, mode);
|
|
11771
|
+
debug.tested.push({
|
|
11772
|
+
deviceId: device.deviceId,
|
|
11773
|
+
label: device.label,
|
|
11774
|
+
attempts,
|
|
11775
|
+
final: result ? {
|
|
11776
|
+
label: result.label,
|
|
11777
|
+
settings: result.settings,
|
|
11778
|
+
aspectRatio: result.aspectRatio,
|
|
11779
|
+
landscape: result.landscape,
|
|
11780
|
+
score: result.score,
|
|
11781
|
+
requested: result.requested
|
|
11782
|
+
} : null
|
|
11783
|
+
});
|
|
11784
|
+
if (result) {
|
|
11785
|
+
const frontRejected = mode === "environment" && this.isFrontLabel(result.label);
|
|
11786
|
+
const geometryOk = result.landscape;
|
|
11787
|
+
if (!frontRejected && geometryOk) {
|
|
11788
|
+
usableResults.push(result);
|
|
11789
|
+
} else {
|
|
11790
|
+
await this.stopMediaStream(result.stream);
|
|
11791
|
+
}
|
|
11792
|
+
}
|
|
11793
|
+
}
|
|
11794
|
+
if (usableResults.length > 0) {
|
|
11795
|
+
usableResults.sort((a, b) => b.score - a.score);
|
|
11796
|
+
const chosen = usableResults[0];
|
|
11797
|
+
for (const candidate of usableResults) {
|
|
11798
|
+
if (candidate !== chosen) {
|
|
11799
|
+
await this.stopMediaStream(candidate.stream);
|
|
11800
|
+
}
|
|
11801
|
+
}
|
|
11802
|
+
debug.chosen = {
|
|
11803
|
+
label: chosen.label,
|
|
11804
|
+
settings: chosen.settings,
|
|
11805
|
+
aspectRatio: chosen.aspectRatio,
|
|
11806
|
+
requested: chosen.requested,
|
|
11807
|
+
source: "device-test"
|
|
11808
|
+
};
|
|
11809
|
+
this.logDebug("camera_chosen", debug.chosen);
|
|
11810
|
+
return {
|
|
11811
|
+
chosen,
|
|
11812
|
+
debug
|
|
11813
|
+
};
|
|
11814
|
+
}
|
|
11815
|
+
for (const size of ExtractorVideo.VIDEO_CANDIDATE_SETTINGS) {
|
|
11816
|
+
try {
|
|
11817
|
+
const stream = await this.openCandidateStream({
|
|
11818
|
+
mode,
|
|
11819
|
+
width: size.width,
|
|
11820
|
+
height: size.height
|
|
11821
|
+
});
|
|
11822
|
+
const track = stream.getVideoTracks()[0];
|
|
11823
|
+
const settings = track.getSettings();
|
|
11824
|
+
const label = track.label || "";
|
|
11825
|
+
const aspectRatio = this.getAspectRatio(settings);
|
|
11826
|
+
const landscape = this.isLandscape(settings);
|
|
11827
|
+
if (mode === "environment" && !landscape) {
|
|
11828
|
+
await this.stopMediaStream(stream);
|
|
11829
|
+
continue;
|
|
11830
|
+
}
|
|
11831
|
+
const chosen = {
|
|
11832
|
+
stream,
|
|
11833
|
+
label,
|
|
11834
|
+
settings,
|
|
11835
|
+
aspectRatio,
|
|
11836
|
+
landscape,
|
|
11837
|
+
score: this.scoreTrack({
|
|
11838
|
+
settings,
|
|
11839
|
+
label
|
|
11840
|
+
}),
|
|
11841
|
+
requested: size
|
|
11842
|
+
};
|
|
11843
|
+
debug.chosen = {
|
|
11844
|
+
label,
|
|
11845
|
+
settings,
|
|
11846
|
+
aspectRatio,
|
|
11847
|
+
requested: size,
|
|
11848
|
+
source: "fallback-facingMode"
|
|
11849
|
+
};
|
|
11850
|
+
this.logDebug("camera_chosen_fallback", debug.chosen);
|
|
11851
|
+
return {
|
|
11852
|
+
chosen,
|
|
11853
|
+
debug
|
|
11854
|
+
};
|
|
11855
|
+
} catch (err) {
|
|
11856
|
+
this.logDebug("fallback_open_error", {
|
|
11857
|
+
requested: size,
|
|
11858
|
+
name: err?.name,
|
|
11859
|
+
message: err?.message || String(err)
|
|
11860
|
+
});
|
|
11861
|
+
}
|
|
11387
11862
|
}
|
|
11863
|
+
throw new Error("Could not start a suitable camera.");
|
|
11388
11864
|
}
|
|
11389
11865
|
async startVideo() {
|
|
11390
11866
|
try {
|
|
11867
|
+
this.logDebug("startVideo_begin");
|
|
11391
11868
|
const serviceConfig = (0,_config__WEBPACK_IMPORTED_MODULE_1__.getScanDocAIConfig)();
|
|
11392
11869
|
await serviceConfig.getAccessToken(false);
|
|
11393
11870
|
const videoElem = document.getElementById("ScanDocAIVideoElement");
|
|
@@ -11399,63 +11876,80 @@ class ExtractorVideo {
|
|
|
11399
11876
|
this.scanStartTime = Date.now();
|
|
11400
11877
|
const cfgValues = (0,_config__WEBPACK_IMPORTED_MODULE_1__.getScanDocAIConfigValues)();
|
|
11401
11878
|
const mode = cfgValues.VIDEO_FACING_MODE ?? "environment";
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
}
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
}
|
|
11412
|
-
this.video.srcObject = stream;
|
|
11879
|
+
const {
|
|
11880
|
+
chosen,
|
|
11881
|
+
debug
|
|
11882
|
+
} = await this.pickBestPhoneCamera(mode);
|
|
11883
|
+
this.video.srcObject = chosen.stream;
|
|
11884
|
+
this.logDebug("srcObject_assigned", {
|
|
11885
|
+
label: chosen.label,
|
|
11886
|
+
settings: chosen.settings
|
|
11887
|
+
});
|
|
11413
11888
|
try {
|
|
11414
11889
|
await this.video.play();
|
|
11890
|
+
this.logDebug("video_play_success", {
|
|
11891
|
+
readyState: this.video.readyState,
|
|
11892
|
+
videoWidth: this.video.videoWidth,
|
|
11893
|
+
videoHeight: this.video.videoHeight
|
|
11894
|
+
});
|
|
11415
11895
|
} catch (e) {
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
|
|
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);
|
|
11896
|
+
this.logDebug("video_play_error", {
|
|
11897
|
+
name: e?.name,
|
|
11898
|
+
message: e?.message || String(e)
|
|
11899
|
+
});
|
|
11900
|
+
throw e;
|
|
11435
11901
|
}
|
|
11902
|
+
this.focusDebug = await this.tryEnableDocumentFocus(chosen.stream);
|
|
11903
|
+
this.cameraDebug = debug;
|
|
11904
|
+
this.video.addEventListener("loadeddata", () => {
|
|
11905
|
+
this.logDebug("video_loadeddata", {
|
|
11906
|
+
videoWidth: this.video.videoWidth,
|
|
11907
|
+
videoHeight: this.video.videoHeight
|
|
11908
|
+
});
|
|
11909
|
+
this.adjustOverlayPosition();
|
|
11910
|
+
});
|
|
11911
|
+
this.video.addEventListener("error", () => {
|
|
11912
|
+
this.logDebug("video_element_error", {
|
|
11913
|
+
error: this.video?.error ? {
|
|
11914
|
+
code: this.video.error.code,
|
|
11915
|
+
message: this.video.error.message
|
|
11916
|
+
} : null
|
|
11917
|
+
});
|
|
11918
|
+
});
|
|
11919
|
+
window.addEventListener("resize", () => this.adjustOverlayPosition());
|
|
11436
11920
|
setTimeout(() => this.adjustOverlayPosition(), 500);
|
|
11437
11921
|
this.scanStartTime = Date.now();
|
|
11438
11922
|
setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
|
|
11439
11923
|
this.showMessage("Starting scanning");
|
|
11924
|
+
this.logDebug("startVideo_success", this.getDebugDump());
|
|
11440
11925
|
return true;
|
|
11441
11926
|
} catch (error) {
|
|
11442
11927
|
console.error("startVideo failed:", error);
|
|
11928
|
+
this.logDebug("startVideo_failed", {
|
|
11929
|
+
name: error?.name,
|
|
11930
|
+
message: error?.message || String(error),
|
|
11931
|
+
dump: this.getDebugDump()
|
|
11932
|
+
});
|
|
11443
11933
|
this.isRunning = false;
|
|
11444
11934
|
this.stopVideo();
|
|
11445
11935
|
this.onExtractedResults({
|
|
11446
11936
|
success: false,
|
|
11447
11937
|
code: error.status === 401 || error.status === 403 ? "004" : "005",
|
|
11448
|
-
info: error.status === 401 || error.status === 403 ? "Authentication failed: Invalid API key/token." : "Startup failed: " + (error.message || "Unknown error")
|
|
11938
|
+
info: error.status === 401 || error.status === 403 ? "Authentication failed: Invalid API key/token." : "Startup failed: " + (error.message || "Unknown error"),
|
|
11939
|
+
debug: ExtractorVideo.DEBUG ? this.getDebugDump() : undefined
|
|
11449
11940
|
});
|
|
11450
11941
|
return false;
|
|
11451
11942
|
}
|
|
11452
11943
|
}
|
|
11453
11944
|
stopVideo() {
|
|
11454
11945
|
this.isRunning = false;
|
|
11946
|
+
this.logDebug("stopVideo_called");
|
|
11455
11947
|
if (this.video) {
|
|
11456
11948
|
this.video.pause();
|
|
11457
11949
|
if (this.video.srcObject !== undefined && this.video.srcObject !== null) {
|
|
11458
|
-
|
|
11950
|
+
try {
|
|
11951
|
+
this.video.srcObject.getTracks().forEach(t => t.stop());
|
|
11952
|
+
} catch (_) {}
|
|
11459
11953
|
}
|
|
11460
11954
|
this.video.srcObject = null;
|
|
11461
11955
|
}
|
|
@@ -11482,7 +11976,7 @@ class ExtractorVideo {
|
|
|
11482
11976
|
const centerX = offsetX + scaleWidth / 2;
|
|
11483
11977
|
const centerY = offsetY + scaleHeight / 2;
|
|
11484
11978
|
dot.style.display = "block";
|
|
11485
|
-
dot.style.left = `${centerX - 8}px`;
|
|
11979
|
+
dot.style.left = `${centerX - 8}px`;
|
|
11486
11980
|
dot.style.top = `${centerY - 8}px`;
|
|
11487
11981
|
}
|
|
11488
11982
|
getHTML() {
|
|
@@ -11491,7 +11985,6 @@ class ExtractorVideo {
|
|
|
11491
11985
|
const messageColor = cfgValues.VIDEO_COLORS?.messageColor;
|
|
11492
11986
|
const isMobile = window.innerWidth < 768 || window.innerHeight > window.innerWidth && window.innerWidth < 1024;
|
|
11493
11987
|
if (isMobile) {
|
|
11494
|
-
// Mobile version with overlay feedback
|
|
11495
11988
|
return `
|
|
11496
11989
|
<div class="mobileVideoArea">
|
|
11497
11990
|
<video id="ScanDocAIVideoElement" class="mobileVideo" autoplay muted playsinline></video>
|
|
@@ -11577,13 +12070,11 @@ class ExtractorVideo {
|
|
|
11577
12070
|
margin-left: 20%;
|
|
11578
12071
|
margin-right: 20%;
|
|
11579
12072
|
}
|
|
11580
|
-
|
|
11581
12073
|
.desktopVideoHolder {
|
|
11582
12074
|
position: relative;
|
|
11583
12075
|
width: 100%;
|
|
11584
12076
|
overflow: hidden;
|
|
11585
12077
|
}
|
|
11586
|
-
|
|
11587
12078
|
.desktopVideo {
|
|
11588
12079
|
width: 100%;
|
|
11589
12080
|
height: auto;
|
|
@@ -11592,7 +12083,6 @@ class ExtractorVideo {
|
|
|
11592
12083
|
max-width: 100vw;
|
|
11593
12084
|
max-height: 100vh;
|
|
11594
12085
|
}
|
|
11595
|
-
|
|
11596
12086
|
.centerGuideDot {
|
|
11597
12087
|
position: absolute;
|
|
11598
12088
|
width: 16px;
|
|
@@ -11603,14 +12093,12 @@ class ExtractorVideo {
|
|
|
11603
12093
|
z-index: 4;
|
|
11604
12094
|
animation: flicker 1s infinite;
|
|
11605
12095
|
pointer-events: none;
|
|
11606
|
-
display: none;
|
|
12096
|
+
display: none;
|
|
11607
12097
|
}
|
|
11608
|
-
|
|
11609
12098
|
@keyframes flicker {
|
|
11610
12099
|
0%, 100% { opacity: 1; }
|
|
11611
12100
|
50% { opacity: 0.3; }
|
|
11612
12101
|
}
|
|
11613
|
-
|
|
11614
12102
|
.backgroundOverlay {
|
|
11615
12103
|
position: absolute;
|
|
11616
12104
|
top: 0;
|
|
@@ -11621,7 +12109,6 @@ class ExtractorVideo {
|
|
|
11621
12109
|
z-index: 2;
|
|
11622
12110
|
pointer-events: none;
|
|
11623
12111
|
}
|
|
11624
|
-
|
|
11625
12112
|
.desktopFeedback {
|
|
11626
12113
|
position: relative;
|
|
11627
12114
|
display: flex;
|
|
@@ -11657,8 +12144,6 @@ function getExtractionVideo(tokenOrCallback, maybeCallback) {
|
|
|
11657
12144
|
EXTRACTION_VIDEO = new ExtractorVideo(cb);
|
|
11658
12145
|
return EXTRACTION_VIDEO;
|
|
11659
12146
|
}
|
|
11660
|
-
|
|
11661
|
-
// If called again, update callback too
|
|
11662
12147
|
if (typeof cb === "function") {
|
|
11663
12148
|
EXTRACTION_VIDEO.onExtractedResults = cb;
|
|
11664
12149
|
} else if (cb !== undefined) {
|
|
@@ -11666,143 +12151,6 @@ function getExtractionVideo(tokenOrCallback, maybeCallback) {
|
|
|
11666
12151
|
}
|
|
11667
12152
|
return EXTRACTION_VIDEO;
|
|
11668
12153
|
}
|
|
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
12154
|
|
|
11807
12155
|
/***/ }),
|
|
11808
12156
|
|