biometry-sdk 2.1.2 → 2.2.0

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/ui.js DELETED
@@ -1,1480 +0,0 @@
1
- var BiometryAttributes;
2
- (function (BiometryAttributes) {
3
- BiometryAttributes["ApiKey"] = "api-key";
4
- BiometryAttributes["UserFullname"] = "user-fullname";
5
- })(BiometryAttributes || (BiometryAttributes = {}));
6
- var BiometryEnrollmentState;
7
- (function (BiometryEnrollmentState) {
8
- BiometryEnrollmentState["Loading"] = "loading";
9
- BiometryEnrollmentState["Success"] = "success";
10
- BiometryEnrollmentState["ErrorNoFace"] = "error-no-face";
11
- BiometryEnrollmentState["ErrorMultipleFaces"] = "error-multiple-faces";
12
- BiometryEnrollmentState["ErrorNotCentered"] = "error-not-centered";
13
- BiometryEnrollmentState["ErrorOther"] = "error-other";
14
- })(BiometryEnrollmentState || (BiometryEnrollmentState = {}));
15
-
16
- class BiometryEnrollment extends HTMLElement {
17
- constructor() {
18
- super();
19
- this.videoElement = null;
20
- this.canvasElement = null;
21
- this.captureButton = null;
22
- this.shadow = this.attachShadow({ mode: "open" });
23
- this.toggleState = this.toggleState.bind(this);
24
- this.capturePhoto = this.capturePhoto.bind(this);
25
- }
26
- static get observedAttributes() {
27
- return Object.values(BiometryAttributes);
28
- }
29
- get endpoint() {
30
- return this.getAttribute("endpoint");
31
- }
32
- set endpoint(value) {
33
- const current = this.getAttribute("endpoint");
34
- if (value !== null && value !== current) {
35
- this.setAttribute("endpoint", value);
36
- }
37
- else if (value === null && current !== null) {
38
- this.removeAttribute("endpoint");
39
- }
40
- }
41
- get userFullname() {
42
- return this.getAttribute("user-fullname");
43
- }
44
- set userFullname(value) {
45
- if (value) {
46
- this.setAttribute("user-fullname", value);
47
- }
48
- else {
49
- this.removeAttribute("user-fullname");
50
- }
51
- }
52
- attributeChangedCallback(name, oldValue, newValue) {
53
- if (name === "endpoint" || name === "user-fullname") {
54
- if (name === "endpoint") {
55
- this.endpoint = newValue;
56
- }
57
- else if (name === "user-fullname") {
58
- this.userFullname = newValue;
59
- }
60
- this.validateAttributes();
61
- }
62
- }
63
- connectedCallback() {
64
- this.validateAttributes();
65
- this.init();
66
- }
67
- disconnectedCallback() {
68
- this.cleanup();
69
- if (this.captureButton) {
70
- this.captureButton.removeEventListener("click", this.capturePhoto);
71
- }
72
- }
73
- validateAttributes() {
74
- if (!this.endpoint) {
75
- console.error("Endpoint is required.");
76
- this.toggleState(BiometryEnrollmentState.ErrorOther);
77
- return;
78
- }
79
- if (!this.userFullname) {
80
- console.error("User fullname is required.");
81
- this.toggleState(BiometryEnrollmentState.ErrorOther);
82
- return;
83
- }
84
- }
85
- init() {
86
- this.shadow.innerHTML = `
87
- <style>
88
- .wrapper {
89
- position: relative;
90
- }
91
- video {
92
- transform: scaleX(-1); /* Flip video for preview */
93
- max-width: 100%;
94
- border-radius: var(--border-radius, 8px);
95
- }
96
- canvas {
97
- display: none;
98
- }
99
- </style>
100
- <div class="wrapper">
101
- <slot name="video">
102
- <video id="video" autoplay playsinline></video>
103
- </slot>
104
- <slot name="canvas">
105
- <canvas id="canvas" style="display: none;"></canvas>
106
- </slot>
107
- <slot name="button">
108
- <button id="button">Capture Photo</button>
109
- </slot>
110
- <div class="status">
111
- <slot name="loading" class="loading"></slot>
112
- <slot name="success" class="success"></slot>
113
- <slot name="error-no-face" class="error-no-face"></slot>
114
- <slot name="error-multiple-faces" class="error-multiple-faces"></slot>
115
- <slot name="error-not-centered" class="error-not-centered"></slot>
116
- <slot name="error-other" class="error-other"></slot>
117
- </div>
118
- </div>
119
- `;
120
- this.attachSlotListeners();
121
- this.setupCamera();
122
- this.toggleState("");
123
- }
124
- cleanup() {
125
- if (this.videoElement?.srcObject) {
126
- const tracks = this.videoElement.srcObject.getTracks();
127
- tracks.forEach((track) => track.stop());
128
- }
129
- if (this.videoElement) {
130
- this.videoElement.srcObject = null;
131
- }
132
- }
133
- attachSlotListeners() {
134
- const videoSlot = this.shadow.querySelector('slot[name="video"]');
135
- const canvasSlot = this.shadow.querySelector('slot[name="canvas"]');
136
- const buttonSlot = this.shadow.querySelector('slot[name="button"]');
137
- const assignedVideoElements = videoSlot.assignedElements();
138
- this.videoElement = (assignedVideoElements.length > 0 ? assignedVideoElements[0] : null) || this.shadow.querySelector("#video");
139
- const assignedCanvasElements = canvasSlot.assignedElements();
140
- this.canvasElement = (assignedCanvasElements.length > 0 ? assignedCanvasElements[0] : null) || this.shadow.querySelector("#canvas");
141
- const assignedButtonElements = buttonSlot.assignedElements();
142
- this.captureButton = (assignedButtonElements.length > 0 ? assignedButtonElements[0] : null) || this.shadow.querySelector("#button");
143
- if (!this.videoElement) {
144
- console.error("Video element is missing.");
145
- return;
146
- }
147
- if (!this.captureButton) {
148
- console.error("Capture button is missing.");
149
- return;
150
- }
151
- else {
152
- this.captureButton.addEventListener("click", this.capturePhoto);
153
- }
154
- }
155
- setupCamera() {
156
- if (!this.videoElement) {
157
- console.error("Video element is missing.");
158
- return;
159
- }
160
- navigator.mediaDevices
161
- .getUserMedia({ video: true })
162
- .then((stream) => {
163
- this.videoElement.srcObject = stream;
164
- })
165
- .catch((error) => {
166
- console.error("Error accessing camera:", error);
167
- });
168
- }
169
- async capturePhoto() {
170
- try {
171
- if (!this.videoElement || !this.canvasElement) {
172
- console.error("Essential elements are not initialized.");
173
- return;
174
- }
175
- const context = this.canvasElement.getContext("2d");
176
- this.canvasElement.width = this.videoElement.videoWidth;
177
- this.canvasElement.height = this.videoElement.videoHeight;
178
- context.drawImage(this.videoElement, 0, 0, this.canvasElement.width, this.canvasElement.height);
179
- this.toggleState("loading");
180
- this.canvasElement.toBlob(async (blob) => {
181
- try {
182
- if (!blob) {
183
- console.error("Failed to capture photo.");
184
- this.toggleState(BiometryEnrollmentState.ErrorOther);
185
- return;
186
- }
187
- const file = new File([blob], "onboard-face.jpg", { type: "image/jpeg" });
188
- const formData = new FormData();
189
- formData.append('photo', file);
190
- formData.append('userFullname', this.userFullname || '');
191
- const response = await fetch(this.endpoint, {
192
- method: 'POST',
193
- body: formData
194
- });
195
- if (!response.ok) {
196
- throw new Error(`HTTP error! status: ${response.status}`);
197
- }
198
- const result = await response.json();
199
- this.resultCode = result?.code;
200
- this.description = result?.description || "Unknown error occurred.";
201
- switch (this.resultCode) {
202
- case 0:
203
- this.toggleState(BiometryEnrollmentState.Success);
204
- break;
205
- case 1:
206
- this.toggleState(BiometryEnrollmentState.ErrorNoFace);
207
- break;
208
- case 2:
209
- this.toggleState(BiometryEnrollmentState.ErrorMultipleFaces);
210
- break;
211
- case 3:
212
- this.toggleState(BiometryEnrollmentState.ErrorNotCentered);
213
- break;
214
- default:
215
- this.toggleState(BiometryEnrollmentState.ErrorOther);
216
- }
217
- console.log("Enrollment result:", result);
218
- }
219
- catch (error) {
220
- console.error("Error in toBlob callback:", error);
221
- this.toggleState(BiometryEnrollmentState.ErrorOther);
222
- }
223
- }, "image/jpeg");
224
- }
225
- catch (error) {
226
- console.error("Error capturing photo:", error);
227
- this.toggleState(BiometryEnrollmentState.ErrorOther);
228
- }
229
- }
230
- toggleState(state) {
231
- const slots = [
232
- BiometryEnrollmentState.Loading,
233
- BiometryEnrollmentState.Success,
234
- BiometryEnrollmentState.ErrorNoFace,
235
- BiometryEnrollmentState.ErrorMultipleFaces,
236
- BiometryEnrollmentState.ErrorNotCentered,
237
- BiometryEnrollmentState.ErrorOther,
238
- ];
239
- slots.forEach((slotName) => {
240
- const slot = this.shadow.querySelector(`slot[name="${slotName}"]`);
241
- if (slot) {
242
- slot.style.display = slotName === state ? "block" : "none";
243
- }
244
- });
245
- }
246
- }
247
- customElements.define("biometry-enrollment", BiometryEnrollment);
248
-
249
- function getDefaultExportFromCjs (x) {
250
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
251
- }
252
-
253
- var fixWebmDuration = {exports: {}};
254
-
255
- var hasRequiredFixWebmDuration;
256
-
257
- function requireFixWebmDuration () {
258
- if (hasRequiredFixWebmDuration) return fixWebmDuration.exports;
259
- hasRequiredFixWebmDuration = 1;
260
- (function (module) {
261
- (function (name, definition) {
262
- if (module.exports) { // CommonJS / Node.js
263
- module.exports = definition();
264
- } else { // Direct include
265
- window.ysFixWebmDuration = definition();
266
- }
267
- })('fix-webm-duration', function () {
268
- /*
269
- * This is the list of possible WEBM file sections by their IDs.
270
- * Possible types: Container, Binary, Uint, Int, String, Float, Date
271
- */
272
- var sections = {
273
- 0xa45dfa3: { name: 'EBML', type: 'Container' },
274
- 0x286: { name: 'EBMLVersion', type: 'Uint' },
275
- 0x2f7: { name: 'EBMLReadVersion', type: 'Uint' },
276
- 0x2f2: { name: 'EBMLMaxIDLength', type: 'Uint' },
277
- 0x2f3: { name: 'EBMLMaxSizeLength', type: 'Uint' },
278
- 0x282: { name: 'DocType', type: 'String' },
279
- 0x287: { name: 'DocTypeVersion', type: 'Uint' },
280
- 0x285: { name: 'DocTypeReadVersion', type: 'Uint' },
281
- 0x6c: { name: 'Void', type: 'Binary' },
282
- 0x3f: { name: 'CRC-32', type: 'Binary' },
283
- 0xb538667: { name: 'SignatureSlot', type: 'Container' },
284
- 0x3e8a: { name: 'SignatureAlgo', type: 'Uint' },
285
- 0x3e9a: { name: 'SignatureHash', type: 'Uint' },
286
- 0x3ea5: { name: 'SignaturePublicKey', type: 'Binary' },
287
- 0x3eb5: { name: 'Signature', type: 'Binary' },
288
- 0x3e5b: { name: 'SignatureElements', type: 'Container' },
289
- 0x3e7b: { name: 'SignatureElementList', type: 'Container' },
290
- 0x2532: { name: 'SignedElement', type: 'Binary' },
291
- 0x8538067: { name: 'Segment', type: 'Container' },
292
- 0x14d9b74: { name: 'SeekHead', type: 'Container' },
293
- 0xdbb: { name: 'Seek', type: 'Container' },
294
- 0x13ab: { name: 'SeekID', type: 'Binary' },
295
- 0x13ac: { name: 'SeekPosition', type: 'Uint' },
296
- 0x549a966: { name: 'Info', type: 'Container' },
297
- 0x33a4: { name: 'SegmentUID', type: 'Binary' },
298
- 0x3384: { name: 'SegmentFilename', type: 'String' },
299
- 0x1cb923: { name: 'PrevUID', type: 'Binary' },
300
- 0x1c83ab: { name: 'PrevFilename', type: 'String' },
301
- 0x1eb923: { name: 'NextUID', type: 'Binary' },
302
- 0x1e83bb: { name: 'NextFilename', type: 'String' },
303
- 0x444: { name: 'SegmentFamily', type: 'Binary' },
304
- 0x2924: { name: 'ChapterTranslate', type: 'Container' },
305
- 0x29fc: { name: 'ChapterTranslateEditionUID', type: 'Uint' },
306
- 0x29bf: { name: 'ChapterTranslateCodec', type: 'Uint' },
307
- 0x29a5: { name: 'ChapterTranslateID', type: 'Binary' },
308
- 0xad7b1: { name: 'TimecodeScale', type: 'Uint' },
309
- 0x489: { name: 'Duration', type: 'Float' },
310
- 0x461: { name: 'DateUTC', type: 'Date' },
311
- 0x3ba9: { name: 'Title', type: 'String' },
312
- 0xd80: { name: 'MuxingApp', type: 'String' },
313
- 0x1741: { name: 'WritingApp', type: 'String' },
314
- // 0xf43b675: { name: 'Cluster', type: 'Container' },
315
- 0x67: { name: 'Timecode', type: 'Uint' },
316
- 0x1854: { name: 'SilentTracks', type: 'Container' },
317
- 0x18d7: { name: 'SilentTrackNumber', type: 'Uint' },
318
- 0x27: { name: 'Position', type: 'Uint' },
319
- 0x2b: { name: 'PrevSize', type: 'Uint' },
320
- 0x23: { name: 'SimpleBlock', type: 'Binary' },
321
- 0x20: { name: 'BlockGroup', type: 'Container' },
322
- 0x21: { name: 'Block', type: 'Binary' },
323
- 0x22: { name: 'BlockVirtual', type: 'Binary' },
324
- 0x35a1: { name: 'BlockAdditions', type: 'Container' },
325
- 0x26: { name: 'BlockMore', type: 'Container' },
326
- 0x6e: { name: 'BlockAddID', type: 'Uint' },
327
- 0x25: { name: 'BlockAdditional', type: 'Binary' },
328
- 0x1b: { name: 'BlockDuration', type: 'Uint' },
329
- 0x7a: { name: 'ReferencePriority', type: 'Uint' },
330
- 0x7b: { name: 'ReferenceBlock', type: 'Int' },
331
- 0x7d: { name: 'ReferenceVirtual', type: 'Int' },
332
- 0x24: { name: 'CodecState', type: 'Binary' },
333
- 0x35a2: { name: 'DiscardPadding', type: 'Int' },
334
- 0xe: { name: 'Slices', type: 'Container' },
335
- 0x68: { name: 'TimeSlice', type: 'Container' },
336
- 0x4c: { name: 'LaceNumber', type: 'Uint' },
337
- 0x4d: { name: 'FrameNumber', type: 'Uint' },
338
- 0x4b: { name: 'BlockAdditionID', type: 'Uint' },
339
- 0x4e: { name: 'Delay', type: 'Uint' },
340
- 0x4f: { name: 'SliceDuration', type: 'Uint' },
341
- 0x48: { name: 'ReferenceFrame', type: 'Container' },
342
- 0x49: { name: 'ReferenceOffset', type: 'Uint' },
343
- 0x4a: { name: 'ReferenceTimeCode', type: 'Uint' },
344
- 0x2f: { name: 'EncryptedBlock', type: 'Binary' },
345
- 0x654ae6b: { name: 'Tracks', type: 'Container' },
346
- 0x2e: { name: 'TrackEntry', type: 'Container' },
347
- 0x57: { name: 'TrackNumber', type: 'Uint' },
348
- 0x33c5: { name: 'TrackUID', type: 'Uint' },
349
- 0x3: { name: 'TrackType', type: 'Uint' },
350
- 0x39: { name: 'FlagEnabled', type: 'Uint' },
351
- 0x8: { name: 'FlagDefault', type: 'Uint' },
352
- 0x15aa: { name: 'FlagForced', type: 'Uint' },
353
- 0x1c: { name: 'FlagLacing', type: 'Uint' },
354
- 0x2de7: { name: 'MinCache', type: 'Uint' },
355
- 0x2df8: { name: 'MaxCache', type: 'Uint' },
356
- 0x3e383: { name: 'DefaultDuration', type: 'Uint' },
357
- 0x34e7a: { name: 'DefaultDecodedFieldDuration', type: 'Uint' },
358
- 0x3314f: { name: 'TrackTimecodeScale', type: 'Float' },
359
- 0x137f: { name: 'TrackOffset', type: 'Int' },
360
- 0x15ee: { name: 'MaxBlockAdditionID', type: 'Uint' },
361
- 0x136e: { name: 'Name', type: 'String' },
362
- 0x2b59c: { name: 'Language', type: 'String' },
363
- 0x6: { name: 'CodecID', type: 'String' },
364
- 0x23a2: { name: 'CodecPrivate', type: 'Binary' },
365
- 0x58688: { name: 'CodecName', type: 'String' },
366
- 0x3446: { name: 'AttachmentLink', type: 'Uint' },
367
- 0x1a9697: { name: 'CodecSettings', type: 'String' },
368
- 0x1b4040: { name: 'CodecInfoURL', type: 'String' },
369
- 0x6b240: { name: 'CodecDownloadURL', type: 'String' },
370
- 0x2a: { name: 'CodecDecodeAll', type: 'Uint' },
371
- 0x2fab: { name: 'TrackOverlay', type: 'Uint' },
372
- 0x16aa: { name: 'CodecDelay', type: 'Uint' },
373
- 0x16bb: { name: 'SeekPreRoll', type: 'Uint' },
374
- 0x2624: { name: 'TrackTranslate', type: 'Container' },
375
- 0x26fc: { name: 'TrackTranslateEditionUID', type: 'Uint' },
376
- 0x26bf: { name: 'TrackTranslateCodec', type: 'Uint' },
377
- 0x26a5: { name: 'TrackTranslateTrackID', type: 'Binary' },
378
- 0x60: { name: 'Video', type: 'Container' },
379
- 0x1a: { name: 'FlagInterlaced', type: 'Uint' },
380
- 0x13b8: { name: 'StereoMode', type: 'Uint' },
381
- 0x13c0: { name: 'AlphaMode', type: 'Uint' },
382
- 0x13b9: { name: 'OldStereoMode', type: 'Uint' },
383
- 0x30: { name: 'PixelWidth', type: 'Uint' },
384
- 0x3a: { name: 'PixelHeight', type: 'Uint' },
385
- 0x14aa: { name: 'PixelCropBottom', type: 'Uint' },
386
- 0x14bb: { name: 'PixelCropTop', type: 'Uint' },
387
- 0x14cc: { name: 'PixelCropLeft', type: 'Uint' },
388
- 0x14dd: { name: 'PixelCropRight', type: 'Uint' },
389
- 0x14b0: { name: 'DisplayWidth', type: 'Uint' },
390
- 0x14ba: { name: 'DisplayHeight', type: 'Uint' },
391
- 0x14b2: { name: 'DisplayUnit', type: 'Uint' },
392
- 0x14b3: { name: 'AspectRatioType', type: 'Uint' },
393
- 0xeb524: { name: 'ColourSpace', type: 'Binary' },
394
- 0xfb523: { name: 'GammaValue', type: 'Float' },
395
- 0x383e3: { name: 'FrameRate', type: 'Float' },
396
- 0x61: { name: 'Audio', type: 'Container' },
397
- 0x35: { name: 'SamplingFrequency', type: 'Float' },
398
- 0x38b5: { name: 'OutputSamplingFrequency', type: 'Float' },
399
- 0x1f: { name: 'Channels', type: 'Uint' },
400
- 0x3d7b: { name: 'ChannelPositions', type: 'Binary' },
401
- 0x2264: { name: 'BitDepth', type: 'Uint' },
402
- 0x62: { name: 'TrackOperation', type: 'Container' },
403
- 0x63: { name: 'TrackCombinePlanes', type: 'Container' },
404
- 0x64: { name: 'TrackPlane', type: 'Container' },
405
- 0x65: { name: 'TrackPlaneUID', type: 'Uint' },
406
- 0x66: { name: 'TrackPlaneType', type: 'Uint' },
407
- 0x69: { name: 'TrackJoinBlocks', type: 'Container' },
408
- 0x6d: { name: 'TrackJoinUID', type: 'Uint' },
409
- 0x40: { name: 'TrickTrackUID', type: 'Uint' },
410
- 0x41: { name: 'TrickTrackSegmentUID', type: 'Binary' },
411
- 0x46: { name: 'TrickTrackFlag', type: 'Uint' },
412
- 0x47: { name: 'TrickMasterTrackUID', type: 'Uint' },
413
- 0x44: { name: 'TrickMasterTrackSegmentUID', type: 'Binary' },
414
- 0x2d80: { name: 'ContentEncodings', type: 'Container' },
415
- 0x2240: { name: 'ContentEncoding', type: 'Container' },
416
- 0x1031: { name: 'ContentEncodingOrder', type: 'Uint' },
417
- 0x1032: { name: 'ContentEncodingScope', type: 'Uint' },
418
- 0x1033: { name: 'ContentEncodingType', type: 'Uint' },
419
- 0x1034: { name: 'ContentCompression', type: 'Container' },
420
- 0x254: { name: 'ContentCompAlgo', type: 'Uint' },
421
- 0x255: { name: 'ContentCompSettings', type: 'Binary' },
422
- 0x1035: { name: 'ContentEncryption', type: 'Container' },
423
- 0x7e1: { name: 'ContentEncAlgo', type: 'Uint' },
424
- 0x7e2: { name: 'ContentEncKeyID', type: 'Binary' },
425
- 0x7e3: { name: 'ContentSignature', type: 'Binary' },
426
- 0x7e4: { name: 'ContentSigKeyID', type: 'Binary' },
427
- 0x7e5: { name: 'ContentSigAlgo', type: 'Uint' },
428
- 0x7e6: { name: 'ContentSigHashAlgo', type: 'Uint' },
429
- 0xc53bb6b: { name: 'Cues', type: 'Container' },
430
- 0x3b: { name: 'CuePoint', type: 'Container' },
431
- 0x33: { name: 'CueTime', type: 'Uint' },
432
- 0x37: { name: 'CueTrackPositions', type: 'Container' },
433
- 0x77: { name: 'CueTrack', type: 'Uint' },
434
- 0x71: { name: 'CueClusterPosition', type: 'Uint' },
435
- 0x70: { name: 'CueRelativePosition', type: 'Uint' },
436
- 0x32: { name: 'CueDuration', type: 'Uint' },
437
- 0x1378: { name: 'CueBlockNumber', type: 'Uint' },
438
- 0x6a: { name: 'CueCodecState', type: 'Uint' },
439
- 0x5b: { name: 'CueReference', type: 'Container' },
440
- 0x16: { name: 'CueRefTime', type: 'Uint' },
441
- 0x17: { name: 'CueRefCluster', type: 'Uint' },
442
- 0x135f: { name: 'CueRefNumber', type: 'Uint' },
443
- 0x6b: { name: 'CueRefCodecState', type: 'Uint' },
444
- 0x941a469: { name: 'Attachments', type: 'Container' },
445
- 0x21a7: { name: 'AttachedFile', type: 'Container' },
446
- 0x67e: { name: 'FileDescription', type: 'String' },
447
- 0x66e: { name: 'FileName', type: 'String' },
448
- 0x660: { name: 'FileMimeType', type: 'String' },
449
- 0x65c: { name: 'FileData', type: 'Binary' },
450
- 0x6ae: { name: 'FileUID', type: 'Uint' },
451
- 0x675: { name: 'FileReferral', type: 'Binary' },
452
- 0x661: { name: 'FileUsedStartTime', type: 'Uint' },
453
- 0x662: { name: 'FileUsedEndTime', type: 'Uint' },
454
- 0x43a770: { name: 'Chapters', type: 'Container' },
455
- 0x5b9: { name: 'EditionEntry', type: 'Container' },
456
- 0x5bc: { name: 'EditionUID', type: 'Uint' },
457
- 0x5bd: { name: 'EditionFlagHidden', type: 'Uint' },
458
- 0x5db: { name: 'EditionFlagDefault', type: 'Uint' },
459
- 0x5dd: { name: 'EditionFlagOrdered', type: 'Uint' },
460
- 0x36: { name: 'ChapterAtom', type: 'Container' },
461
- 0x33c4: { name: 'ChapterUID', type: 'Uint' },
462
- 0x1654: { name: 'ChapterStringUID', type: 'String' },
463
- 0x11: { name: 'ChapterTimeStart', type: 'Uint' },
464
- 0x12: { name: 'ChapterTimeEnd', type: 'Uint' },
465
- 0x18: { name: 'ChapterFlagHidden', type: 'Uint' },
466
- 0x598: { name: 'ChapterFlagEnabled', type: 'Uint' },
467
- 0x2e67: { name: 'ChapterSegmentUID', type: 'Binary' },
468
- 0x2ebc: { name: 'ChapterSegmentEditionUID', type: 'Uint' },
469
- 0x23c3: { name: 'ChapterPhysicalEquiv', type: 'Uint' },
470
- 0xf: { name: 'ChapterTrack', type: 'Container' },
471
- 0x9: { name: 'ChapterTrackNumber', type: 'Uint' },
472
- 0x0: { name: 'ChapterDisplay', type: 'Container' },
473
- 0x5: { name: 'ChapString', type: 'String' },
474
- 0x37c: { name: 'ChapLanguage', type: 'String' },
475
- 0x37e: { name: 'ChapCountry', type: 'String' },
476
- 0x2944: { name: 'ChapProcess', type: 'Container' },
477
- 0x2955: { name: 'ChapProcessCodecID', type: 'Uint' },
478
- 0x50d: { name: 'ChapProcessPrivate', type: 'Binary' },
479
- 0x2911: { name: 'ChapProcessCommand', type: 'Container' },
480
- 0x2922: { name: 'ChapProcessTime', type: 'Uint' },
481
- 0x2933: { name: 'ChapProcessData', type: 'Binary' },
482
- 0x254c367: { name: 'Tags', type: 'Container' },
483
- 0x3373: { name: 'Tag', type: 'Container' },
484
- 0x23c0: { name: 'Targets', type: 'Container' },
485
- 0x28ca: { name: 'TargetTypeValue', type: 'Uint' },
486
- 0x23ca: { name: 'TargetType', type: 'String' },
487
- 0x23c5: { name: 'TagTrackUID', type: 'Uint' },
488
- 0x23c9: { name: 'TagEditionUID', type: 'Uint' },
489
- 0x23c4: { name: 'TagChapterUID', type: 'Uint' },
490
- 0x23c6: { name: 'TagAttachmentUID', type: 'Uint' },
491
- 0x27c8: { name: 'SimpleTag', type: 'Container' },
492
- 0x5a3: { name: 'TagName', type: 'String' },
493
- 0x47a: { name: 'TagLanguage', type: 'String' },
494
- 0x484: { name: 'TagDefault', type: 'Uint' },
495
- 0x487: { name: 'TagString', type: 'String' },
496
- 0x485: { name: 'TagBinary', type: 'Binary' }
497
- };
498
-
499
- function doInherit(newClass, baseClass) {
500
- newClass.prototype = Object.create(baseClass.prototype);
501
- newClass.prototype.constructor = newClass;
502
- }
503
-
504
- function WebmBase(name, type) {
505
- this.name = name || 'Unknown';
506
- this.type = type || 'Unknown';
507
- }
508
- WebmBase.prototype.updateBySource = function() { };
509
- WebmBase.prototype.setSource = function(source) {
510
- this.source = source;
511
- this.updateBySource();
512
- };
513
- WebmBase.prototype.updateByData = function() { };
514
- WebmBase.prototype.setData = function(data) {
515
- this.data = data;
516
- this.updateByData();
517
- };
518
-
519
- function WebmUint(name, type) {
520
- WebmBase.call(this, name, type || 'Uint');
521
- }
522
- doInherit(WebmUint, WebmBase);
523
- function padHex(hex) {
524
- return hex.length % 2 === 1 ? '0' + hex : hex;
525
- }
526
- WebmUint.prototype.updateBySource = function() {
527
- // use hex representation of a number instead of number value
528
- this.data = '';
529
- for (var i = 0; i < this.source.length; i++) {
530
- var hex = this.source[i].toString(16);
531
- this.data += padHex(hex);
532
- }
533
- };
534
- WebmUint.prototype.updateByData = function() {
535
- var length = this.data.length / 2;
536
- this.source = new Uint8Array(length);
537
- for (var i = 0; i < length; i++) {
538
- var hex = this.data.substr(i * 2, 2);
539
- this.source[i] = parseInt(hex, 16);
540
- }
541
- };
542
- WebmUint.prototype.getValue = function() {
543
- return parseInt(this.data, 16);
544
- };
545
- WebmUint.prototype.setValue = function(value) {
546
- this.setData(padHex(value.toString(16)));
547
- };
548
-
549
- function WebmFloat(name, type) {
550
- WebmBase.call(this, name, type || 'Float');
551
- }
552
- doInherit(WebmFloat, WebmBase);
553
- WebmFloat.prototype.getFloatArrayType = function() {
554
- return this.source && this.source.length === 4 ? Float32Array : Float64Array;
555
- };
556
- WebmFloat.prototype.updateBySource = function() {
557
- var byteArray = this.source.reverse();
558
- var floatArrayType = this.getFloatArrayType();
559
- var floatArray = new floatArrayType(byteArray.buffer);
560
- this.data = floatArray[0];
561
- };
562
- WebmFloat.prototype.updateByData = function() {
563
- var floatArrayType = this.getFloatArrayType();
564
- var floatArray = new floatArrayType([ this.data ]);
565
- var byteArray = new Uint8Array(floatArray.buffer);
566
- this.source = byteArray.reverse();
567
- };
568
- WebmFloat.prototype.getValue = function() {
569
- return this.data;
570
- };
571
- WebmFloat.prototype.setValue = function(value) {
572
- this.setData(value);
573
- };
574
-
575
- function WebmContainer(name, type) {
576
- WebmBase.call(this, name, type || 'Container');
577
- }
578
- doInherit(WebmContainer, WebmBase);
579
- WebmContainer.prototype.readByte = function() {
580
- return this.source[this.offset++];
581
- };
582
- WebmContainer.prototype.readUint = function() {
583
- var firstByte = this.readByte();
584
- var bytes = 8 - firstByte.toString(2).length;
585
- var value = firstByte - (1 << (7 - bytes));
586
- for (var i = 0; i < bytes; i++) {
587
- // don't use bit operators to support x86
588
- value *= 256;
589
- value += this.readByte();
590
- }
591
- return value;
592
- };
593
- WebmContainer.prototype.updateBySource = function() {
594
- this.data = [];
595
- for (this.offset = 0; this.offset < this.source.length; this.offset = end) {
596
- var id = this.readUint();
597
- var len = this.readUint();
598
- var end = Math.min(this.offset + len, this.source.length);
599
- var data = this.source.slice(this.offset, end);
600
-
601
- var info = sections[id] || { name: 'Unknown', type: 'Unknown' };
602
- var ctr = WebmBase;
603
- switch (info.type) {
604
- case 'Container':
605
- ctr = WebmContainer;
606
- break;
607
- case 'Uint':
608
- ctr = WebmUint;
609
- break;
610
- case 'Float':
611
- ctr = WebmFloat;
612
- break;
613
- }
614
- var section = new ctr(info.name, info.type);
615
- section.setSource(data);
616
- this.data.push({
617
- id: id,
618
- idHex: id.toString(16),
619
- data: section
620
- });
621
- }
622
- };
623
- WebmContainer.prototype.writeUint = function(x, draft) {
624
- for (var bytes = 1, flag = 0x80; x >= flag && bytes < 8; bytes++, flag *= 0x80) { }
625
-
626
- if (!draft) {
627
- var value = flag + x;
628
- for (var i = bytes - 1; i >= 0; i--) {
629
- // don't use bit operators to support x86
630
- var c = value % 256;
631
- this.source[this.offset + i] = c;
632
- value = (value - c) / 256;
633
- }
634
- }
635
-
636
- this.offset += bytes;
637
- };
638
- WebmContainer.prototype.writeSections = function(draft) {
639
- this.offset = 0;
640
- for (var i = 0; i < this.data.length; i++) {
641
- var section = this.data[i],
642
- content = section.data.source,
643
- contentLength = content.length;
644
- this.writeUint(section.id, draft);
645
- this.writeUint(contentLength, draft);
646
- if (!draft) {
647
- this.source.set(content, this.offset);
648
- }
649
- this.offset += contentLength;
650
- }
651
- return this.offset;
652
- };
653
- WebmContainer.prototype.updateByData = function() {
654
- // run without accessing this.source to determine total length - need to know it to create Uint8Array
655
- var length = this.writeSections('draft');
656
- this.source = new Uint8Array(length);
657
- // now really write data
658
- this.writeSections();
659
- };
660
- WebmContainer.prototype.getSectionById = function(id) {
661
- for (var i = 0; i < this.data.length; i++) {
662
- var section = this.data[i];
663
- if (section.id === id) {
664
- return section.data;
665
- }
666
- }
667
- return null;
668
- };
669
-
670
- function WebmFile(source) {
671
- WebmContainer.call(this, 'File', 'File');
672
- this.setSource(source);
673
- }
674
- doInherit(WebmFile, WebmContainer);
675
- WebmFile.prototype.fixDuration = function(duration, options) {
676
- var logger = options && options.logger;
677
- if (logger === undefined) {
678
- logger = function(message) {
679
- console.log(message);
680
- };
681
- } else if (!logger) {
682
- logger = function() { };
683
- }
684
-
685
- var segmentSection = this.getSectionById(0x8538067);
686
- if (!segmentSection) {
687
- logger('[fix-webm-duration] Segment section is missing');
688
- return false;
689
- }
690
-
691
- var infoSection = segmentSection.getSectionById(0x549a966);
692
- if (!infoSection) {
693
- logger('[fix-webm-duration] Info section is missing');
694
- return false;
695
- }
696
-
697
- var timeScaleSection = infoSection.getSectionById(0xad7b1);
698
- if (!timeScaleSection) {
699
- logger('[fix-webm-duration] TimecodeScale section is missing');
700
- return false;
701
- }
702
-
703
- var durationSection = infoSection.getSectionById(0x489);
704
- if (durationSection) {
705
- if (durationSection.getValue() <= 0) {
706
- logger(`[fix-webm-duration] Duration section is present, but the value is ${durationSection.getValue()}`);
707
- durationSection.setValue(duration);
708
- } else {
709
- logger(`[fix-webm-duration] Duration section is present, and the value is ${durationSection.getValue()}`);
710
- return false;
711
- }
712
- } else {
713
- logger('[fix-webm-duration] Duration section is missing');
714
- // append Duration section
715
- durationSection = new WebmFloat('Duration', 'Float');
716
- durationSection.setValue(duration);
717
- infoSection.data.push({
718
- id: 0x489,
719
- data: durationSection
720
- });
721
- }
722
-
723
- // set default time scale to 1 millisecond (1000000 nanoseconds)
724
- timeScaleSection.setValue(1000000);
725
- infoSection.updateByData();
726
- segmentSection.updateByData();
727
- this.updateByData();
728
-
729
- return true;
730
- };
731
- WebmFile.prototype.toBlob = function(mimeType) {
732
- return new Blob([ this.source.buffer ], { type: mimeType || 'video/webm' });
733
- };
734
-
735
- function fixWebmDuration(blob, duration, callback, options) {
736
- // The callback may be omitted - then the third argument is options
737
- if (typeof callback === "object") {
738
- options = callback;
739
- callback = undefined;
740
- }
741
-
742
- if (!callback) {
743
- return new Promise(function(resolve) {
744
- fixWebmDuration(blob, duration, resolve, options);
745
- });
746
- }
747
-
748
- try {
749
- var reader = new FileReader();
750
- reader.onloadend = function() {
751
- try {
752
- var file = new WebmFile(new Uint8Array(reader.result));
753
- if (file.fixDuration(duration, options)) {
754
- blob = file.toBlob(blob.type);
755
- }
756
- } catch (ex) {
757
- // ignore
758
- }
759
- callback(blob);
760
- };
761
- reader.readAsArrayBuffer(blob);
762
- } catch (ex) {
763
- callback(blob);
764
- }
765
- }
766
-
767
- // Support AMD import default
768
- fixWebmDuration.default = fixWebmDuration;
769
-
770
- return fixWebmDuration;
771
- });
772
- } (fixWebmDuration));
773
- return fixWebmDuration.exports;
774
- }
775
-
776
- var fixWebmDurationExports = requireFixWebmDuration();
777
- var ysFixWebmDuration = /*@__PURE__*/getDefaultExportFromCjs(fixWebmDurationExports);
778
-
779
- class ProcessVideo extends HTMLElement {
780
- constructor() {
781
- super();
782
- this.previewStream = null;
783
- this.recordedChunks = [];
784
- this.mediaRecorder = null;
785
- this.videoFile = null;
786
- this.startTime = 0;
787
- this.timerInterval = null;
788
- this.recordingTimeout = null;
789
- this.errorState = null;
790
- this.timeLimit = 30;
791
- this.phrase = this.generateDefaultPhrase();
792
- // Attach shadow DOM and initialize UI
793
- this.attachShadow({ mode: 'open' });
794
- this.initializeUI();
795
- }
796
- get endpoint() {
797
- return this.getAttribute("endpoint");
798
- }
799
- set endpoint(value) {
800
- const current = this.getAttribute("endpoint");
801
- if (value !== null && value !== current) {
802
- this.setAttribute("endpoint", value);
803
- }
804
- else if (value === null && current !== null) {
805
- this.removeAttribute("endpoint");
806
- }
807
- }
808
- disconnectedCallback() {
809
- this.stopRecording();
810
- if (this.previewStream) {
811
- this.previewStream.getTracks().forEach(track => track.stop());
812
- }
813
- }
814
- attributeChangedCallback(name, _oldValue, newValue) {
815
- if (name === 'endpoint' && newValue !== this.endpoint) {
816
- this.endpoint = newValue;
817
- }
818
- }
819
- generateDefaultPhrase() {
820
- return Math.random().toString().slice(2, 10); // 8-digit random phrase
821
- }
822
- initializeUI() {
823
- const phraseDisplay = this.phrase
824
- .split("")
825
- .map((digit) => `<span class="digit">${digit}</span>`)
826
- .join(" ");
827
- this.shadowRoot.innerHTML = `
828
- <style>
829
- :host {
830
- display: block;
831
- font-family: Arial, sans-serif;
832
- --primary-color: #007bff;
833
- --secondary-color: #6c757d;
834
- --button-bg: var(--primary-color);
835
- --button-text-color: #fff;
836
- --input-border-color: var(--secondary-color);
837
- --input-focus-border-color: var(--primary-color);
838
- --spacing: 16px;
839
- --button-padding: 10px 20px;
840
- --border-radius: 4px;
841
- }
842
-
843
- .container {
844
- display: flex;
845
- flex-direction: column;
846
- align-items: center;
847
- justify-content: center;
848
- gap: var(--spacing);
849
- padding: var(--spacing);
850
- max-width: 500px;
851
- margin: 0 auto;
852
- text-align: center;
853
- }
854
-
855
- .video-wrapper {
856
- position: relative;
857
- display: inline-block;
858
- }
859
-
860
- #timer-overlay {
861
- position: absolute;
862
- top: 10px;
863
- left: 10px;
864
- background-color: rgba(0, 0, 0, 0.7);
865
- color: white;
866
- padding: 5px 10px;
867
- border-radius: 4px;
868
- font-family: Arial, sans-serif;
869
- font-size: 14px;
870
- }
871
-
872
- video {
873
- max-width: 100%;
874
- border-radius: var(--border-radius);
875
- }
876
-
877
- input[type="text"], input[type="file"] {
878
- padding: var(--button-padding);
879
- border: 1px solid var(--input-border-color);
880
- border-radius: var(--border-radius);
881
- width: 100%;
882
- max-width: 100%;
883
- }
884
-
885
- input[type="text"]:focus, input[type="file"]:focus {
886
- outline: none;
887
- border-color: var(--input-focus-border-color);
888
- }
889
-
890
- .hidden {
891
- display: none;
892
- }
893
-
894
- .phrase-display {
895
- font-size: 24px;
896
- font-weight: bold;
897
- display: flex;
898
- gap: 8px;
899
- justify-content: center;
900
- }
901
- .digit {
902
- padding: 4px;
903
- border: 1px solid #ccc;
904
- border-radius: 4px;
905
- text-align: center;
906
- width: 24px;
907
- }
908
-
909
- </style>
910
- <div class="container">
911
- <slot name="video">
912
- <div class="video-wrapper">
913
- <video id="video-preview" muted autoplay></video>
914
- <div id="timer-overlay" class="hidden">00:00</div>
915
- </div>
916
- </slot>
917
- <slot name="phrase-display">
918
- <div class="phrase-display">
919
- ${phraseDisplay}
920
- </div>
921
- </slot>
922
- <slot name="record-button">
923
- <button id="record-button">Start Recording</button>
924
- </slot>
925
- <slot name="stop-button">
926
- <button id="stop-button" disabled>Stop Recording</button>
927
- </slot>
928
- <slot name="file-input">
929
- <input type="file" accept="video/*" id="file-input" />
930
- </slot>
931
- <slot name="submit-button">
932
- <button id="submit-button">Submit Video</button>
933
- </slot>
934
- <slot name="loading">
935
- <div class="message">Loading...</div>
936
- </slot>
937
- <slot name="error">
938
- <div class="message error">An error occurred</div>
939
- </slot>
940
- <slot name="success">
941
- <div class="message success">Video submitted successfully!</div>
942
- </slot>
943
- </div>
944
- `;
945
- this.attachSlotListeners();
946
- this.setupPreview();
947
- this.toggleState(null);
948
- }
949
- attachSlotListeners() {
950
- const videoSlot = this.shadowRoot.querySelector('slot[name="video"]');
951
- const recordButtonSlot = this.shadowRoot.querySelector('slot[name="record-button"]');
952
- const stopButtonSlot = this.shadowRoot.querySelector('slot[name="stop-button"]');
953
- const fileInputSlot = this.shadowRoot.querySelector('slot[name="file-input"]');
954
- const submitButtonSlot = this.shadowRoot.querySelector('slot[name="submit-button"]');
955
- this.videoElement = this.getSlotElement(videoSlot, '#video-preview', HTMLVideoElement);
956
- this.recordButton = this.getSlotElement(recordButtonSlot, '#record-button', HTMLButtonElement);
957
- this.stopButton = this.getSlotElement(stopButtonSlot, '#stop-button', HTMLButtonElement);
958
- this.fileInput = this.getSlotElement(fileInputSlot, '#file-input', HTMLInputElement);
959
- this.submitButton = this.getSlotElement(submitButtonSlot, '#submit-button', HTMLButtonElement);
960
- if (this.fileInput) {
961
- this.fileInput.addEventListener('change', (e) => this.handleFileUpload(e));
962
- }
963
- if (this.recordButton) {
964
- this.recordButton.addEventListener("click", () => this.startRecording());
965
- }
966
- if (this.stopButton) {
967
- this.stopButton.addEventListener("click", () => this.stopRecording());
968
- }
969
- if (this.submitButton) {
970
- this.submitButton.addEventListener("click", () => this.handleSubmit());
971
- }
972
- }
973
- getSlotElement(slot, fallbackSelector, elementType) {
974
- const assignedElements = slot.assignedElements();
975
- return (assignedElements.length > 0 ? assignedElements[0] : null) || this.shadowRoot.querySelector(fallbackSelector);
976
- }
977
- replaceSlotContent(slotName, content) {
978
- const slot = this.shadowRoot.querySelector(`slot[name="${slotName}"]`);
979
- if (slot) {
980
- if (typeof content === 'string') {
981
- slot.innerHTML = content;
982
- }
983
- else if (content instanceof HTMLElement) {
984
- slot.innerHTML = '';
985
- slot.appendChild(content);
986
- }
987
- }
988
- }
989
- removeSlotListener(slotName, event, callback) {
990
- const slot = this.shadowRoot.querySelector(`slot[name="${slotName}"]`);
991
- if (slot) {
992
- const assignedNodes = slot.assignedElements();
993
- assignedNodes.forEach((node) => {
994
- node.removeEventListener(event, callback);
995
- });
996
- }
997
- }
998
- toggleState(state) {
999
- const states = ['loading', 'success', 'error'];
1000
- states.forEach((slotName) => {
1001
- const slot = this.shadowRoot.querySelector(`slot[name="${slotName}"]`);
1002
- if (slot) {
1003
- slot.style.display = slotName === state ? 'block' : 'none';
1004
- }
1005
- });
1006
- }
1007
- convertPhraseToWords(phrase) {
1008
- const digitWords = [
1009
- "zero", "one", "two", "three", "four",
1010
- "five", "six", "seven", "eight", "nine"
1011
- ];
1012
- return phrase
1013
- .split("")
1014
- .map((digit) => digitWords[parseInt(digit, 10)])
1015
- .join(" ");
1016
- }
1017
- async setupPreview() {
1018
- try {
1019
- const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
1020
- this.previewStream = stream;
1021
- this.videoElement.srcObject = stream;
1022
- this.videoElement.controls = false;
1023
- this.videoElement.play();
1024
- }
1025
- catch (error) {
1026
- this.toggleState('error');
1027
- console.error('Error setting up video preview:', error);
1028
- }
1029
- }
1030
- async startTimer() {
1031
- const timerOverlay = this.shadowRoot.querySelector('#timer-overlay');
1032
- timerOverlay.textContent = '00:00';
1033
- timerOverlay.classList.remove('hidden');
1034
- let seconds = 0;
1035
- this.timerInterval = setInterval(() => {
1036
- seconds++;
1037
- const minutes = Math.floor(seconds / 60);
1038
- const remainingSeconds = seconds % 60;
1039
- timerOverlay.textContent = `${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
1040
- }, 1000);
1041
- this.recordingTimeout = setTimeout(() => {
1042
- this.stopRecording();
1043
- }, this.timeLimit * 1000);
1044
- }
1045
- async stopTimer() {
1046
- if (this.recordingTimeout) {
1047
- clearTimeout(this.recordingTimeout);
1048
- }
1049
- if (this.timerInterval) {
1050
- clearInterval(this.timerInterval);
1051
- this.timerInterval = null;
1052
- }
1053
- const timerOverlay = this.shadowRoot.querySelector('#timer-overlay');
1054
- timerOverlay.classList.add('hidden');
1055
- }
1056
- async startRecording() {
1057
- if (!window.MediaRecorder) {
1058
- console.error('MediaRecorder API is not supported in this browser');
1059
- return;
1060
- }
1061
- try {
1062
- if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
1063
- console.log('Recording already in progress.');
1064
- return;
1065
- }
1066
- if (!this.previewStream) {
1067
- console.log('Initializing preview stream...');
1068
- this.previewStream = await navigator.mediaDevices.getUserMedia({
1069
- video: true,
1070
- audio: true,
1071
- });
1072
- }
1073
- this.videoElement.muted = true;
1074
- this.videoElement.srcObject = this.previewStream;
1075
- this.videoElement.currentTime = 0;
1076
- await this.videoElement.play();
1077
- this.mediaRecorder = new MediaRecorder(this.previewStream);
1078
- this.recordedChunks = [];
1079
- this.mediaRecorder.ondataavailable = (event) => {
1080
- if (event.data.size > 0) {
1081
- this.recordedChunks.push(event.data);
1082
- }
1083
- };
1084
- this.mediaRecorder.onstop = () => {
1085
- const duration = Date.now() - this.startTime;
1086
- const buggyBlob = new Blob(this.recordedChunks, { type: 'video/webm' });
1087
- ysFixWebmDuration(buggyBlob, duration, { logger: false })
1088
- .then((fixedBlob) => {
1089
- this.onStopMediaRecorder(fixedBlob);
1090
- });
1091
- };
1092
- this.mediaRecorder.start();
1093
- this.startTimer();
1094
- this.startTime = Date.now();
1095
- this.recordButton.disabled = true;
1096
- this.stopButton.disabled = false;
1097
- this.videoElement.controls = false;
1098
- }
1099
- catch (error) {
1100
- console.error('Error starting video recording:', error);
1101
- }
1102
- }
1103
- stopRecording() {
1104
- try {
1105
- if (!this.mediaRecorder || this.mediaRecorder.state === 'inactive') {
1106
- console.log('No recording in progress.');
1107
- return;
1108
- }
1109
- this.mediaRecorder.stop();
1110
- if (this.previewStream) {
1111
- this.previewStream.getTracks().forEach(track => track.stop());
1112
- }
1113
- this.videoElement.srcObject = null;
1114
- this.videoElement.src = '';
1115
- this.videoElement.controls = false;
1116
- this.recordButton.disabled = false;
1117
- this.stopButton.disabled = true;
1118
- this.mediaRecorder = null;
1119
- this.previewStream = null;
1120
- }
1121
- catch (error) {
1122
- console.error('Error stopping video recording:', error);
1123
- }
1124
- }
1125
- onStopMediaRecorder(blob) {
1126
- const videoURL = URL.createObjectURL(blob);
1127
- this.videoFile = new File([blob], 'recorded_video.webm', { type: 'video/webm' });
1128
- this.recordedChunks = [];
1129
- this.videoElement.src = videoURL;
1130
- this.videoElement.controls = true;
1131
- this.videoElement.play();
1132
- this.videoElement.muted = false;
1133
- this.stopTimer();
1134
- // Clean up the object URL when the video loads
1135
- this.videoElement.onloadeddata = () => {
1136
- URL.revokeObjectURL(videoURL);
1137
- };
1138
- }
1139
- handleFileUpload(event) {
1140
- const file = event.target.files?.[0];
1141
- if (file?.type?.startsWith('video/')) {
1142
- if (file.size > 100 * 1024 * 1024) { // 100MB limit
1143
- this.toggleState('error');
1144
- console.error('File size exceeds limit of 100MB');
1145
- return;
1146
- }
1147
- this.videoFile = file;
1148
- this.videoElement.src = URL.createObjectURL(file);
1149
- this.videoElement.play();
1150
- }
1151
- else {
1152
- this.toggleState('error');
1153
- console.error('Please select a valid video file.');
1154
- }
1155
- }
1156
- async handleSubmit() {
1157
- if (!this.videoFile) {
1158
- this.toggleState('error');
1159
- console.error('No video file to submit.');
1160
- return;
1161
- }
1162
- if (!this.endpoint) {
1163
- this.toggleState('error');
1164
- console.error('Endpoint must be provided.');
1165
- return;
1166
- }
1167
- if (!this.userFullname) {
1168
- this.toggleState('error');
1169
- console.error('User full name must be provided.');
1170
- return;
1171
- }
1172
- this.toggleState('loading');
1173
- try {
1174
- const formData = new FormData();
1175
- formData.append('video', this.videoFile);
1176
- formData.append('phrase', this.convertPhraseToWords(this.phrase));
1177
- formData.append('userFullname', this.userFullname);
1178
- const response = await fetch(this.endpoint, {
1179
- method: 'POST',
1180
- body: formData
1181
- });
1182
- if (!response.ok) {
1183
- throw new Error(`HTTP error! status: ${response.status}`);
1184
- }
1185
- const result = await response.json();
1186
- console.log('Response from endpoint:', result);
1187
- this.toggleState('success');
1188
- }
1189
- catch (error) {
1190
- this.toggleState('error');
1191
- console.error('Error submitting video:', error);
1192
- }
1193
- }
1194
- static get observedAttributes() {
1195
- return ['endpoint', 'user-fullname'];
1196
- }
1197
- get userFullname() {
1198
- return this.getAttribute('user-fullname');
1199
- }
1200
- set userFullname(value) {
1201
- if (value) {
1202
- this.setAttribute('user-fullname', value);
1203
- }
1204
- else {
1205
- this.removeAttribute('user-fullname');
1206
- }
1207
- }
1208
- get isRecording() {
1209
- return this.mediaRecorder?.state === 'recording';
1210
- }
1211
- get currentPhrase() {
1212
- return this.phrase;
1213
- }
1214
- get videoDuration() {
1215
- return this.videoElement?.duration || null;
1216
- }
1217
- get currentFile() {
1218
- return this.videoFile;
1219
- }
1220
- get currentStream() {
1221
- return this.previewStream;
1222
- }
1223
- get videoElementRef() {
1224
- return this.videoElement;
1225
- }
1226
- get fileInputRef() {
1227
- return this.fileInput;
1228
- }
1229
- get recordingTimeLimit() {
1230
- return this.timeLimit;
1231
- }
1232
- set recordingTimeLimit(value) {
1233
- this.timeLimit = value;
1234
- if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {
1235
- if (this.recordingTimeout) {
1236
- clearTimeout(this.recordingTimeout);
1237
- }
1238
- this.recordingTimeout = setTimeout(() => this.stopRecording(), this.timeLimit * 1000);
1239
- }
1240
- }
1241
- }
1242
- customElements.define('process-video', ProcessVideo);
1243
-
1244
- class DocAuth extends HTMLElement {
1245
- constructor() {
1246
- super();
1247
- this.videoElement = null;
1248
- this.canvasElement = null;
1249
- this.captureButton = null;
1250
- this.shadow = this.attachShadow({ mode: "open" });
1251
- this.captureDocument = this.captureDocument.bind(this);
1252
- }
1253
- static get observedAttributes() {
1254
- return ["endpoint", "user-fullname"];
1255
- }
1256
- get endpoint() {
1257
- return this.getAttribute("endpoint");
1258
- }
1259
- set endpoint(value) {
1260
- if (value !== this.getAttribute("endpoint")) {
1261
- if (value === null) {
1262
- this.removeAttribute("endpoint");
1263
- }
1264
- else {
1265
- this.setAttribute("endpoint", value);
1266
- }
1267
- }
1268
- }
1269
- get userFullname() {
1270
- return this.getAttribute("user-fullname");
1271
- }
1272
- set userFullname(value) {
1273
- if (value !== this.getAttribute("user-fullname")) {
1274
- if (value === null) {
1275
- this.removeAttribute("user-fullname");
1276
- }
1277
- else {
1278
- this.setAttribute("user-fullname", value);
1279
- }
1280
- }
1281
- }
1282
- attributeChangedCallback(name, oldValue, newValue) {
1283
- if (name === "endpoint" || name === "user-fullname") {
1284
- this.validateAttributes();
1285
- }
1286
- }
1287
- connectedCallback() {
1288
- this.validateAttributes();
1289
- this.init();
1290
- }
1291
- disconnectedCallback() {
1292
- this.cleanup();
1293
- if (this.captureButton) {
1294
- this.captureButton.removeEventListener("click", this.captureDocument);
1295
- }
1296
- }
1297
- validateAttributes() {
1298
- if (!this.endpoint) {
1299
- console.error("Endpoint is required.");
1300
- this.toggleState("error");
1301
- return;
1302
- }
1303
- if (!this.userFullname) {
1304
- console.error("User fullname is required.");
1305
- this.toggleState("error");
1306
- return;
1307
- }
1308
- }
1309
- init() {
1310
- this.shadow.innerHTML = `
1311
- <style>
1312
- :host {
1313
- display: block;
1314
- font-family: Arial, sans-serif;
1315
- --primary-color: #007bff;
1316
- --button-bg: var(--primary-color);
1317
- --button-text-color: #fff;
1318
- --border-radius: 8px;
1319
- --scan-area-border: 2px dashed var(--primary-color);
1320
- --scan-area-bg: rgba(0, 123, 255, 0.05);
1321
- --button-padding: 10px 24px;
1322
- --button-radius: 6px;
1323
- --button-hover-bg: #0056b3;
1324
- }
1325
- .container {
1326
- display: flex;
1327
- flex-direction: column;
1328
- align-items: center;
1329
- gap: 20px;
1330
- padding: 24px;
1331
- background: #fff;
1332
- border-radius: var(--border-radius);
1333
- box-shadow: 0 2px 8px rgba(0,0,0,0.04);
1334
- max-width: 420px;
1335
- margin: 0 auto;
1336
- }
1337
- .scan-area {
1338
- position: relative;
1339
- width: 340px;
1340
- height: 220px;
1341
- background: var(--scan-area-bg);
1342
- border: var(--scan-area-border);
1343
- border-radius: var(--border-radius);
1344
- overflow: hidden;
1345
- display: flex;
1346
- align-items: center;
1347
- justify-content: center;
1348
- }
1349
- video {
1350
- width: 100%;
1351
- height: 100%;
1352
- object-fit: cover;
1353
- border-radius: var(--border-radius);
1354
- }
1355
- canvas {
1356
- display: none;
1357
- }
1358
- .capture-btn {
1359
- background: var(--button-bg);
1360
- color: var(--button-text-color);
1361
- border: none;
1362
- border-radius: var(--button-radius);
1363
- padding: var(--button-padding);
1364
- font-size: 1rem;
1365
- cursor: pointer;
1366
- transition: background 0.2s;
1367
- }
1368
- .capture-btn:hover {
1369
- background: var(--button-hover-bg);
1370
- }
1371
- .status {
1372
- min-height: 24px;
1373
- font-size: 0.95rem;
1374
- color: #d32f2f;
1375
- text-align: center;
1376
- }
1377
- .status.success {
1378
- color: #388e3c;
1379
- }
1380
- </style>
1381
- <div class="container">
1382
- <div class="scan-area">
1383
- <video id="video" autoplay playsinline></video>
1384
- <canvas id="canvas"></canvas>
1385
- </div>
1386
- <button class="capture-btn" id="capture-btn">Capture Document</button>
1387
- <div class="status" id="status"></div>
1388
- </div>
1389
- `;
1390
- this.attachElements();
1391
- this.setupCamera();
1392
- this.toggleState("");
1393
- }
1394
- cleanup() {
1395
- if (this.videoElement?.srcObject) {
1396
- const tracks = this.videoElement.srcObject.getTracks();
1397
- tracks.forEach((track) => track.stop());
1398
- }
1399
- if (this.videoElement) {
1400
- this.videoElement.srcObject = null;
1401
- }
1402
- }
1403
- attachElements() {
1404
- this.videoElement = this.shadow.querySelector("#video");
1405
- this.canvasElement = this.shadow.querySelector("#canvas");
1406
- this.captureButton = this.shadow.querySelector("#capture-btn");
1407
- if (this.captureButton) {
1408
- this.captureButton.addEventListener("click", this.captureDocument);
1409
- }
1410
- }
1411
- setupCamera() {
1412
- if (!this.videoElement) {
1413
- console.error("Video element is missing.");
1414
- return;
1415
- }
1416
- navigator.mediaDevices
1417
- .getUserMedia({ video: { facingMode: "environment" } })
1418
- .then((stream) => {
1419
- this.videoElement.srcObject = stream;
1420
- })
1421
- .catch((error) => {
1422
- this.toggleState("error");
1423
- this.setStatus("Error accessing camera: " + error, false);
1424
- });
1425
- }
1426
- async captureDocument() {
1427
- if (!this.videoElement || !this.canvasElement) {
1428
- this.setStatus("Camera not ready.", false);
1429
- return;
1430
- }
1431
- // Set canvas size to video size for high quality
1432
- this.canvasElement.width = this.videoElement.videoWidth;
1433
- this.canvasElement.height = this.videoElement.videoHeight;
1434
- const context = this.canvasElement.getContext("2d");
1435
- context.drawImage(this.videoElement, 0, 0, this.canvasElement.width, this.canvasElement.height);
1436
- this.toggleState("loading");
1437
- this.setStatus("Uploading...", false);
1438
- this.canvasElement.toBlob(async (blob) => {
1439
- try {
1440
- if (!blob) {
1441
- this.setStatus("Failed to capture image.", false);
1442
- this.toggleState("error");
1443
- return;
1444
- }
1445
- const file = new File([blob], "id-document.jpg", { type: "image/jpeg" });
1446
- const formData = new FormData();
1447
- formData.append("document", file);
1448
- formData.append("userFullname", this.userFullname || "");
1449
- const response = await fetch(this.endpoint, {
1450
- method: "POST",
1451
- body: formData,
1452
- });
1453
- if (!response.ok) {
1454
- throw new Error(`HTTP error! status: ${response.status}`);
1455
- }
1456
- this.setStatus("Document uploaded successfully!", true);
1457
- this.toggleState("success");
1458
- }
1459
- catch (error) {
1460
- this.setStatus("Error uploading document.", false);
1461
- this.toggleState("error");
1462
- }
1463
- }, "image/jpeg", 0.98); // High quality JPEG
1464
- }
1465
- setStatus(message, success) {
1466
- const statusDiv = this.shadow.querySelector("#status");
1467
- if (statusDiv) {
1468
- statusDiv.textContent = message;
1469
- statusDiv.className = "status" + (success ? " success" : "");
1470
- }
1471
- }
1472
- toggleState(state) {
1473
- // For extensibility, could add more UI feedback here
1474
- // Currently just updates status color
1475
- }
1476
- }
1477
- customElements.define("doc-auth", DocAuth);
1478
-
1479
- export { BiometryEnrollment, DocAuth, ProcessVideo };
1480
- //# sourceMappingURL=ui.js.map