@jaak.ai/stamps 2.5.2 → 2.5.4-dev.1
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/cjs/jaak-stamps.cjs.entry.js +349 -23
- package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
- package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
- package/dist/collection/components/my-component/my-component.js +349 -23
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/components/jaak-stamps.js +349 -23
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/jaak-stamps.entry.js +349 -23
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/{p-49ed15ce.entry.js → p-0f4d77b3.entry.js} +3 -3
- package/dist/jaak-stamps-webcomponent/p-0f4d77b3.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +10 -0
- package/package.json +1 -1
- package/dist/jaak-stamps-webcomponent/p-49ed15ce.entry.js.map +0 -1
|
@@ -124,6 +124,18 @@ export class JaakStamps {
|
|
|
124
124
|
processedFramesCount = 0; // Contador total de frames procesados
|
|
125
125
|
hasAutoSwitchedToManual = false;
|
|
126
126
|
_manualModeLoggedOnce = false;
|
|
127
|
+
VIDEO_DIAGNOSTIC_EVENTS = [
|
|
128
|
+
'loadstart',
|
|
129
|
+
'loadedmetadata',
|
|
130
|
+
'loadeddata',
|
|
131
|
+
'canplay',
|
|
132
|
+
'playing',
|
|
133
|
+
'pause',
|
|
134
|
+
'stalled',
|
|
135
|
+
'suspend',
|
|
136
|
+
'abort',
|
|
137
|
+
'emptied',
|
|
138
|
+
];
|
|
127
139
|
// Canvas pool for optimized screenshot capture
|
|
128
140
|
canvasPool = [];
|
|
129
141
|
MAX_CANVAS_POOL_SIZE = 3;
|
|
@@ -852,6 +864,130 @@ export class JaakStamps {
|
|
|
852
864
|
memoryMB: deviceMemory ? deviceMemory * 1024 : null
|
|
853
865
|
};
|
|
854
866
|
}
|
|
867
|
+
debugLog(message, details) {
|
|
868
|
+
if (!this.debug) {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (typeof details === 'undefined') {
|
|
872
|
+
console.log(`[jaak-stamps][diag] ${message}`);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
console.log(`[jaak-stamps][diag] ${message}`, details);
|
|
876
|
+
}
|
|
877
|
+
debugWarn(message, details) {
|
|
878
|
+
if (!this.debug) {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
if (typeof details === 'undefined') {
|
|
882
|
+
console.warn(`[jaak-stamps][diag] ${message}`);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
console.warn(`[jaak-stamps][diag] ${message}`, details);
|
|
886
|
+
}
|
|
887
|
+
summarizeId(id) {
|
|
888
|
+
if (!id) {
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
return id.length <= 8 ? id : `${id.slice(0, 8)}...`;
|
|
892
|
+
}
|
|
893
|
+
getCameraDevicesSnapshot() {
|
|
894
|
+
return this.cameraService.getAvailableCameras().map(camera => ({
|
|
895
|
+
label: camera.label || '(sin label)',
|
|
896
|
+
deviceId: this.summarizeId(camera.deviceId),
|
|
897
|
+
groupId: this.summarizeId(camera.groupId),
|
|
898
|
+
kind: camera.kind,
|
|
899
|
+
}));
|
|
900
|
+
}
|
|
901
|
+
sanitizeTrackSettings(settings) {
|
|
902
|
+
if (!settings) {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
...settings,
|
|
907
|
+
deviceId: this.summarizeId(settings.deviceId),
|
|
908
|
+
groupId: this.summarizeId(settings.groupId),
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
sanitizeTrackConstraints(track) {
|
|
912
|
+
if (!track) {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
const constraints = track.getConstraints ? track.getConstraints() : {};
|
|
916
|
+
const sanitizedConstraints = { ...constraints };
|
|
917
|
+
if (typeof sanitizedConstraints.deviceId === 'string') {
|
|
918
|
+
sanitizedConstraints.deviceId = this.summarizeId(sanitizedConstraints.deviceId);
|
|
919
|
+
}
|
|
920
|
+
return sanitizedConstraints;
|
|
921
|
+
}
|
|
922
|
+
getStreamSnapshot(stream) {
|
|
923
|
+
if (!stream) {
|
|
924
|
+
return { hasStream: false };
|
|
925
|
+
}
|
|
926
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
927
|
+
if (!videoTrack) {
|
|
928
|
+
return {
|
|
929
|
+
hasStream: true,
|
|
930
|
+
streamActive: stream.active,
|
|
931
|
+
trackCount: stream.getTracks().length,
|
|
932
|
+
hasVideoTrack: false,
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
return {
|
|
936
|
+
hasStream: true,
|
|
937
|
+
streamActive: stream.active,
|
|
938
|
+
trackCount: stream.getTracks().length,
|
|
939
|
+
trackLabel: videoTrack.label || '(sin label)',
|
|
940
|
+
trackEnabled: videoTrack.enabled,
|
|
941
|
+
trackMuted: videoTrack.muted,
|
|
942
|
+
trackReadyState: videoTrack.readyState,
|
|
943
|
+
settings: this.sanitizeTrackSettings(videoTrack.getSettings ? videoTrack.getSettings() : undefined),
|
|
944
|
+
constraints: this.sanitizeTrackConstraints(videoTrack),
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
getVideoElementSnapshot() {
|
|
948
|
+
if (!this.videoRef) {
|
|
949
|
+
return { hasVideoRef: false };
|
|
950
|
+
}
|
|
951
|
+
const computedStyle = this.el.ownerDocument?.defaultView?.getComputedStyle(this.videoRef);
|
|
952
|
+
return {
|
|
953
|
+
hasVideoRef: true,
|
|
954
|
+
isConnected: this.videoRef.isConnected,
|
|
955
|
+
readyState: this.videoRef.readyState,
|
|
956
|
+
networkState: this.videoRef.networkState,
|
|
957
|
+
paused: this.videoRef.paused,
|
|
958
|
+
muted: this.videoRef.muted,
|
|
959
|
+
autoplay: this.videoRef.autoplay,
|
|
960
|
+
playsInline: this.videoRef.playsInline,
|
|
961
|
+
currentTime: Number.isFinite(this.videoRef.currentTime)
|
|
962
|
+
? Number(this.videoRef.currentTime.toFixed(3))
|
|
963
|
+
: this.videoRef.currentTime,
|
|
964
|
+
videoWidth: this.videoRef.videoWidth,
|
|
965
|
+
videoHeight: this.videoRef.videoHeight,
|
|
966
|
+
clientWidth: this.videoRef.clientWidth,
|
|
967
|
+
clientHeight: this.videoRef.clientHeight,
|
|
968
|
+
hasSrcObject: !!this.videoRef.srcObject,
|
|
969
|
+
display: computedStyle?.display ?? 'unknown',
|
|
970
|
+
visibility: computedStyle?.visibility ?? 'unknown',
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
attachVideoDiagnosticListeners() {
|
|
974
|
+
if (!this.debug || !this.videoRef) {
|
|
975
|
+
return () => undefined;
|
|
976
|
+
}
|
|
977
|
+
const video = this.videoRef;
|
|
978
|
+
const listeners = this.VIDEO_DIAGNOSTIC_EVENTS.map(eventName => {
|
|
979
|
+
const handler = () => {
|
|
980
|
+
this.debugLog(`video event "${eventName}"`, this.getVideoElementSnapshot());
|
|
981
|
+
};
|
|
982
|
+
video.addEventListener(eventName, handler);
|
|
983
|
+
return { eventName, handler };
|
|
984
|
+
});
|
|
985
|
+
return () => {
|
|
986
|
+
listeners.forEach(({ eventName, handler }) => {
|
|
987
|
+
video.removeEventListener(eventName, handler);
|
|
988
|
+
});
|
|
989
|
+
};
|
|
990
|
+
}
|
|
855
991
|
// DETECTION METHODS
|
|
856
992
|
async startDetection() {
|
|
857
993
|
try {
|
|
@@ -938,12 +1074,19 @@ export class JaakStamps {
|
|
|
938
1074
|
this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
|
|
939
1075
|
try {
|
|
940
1076
|
await this.cameraService.enumerateDevices();
|
|
1077
|
+
this.debugLog('step 2/5 enumerateDevices()', {
|
|
1078
|
+
cameras: this.getCameraDevicesSnapshot(),
|
|
1079
|
+
selectedCameraId: this.summarizeId(this.cameraService.getSelectedCameraId() || undefined),
|
|
1080
|
+
});
|
|
941
1081
|
}
|
|
942
1082
|
catch (enumerateError) {
|
|
943
1083
|
throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
|
|
944
1084
|
}
|
|
945
1085
|
try {
|
|
946
1086
|
await this.updateCameraInfoWithAutofocus();
|
|
1087
|
+
this.debugLog('step 2/5 updateCameraInfoWithAutofocus()', {
|
|
1088
|
+
cameraInfo: this.cameraInfoWithAutofocus,
|
|
1089
|
+
});
|
|
947
1090
|
}
|
|
948
1091
|
catch (cameraInfoError) {
|
|
949
1092
|
throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
|
|
@@ -953,6 +1096,10 @@ export class JaakStamps {
|
|
|
953
1096
|
let stream;
|
|
954
1097
|
try {
|
|
955
1098
|
stream = await this.cameraService.setupCamera();
|
|
1099
|
+
this.debugLog('step 3/5 setupCamera() resolved', {
|
|
1100
|
+
stream: this.getStreamSnapshot(stream),
|
|
1101
|
+
videoRef: this.getVideoElementSnapshot(),
|
|
1102
|
+
});
|
|
956
1103
|
}
|
|
957
1104
|
catch (setupError) {
|
|
958
1105
|
throw new Error(`Error al configurar cámara: ${setupError.message}`);
|
|
@@ -965,7 +1112,15 @@ export class JaakStamps {
|
|
|
965
1112
|
this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
|
|
966
1113
|
}
|
|
967
1114
|
try {
|
|
1115
|
+
this.debugLog('step 4/5 initializeVideoStream() start', {
|
|
1116
|
+
stream: this.getStreamSnapshot(stream),
|
|
1117
|
+
videoRef: this.getVideoElementSnapshot(),
|
|
1118
|
+
});
|
|
968
1119
|
await this.initializeVideoStream(stream);
|
|
1120
|
+
this.debugLog('step 4/5 initializeVideoStream() completed', {
|
|
1121
|
+
stream: this.getStreamSnapshot(stream),
|
|
1122
|
+
videoRef: this.getVideoElementSnapshot(),
|
|
1123
|
+
});
|
|
969
1124
|
}
|
|
970
1125
|
catch (videoError) {
|
|
971
1126
|
throw new Error(`Error al inicializar video: ${videoError.message}`);
|
|
@@ -983,37 +1138,199 @@ export class JaakStamps {
|
|
|
983
1138
|
if (!this.useDocumentDetector) {
|
|
984
1139
|
this.showManualCaptureButton = true;
|
|
985
1140
|
}
|
|
1141
|
+
this.debugLog('step 5/5 capture loop ready', {
|
|
1142
|
+
videoRef: this.getVideoElementSnapshot(),
|
|
1143
|
+
stream: this.getStreamSnapshot(this.videoStream),
|
|
1144
|
+
useDocumentDetector: this.useDocumentDetector,
|
|
1145
|
+
});
|
|
986
1146
|
this.detectFrame();
|
|
987
1147
|
}
|
|
988
1148
|
catch (err) {
|
|
1149
|
+
this.debugWarn('startDetection() failed', {
|
|
1150
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1151
|
+
videoRef: this.getVideoElementSnapshot(),
|
|
1152
|
+
stream: this.getStreamSnapshot(this.videoStream),
|
|
1153
|
+
});
|
|
989
1154
|
console.error('[jaak-stamps] Error al preparar captura:', err);
|
|
990
1155
|
this.updateStatus('Error al iniciar', 'No se pudo preparar la captura', 'error');
|
|
991
1156
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
992
1157
|
}
|
|
993
1158
|
}
|
|
994
1159
|
async initializeVideoStream(stream) {
|
|
995
|
-
if (this.videoRef) {
|
|
996
|
-
this.videoRef.srcObject = stream;
|
|
997
|
-
this.videoStream = stream;
|
|
998
|
-
const isRear = this.cameraService.isRearCamera(stream);
|
|
999
|
-
this.shouldMirrorVideo = !isRear;
|
|
1000
|
-
return new Promise((resolve) => {
|
|
1001
|
-
this.videoRef.onloadedmetadata = async () => {
|
|
1002
|
-
await this.videoRef.play();
|
|
1003
|
-
this.stateManager.updateCaptureState({ isVideoActive: true });
|
|
1004
|
-
// Recalculate mask dimensions when new camera stream loads
|
|
1005
|
-
if (this.detectionContainer) {
|
|
1006
|
-
const container = this.detectionContainer.parentElement;
|
|
1007
|
-
const rect = container.getBoundingClientRect();
|
|
1008
|
-
this.updateMaskDimensions(rect);
|
|
1009
|
-
}
|
|
1010
|
-
resolve();
|
|
1011
|
-
};
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
else {
|
|
1160
|
+
if (!this.videoRef) {
|
|
1015
1161
|
throw new Error('Video element not available');
|
|
1016
1162
|
}
|
|
1163
|
+
const detachVideoDiagnosticListeners = this.attachVideoDiagnosticListeners();
|
|
1164
|
+
this.debugLog('initializeVideoStream() pre-assign', {
|
|
1165
|
+
stream: this.getStreamSnapshot(stream),
|
|
1166
|
+
videoRef: this.getVideoElementSnapshot(),
|
|
1167
|
+
});
|
|
1168
|
+
this.videoRef.srcObject = stream;
|
|
1169
|
+
this.videoStream = stream;
|
|
1170
|
+
this.debugLog('initializeVideoStream() post-srcObject assignment', {
|
|
1171
|
+
stream: this.getStreamSnapshot(stream),
|
|
1172
|
+
videoRef: this.getVideoElementSnapshot(),
|
|
1173
|
+
});
|
|
1174
|
+
const isRear = this.cameraService.isRearCamera(stream);
|
|
1175
|
+
this.shouldMirrorVideo = !isRear;
|
|
1176
|
+
const LOAD_METADATA_TIMEOUT_MS = 10000;
|
|
1177
|
+
// Diagnostic instrumentation: when the timeout fires we need enough
|
|
1178
|
+
// context to distinguish between "OS muted the track", "permission
|
|
1179
|
+
// revoked mid-flight", "device never produced frames" and other modes
|
|
1180
|
+
// that all surface as the same generic timeout error.
|
|
1181
|
+
const track = stream.getVideoTracks()[0];
|
|
1182
|
+
const waitStart = performance.now();
|
|
1183
|
+
const trackEvents = [];
|
|
1184
|
+
const recordTrackEvent = (event) => {
|
|
1185
|
+
trackEvents.push({
|
|
1186
|
+
at: Math.round(performance.now() - waitStart),
|
|
1187
|
+
event,
|
|
1188
|
+
state: track?.readyState,
|
|
1189
|
+
});
|
|
1190
|
+
};
|
|
1191
|
+
const onTrackMute = () => recordTrackEvent('mute');
|
|
1192
|
+
const onTrackUnmute = () => recordTrackEvent('unmute');
|
|
1193
|
+
const onTrackEnded = () => recordTrackEvent('ended');
|
|
1194
|
+
if (track) {
|
|
1195
|
+
track.addEventListener('mute', onTrackMute);
|
|
1196
|
+
track.addEventListener('unmute', onTrackUnmute);
|
|
1197
|
+
track.addEventListener('ended', onTrackEnded);
|
|
1198
|
+
}
|
|
1199
|
+
const cleanupTrackListeners = () => {
|
|
1200
|
+
if (!track)
|
|
1201
|
+
return;
|
|
1202
|
+
track.removeEventListener('mute', onTrackMute);
|
|
1203
|
+
track.removeEventListener('unmute', onTrackUnmute);
|
|
1204
|
+
track.removeEventListener('ended', onTrackEnded);
|
|
1205
|
+
};
|
|
1206
|
+
const captureDiagnostics = () => {
|
|
1207
|
+
const settings = track?.getSettings?.();
|
|
1208
|
+
const computed = this.videoRef ? getComputedStyle(this.videoRef) : null;
|
|
1209
|
+
return {
|
|
1210
|
+
reason: 'VIDEO_METADATA_TIMEOUT',
|
|
1211
|
+
waitedMs: Math.round(performance.now() - waitStart),
|
|
1212
|
+
stream: {
|
|
1213
|
+
videoTrackCount: stream.getVideoTracks().length,
|
|
1214
|
+
audioTrackCount: stream.getAudioTracks().length,
|
|
1215
|
+
},
|
|
1216
|
+
track: track ? {
|
|
1217
|
+
readyState: track.readyState,
|
|
1218
|
+
muted: track.muted,
|
|
1219
|
+
enabled: track.enabled,
|
|
1220
|
+
label: track.label,
|
|
1221
|
+
kind: track.kind,
|
|
1222
|
+
settings: settings ? {
|
|
1223
|
+
width: settings.width,
|
|
1224
|
+
height: settings.height,
|
|
1225
|
+
frameRate: settings.frameRate,
|
|
1226
|
+
deviceId: settings.deviceId,
|
|
1227
|
+
facingMode: settings.facingMode,
|
|
1228
|
+
} : null,
|
|
1229
|
+
} : null,
|
|
1230
|
+
video: this.videoRef ? {
|
|
1231
|
+
readyState: this.videoRef.readyState,
|
|
1232
|
+
networkState: this.videoRef.networkState,
|
|
1233
|
+
error: this.videoRef.error ? { code: this.videoRef.error.code, message: this.videoRef.error.message } : null,
|
|
1234
|
+
paused: this.videoRef.paused,
|
|
1235
|
+
offsetWidth: this.videoRef.offsetWidth,
|
|
1236
|
+
offsetHeight: this.videoRef.offsetHeight,
|
|
1237
|
+
videoWidth: this.videoRef.videoWidth,
|
|
1238
|
+
videoHeight: this.videoRef.videoHeight,
|
|
1239
|
+
display: computed?.display,
|
|
1240
|
+
visibility: computed?.visibility,
|
|
1241
|
+
} : null,
|
|
1242
|
+
document: {
|
|
1243
|
+
visibilityState: document.visibilityState,
|
|
1244
|
+
hasFocus: document.hasFocus(),
|
|
1245
|
+
},
|
|
1246
|
+
trackEvents,
|
|
1247
|
+
userAgent: navigator.userAgent,
|
|
1248
|
+
};
|
|
1249
|
+
};
|
|
1250
|
+
const finalizeStreamInitialization = async () => {
|
|
1251
|
+
try {
|
|
1252
|
+
await this.videoRef.play();
|
|
1253
|
+
}
|
|
1254
|
+
catch (playError) {
|
|
1255
|
+
console.error('[jaak-stamps] video.play() failed (likely autoplay policy):', playError);
|
|
1256
|
+
throw playError;
|
|
1257
|
+
}
|
|
1258
|
+
this.stateManager.updateCaptureState({ isVideoActive: true });
|
|
1259
|
+
if (this.detectionContainer) {
|
|
1260
|
+
const container = this.detectionContainer.parentElement;
|
|
1261
|
+
const rect = container.getBoundingClientRect();
|
|
1262
|
+
this.updateMaskDimensions(rect);
|
|
1263
|
+
}
|
|
1264
|
+
this.debugLog('initializeVideoStream() finalized after play()', {
|
|
1265
|
+
stream: this.getStreamSnapshot(stream),
|
|
1266
|
+
videoRef: this.getVideoElementSnapshot(),
|
|
1267
|
+
});
|
|
1268
|
+
};
|
|
1269
|
+
// If metadata is already available (readyState >= HAVE_METADATA = 1),
|
|
1270
|
+
// the 'loadedmetadata' event will not fire again. Handle both cases.
|
|
1271
|
+
if (this.videoRef.readyState >= 1) {
|
|
1272
|
+
this.debugLog('video metadata already available, skipping event wait', this.getVideoElementSnapshot());
|
|
1273
|
+
cleanupTrackListeners();
|
|
1274
|
+
await finalizeStreamInitialization();
|
|
1275
|
+
detachVideoDiagnosticListeners();
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
return new Promise((resolve, reject) => {
|
|
1279
|
+
let settled = false;
|
|
1280
|
+
const cleanup = () => {
|
|
1281
|
+
this.videoRef.onloadedmetadata = null;
|
|
1282
|
+
this.videoRef.onerror = null;
|
|
1283
|
+
detachVideoDiagnosticListeners();
|
|
1284
|
+
};
|
|
1285
|
+
const timeoutId = setTimeout(() => {
|
|
1286
|
+
if (settled)
|
|
1287
|
+
return;
|
|
1288
|
+
settled = true;
|
|
1289
|
+
const diagnostics = captureDiagnostics();
|
|
1290
|
+
this.debugWarn(`initializeVideoStream() timeout after ${LOAD_METADATA_TIMEOUT_MS}ms`, {
|
|
1291
|
+
videoRef: this.getVideoElementSnapshot(),
|
|
1292
|
+
stream: this.getStreamSnapshot(stream),
|
|
1293
|
+
});
|
|
1294
|
+
console.error('[jaak-stamps] Video metadata timeout diagnostics (json):', JSON.stringify(diagnostics));
|
|
1295
|
+
cleanup();
|
|
1296
|
+
cleanupTrackListeners();
|
|
1297
|
+
reject(new Error(`Video metadata load timeout after ${LOAD_METADATA_TIMEOUT_MS}ms`));
|
|
1298
|
+
}, LOAD_METADATA_TIMEOUT_MS);
|
|
1299
|
+
this.videoRef.onloadedmetadata = async () => {
|
|
1300
|
+
if (settled)
|
|
1301
|
+
return;
|
|
1302
|
+
settled = true;
|
|
1303
|
+
clearTimeout(timeoutId);
|
|
1304
|
+
this.debugLog('initializeVideoStream() onloadedmetadata fired', {
|
|
1305
|
+
videoRef: this.getVideoElementSnapshot(),
|
|
1306
|
+
stream: this.getStreamSnapshot(stream),
|
|
1307
|
+
});
|
|
1308
|
+
cleanupTrackListeners();
|
|
1309
|
+
try {
|
|
1310
|
+
await finalizeStreamInitialization();
|
|
1311
|
+
cleanup();
|
|
1312
|
+
resolve();
|
|
1313
|
+
}
|
|
1314
|
+
catch (err) {
|
|
1315
|
+
cleanup();
|
|
1316
|
+
reject(err);
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
this.videoRef.onerror = (event) => {
|
|
1320
|
+
if (settled)
|
|
1321
|
+
return;
|
|
1322
|
+
settled = true;
|
|
1323
|
+
clearTimeout(timeoutId);
|
|
1324
|
+
this.debugWarn('initializeVideoStream() video element emitted error', {
|
|
1325
|
+
event,
|
|
1326
|
+
videoRef: this.getVideoElementSnapshot(),
|
|
1327
|
+
stream: this.getStreamSnapshot(stream),
|
|
1328
|
+
});
|
|
1329
|
+
cleanup();
|
|
1330
|
+
cleanupTrackListeners();
|
|
1331
|
+
reject(new Error(`Video element error: ${event}`));
|
|
1332
|
+
};
|
|
1333
|
+
});
|
|
1017
1334
|
}
|
|
1018
1335
|
async detectFrame() {
|
|
1019
1336
|
try {
|
|
@@ -1236,7 +1553,16 @@ export class JaakStamps {
|
|
|
1236
1553
|
isCapturing: false
|
|
1237
1554
|
};
|
|
1238
1555
|
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
1239
|
-
return (h("div", { key: '
|
|
1556
|
+
return (h("div", { key: '958fb0a94dcab5e15b2da9b2f6e1219d85e1f03a', class: "detector-container" }, !this.licenseValid && this.licenseError && (h("div", { key: 'b455ba5f14b5415d2b49c8ace3ead469cd2a7acc', class: "license-error-container" }, h("div", { key: '4c1e7f980319cf125b5186e26158c37a7a97842c', class: "license-error-card" }, h("svg", { key: '4c411ef193d03c543fe9298bc3aa077dcaf6b7ea', class: "license-error-icon", width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, h("path", { key: '24f0eb292901ee9cef24afdc2161bc69f6a51cd0', d: "M12 22C12 22 20 18 20 12V5L12 2L4 5V12C4 18 12 22 12 22Z", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("path", { key: 'e5e61abf14d9422633d1c3de9efb7d1a909c941c', d: "M12 8V12", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }), h("circle", { key: 'f5612a5748a26d33fbfa9a60ea084614f53197f9', cx: "12", cy: "16", r: "0.5", fill: "currentColor", stroke: "currentColor", "stroke-width": "1" })), h("h2", { key: '5ba51ab22b3dc341a5a59661a5f4f87d6dcc4f96', class: "license-error-title" }, "Licencia Requerida"), h("p", { key: 'd162830f9b888ff9d179abd4d532287c3edd2b44', class: "license-error-message" }, this.licenseError), h("p", { key: '8ac80117990849f03c4cac091a9642160b49c109', class: "license-error-help" }, "Contacte a soporte: ", h("a", { key: 'df4b871006f2d4e5f95be43b4a2c3699288d3773', href: "mailto:support@jaak.ai" }, "support@jaak.ai")), h("div", { key: 'aa1aacb0dc56c76cce5d6bdac373f2e64d7b3f18', class: "license-error-footer" }, "JAAK Stamps")))), this.licenseValid && (h("div", { key: '0a5a3ad11c511d0d8f1b33a4a2fff32aede47277', class: "video-container" }, h("video", { key: 'db9c667c035c27aec76625385bb7b1ec0624736e', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: {
|
|
1557
|
+
// Keep the element rendered (display: block) so the browser
|
|
1558
|
+
// loads stream metadata and fires 'loadedmetadata'. Hiding it
|
|
1559
|
+
// with display:none prevents metadata loading in Chrome and
|
|
1560
|
+
// causes initializeVideoStream to hang waiting for the event.
|
|
1561
|
+
// Use opacity + visibility to hide visually while keeping the
|
|
1562
|
+
// render context alive.
|
|
1563
|
+
opacity: captureState.isVideoActive ? '1' : '0',
|
|
1564
|
+
visibility: captureState.isVideoActive ? 'visible' : 'hidden',
|
|
1565
|
+
} }), h("div", { key: '424765f58b596d8459ae903245ee8e0717eaf14a', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
|
|
1240
1566
|
position: 'absolute',
|
|
1241
1567
|
left: `${box.x}px`,
|
|
1242
1568
|
top: `${box.y}px`,
|
|
@@ -1245,9 +1571,9 @@ export class JaakStamps {
|
|
|
1245
1571
|
border: '2px solid #32406C',
|
|
1246
1572
|
pointerEvents: 'none',
|
|
1247
1573
|
boxSizing: 'border-box'
|
|
1248
|
-
} })))), this.isMaskReady && (h("div", { key: '
|
|
1574
|
+
} })))), this.isMaskReady && (h("div", { key: '26b11d2375ac441dbd8b5eaec4f5976df4d1f0aa', class: "overlay-mask" }, h("div", { key: '51d95dab401719ed84533a76e58ce00c12d4f72a', class: "card-outline" }, h("div", { key: 'd2b87cb68858e74340611a46ea7e3efc1691f46e', class: "side side-top" }), h("div", { key: '55ff7658fcc123535c9710133ef4d8973bca1d2e', class: "side side-right" }), h("div", { key: '0dfa4f296ff756afa2003eb4fb368d208128c47e', class: "side side-bottom" }), h("div", { key: '9aefe3fef283ee27cbe412d27a1a873cd871eb1a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'ae93e17580fe8d6efa460857011ebf36d4b162b6', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'a9835585d26c8819ffb12e5a278d19c89d17ec6a', class: "back-capture-section" }, h("div", { key: '7563c10750eb649c337379bcd1966ebbe4c0a7be', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode || this.showManualCaptureButton) && (h("button", { key: '0c44faf4123197765b0bad30f5674ce7b1cc4f00', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: 'efed4645446e3ac5189dcc909998bda49771e61d', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: '7f21e533d151507cb8aefeb232e2e6924ec4a935', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: '2634e6953b71e30b658dc632d853124f5ce5bb91', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
|
|
1249
1575
|
? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
|
|
1250
|
-
: 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: '
|
|
1576
|
+
: 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'ed8cf74950d02b58d4c8628663ec692d964757f0', class: "camera-controls" }, h("button", { key: 'd309850ed774ac08e63e1eb1ea0019f7c80d3db6', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: '4a70996d233bffbeac7badc52537d94453db30d3', class: "camera-selector-dropdown" }, h("div", { key: '7f893af0afeff55fe63e4971d0afc24516d2b6a6', class: "camera-selector-header" }, h("span", { key: 'd20382742b5ee5d90f678988d0d2c1c964f615d6' }, "Seleccionar C\u00E1mara"), h("button", { key: '1542d189db50740dd10e2ea843f7c5dccc5e24f2', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'e7907a23b4c7571c455f906333ccbd8e4172d3c9', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '8615ca9b81f57bbf5d52d40fd827b79a637f5e4f', class: "device-info" }, h("small", { key: '6f07c1bfb1a2b3824ccdfae4ebca11b0fa0297fe' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'c1c18723517003f2bcb9f68c527a13c4e6f30095', class: "manual-capture-section" }, h("button", { key: '94d21c348697cd28ecb44a5144e1e105f10e377c', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: '74333b317328ad70da763e5d127635f3b6dc13d1', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: '6761b16e393c5d496c5fde729b6b36580cfede0c', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '7556dddec2de2c134f9b09fbd38569366c35da48', class: "flip-animation" }, h("div", { key: 'baa3cd8993ef761ebfb94ccda6d66896959f32ee', class: "id-card-icon" }), h("div", { key: '3c5027ada509290b9abbf599849dbd19e50113af', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '1826a908ea94637428d9e800668d9ee02f0f03de', class: "success-animation" }, h("div", { key: '60d8e894ceed0c5198edb67a1e3e0fa24b9aaa3b', class: "check-icon" }), h("div", { key: '106d6ccec79f64b94273baeb8984f23e71987076', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '804e4fe62de056dca22964283282e32336f8eb00', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '108d6cab9eb7826cbe396806652a6b096ce0736d', class: "status-spinner" })), h("div", { key: '4abe0717c6b66b2bf44eb2da513fb6c22af31725', class: "status-content" }, h("div", { key: '77fb79d89df1f9e25c3c4962235b0605c7b6237f', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '2b24b09eabbbbbedd1270ebbf72f32f19ee063cb', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '7c6bf594cdb26334bfe62ee09d860b19399b4fd2', class: "performance-monitor" }, h("div", { key: '06f2b94e8a08e8afe94a2947d928a4c3c1ec260f', class: "performance-expanded" }, h("div", { key: 'c9302538e10e53e81a5685226c89a293ffd1608b', class: "metrics-row" }, h("div", { key: '7499bee1d362bf9910b401c2f85961847facc9f8', class: "metric-compact" }, h("span", { key: 'f005aab118350af46520ece3615544976b5d533f', class: "metric-label" }, "FPS"), h("span", { key: '69102a7df1fdc559883f807a8d26ae9268e56b01', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '0a63031beb60eeeb19cd50637f506661cdb1da16', class: "metric-compact" }, h("span", { key: '0231014ed6f077d780252c3b822a269148675d2f', class: "metric-label" }, "MEM"), h("span", { key: '859eda6914fdab077635f983fd4ca01daaf47ce7', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '102218bc7cf2d29c3c8ef7f91febf924ecb418a3', class: "metrics-row" }, h("div", { key: '205e50b3dbc43c541b76c21ef8e19a3defe065ce', class: "metric-compact" }, h("span", { key: '42eace007731c4642040e33f3e20462d7e150494', class: "metric-label" }, "INF"), h("span", { key: 'ff2d169c660011b24fa3340479d91befc9c2d06c', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'ec3ec57e442b3aefb38acf774873b0b181731523', class: "metric-compact" }, h("span", { key: '27d6d74dcdffbdafb4382c40b60f032b20dc5ade', class: "metric-label" }, "FRAME"), h("span", { key: 'e55795508f97a27c0b03736de2e5960560970378', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '60e80821ed57f1a724409e26a232fa2792886011', class: "metrics-row" }, h("div", { key: '7deb1504fb3705affaab105782ed7107d6e1a501', class: "metric-compact" }, h("span", { key: '0658ce3204ce120557caa248505def48a8a205dd', class: "metric-label" }, "DET"), h("span", { key: '6b14fafc3903912380edda27e5024e1221917db7', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'ec602c3b4d7d8ab0ffbf165d98e8b2e7d7d0b0ec', class: "metric-compact" }, h("span", { key: '9615ae1d383d383f116d2953f05ddb252336f7be', class: "metric-label" }, "RATE"), h("span", { key: 'eca7277e9185b9ff4232e556e707a3f1ce80365f', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '3b6b9368797965d5795c9752d2b4a08a9934b441', class: "watermark" }, h("img", { key: 'b40ffaeb6f88d1a744bffeb64ac11d4f19be6220', src: "https://static.jaak.ai/commons/powered-by-jaak.png", alt: "Powered by Jaak" }))))));
|
|
1251
1577
|
}
|
|
1252
1578
|
// Utility methods
|
|
1253
1579
|
updateDetectionBoxes(boxes) {
|