biometry-sdk 1.2.1 → 1.2.2
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/README.md +77 -52
- package/dist/sdk.js +1 -1
- package/package.json +1 -1
- package/dist/components/process-video.d.ts +0 -51
- package/dist/components/process-video.js +0 -452
package/README.md
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
# biometry-sdk
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
|
-
The **Biometry Web SDK** is a software development kit
|
|
4
|
+
The **Biometry Web SDK** is a software development kit designed to simplify the integration of Biometry's API services into your web application. Providing tools, UI components, and utilities enables biometric onboarding (face and voice), liveness checks, and user consent.
|
|
5
|
+
|
|
5
6
|
|
|
6
7
|
## Features
|
|
7
|
-
- **Consent
|
|
8
|
-
-
|
|
8
|
+
- **Consent Management**:
|
|
9
|
+
- Ask a permission to store their biometric data for authentication using Biometry.
|
|
10
|
+
- Collect user permission to store their biometric data for authentication using Biometry.
|
|
11
|
+
- Important: You must obtain consent before performing any onboarding or video processing.
|
|
12
|
+
- **Voice onboarding**:
|
|
13
|
+
- Enroll a user’s voice, creating a voice model for future authentication.
|
|
9
14
|
- **Face onboarding**: Onboard face for face recognition.
|
|
10
|
-
-
|
|
11
|
-
- **Face
|
|
12
|
-
- **
|
|
13
|
-
-
|
|
15
|
+
- Onboard a user’s face for facial recognition.
|
|
16
|
+
- **Face Onboarding UI Component:** A ready-to-use, customizable component for capturing and processing face data.
|
|
17
|
+
- **Face match**:
|
|
18
|
+
- Compares an extracted image from a user’s personal document to an image frame captured during onboarding or `/process-video.`
|
|
19
|
+
- **Process video**:
|
|
20
|
+
- Checks user liveness and authorizes users based on video input.
|
|
21
|
+
- **Process Video UI Component:** A ready-to-use, customizable component for capturing and processing video.
|
|
22
|
+
|
|
14
23
|
|
|
15
24
|
## Installation
|
|
16
25
|
```bash
|
|
@@ -33,31 +42,30 @@ console.log(response);
|
|
|
33
42
|
|
|
34
43
|
## Example
|
|
35
44
|
|
|
36
|
-
You can find an example in the example/ directory. The example demonstrates how
|
|
37
|
-
|
|
45
|
+
You can find an example in the example/ directory. The example demonstrates how you might integrate the BiometrySDK in a React component with the state.
|
|
38
46
|
## UI Components
|
|
39
|
-
The
|
|
47
|
+
The Biometry Web SDK includes reusable, customizable web components for crucial features. These components are easy to embed into your application and handle the most common biometric operations with minimal setup.
|
|
40
48
|
|
|
41
49
|
### Face Onboarding Component
|
|
42
|
-
|
|
50
|
+
This component provides an intuitive interface for onboarding users with their cameras. It integrates directly with the `BiometrySDK backend`, managing camera capture, consent checks, and error handling.
|
|
43
51
|
|
|
44
52
|
### Integration
|
|
45
53
|
Here's how to integrate the `Face Onboarding` component into your application:
|
|
46
54
|
|
|
47
55
|
**Option 1: Using npm (Recommended for full SDK usage)**
|
|
48
56
|
1. Install the SDK package via **npm**:
|
|
49
|
-
```bash
|
|
50
|
-
npm install biometry-sdk
|
|
51
|
-
```
|
|
57
|
+
```bash
|
|
58
|
+
npm install biometry-sdk
|
|
59
|
+
```
|
|
52
60
|
2. Import the component in your **index.js** or equivalent JavaScript file:
|
|
53
|
-
```javascript
|
|
54
|
-
// index.js
|
|
55
|
-
import './node_modules/biometry-sdk/dist/components/biometry-onboarding.js';
|
|
56
|
-
```
|
|
61
|
+
```javascript
|
|
62
|
+
// index.js
|
|
63
|
+
import './node_modules/biometry-sdk/dist/components/biometry-onboarding.js';
|
|
64
|
+
```
|
|
57
65
|
3. Connect the script to your **HTML file** and use the component:
|
|
58
|
-
```html
|
|
59
|
-
<script type="module" src="./index.js"></script>
|
|
60
|
-
```
|
|
66
|
+
```html
|
|
67
|
+
<script type="module" src="./index.js"></script>
|
|
68
|
+
```
|
|
61
69
|
|
|
62
70
|
|
|
63
71
|
**Option 2: Using CDN (Quick Integration)**
|
|
@@ -66,9 +74,14 @@ import './node_modules/biometry-sdk/dist/components/biometry-onboarding.js';
|
|
|
66
74
|
```
|
|
67
75
|
|
|
68
76
|
### Usage
|
|
69
|
-
|
|
77
|
+
**Required attributes:**
|
|
78
|
+
- `api-key`: Your Biometry API key.
|
|
79
|
+
- `user-fullname`: The user’s full name (used in data storage and consent).
|
|
70
80
|
|
|
71
|
-
|
|
81
|
+
**Slots:**
|
|
82
|
+
- `video`: Your custom <video> element.
|
|
83
|
+
- `button`: Custom capture button.
|
|
84
|
+
- `loading`, `success`, `error-no-face`, `error-multiple-faces`, `error-not-centered`, `error-other`: Custom UI messages for different states.
|
|
72
85
|
|
|
73
86
|
**Basic Usage**
|
|
74
87
|
```html
|
|
@@ -98,38 +111,31 @@ Custom slots allow you to style and customize UI elements, loading, success, and
|
|
|
98
111
|
```
|
|
99
112
|
|
|
100
113
|
### Process Video Component
|
|
101
|
-
The Process Video component
|
|
114
|
+
The **Process Video** component enables you to record, upload, and process a video within your application. It integrates with Biometry's services to check liveness and authorize the user.
|
|
102
115
|
|
|
103
116
|
### Integration
|
|
104
117
|
**Option 1: Install via npm**
|
|
105
|
-
|
|
106
118
|
1. To include the component in your project, install the biometry-sdk package:
|
|
107
|
-
```bash
|
|
108
|
-
npm install biometry-sdk
|
|
109
|
-
```
|
|
110
|
-
|
|
119
|
+
```bash
|
|
120
|
+
npm install biometry-sdk
|
|
121
|
+
```
|
|
111
122
|
2. After installation, import the component into your project:
|
|
112
|
-
```javascript
|
|
113
|
-
// index.js
|
|
114
|
-
import './node_modules/biometry-sdk/dist/components/process-video.js'
|
|
115
|
-
```
|
|
116
|
-
|
|
123
|
+
```javascript
|
|
124
|
+
// index.js
|
|
125
|
+
import './node_modules/biometry-sdk/dist/components/process-video.js'
|
|
126
|
+
```
|
|
117
127
|
3. Include the component in your HTML:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
**Option 2:
|
|
124
|
-
|
|
125
|
-
You can skip the npm installation and include the component directly in your HTML:
|
|
128
|
+
You can skip the npm installation and include the component directly in your HTML:
|
|
129
|
+
```html
|
|
130
|
+
<script type="module" src="./index.js"></script>
|
|
131
|
+
<process-video ...></process-video>
|
|
132
|
+
```
|
|
133
|
+
**Option 2: Using CDN (Quick Integration)**
|
|
126
134
|
```html
|
|
127
135
|
<script type="module" src="https://cdn.jsdelivr.net/npm/biometry-sdk/dist/components/process-video.js"></script>
|
|
128
136
|
<process-video ...></process-video>
|
|
129
137
|
```
|
|
130
|
-
|
|
131
138
|
### Usage
|
|
132
|
-
|
|
133
139
|
**Basic Usage**
|
|
134
140
|
```html
|
|
135
141
|
<process-video
|
|
@@ -163,16 +169,35 @@ You can skip the npm installation and include the component directly in your HTM
|
|
|
163
169
|
<div slot="success">Video submitted successfully!</div>
|
|
164
170
|
</process-video>
|
|
165
171
|
```
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
+
**Note:**
|
|
173
|
+
- All default elements and messages are functional out-of-the-box.
|
|
174
|
+
- Replace slots if you want to customize the UI or functionality.
|
|
175
|
+
- Call giveConsent() before using any biometric methods to ensure compliance with data processing requirements.
|
|
176
|
+
|
|
177
|
+
## Best Practices
|
|
178
|
+
1. **Always Acquire Consent**
|
|
179
|
+
- Before performing Face Onboarding or Process Video, you can call:
|
|
180
|
+
```javascript
|
|
181
|
+
sdk.giveConsent(true, userFullName);
|
|
182
|
+
```
|
|
183
|
+
- Or directly send a request to the `/consent` in the [official documentation](https://developer.biometrysolutions.com/overview/).
|
|
184
|
+
|
|
185
|
+
This ensures legal compliance and user awareness when storing and processing biometric data.
|
|
186
|
+
3. **Handle Errors Gracefully**
|
|
187
|
+
- The SDK methods throw errors if something goes wrong (e.g., network, permission, or detection errors). Use try/catch or .catch() to handle them.
|
|
188
|
+
4. **Security**
|
|
189
|
+
- Protect your API key. Avoid exposing it in public repositories or client-side code if possible.
|
|
190
|
+
|
|
172
191
|
## License
|
|
173
192
|
|
|
174
193
|
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
|
|
175
194
|
|
|
176
195
|
## More Information
|
|
177
|
-
|
|
178
|
-
|
|
196
|
+
For more detailed information on Biometry’s API endpoints, parameters, and responses, visit the official [Biometry API Documentation](https://developer.biometrysolutions.com/overview/). If you have questions or need help, please reach out to our support team or create a GitHub issue.
|
|
197
|
+
|
|
198
|
+
## Quick Reference
|
|
199
|
+
- Install: `npm install biometry-sdk`
|
|
200
|
+
- Consent: `sdk.giveConsent(true, userFullName)` (Required before onboarding/processing)
|
|
201
|
+
- Voice Onboarding: `sdk.enrollVoice(file, userFullName)`
|
|
202
|
+
- Face Onboarding: `sdk.enrollFace(file, userFullName)`
|
|
203
|
+
- Process Video: `sdk.processVideo(file, phrase, userFullName)`
|
package/dist/sdk.js
CHANGED
|
@@ -42,7 +42,7 @@ export class BiometrySDK {
|
|
|
42
42
|
is_consent_given: isConsentGiven,
|
|
43
43
|
user_fullname: userFullName,
|
|
44
44
|
};
|
|
45
|
-
const response = await this.request('/consent', 'POST', body);
|
|
45
|
+
const response = await this.request('/api-consent/consent', 'POST', body);
|
|
46
46
|
return {
|
|
47
47
|
is_consent_given: response.is_consent_given,
|
|
48
48
|
user_fullname: response.user_fullname,
|
package/package.json
CHANGED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
export declare class ProcessVideoComponent extends HTMLElement {
|
|
2
|
-
private sdk;
|
|
3
|
-
private apiKey;
|
|
4
|
-
private phrase;
|
|
5
|
-
private previewStream;
|
|
6
|
-
private recordedChunks;
|
|
7
|
-
private mediaRecorder;
|
|
8
|
-
private videoFile;
|
|
9
|
-
private timerInterval;
|
|
10
|
-
private recordingTimeout;
|
|
11
|
-
private videoElement;
|
|
12
|
-
private fileInput;
|
|
13
|
-
private recordButton;
|
|
14
|
-
private stopButton;
|
|
15
|
-
private submitButton;
|
|
16
|
-
private errorState;
|
|
17
|
-
private timeLimit;
|
|
18
|
-
constructor();
|
|
19
|
-
private initializeSDK;
|
|
20
|
-
connectedCallback(): void;
|
|
21
|
-
disconnectedCallback(): void;
|
|
22
|
-
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
23
|
-
private generateDefaultPhrase;
|
|
24
|
-
initializeUI(): void;
|
|
25
|
-
private attachSlotListeners;
|
|
26
|
-
private getSlotElement;
|
|
27
|
-
replaceSlotContent(slotName: string, content: string | HTMLElement): void;
|
|
28
|
-
removeSlotListener(slotName: string, event: string, callback: EventListener): void;
|
|
29
|
-
private toggleState;
|
|
30
|
-
private convertPhraseToWords;
|
|
31
|
-
private setupPreview;
|
|
32
|
-
private startTimer;
|
|
33
|
-
private stopTimer;
|
|
34
|
-
startRecording(): Promise<void>;
|
|
35
|
-
stopRecording(): void;
|
|
36
|
-
private handleFileUpload;
|
|
37
|
-
handleSubmit(): Promise<void>;
|
|
38
|
-
static get observedAttributes(): string[];
|
|
39
|
-
get userFullname(): string | null;
|
|
40
|
-
set userFullname(value: string | null);
|
|
41
|
-
get isRecording(): boolean;
|
|
42
|
-
get currentPhrase(): string;
|
|
43
|
-
get videoDuration(): number | null;
|
|
44
|
-
get currentFile(): File | null;
|
|
45
|
-
get currentStream(): MediaStream | null;
|
|
46
|
-
set sdkInstance(newSdk: any);
|
|
47
|
-
get videoElementRef(): HTMLVideoElement;
|
|
48
|
-
get fileInputRef(): HTMLInputElement;
|
|
49
|
-
get recordingTimeLimit(): number;
|
|
50
|
-
set recordingTimeLimit(value: number);
|
|
51
|
-
}
|
|
@@ -1,452 +0,0 @@
|
|
|
1
|
-
import { BiometrySDK } from "../sdk.js";
|
|
2
|
-
export class ProcessVideoComponent extends HTMLElement {
|
|
3
|
-
constructor() {
|
|
4
|
-
super();
|
|
5
|
-
this.apiKey = null;
|
|
6
|
-
this.previewStream = null;
|
|
7
|
-
this.recordedChunks = [];
|
|
8
|
-
this.mediaRecorder = null;
|
|
9
|
-
this.videoFile = null;
|
|
10
|
-
this.timerInterval = null;
|
|
11
|
-
this.recordingTimeout = null;
|
|
12
|
-
this.errorState = null;
|
|
13
|
-
this.timeLimit = 30;
|
|
14
|
-
this.phrase = this.generateDefaultPhrase();
|
|
15
|
-
// Attach shadow DOM and initialize UI
|
|
16
|
-
this.attachShadow({ mode: 'open' });
|
|
17
|
-
this.apiKey = this.getAttribute('api-key');
|
|
18
|
-
this.initializeSDK();
|
|
19
|
-
this.initializeUI();
|
|
20
|
-
}
|
|
21
|
-
initializeSDK() {
|
|
22
|
-
if (this.apiKey) {
|
|
23
|
-
this.sdk = new BiometrySDK(this.apiKey);
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
this.toggleState('error');
|
|
27
|
-
console.error('API key is required to initialize the SDK.');
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
connectedCallback() {
|
|
31
|
-
if (this.apiKey) {
|
|
32
|
-
this.initializeSDK();
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
console.error('API key is required.');
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
disconnectedCallback() {
|
|
39
|
-
this.stopRecording();
|
|
40
|
-
if (this.previewStream) {
|
|
41
|
-
this.previewStream.getTracks().forEach(track => track.stop());
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
45
|
-
if (name === 'api-key' && newValue !== oldValue) {
|
|
46
|
-
this.apiKey = newValue;
|
|
47
|
-
this.initializeSDK();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
generateDefaultPhrase() {
|
|
51
|
-
return Math.random().toString().slice(2, 10); // 8-digit random phrase
|
|
52
|
-
}
|
|
53
|
-
initializeUI() {
|
|
54
|
-
const phraseDisplay = this.phrase
|
|
55
|
-
.split("")
|
|
56
|
-
.map((digit) => `<span class="digit">${digit}</span>`)
|
|
57
|
-
.join(" ");
|
|
58
|
-
this.shadowRoot.innerHTML = `
|
|
59
|
-
<style>
|
|
60
|
-
:host {
|
|
61
|
-
display: block;
|
|
62
|
-
font-family: Arial, sans-serif;
|
|
63
|
-
--primary-color: #007bff;
|
|
64
|
-
--secondary-color: #6c757d;
|
|
65
|
-
--button-bg: var(--primary-color);
|
|
66
|
-
--button-text-color: #fff;
|
|
67
|
-
--input-border-color: var(--secondary-color);
|
|
68
|
-
--input-focus-border-color: var(--primary-color);
|
|
69
|
-
--spacing: 16px;
|
|
70
|
-
--button-padding: 10px 20px;
|
|
71
|
-
--border-radius: 4px;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.container {
|
|
75
|
-
display: flex;
|
|
76
|
-
flex-direction: column;
|
|
77
|
-
align-items: center;
|
|
78
|
-
justify-content: center;
|
|
79
|
-
gap: var(--spacing);
|
|
80
|
-
padding: var(--spacing);
|
|
81
|
-
max-width: 500px;
|
|
82
|
-
margin: 0 auto;
|
|
83
|
-
text-align: center;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.video-wrapper {
|
|
87
|
-
position: relative;
|
|
88
|
-
display: inline-block;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
#timer-overlay {
|
|
92
|
-
position: absolute;
|
|
93
|
-
top: 10px;
|
|
94
|
-
left: 10px;
|
|
95
|
-
background-color: rgba(0, 0, 0, 0.7);
|
|
96
|
-
color: white;
|
|
97
|
-
padding: 5px 10px;
|
|
98
|
-
border-radius: 4px;
|
|
99
|
-
font-family: Arial, sans-serif;
|
|
100
|
-
font-size: 14px;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
video {
|
|
104
|
-
max-width: 100%;
|
|
105
|
-
border-radius: var(--border-radius);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
input[type="text"], input[type="file"] {
|
|
109
|
-
padding: var(--button-padding);
|
|
110
|
-
border: 1px solid var(--input-border-color);
|
|
111
|
-
border-radius: var(--border-radius);
|
|
112
|
-
width: 100%;
|
|
113
|
-
max-width: 100%;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
input[type="text"]:focus, input[type="file"]:focus {
|
|
117
|
-
outline: none;
|
|
118
|
-
border-color: var(--input-focus-border-color);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.hidden {
|
|
122
|
-
display: none;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.phrase-display {
|
|
126
|
-
font-size: 24px;
|
|
127
|
-
font-weight: bold;
|
|
128
|
-
display: flex;
|
|
129
|
-
gap: 8px;
|
|
130
|
-
justify-content: center;
|
|
131
|
-
}
|
|
132
|
-
.digit {
|
|
133
|
-
padding: 4px;
|
|
134
|
-
border: 1px solid #ccc;
|
|
135
|
-
border-radius: 4px;
|
|
136
|
-
text-align: center;
|
|
137
|
-
width: 24px;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
</style>
|
|
141
|
-
<div class="container">
|
|
142
|
-
<slot name="video">
|
|
143
|
-
<div class="video-wrapper">
|
|
144
|
-
<video id="video-preview" muted autoplay></video>
|
|
145
|
-
<div id="timer-overlay" class="hidden">00:00</div>
|
|
146
|
-
</div>
|
|
147
|
-
</slot>
|
|
148
|
-
<slot name="phrase-display">
|
|
149
|
-
<div class="phrase-display">
|
|
150
|
-
${phraseDisplay}
|
|
151
|
-
</div>
|
|
152
|
-
</slot>
|
|
153
|
-
<slot name="record-button">
|
|
154
|
-
<button id="record-button">Start Recording</button>
|
|
155
|
-
</slot>
|
|
156
|
-
<slot name="stop-button">
|
|
157
|
-
<button id="stop-button" disabled>Stop Recording</button>
|
|
158
|
-
</slot>
|
|
159
|
-
<slot name="file-input">
|
|
160
|
-
<input type="file" accept="video/*" id="file-input" />
|
|
161
|
-
</slot>
|
|
162
|
-
<slot name="submit-button">
|
|
163
|
-
<button id="submit-button">Submit Video</button>
|
|
164
|
-
</slot>
|
|
165
|
-
<slot name="loading">
|
|
166
|
-
<div class="message">Loading...</div>
|
|
167
|
-
</slot>
|
|
168
|
-
<slot name="error">
|
|
169
|
-
<div class="message error">An error occurred</div>
|
|
170
|
-
</slot>
|
|
171
|
-
<slot name="success">
|
|
172
|
-
<div class="message success">Video submitted successfully!</div>
|
|
173
|
-
</slot>
|
|
174
|
-
</div>
|
|
175
|
-
`;
|
|
176
|
-
this.attachSlotListeners();
|
|
177
|
-
this.setupPreview();
|
|
178
|
-
this.toggleState(null);
|
|
179
|
-
}
|
|
180
|
-
attachSlotListeners() {
|
|
181
|
-
const videoSlot = this.shadowRoot.querySelector('slot[name="video"]');
|
|
182
|
-
const recordButtonSlot = this.shadowRoot.querySelector('slot[name="record-button"]');
|
|
183
|
-
const stopButtonSlot = this.shadowRoot.querySelector('slot[name="stop-button"]');
|
|
184
|
-
const fileInputSlot = this.shadowRoot.querySelector('slot[name="file-input"]');
|
|
185
|
-
const submitButtonSlot = this.shadowRoot.querySelector('slot[name="submit-button"]');
|
|
186
|
-
this.videoElement = this.getSlotElement(videoSlot, '#video-preview', HTMLVideoElement);
|
|
187
|
-
this.recordButton = this.getSlotElement(recordButtonSlot, '#record-button', HTMLButtonElement);
|
|
188
|
-
this.stopButton = this.getSlotElement(stopButtonSlot, '#stop-button', HTMLButtonElement);
|
|
189
|
-
this.fileInput = this.getSlotElement(fileInputSlot, '#file-input', HTMLInputElement);
|
|
190
|
-
this.submitButton = this.getSlotElement(submitButtonSlot, '#submit-button', HTMLButtonElement);
|
|
191
|
-
if (this.fileInput) {
|
|
192
|
-
this.fileInput.addEventListener('change', (e) => this.handleFileUpload(e));
|
|
193
|
-
}
|
|
194
|
-
if (this.recordButton) {
|
|
195
|
-
this.recordButton.addEventListener("click", () => this.startRecording());
|
|
196
|
-
}
|
|
197
|
-
if (this.stopButton) {
|
|
198
|
-
this.stopButton.addEventListener("click", () => this.stopRecording());
|
|
199
|
-
}
|
|
200
|
-
if (this.submitButton) {
|
|
201
|
-
this.submitButton.addEventListener("click", () => this.handleSubmit());
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
getSlotElement(slot, fallbackSelector, elementType) {
|
|
205
|
-
const assignedElements = slot.assignedElements();
|
|
206
|
-
return (assignedElements.length > 0 ? assignedElements[0] : null) || this.shadowRoot.querySelector(fallbackSelector);
|
|
207
|
-
}
|
|
208
|
-
replaceSlotContent(slotName, content) {
|
|
209
|
-
const slot = this.shadowRoot.querySelector(`slot[name="${slotName}"]`);
|
|
210
|
-
if (slot) {
|
|
211
|
-
if (typeof content === 'string') {
|
|
212
|
-
slot.innerHTML = content;
|
|
213
|
-
}
|
|
214
|
-
else if (content instanceof HTMLElement) {
|
|
215
|
-
slot.innerHTML = '';
|
|
216
|
-
slot.appendChild(content);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
removeSlotListener(slotName, event, callback) {
|
|
221
|
-
const slot = this.shadowRoot.querySelector(`slot[name="${slotName}"]`);
|
|
222
|
-
if (slot) {
|
|
223
|
-
const assignedNodes = slot.assignedElements();
|
|
224
|
-
assignedNodes.forEach((node) => {
|
|
225
|
-
node.removeEventListener(event, callback);
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
toggleState(state) {
|
|
230
|
-
const states = ['loading', 'success', 'error'];
|
|
231
|
-
states.forEach((slotName) => {
|
|
232
|
-
const slot = this.shadowRoot.querySelector(`slot[name="${slotName}"]`);
|
|
233
|
-
if (slot) {
|
|
234
|
-
slot.style.display = slotName === state ? 'block' : 'none';
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
convertPhraseToWords(phrase) {
|
|
239
|
-
const digitWords = [
|
|
240
|
-
"zero", "one", "two", "three", "four",
|
|
241
|
-
"five", "six", "seven", "eight", "nine"
|
|
242
|
-
];
|
|
243
|
-
return phrase
|
|
244
|
-
.split("")
|
|
245
|
-
.map((digit) => digitWords[parseInt(digit, 10)])
|
|
246
|
-
.join(" ");
|
|
247
|
-
}
|
|
248
|
-
async setupPreview() {
|
|
249
|
-
try {
|
|
250
|
-
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
251
|
-
this.previewStream = stream;
|
|
252
|
-
this.videoElement.srcObject = stream;
|
|
253
|
-
this.videoElement.controls = false;
|
|
254
|
-
this.videoElement.play();
|
|
255
|
-
}
|
|
256
|
-
catch (error) {
|
|
257
|
-
this.toggleState('error');
|
|
258
|
-
console.error('Error setting up video preview:', error);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
async startTimer() {
|
|
262
|
-
const timerOverlay = this.shadowRoot.querySelector('#timer-overlay');
|
|
263
|
-
timerOverlay.textContent = '00:00';
|
|
264
|
-
timerOverlay.classList.remove('hidden');
|
|
265
|
-
let seconds = 0;
|
|
266
|
-
this.timerInterval = setInterval(() => {
|
|
267
|
-
seconds++;
|
|
268
|
-
const minutes = Math.floor(seconds / 60);
|
|
269
|
-
const remainingSeconds = seconds % 60;
|
|
270
|
-
timerOverlay.textContent = `${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
|
|
271
|
-
}, 1000);
|
|
272
|
-
this.recordingTimeout = setTimeout(() => {
|
|
273
|
-
this.stopRecording();
|
|
274
|
-
}, this.timeLimit * 1000);
|
|
275
|
-
}
|
|
276
|
-
async stopTimer() {
|
|
277
|
-
if (this.recordingTimeout) {
|
|
278
|
-
clearTimeout(this.recordingTimeout);
|
|
279
|
-
}
|
|
280
|
-
if (this.timerInterval) {
|
|
281
|
-
clearInterval(this.timerInterval);
|
|
282
|
-
this.timerInterval = null;
|
|
283
|
-
}
|
|
284
|
-
const timerOverlay = this.shadowRoot.querySelector('#timer-overlay');
|
|
285
|
-
timerOverlay.classList.add('hidden');
|
|
286
|
-
}
|
|
287
|
-
async startRecording() {
|
|
288
|
-
if (!window.MediaRecorder) {
|
|
289
|
-
console.error('MediaRecorder API is not supported in this browser');
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
try {
|
|
293
|
-
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
|
|
294
|
-
console.log('Recording already in progress.');
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
if (!this.previewStream) {
|
|
298
|
-
console.log('Initializing preview stream...');
|
|
299
|
-
this.previewStream = await navigator.mediaDevices.getUserMedia({
|
|
300
|
-
video: true,
|
|
301
|
-
audio: true,
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
this.videoElement.muted = true;
|
|
305
|
-
this.videoElement.srcObject = this.previewStream;
|
|
306
|
-
this.videoElement.currentTime = 0;
|
|
307
|
-
await this.videoElement.play();
|
|
308
|
-
this.mediaRecorder = new MediaRecorder(this.previewStream);
|
|
309
|
-
this.recordedChunks = [];
|
|
310
|
-
this.mediaRecorder.ondataavailable = (event) => {
|
|
311
|
-
if (event.data.size > 0) {
|
|
312
|
-
this.recordedChunks.push(event.data);
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
this.mediaRecorder.onstop = () => {
|
|
316
|
-
const blob = new Blob(this.recordedChunks, { type: 'video/webm' });
|
|
317
|
-
const videoURL = URL.createObjectURL(blob);
|
|
318
|
-
this.videoFile = new File([blob], 'recorded_video.webm', { type: 'video/webm' });
|
|
319
|
-
this.videoElement.src = videoURL;
|
|
320
|
-
this.videoElement.controls = true;
|
|
321
|
-
this.videoElement.play();
|
|
322
|
-
this.videoElement.muted = false;
|
|
323
|
-
this.stopTimer();
|
|
324
|
-
};
|
|
325
|
-
this.mediaRecorder.start();
|
|
326
|
-
this.startTimer();
|
|
327
|
-
this.recordButton.disabled = true;
|
|
328
|
-
this.stopButton.disabled = false;
|
|
329
|
-
this.videoElement.controls = false;
|
|
330
|
-
}
|
|
331
|
-
catch (error) {
|
|
332
|
-
console.error('Error starting video recording:', error);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
stopRecording() {
|
|
336
|
-
try {
|
|
337
|
-
if (!this.mediaRecorder || this.mediaRecorder.state === 'inactive') {
|
|
338
|
-
console.log('No recording in progress.');
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
this.mediaRecorder.stop();
|
|
342
|
-
if (this.previewStream) {
|
|
343
|
-
this.previewStream.getTracks().forEach(track => track.stop());
|
|
344
|
-
}
|
|
345
|
-
this.videoElement.srcObject = null;
|
|
346
|
-
this.videoElement.src = '';
|
|
347
|
-
this.videoElement.controls = false;
|
|
348
|
-
this.recordButton.disabled = false;
|
|
349
|
-
this.stopButton.disabled = true;
|
|
350
|
-
this.mediaRecorder = null;
|
|
351
|
-
this.recordedChunks = [];
|
|
352
|
-
this.previewStream = null;
|
|
353
|
-
}
|
|
354
|
-
catch (error) {
|
|
355
|
-
console.error('Error stopping video recording:', error);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
handleFileUpload(event) {
|
|
359
|
-
var _a, _b;
|
|
360
|
-
const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
361
|
-
if ((_b = file === null || file === void 0 ? void 0 : file.type) === null || _b === void 0 ? void 0 : _b.startsWith('video/')) {
|
|
362
|
-
if (file.size > 100 * 1024 * 1024) { // 100MB limit
|
|
363
|
-
this.toggleState('error');
|
|
364
|
-
console.error('File size exceeds limit of 100MB');
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
this.videoFile = file;
|
|
368
|
-
this.videoElement.src = URL.createObjectURL(file);
|
|
369
|
-
this.videoElement.play();
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
this.toggleState('error');
|
|
373
|
-
console.error('Please select a valid video file.');
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
async handleSubmit() {
|
|
377
|
-
if (!this.videoFile) {
|
|
378
|
-
this.toggleState('error');
|
|
379
|
-
console.error('No video file to submit.');
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
if (!this.apiKey || !this.userFullname) {
|
|
383
|
-
this.toggleState('error');
|
|
384
|
-
console.error('API key and user fullname must be provided.');
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
this.toggleState('loading');
|
|
388
|
-
try {
|
|
389
|
-
const phraseInWords = this.convertPhraseToWords(this.phrase);
|
|
390
|
-
const result = await this.sdk.processVideo(this.videoFile, phraseInWords, this.userFullname);
|
|
391
|
-
console.log('Response from processVideo:', result);
|
|
392
|
-
this.toggleState('success');
|
|
393
|
-
}
|
|
394
|
-
catch (error) {
|
|
395
|
-
this.toggleState('error');
|
|
396
|
-
console.error('Error submitting video:', error);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
static get observedAttributes() {
|
|
400
|
-
return ['api-key', 'user-fullname'];
|
|
401
|
-
}
|
|
402
|
-
get userFullname() {
|
|
403
|
-
return this.getAttribute('user-fullname');
|
|
404
|
-
}
|
|
405
|
-
set userFullname(value) {
|
|
406
|
-
if (value) {
|
|
407
|
-
this.setAttribute('user-fullname', value);
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
this.removeAttribute('user-fullname');
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
get isRecording() {
|
|
414
|
-
var _a;
|
|
415
|
-
return ((_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.state) === 'recording';
|
|
416
|
-
}
|
|
417
|
-
get currentPhrase() {
|
|
418
|
-
return this.phrase;
|
|
419
|
-
}
|
|
420
|
-
get videoDuration() {
|
|
421
|
-
var _a;
|
|
422
|
-
return ((_a = this.videoElement) === null || _a === void 0 ? void 0 : _a.duration) || null;
|
|
423
|
-
}
|
|
424
|
-
get currentFile() {
|
|
425
|
-
return this.videoFile;
|
|
426
|
-
}
|
|
427
|
-
get currentStream() {
|
|
428
|
-
return this.previewStream;
|
|
429
|
-
}
|
|
430
|
-
set sdkInstance(newSdk) {
|
|
431
|
-
this.sdk = newSdk;
|
|
432
|
-
}
|
|
433
|
-
get videoElementRef() {
|
|
434
|
-
return this.videoElement;
|
|
435
|
-
}
|
|
436
|
-
get fileInputRef() {
|
|
437
|
-
return this.fileInput;
|
|
438
|
-
}
|
|
439
|
-
get recordingTimeLimit() {
|
|
440
|
-
return this.timeLimit;
|
|
441
|
-
}
|
|
442
|
-
set recordingTimeLimit(value) {
|
|
443
|
-
this.timeLimit = value;
|
|
444
|
-
if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {
|
|
445
|
-
if (this.recordingTimeout) {
|
|
446
|
-
clearTimeout(this.recordingTimeout);
|
|
447
|
-
}
|
|
448
|
-
this.recordingTimeout = setTimeout(() => this.stopRecording(), this.timeLimit * 1000);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
customElements.define('process-video', ProcessVideoComponent);
|