UNPKG

11.7 kBJavaScriptView Raw
1import { useGlobalStore } from './store.js';
2import { useEventEmitter } from './event-emitter.js';
3import { STEP_RUN } from './constant.js';
4
5const globalStore = useGlobalStore();
6
7useEventEmitter().on('change:step', (currentStep) => {
8 if (currentStep === STEP_RUN) {
9 showUserData();
10 }
11});
12
13/**
14 * 로컬 스토어
15 */
16const localStore = {
17 videoElement: null,
18 buttonGroupElements: {},
19 stepButtonGroup: {
20 // 준비 중
21 loading: [],
22 // 대기
23 ready: [],
24 // 카메라 연결
25 connected: [],
26 // 녹화
27 recoding: [],
28 // 녹화 종료
29 afterRecording: []
30 },
31 loading: false,
32 // 레코더
33 recorder: null,
34 // 스트림
35 stream: null,
36 // 레코딩 데이터
37 recordingData: {
38 imageUri: null,
39 videoUri: null,
40 imageBlob: null,
41 videoBlob: null
42 },
43 collector: null
44};
45
46window.step3Store = localStore;
47
48/**
49 * 초기화
50 */
51let isInit = false;
52
53function init() {
54 if (isInit) {
55 return;
56 }
57 isInit = true;
58 setElements();
59 bindHandler();
60 showUserData();
61 showButtonGroup('loading');
62 createCollector().then(() => {
63 showButtonGroup('ready');
64 });
65}
66
67/**
68 * collector 인스턴스 생성
69 * @returns Promise<any>
70 */
71function createCollector() {
72 return new Promise((resolve) =>
73 setTimeout(() => {
74 resolve(true);
75 }, 250)
76 ).then(() => {
77 // collector 생성 초기옵션
78 const collectorConfig = {
79 userInfo: {
80 ...globalStore.getUserData()
81 },
82 deviceType: 'pc',
83 webAuth: {
84 userId: 'webAuth_user',
85 token: 'webAuth_token',
86 via: 'webAuth_via'
87 },
88 retryCount: 0,
89 authRenewCount: 0,
90 authRenewBeforeCallback: (webAuth) => {
91 /**
92 * 설정 정보 중 webAuth({ userId, token, via})` 의 일부 데이터 갱신이 필요하다면 아래와 같이 새로운 webAuth 정보로 갱신 처리한다.
93 * 갱신이 불필요한 경우 Promise.resolve(webAuth)를 반환합니다.
94 */
95 const tokenAPI = () => {
96 return new Promise((resolve) => {
97 setTimeout(() => {
98 resolve({ token: 'webAuth_new_token', userId: 'webAuth_new_uid', via: 'new_via' });
99 }, 500);
100 });
101 };
102
103 return tokenAPI().then((newWebAuth) => {
104 webAuth.token = newWebAuth.token;
105 webAuth.userId = newWebAuth.userId;
106 webAuth.via = newWebAuth.via;
107 });
108 }
109 };
110
111 // collector 인스턴스 생성
112 globalStore.setCollector(new window.nhnCDSDK.Collector(collectorConfig));
113 const collector = globalStore.getCollector();
114
115 // 요청 실패 리스너 등록
116 collector.on('api:success', (data) => {
117 console.log('요청 결과 : ', data);
118 });
119 collector.on('api:fail', ({ resultCode, resultMessage }) => {
120 console.log(`응답 데이터 에러 : ${resultCode} ${resultMessage}`);
121 });
122 collector.on('api:error', (error) => {
123 console.log('요청 실패', error);
124 });
125 });
126}
127
128/**
129 * element 정보 저장
130 */
131function setElements() {
132 const buttonConnect = document.querySelector('#uid_camera_connect');
133 const buttonStart = document.querySelector('#uid_record_start');
134 const buttonEnd = document.querySelector('#uid_record_end');
135 const buttonSendVoiceData = document.querySelector('#uid_send_voice');
136 const buttonSendSnapshotData = document.querySelector('#uid_send_snapshot');
137 const buttonReset = document.querySelector('#uid_run_ui_reset');
138 const buttonRevokeToken = document.querySelector('#uid_revoke_token');
139 const configLoadingMsg = document.querySelector('#uid_step3_config_loading');
140
141 localStore.videoElement = document.querySelector('#uid_local_video');
142 localStore.resultElement = document.querySelector('#uid_record_result');
143 localStore.endTestElement = document.querySelector('#uid_end_test_btn');
144 localStore.buttonGroupElements.connect = buttonConnect;
145 localStore.buttonGroupElements.start = buttonStart;
146 localStore.buttonGroupElements.end = buttonEnd;
147 localStore.buttonGroupElements.sendVoiceData = buttonSendVoiceData;
148 localStore.buttonGroupElements.sendSnapshotData = buttonSendSnapshotData;
149 localStore.buttonGroupElements.reset = buttonReset;
150 localStore.buttonGroupElements.revokeToken = buttonRevokeToken;
151 localStore.buttonGroupElements.configLoadingMsg = configLoadingMsg;
152
153 localStore.stepButtonGroup.loading = [configLoadingMsg];
154 localStore.stepButtonGroup.ready = [buttonConnect];
155 localStore.stepButtonGroup.connected = [buttonStart];
156 localStore.stepButtonGroup.recoding = [buttonEnd];
157 localStore.stepButtonGroup.afterRecording = [buttonSendVoiceData, buttonSendSnapshotData, buttonReset, buttonRevokeToken];
158}
159
160/**
161 * 상태별 버튼 그룹 노출 제어
162 * @param {string} groupName
163 */
164function showButtonGroup(groupName) {
165 if (localStore.stepButtonGroup[groupName]) {
166 Object.values(localStore.buttonGroupElements).forEach((button) => {
167 button.style.display = 'none';
168 });
169 Object.values(localStore.stepButtonGroup[groupName]).forEach((button) => {
170 button.style.display = 'inline-block';
171 });
172 }
173}
174
175/**
176 * 이벤트 바인딩
177 */
178function bindHandler() {
179 // 시험 종료
180 localStore.endTestElement.addEventListener('click', function () {
181 globalStore.getCommunicator().endTest();
182 globalStore.prevStep();
183 });
184 // 카메라 연결
185 localStore.buttonGroupElements.connect.addEventListener('click', connectHandler);
186 // 녹화 시작
187 localStore.buttonGroupElements.start.addEventListener('click', recodingHandler);
188 // 녹화 저장
189 localStore.buttonGroupElements.end.addEventListener('click', afterRecodingHandler);
190 // 음성 데이터 전송
191 localStore.buttonGroupElements.sendVoiceData.addEventListener('click', sendVoiceHandler);
192 // 스냅샷 데이터 전송
193 localStore.buttonGroupElements.sendSnapshotData.addEventListener('click', sendSnapshotHandler);
194 // UI 초기화
195 localStore.buttonGroupElements.reset.addEventListener('click', resetHandler);
196 // 초기화
197 localStore.buttonGroupElements.revokeToken.addEventListener('click', revokeAccessTokenHandler);
198}
199
200/**
201 * API 결과 노출
202 * @param {string} apiName
203 * @param {Object} data
204 */
205function renderAPIResult(apiName, data) {
206 const container = document.querySelector('#uid_step_run_api_result');
207 let renderData = '';
208 if (data) {
209 renderData = JSON.stringify(data, null, ' ');
210 }
211 let html = '';
212 if (apiName) {
213 html = `<h4>${apiName}</h4>`;
214 }
215 container.innerHTML = `${html}${renderData}`;
216}
217
218/**
219 * 카메라 연결
220 */
221function connectHandler() {
222 window.navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' }, audio: true }).then((stream) => {
223 localStore.stream = stream;
224 localStore.videoElement.srcObject = stream;
225 showButtonGroup('connected');
226 });
227}
228
229/**
230 * 녹화 시작
231 */
232function recodingHandler() {
233 if (!localStore.recorder) {
234 localStore.recorder = window.RecordRTC(localStore.stream, {
235 type: 'audio',
236 mimeType: 'audio/wav',
237 disableLogs: true
238 });
239 }
240 localStore.resultElement.innerHTML = '';
241 localStore.recorder.startRecording();
242 showButtonGroup('recoding');
243}
244
245/**
246 * 녹화 저장
247 */
248function afterRecodingHandler() {
249 const { recorder } = localStore;
250 recorder.stopRecording(() => {
251 const blob = recorder.getBlob();
252 if (blob) {
253 const URL = window.webkitURL || window.URL;
254 const videoUri = URL.createObjectURL(blob);
255 const canvas = createSnapshotCanvas();
256 const imageUri = canvas.toDataURL('image/jpg');
257
258 canvas.toBlob((canvasBlob) => {
259 localStore.recordingData = {
260 imageUri: imageUri,
261 videoUri: videoUri,
262 videoBlob: blob,
263 imageBlob: canvasBlob
264 };
265
266 renderResult();
267 showButtonGroup('afterRecording');
268 });
269 }
270 });
271}
272
273/**
274 * 레코딩 결과 노출
275 */
276function renderResult() {
277 const { imageUri, videoUri } = localStore.recordingData;
278 localStore.resultElement.innerHTML = `
279 <h3>레코딩 결과</h3>
280 <img src="${imageUri}" width="100" height="80" alt="snapshot" />
281 <video src="${videoUri}" controls width="300" height="80" />
282 `;
283}
284
285/**
286 * 스냅샷 캔버스 생성
287 * @returns HTMLCanvasElement
288 */
289function createSnapshotCanvas() {
290 const canvas = document.createElement('canvas');
291 canvas.width = 640;
292 canvas.height = 480;
293 const ctx = canvas.getContext('2d');
294 ctx.drawImage(localStore.videoElement, 0, 0, canvas.width, canvas.height);
295 return canvas;
296}
297
298/**
299 * 음성 데이터 전송
300 */
301function sendVoiceHandler() {
302 const collector = globalStore.getCollector();
303 if (!collector) {
304 console.error('collector 인스턴스가 존재하지 않음');
305 return;
306 }
307 const { imageBlob, videoBlob } = localStore.recordingData;
308 if (!imageBlob || !videoBlob) {
309 console.error('저장된 데이터 없음');
310 return;
311 }
312
313 const formData = new FormData();
314 formData.set(
315 'file',
316 new File([videoBlob], 'cam-voice.wav', {
317 type: 'audio/wav'
318 })
319 );
320 const query = { reqTime: Math.floor(Date.now() / 1000) };
321
322 // collector API 호출
323 collector
324 .fetchVoiceDetect(formData, query)
325 .then((res) => {
326 renderAPIResult('음성 데이터 전송', res);
327 })
328 .catch((err) => {
329 const { stack, message } = err;
330 let result = { message, stack };
331 if (err?.header?.isSuccessful === false) {
332 result = { ...err };
333 }
334 renderAPIResult('음성 데이터 전송', result);
335 });
336}
337
338/**
339 * 스냅샷 데이터 전송
340 */
341function sendSnapshotHandler() {
342 const collector = globalStore.getCollector();
343 if (!collector) {
344 console.error('collector 인스턴스가 존재하지 않음');
345 return;
346 }
347 const { imageBlob, videoBlob } = localStore.recordingData;
348 if (!imageBlob || !videoBlob) {
349 console.error('저장된 데이터 없음');
350 return;
351 }
352
353 const data = new FormData();
354 data.append('file', new File([imageBlob], 'cam-snapshot.jpg', { type: 'image/jpeg' }));
355 const query = {
356 reqTime: Math.floor(Date.now() / 1000),
357 camLocation: 'front'
358 };
359
360 // collector API 호출
361 collector
362 .fetchBehaviorDetect(data, query)
363 .then((res) => {
364 renderAPIResult('스냅샷 데이터 전송', res);
365 })
366 .catch((err) => {
367 const { stack, message } = err;
368 let result = { message, stack };
369 if (err?.header?.isSuccessful === false) {
370 result = { ...err };
371 }
372 renderAPIResult('스냅샷 데이터 전송', result);
373 });
374}
375
376/**
377 * UI 초기화
378 */
379function resetHandler() {
380 localStore.recordingData = {
381 imageUri: null,
382 videoUri: null,
383 imageBlob: null,
384 videoBlob: null
385 };
386 localStore.resultElement.innerHTML = '';
387 localStore.videoElement.srcObject = null;
388 showButtonGroup('ready');
389 renderAPIResult(null);
390}
391
392/**
393 * access token 초기화
394 */
395function revokeAccessTokenHandler() {
396 const collector = globalStore.getCollector();
397 if (!collector) {
398 console.error('collector 인스턴스가 존재하지 않음');
399 return;
400 }
401 // collector API 호출
402 collector
403 .revokeAccessToken()
404 .then((res) => {
405 renderAPIResult('access token 초기화', res);
406 })
407 .catch((err) => {
408 const { stack, message } = err;
409 let result = { message, stack };
410 if (err?.header?.isSuccessful === false) {
411 result = { ...err };
412 }
413 renderAPIResult('access token 초기화', result);
414 });
415}
416
417/**
418 * 유저 정보, api base url 노출
419 */
420function showUserData() {
421 document.querySelector('#uid_user_info_preview_step3').innerHTML = JSON.stringify(globalStore.getUserData());
422}
423
424export default {
425 init
426};