biometry-sdk 1.1.1 → 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 +70 -1
- package/dist/components/process-video.d.ts +51 -0
- package/dist/components/process-video.js +452 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,7 +98,76 @@ Custom slots allow you to style and customize UI elements, loading, success, and
|
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
### Process Video Component
|
|
101
|
-
|
|
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.
|
|
102
171
|
|
|
103
172
|
## License
|
|
104
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);
|