biometry-sdk 1.1.0 → 1.2.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/README.md +95 -4
- package/dist/components/process-video.d.ts +51 -0
- package/dist/components/process-video.js +452 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# biometry-
|
|
1
|
+
# biometry-sdk
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
The **Biometry Web SDK** is a software development kit (SDK) designed to facilitate the integration of Biometry's API services.
|
|
@@ -41,14 +41,35 @@ The **Biometry Web SDK** includes reusable and customizable web components for k
|
|
|
41
41
|
### Face Onboarding Component
|
|
42
42
|
The `Face Onboarding` component provides an intuitive interface for onboarding users with their camera. It integrates with the `BiometrySDK` to handle backend communication and error states.
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
### Integration
|
|
45
45
|
Here's how to integrate the `Face Onboarding` component into your application:
|
|
46
46
|
|
|
47
|
-
**
|
|
47
|
+
**Option 1: Using npm (Recommended for full SDK usage)**
|
|
48
|
+
1. Install the SDK package via **npm**:
|
|
49
|
+
```bash
|
|
50
|
+
npm install biometry-sdk
|
|
51
|
+
```
|
|
52
|
+
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
|
+
```
|
|
57
|
+
3. Connect the script to your **HTML file** and use the component:
|
|
58
|
+
```html
|
|
59
|
+
<script type="module" src="./index.js"></script>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
**Option 2: Using CDN (Quick Integration)**
|
|
48
64
|
```html
|
|
49
|
-
<script type="module" src="
|
|
65
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/biometry-sdk/dist/components/biometry-onboarding.js"></script>
|
|
50
66
|
```
|
|
51
67
|
|
|
68
|
+
### Usage
|
|
69
|
+
The `api-key` and `user-fullname` attributes are required for the component to function.
|
|
70
|
+
|
|
71
|
+
Custom slots allow you to style and customize UI elements, loading, success, and error states.
|
|
72
|
+
|
|
52
73
|
**Basic Usage**
|
|
53
74
|
```html
|
|
54
75
|
<biometry-onboarding
|
|
@@ -77,6 +98,76 @@ Here's how to integrate the `Face Onboarding` component into your application:
|
|
|
77
98
|
```
|
|
78
99
|
|
|
79
100
|
### Process Video Component
|
|
101
|
+
The Process Video component allows developers to record, upload, and process video directly within their applications through Biometry services to check liveness and authorize user.
|
|
102
|
+
|
|
103
|
+
### Integration
|
|
104
|
+
**Option 1: Install via npm**
|
|
105
|
+
|
|
106
|
+
1. To include the component in your project, install the biometry-sdk package:
|
|
107
|
+
```bash
|
|
108
|
+
npm install biometry-sdk
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
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
|
+
|
|
117
|
+
3. Include the component in your HTML:
|
|
118
|
+
```html
|
|
119
|
+
<script type="module" src="./index.js"></script>
|
|
120
|
+
<process-video ...></process-video>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Option 2: Import directly via CDN**
|
|
124
|
+
|
|
125
|
+
You can skip the npm installation and include the component directly in your HTML:
|
|
126
|
+
```html
|
|
127
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/biometry-sdk/dist/components/process-video.js"></script>
|
|
128
|
+
<process-video ...></process-video>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Usage
|
|
132
|
+
|
|
133
|
+
**Basic Usage**
|
|
134
|
+
```html
|
|
135
|
+
<process-video
|
|
136
|
+
api-key="your-api-key"
|
|
137
|
+
user-fullname="Lionel Messi"
|
|
138
|
+
></process-video>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Advanced Usage**
|
|
142
|
+
```html
|
|
143
|
+
<process-video
|
|
144
|
+
api-key="eyJhb...apikey"
|
|
145
|
+
user-fullname="John Doe Uulu"
|
|
146
|
+
>
|
|
147
|
+
<!-- Custom video element -->
|
|
148
|
+
<video slot="video" muted playsinline style="border-radius: 1rem;"></video>
|
|
149
|
+
|
|
150
|
+
<!-- Custom buttons -->
|
|
151
|
+
<button slot="record-button">Custom Record</button>
|
|
152
|
+
<button slot="stop-button">Custom Stop</button>
|
|
153
|
+
|
|
154
|
+
<!-- Custom file input -->
|
|
155
|
+
<input slot="file-input" type="file" accept="video/*" />
|
|
156
|
+
|
|
157
|
+
<!-- Custom submit button -->
|
|
158
|
+
<button slot="submit-button">Custom Submit</button>
|
|
159
|
+
|
|
160
|
+
<!-- Custom messages -->
|
|
161
|
+
<div slot="loading">Processing...</div>
|
|
162
|
+
<div slot="error">An error occurred. Please try again.</div>
|
|
163
|
+
<div slot="success">Video submitted successfully!</div>
|
|
164
|
+
</process-video>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Notes**
|
|
168
|
+
- By default, all elements are functional without customization. Replace slots only if customization is required.
|
|
169
|
+
|
|
170
|
+
- To regenerate the video preview or handle custom actions, use JavaScript to interact with the provided slots or the component's public methods.
|
|
80
171
|
|
|
81
172
|
## License
|
|
82
173
|
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,452 @@
|
|
|
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);
|