easyproctor-hml 2.7.11 → 2.7.12
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/esm/index.js +15 -867
- package/index.js +15 -867
- package/new-flow/recorders/CameraRecorder.d.ts +0 -24
- package/package.json +1 -1
- package/plugins/recorder.d.ts +1 -5
- package/unpkg/easyproctor.min.js +41 -44
- package/new-flow/chunk/BackgroundUploadService.d.ts +0 -38
- package/new-flow/chunk/ChunkStorageService.d.ts +0 -25
package/index.js
CHANGED
|
@@ -30855,16 +30855,13 @@ function isMobileDevice() {
|
|
|
30855
30855
|
}
|
|
30856
30856
|
return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
30857
30857
|
}
|
|
30858
|
-
function isSafeBrowser() {
|
|
30859
|
-
return versionVerify() !== "1.0.0.0";
|
|
30860
|
-
}
|
|
30861
30858
|
|
|
30862
30859
|
// src/plugins/recorder.ts
|
|
30863
30860
|
var proctoringId;
|
|
30864
30861
|
function setRecorderProctoringId(id) {
|
|
30865
30862
|
proctoringId = id;
|
|
30866
30863
|
}
|
|
30867
|
-
function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false
|
|
30864
|
+
function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false) {
|
|
30868
30865
|
let resolvePromise;
|
|
30869
30866
|
let onBufferSizeInterval;
|
|
30870
30867
|
let lastEvent;
|
|
@@ -30872,7 +30869,6 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
|
|
|
30872
30869
|
bufferSize = 0;
|
|
30873
30870
|
let startTime;
|
|
30874
30871
|
let duration = 0;
|
|
30875
|
-
let chunkIndex = 0;
|
|
30876
30872
|
let recorderOptions = {
|
|
30877
30873
|
// eslint-disable-next-line no-useless-escape
|
|
30878
30874
|
mimeType: "video/webm",
|
|
@@ -30907,10 +30903,6 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
|
|
|
30907
30903
|
mediaRecorder2.ondataavailable = (e3) => {
|
|
30908
30904
|
bufferSize = bufferSize + e3.data.size;
|
|
30909
30905
|
if (e3.data.size > 0) {
|
|
30910
|
-
if (recorderOpts == null ? void 0 : recorderOpts.onChunkAvailable) {
|
|
30911
|
-
recorderOpts.onChunkAvailable(e3.data, chunkIndex);
|
|
30912
|
-
chunkIndex++;
|
|
30913
|
-
}
|
|
30914
30906
|
buffer.push(e3.data);
|
|
30915
30907
|
}
|
|
30916
30908
|
};
|
|
@@ -30942,13 +30934,7 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
|
|
|
30942
30934
|
};
|
|
30943
30935
|
try {
|
|
30944
30936
|
console.log("State antes do start:", recorder2.state);
|
|
30945
|
-
|
|
30946
|
-
if ((recorderOpts == null ? void 0 : recorderOpts.timeslice) && (recorderOpts == null ? void 0 : recorderOpts.timeslice) > 0) {
|
|
30947
|
-
recorder2.start(recorderOpts.timeslice);
|
|
30948
|
-
} else {
|
|
30949
|
-
recorder2.start(1e4);
|
|
30950
|
-
}
|
|
30951
|
-
bufferSize = 0;
|
|
30937
|
+
recorder2.start(1e4);
|
|
30952
30938
|
startTime = new Date(Date.now());
|
|
30953
30939
|
} catch (e3) {
|
|
30954
30940
|
console.error("Recorder erro ao chamar start event:", e3);
|
|
@@ -31002,6 +30988,9 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
|
|
|
31002
30988
|
console.log("stopRecording Recorder n\xE3o est\xE1 em estado recording");
|
|
31003
30989
|
resolve();
|
|
31004
30990
|
}
|
|
30991
|
+
stream4.getTracks().forEach((el) => {
|
|
30992
|
+
el.stop();
|
|
30993
|
+
});
|
|
31005
30994
|
});
|
|
31006
30995
|
}
|
|
31007
30996
|
function pauseRecording() {
|
|
@@ -31409,617 +31398,11 @@ var VolumeMeter = class {
|
|
|
31409
31398
|
}
|
|
31410
31399
|
};
|
|
31411
31400
|
|
|
31412
|
-
// src/new-flow/chunk/ChunkStorageService.ts
|
|
31413
|
-
var _ChunkStorageService = class _ChunkStorageService {
|
|
31414
|
-
constructor() {
|
|
31415
|
-
this.db = null;
|
|
31416
|
-
}
|
|
31417
|
-
/**
|
|
31418
|
-
* Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
|
|
31419
|
-
*/
|
|
31420
|
-
async connect() {
|
|
31421
|
-
if (this.db) return this.db;
|
|
31422
|
-
return new Promise((resolve, reject) => {
|
|
31423
|
-
const request = window.indexedDB.open(
|
|
31424
|
-
_ChunkStorageService.DB_NAME,
|
|
31425
|
-
_ChunkStorageService.DB_VERSION
|
|
31426
|
-
);
|
|
31427
|
-
request.onerror = () => {
|
|
31428
|
-
reject(new Error("N\xE3o foi poss\xEDvel conectar ao IndexedDB para chunks."));
|
|
31429
|
-
};
|
|
31430
|
-
request.onupgradeneeded = () => {
|
|
31431
|
-
const db = request.result;
|
|
31432
|
-
if (db.objectStoreNames.contains(_ChunkStorageService.STORE_NAME)) {
|
|
31433
|
-
db.deleteObjectStore(_ChunkStorageService.STORE_NAME);
|
|
31434
|
-
}
|
|
31435
|
-
const store = db.createObjectStore(_ChunkStorageService.STORE_NAME, {
|
|
31436
|
-
keyPath: "id",
|
|
31437
|
-
autoIncrement: true
|
|
31438
|
-
});
|
|
31439
|
-
store.createIndex("proctoringId", "proctoringId", { unique: false });
|
|
31440
|
-
store.createIndex("uploaded", "uploaded", { unique: false });
|
|
31441
|
-
store.createIndex("proctoringId_uploaded", ["proctoringId", "uploaded"], {
|
|
31442
|
-
unique: false
|
|
31443
|
-
});
|
|
31444
|
-
};
|
|
31445
|
-
request.onsuccess = () => {
|
|
31446
|
-
this.db = request.result;
|
|
31447
|
-
resolve(this.db);
|
|
31448
|
-
};
|
|
31449
|
-
});
|
|
31450
|
-
}
|
|
31451
|
-
/**
|
|
31452
|
-
* Salva um chunk de vídeo no IndexedDB.
|
|
31453
|
-
*/
|
|
31454
|
-
async saveChunk(chunk) {
|
|
31455
|
-
const db = await this.connect();
|
|
31456
|
-
return new Promise((resolve, reject) => {
|
|
31457
|
-
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31458
|
-
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31459
|
-
const request = store.add(chunk);
|
|
31460
|
-
request.onsuccess = () => {
|
|
31461
|
-
resolve(request.result);
|
|
31462
|
-
};
|
|
31463
|
-
request.onerror = () => {
|
|
31464
|
-
var _a2;
|
|
31465
|
-
reject(new Error(`Erro ao salvar chunk no IndexedDB: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31466
|
-
};
|
|
31467
|
-
});
|
|
31468
|
-
}
|
|
31469
|
-
/**
|
|
31470
|
-
* Retorna todos os chunks pendentes (não enviados) de um proctoringId específico.
|
|
31471
|
-
*/
|
|
31472
|
-
async getPendingChunks(proctoringId2) {
|
|
31473
|
-
const db = await this.connect();
|
|
31474
|
-
return new Promise((resolve, reject) => {
|
|
31475
|
-
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31476
|
-
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31477
|
-
const index = store.index("proctoringId_uploaded");
|
|
31478
|
-
const range = IDBKeyRange.only([proctoringId2, 0]);
|
|
31479
|
-
const request = index.getAll(range);
|
|
31480
|
-
request.onsuccess = () => {
|
|
31481
|
-
const chunks = request.result.sort(
|
|
31482
|
-
(a3, b3) => a3.chunkIndex - b3.chunkIndex
|
|
31483
|
-
);
|
|
31484
|
-
resolve(chunks);
|
|
31485
|
-
};
|
|
31486
|
-
request.onerror = () => {
|
|
31487
|
-
var _a2;
|
|
31488
|
-
reject(new Error(`Erro ao buscar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31489
|
-
};
|
|
31490
|
-
});
|
|
31491
|
-
}
|
|
31492
|
-
/**
|
|
31493
|
-
* Retorna todos os chunks (enviados ou não) de um proctoringId específico.
|
|
31494
|
-
*/
|
|
31495
|
-
async getAllChunks(proctoringId2) {
|
|
31496
|
-
const db = await this.connect();
|
|
31497
|
-
return new Promise((resolve, reject) => {
|
|
31498
|
-
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31499
|
-
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31500
|
-
const index = store.index("proctoringId");
|
|
31501
|
-
const range = IDBKeyRange.only(proctoringId2);
|
|
31502
|
-
const request = index.getAll(range);
|
|
31503
|
-
request.onsuccess = () => {
|
|
31504
|
-
const chunks = request.result.sort(
|
|
31505
|
-
(a3, b3) => a3.chunkIndex - b3.chunkIndex
|
|
31506
|
-
);
|
|
31507
|
-
resolve(chunks);
|
|
31508
|
-
};
|
|
31509
|
-
request.onerror = () => {
|
|
31510
|
-
var _a2;
|
|
31511
|
-
reject(new Error(`Erro ao buscar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31512
|
-
};
|
|
31513
|
-
});
|
|
31514
|
-
}
|
|
31515
|
-
/**
|
|
31516
|
-
* Marca um chunk como enviado (uploaded = 1).
|
|
31517
|
-
*/
|
|
31518
|
-
async markAsUploaded(chunkId) {
|
|
31519
|
-
const db = await this.connect();
|
|
31520
|
-
return new Promise((resolve, reject) => {
|
|
31521
|
-
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31522
|
-
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31523
|
-
const getRequest = store.get(chunkId);
|
|
31524
|
-
getRequest.onsuccess = () => {
|
|
31525
|
-
const chunk = getRequest.result;
|
|
31526
|
-
if (!chunk) {
|
|
31527
|
-
resolve();
|
|
31528
|
-
return;
|
|
31529
|
-
}
|
|
31530
|
-
chunk.uploaded = 1;
|
|
31531
|
-
const putRequest = store.put(chunk);
|
|
31532
|
-
putRequest.onsuccess = () => resolve();
|
|
31533
|
-
putRequest.onerror = () => {
|
|
31534
|
-
var _a2;
|
|
31535
|
-
return reject(new Error(`Erro ao marcar chunk como enviado: ${(_a2 = putRequest.error) == null ? void 0 : _a2.message}`));
|
|
31536
|
-
};
|
|
31537
|
-
};
|
|
31538
|
-
getRequest.onerror = () => {
|
|
31539
|
-
var _a2;
|
|
31540
|
-
return reject(new Error(`Erro ao buscar chunk para marcar: ${(_a2 = getRequest.error) == null ? void 0 : _a2.message}`));
|
|
31541
|
-
};
|
|
31542
|
-
});
|
|
31543
|
-
}
|
|
31544
|
-
/**
|
|
31545
|
-
* Remove todos os chunks já enviados de um proctoringId para liberar espaço.
|
|
31546
|
-
*/
|
|
31547
|
-
async clearUploadedChunks(proctoringId2) {
|
|
31548
|
-
const db = await this.connect();
|
|
31549
|
-
return new Promise((resolve, reject) => {
|
|
31550
|
-
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31551
|
-
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31552
|
-
const index = store.index("proctoringId_uploaded");
|
|
31553
|
-
const range = IDBKeyRange.only([proctoringId2, 1]);
|
|
31554
|
-
const request = index.openCursor(range);
|
|
31555
|
-
request.onsuccess = () => {
|
|
31556
|
-
const cursor = request.result;
|
|
31557
|
-
if (cursor) {
|
|
31558
|
-
cursor.delete();
|
|
31559
|
-
cursor.continue();
|
|
31560
|
-
} else {
|
|
31561
|
-
resolve();
|
|
31562
|
-
}
|
|
31563
|
-
};
|
|
31564
|
-
request.onerror = () => {
|
|
31565
|
-
var _a2;
|
|
31566
|
-
return reject(new Error(`Erro ao limpar chunks enviados: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31567
|
-
};
|
|
31568
|
-
});
|
|
31569
|
-
}
|
|
31570
|
-
/**
|
|
31571
|
-
* Remove TODOS os chunks de um proctoringId (limpeza completa pós-finalização).
|
|
31572
|
-
*/
|
|
31573
|
-
async clearAllChunks(proctoringId2) {
|
|
31574
|
-
const db = await this.connect();
|
|
31575
|
-
return new Promise((resolve, reject) => {
|
|
31576
|
-
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31577
|
-
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31578
|
-
const index = store.index("proctoringId");
|
|
31579
|
-
const range = IDBKeyRange.only(proctoringId2);
|
|
31580
|
-
const request = index.openCursor(range);
|
|
31581
|
-
request.onsuccess = () => {
|
|
31582
|
-
const cursor = request.result;
|
|
31583
|
-
if (cursor) {
|
|
31584
|
-
cursor.delete();
|
|
31585
|
-
cursor.continue();
|
|
31586
|
-
} else {
|
|
31587
|
-
resolve();
|
|
31588
|
-
}
|
|
31589
|
-
};
|
|
31590
|
-
request.onerror = () => {
|
|
31591
|
-
var _a2;
|
|
31592
|
-
return reject(new Error(`Erro ao limpar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31593
|
-
};
|
|
31594
|
-
});
|
|
31595
|
-
}
|
|
31596
|
-
/**
|
|
31597
|
-
* Verifica se existem chunks pendentes para qualquer proctoringId.
|
|
31598
|
-
* Útil na recuperação pós-crash.
|
|
31599
|
-
*/
|
|
31600
|
-
async hasAnyPendingChunks() {
|
|
31601
|
-
const db = await this.connect();
|
|
31602
|
-
return new Promise((resolve, reject) => {
|
|
31603
|
-
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31604
|
-
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31605
|
-
const index = store.index("uploaded");
|
|
31606
|
-
const range = IDBKeyRange.only(0);
|
|
31607
|
-
const request = index.count(range);
|
|
31608
|
-
request.onsuccess = () => {
|
|
31609
|
-
resolve(request.result > 0);
|
|
31610
|
-
};
|
|
31611
|
-
request.onerror = () => {
|
|
31612
|
-
var _a2;
|
|
31613
|
-
return reject(new Error(`Erro ao verificar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31614
|
-
};
|
|
31615
|
-
});
|
|
31616
|
-
}
|
|
31617
|
-
/**
|
|
31618
|
-
* Retorna todos os proctoringIds que possuem chunks pendentes.
|
|
31619
|
-
* Útil na recuperação pós-crash para saber quais sessões precisam ser finalizadas.
|
|
31620
|
-
*/
|
|
31621
|
-
async getPendingProctoringIds() {
|
|
31622
|
-
const db = await this.connect();
|
|
31623
|
-
return new Promise((resolve, reject) => {
|
|
31624
|
-
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31625
|
-
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31626
|
-
const index = store.index("uploaded");
|
|
31627
|
-
const range = IDBKeyRange.only(0);
|
|
31628
|
-
const request = index.getAll(range);
|
|
31629
|
-
request.onsuccess = () => {
|
|
31630
|
-
const chunks = request.result;
|
|
31631
|
-
const ids = [...new Set(chunks.map((c3) => c3.proctoringId))];
|
|
31632
|
-
resolve(ids);
|
|
31633
|
-
};
|
|
31634
|
-
request.onerror = () => {
|
|
31635
|
-
var _a2;
|
|
31636
|
-
return reject(new Error(`Erro ao buscar proctoringIds pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31637
|
-
};
|
|
31638
|
-
});
|
|
31639
|
-
}
|
|
31640
|
-
/**
|
|
31641
|
-
* Fecha a conexão com o banco.
|
|
31642
|
-
*/
|
|
31643
|
-
close() {
|
|
31644
|
-
if (this.db) {
|
|
31645
|
-
this.db.close();
|
|
31646
|
-
this.db = null;
|
|
31647
|
-
}
|
|
31648
|
-
}
|
|
31649
|
-
};
|
|
31650
|
-
_ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
|
|
31651
|
-
/** Incrementado para v2 para recriar índices com tipos numéricos em vez de boolean */
|
|
31652
|
-
_ChunkStorageService.DB_VERSION = 2;
|
|
31653
|
-
_ChunkStorageService.STORE_NAME = "chunks";
|
|
31654
|
-
var ChunkStorageService = _ChunkStorageService;
|
|
31655
|
-
|
|
31656
|
-
// src/new-flow/chunk/BackgroundUploadService.ts
|
|
31657
|
-
var DEFAULT_CONFIG = {
|
|
31658
|
-
pollInterval: 5e3,
|
|
31659
|
-
maxRetries: 5,
|
|
31660
|
-
baseRetryDelay: 2e3,
|
|
31661
|
-
cleanAfterUpload: true
|
|
31662
|
-
};
|
|
31663
|
-
var BackgroundUploadService = class _BackgroundUploadService {
|
|
31664
|
-
constructor(proctoringId2, token, backend, chunkStorage, config) {
|
|
31665
|
-
this.pollTimer = null;
|
|
31666
|
-
this.isProcessing = false;
|
|
31667
|
-
this.isRunning = false;
|
|
31668
|
-
/** Mapa de chunkId -> número de tentativas já feitas */
|
|
31669
|
-
this.retryCount = /* @__PURE__ */ new Map();
|
|
31670
|
-
/** GCS Resumable Upload State */
|
|
31671
|
-
this.sessionUrl = null;
|
|
31672
|
-
this.currentOffset = 0;
|
|
31673
|
-
this.totalBytesPurged = 0;
|
|
31674
|
-
this.STORAGE_KEY_PREFIX = "ep_upload_session_";
|
|
31675
|
-
this.GCS_CHUNK_SIZE = 256 * 1024;
|
|
31676
|
-
this.proctoringId = proctoringId2.trim();
|
|
31677
|
-
this.token = token;
|
|
31678
|
-
this.backend = backend;
|
|
31679
|
-
this.chunkStorage = chunkStorage;
|
|
31680
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
31681
|
-
this.loadSessionState();
|
|
31682
|
-
}
|
|
31683
|
-
loadSessionState() {
|
|
31684
|
-
try {
|
|
31685
|
-
const stored = localStorage.getItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
|
|
31686
|
-
if (stored) {
|
|
31687
|
-
const { sessionUrl, currentOffset, totalBytesPurged } = JSON.parse(stored);
|
|
31688
|
-
this.sessionUrl = sessionUrl;
|
|
31689
|
-
this.currentOffset = currentOffset;
|
|
31690
|
-
this.totalBytesPurged = totalBytesPurged || 0;
|
|
31691
|
-
}
|
|
31692
|
-
} catch (e3) {
|
|
31693
|
-
console.warn("[BackgroundUpload] Erro ao carregar estado da sess\xE3o:", e3);
|
|
31694
|
-
}
|
|
31695
|
-
}
|
|
31696
|
-
saveSessionState() {
|
|
31697
|
-
try {
|
|
31698
|
-
localStorage.setItem(
|
|
31699
|
-
`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`,
|
|
31700
|
-
JSON.stringify({
|
|
31701
|
-
sessionUrl: this.sessionUrl,
|
|
31702
|
-
currentOffset: this.currentOffset,
|
|
31703
|
-
totalBytesPurged: this.totalBytesPurged
|
|
31704
|
-
})
|
|
31705
|
-
);
|
|
31706
|
-
} catch (e3) {
|
|
31707
|
-
console.warn("[BackgroundUpload] Erro ao salvar estado da sess\xE3o:", e3);
|
|
31708
|
-
}
|
|
31709
|
-
}
|
|
31710
|
-
clearSessionState() {
|
|
31711
|
-
try {
|
|
31712
|
-
localStorage.removeItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
|
|
31713
|
-
this.sessionUrl = null;
|
|
31714
|
-
this.currentOffset = 0;
|
|
31715
|
-
this.totalBytesPurged = 0;
|
|
31716
|
-
} catch (e3) {
|
|
31717
|
-
console.warn("[BackgroundUpload] Erro ao limpar estado da sess\xE3o:", e3);
|
|
31718
|
-
}
|
|
31719
|
-
}
|
|
31720
|
-
/**
|
|
31721
|
-
* Inicia o serviço de upload em background. Faz polling periódico no IndexedDB
|
|
31722
|
-
* para enviar chunks pendentes.
|
|
31723
|
-
*/
|
|
31724
|
-
start() {
|
|
31725
|
-
if (this.isRunning) return;
|
|
31726
|
-
this.isRunning = true;
|
|
31727
|
-
console.log(`[BackgroundUpload] Iniciando servi\xE7o para proctoringId: ${this.proctoringId}`);
|
|
31728
|
-
this.processQueue();
|
|
31729
|
-
this.pollTimer = setInterval(() => {
|
|
31730
|
-
this.processQueue(false);
|
|
31731
|
-
}, this.config.pollInterval);
|
|
31732
|
-
}
|
|
31733
|
-
/**
|
|
31734
|
-
* Para o serviço de upload em background.
|
|
31735
|
-
*/
|
|
31736
|
-
stop() {
|
|
31737
|
-
this.isRunning = false;
|
|
31738
|
-
if (this.pollTimer) {
|
|
31739
|
-
clearInterval(this.pollTimer);
|
|
31740
|
-
this.pollTimer = null;
|
|
31741
|
-
}
|
|
31742
|
-
console.log(`[BackgroundUpload] Servi\xE7o parado para proctoringId: ${this.proctoringId}`);
|
|
31743
|
-
}
|
|
31744
|
-
/**
|
|
31745
|
-
* Força o processamento de todos os chunks pendentes e encerra a sessão GCS.
|
|
31746
|
-
* Útil quando a gravação é finalizada.
|
|
31747
|
-
*/
|
|
31748
|
-
async flush() {
|
|
31749
|
-
console.log(`[BackgroundUpload] Flush: enviando todos os chunks pendentes e finalizando...`);
|
|
31750
|
-
let waitAttempts = 0;
|
|
31751
|
-
while (this.isProcessing && waitAttempts < 10) {
|
|
31752
|
-
await this.sleep(1e3);
|
|
31753
|
-
waitAttempts++;
|
|
31754
|
-
}
|
|
31755
|
-
let flushRetries = 0;
|
|
31756
|
-
const maxFlushRetries = 3;
|
|
31757
|
-
while (flushRetries < maxFlushRetries) {
|
|
31758
|
-
try {
|
|
31759
|
-
await this.processQueue(true);
|
|
31760
|
-
console.log(`[BackgroundUpload] Flush completado com sucesso.`);
|
|
31761
|
-
return;
|
|
31762
|
-
} catch (error) {
|
|
31763
|
-
flushRetries++;
|
|
31764
|
-
console.error(`[BackgroundUpload] Erro no flush (tentativa ${flushRetries}/${maxFlushRetries}):`, error);
|
|
31765
|
-
if (flushRetries < maxFlushRetries) {
|
|
31766
|
-
await this.sleep(2e3);
|
|
31767
|
-
}
|
|
31768
|
-
}
|
|
31769
|
-
}
|
|
31770
|
-
throw new Error(`[BackgroundUpload] Falha ao finalizar upload ap\xF3s ${maxFlushRetries} tentativas.`);
|
|
31771
|
-
}
|
|
31772
|
-
/**
|
|
31773
|
-
* Sincroniza o offset local com o estado real no Google Cloud Storage.
|
|
31774
|
-
*/
|
|
31775
|
-
async syncOffset() {
|
|
31776
|
-
if (!this.sessionUrl) return 0;
|
|
31777
|
-
try {
|
|
31778
|
-
console.log(`[BackgroundUpload] Sincronizando offset com GCS...`);
|
|
31779
|
-
const response = await fetch(this.sessionUrl, {
|
|
31780
|
-
method: "PUT",
|
|
31781
|
-
headers: {
|
|
31782
|
-
"Content-Range": "bytes */*"
|
|
31783
|
-
}
|
|
31784
|
-
});
|
|
31785
|
-
console.log(`[BackgroundUpload] Status da sincroniza\xE7\xE3o (syncOffset): ${response.status}`);
|
|
31786
|
-
if (response.status === 308) {
|
|
31787
|
-
const range = response.headers.get("Range");
|
|
31788
|
-
if (range) {
|
|
31789
|
-
const lastByte = parseInt(range.split("-")[1], 10);
|
|
31790
|
-
this.currentOffset = lastByte + 1;
|
|
31791
|
-
this.saveSessionState();
|
|
31792
|
-
console.log(`[BackgroundUpload] Offset sincronizado: ${this.currentOffset}`);
|
|
31793
|
-
} else {
|
|
31794
|
-
this.currentOffset = 0;
|
|
31795
|
-
}
|
|
31796
|
-
} else if (response.ok || response.status === 201) {
|
|
31797
|
-
console.log("[BackgroundUpload] Sincroniza\xE7\xE3o indicou upload JA FINALIZADO.");
|
|
31798
|
-
this.currentOffset = -1;
|
|
31799
|
-
} else {
|
|
31800
|
-
console.warn(`[BackgroundUpload] Status inesperado na sincroniza\xE7\xE3o: ${response.status}`);
|
|
31801
|
-
}
|
|
31802
|
-
} catch (error) {
|
|
31803
|
-
console.warn("[BackgroundUpload] Erro ao sincronizar offset:", error);
|
|
31804
|
-
}
|
|
31805
|
-
return this.currentOffset;
|
|
31806
|
-
}
|
|
31807
|
-
/**
|
|
31808
|
-
* Verifica e envia chunks pendentes para o backend.
|
|
31809
|
-
* @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
|
|
31810
|
-
*/
|
|
31811
|
-
async processQueue(isFinal = false) {
|
|
31812
|
-
var _a2, _b;
|
|
31813
|
-
if (this.isProcessing) return;
|
|
31814
|
-
this.isProcessing = true;
|
|
31815
|
-
try {
|
|
31816
|
-
if (this.sessionUrl) {
|
|
31817
|
-
await this.syncOffset();
|
|
31818
|
-
if (this.currentOffset === -1) {
|
|
31819
|
-
console.log("[BackgroundUpload] Sess\xE3o j\xE1 finalizada no servidor.");
|
|
31820
|
-
this.clearSessionState();
|
|
31821
|
-
this.isProcessing = false;
|
|
31822
|
-
return;
|
|
31823
|
-
}
|
|
31824
|
-
}
|
|
31825
|
-
const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
|
|
31826
|
-
const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
|
|
31827
|
-
if (pendingChunks.length === 0 && !isFinal) {
|
|
31828
|
-
this.isProcessing = false;
|
|
31829
|
-
return;
|
|
31830
|
-
}
|
|
31831
|
-
console.log(`[BackgroundUpload] ${pendingChunks.length} chunks pendentes encontrados. Modo final: ${isFinal}`);
|
|
31832
|
-
let virtualStart = this.totalBytesPurged;
|
|
31833
|
-
const chunksWithMeta = allChunks.map((c3) => {
|
|
31834
|
-
const start = virtualStart;
|
|
31835
|
-
const end = start + c3.blob.size - 1;
|
|
31836
|
-
virtualStart += c3.blob.size;
|
|
31837
|
-
return { chunk: c3, start, end };
|
|
31838
|
-
});
|
|
31839
|
-
let combinedBlobParts = [];
|
|
31840
|
-
let lastProcessedChunkId = null;
|
|
31841
|
-
let finalChunkIndex = 0;
|
|
31842
|
-
let mimeType = pendingChunks[0].mimeType;
|
|
31843
|
-
for (const meta of chunksWithMeta) {
|
|
31844
|
-
if (this.currentOffset > meta.end) continue;
|
|
31845
|
-
const sliceStart = Math.max(0, this.currentOffset - meta.start);
|
|
31846
|
-
const chunkSlice = meta.chunk.blob.slice(sliceStart);
|
|
31847
|
-
combinedBlobParts.push(chunkSlice);
|
|
31848
|
-
lastProcessedChunkId = meta.chunk.id;
|
|
31849
|
-
finalChunkIndex = meta.chunk.chunkIndex;
|
|
31850
|
-
}
|
|
31851
|
-
if (combinedBlobParts.length === 0 && !isFinal) {
|
|
31852
|
-
this.isProcessing = false;
|
|
31853
|
-
return;
|
|
31854
|
-
}
|
|
31855
|
-
let fullBlob = new Blob(combinedBlobParts, { type: mimeType });
|
|
31856
|
-
let sendableSize = fullBlob.size;
|
|
31857
|
-
let totalSizeForHeader = void 0;
|
|
31858
|
-
if (!isFinal) {
|
|
31859
|
-
sendableSize = Math.floor(fullBlob.size / this.GCS_CHUNK_SIZE) * this.GCS_CHUNK_SIZE;
|
|
31860
|
-
if (sendableSize === 0) {
|
|
31861
|
-
console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk...");
|
|
31862
|
-
this.isProcessing = false;
|
|
31863
|
-
return;
|
|
31864
|
-
}
|
|
31865
|
-
} else {
|
|
31866
|
-
totalSizeForHeader = virtualStart;
|
|
31867
|
-
}
|
|
31868
|
-
const blobToSend = fullBlob.slice(0, sendableSize);
|
|
31869
|
-
try {
|
|
31870
|
-
await this.uploadData(blobToSend, mimeType, finalChunkIndex, totalSizeForHeader);
|
|
31871
|
-
for (const meta of chunksWithMeta) {
|
|
31872
|
-
if (meta.chunk.uploaded === 0 && meta.end < this.currentOffset) {
|
|
31873
|
-
await this.chunkStorage.markAsUploaded(meta.chunk.id);
|
|
31874
|
-
this.retryCount.delete(meta.chunk.id);
|
|
31875
|
-
(_a2 = this.onChunkUploaded) == null ? void 0 : _a2.call(this, meta.chunk.id, meta.chunk.chunkIndex);
|
|
31876
|
-
console.log(`[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} marcado como enviado.`);
|
|
31877
|
-
}
|
|
31878
|
-
}
|
|
31879
|
-
if (this.config.cleanAfterUpload) {
|
|
31880
|
-
const chunksToClear = chunksWithMeta.filter((meta) => meta.chunk.uploaded === 1 || meta.chunk.uploaded === 0 && meta.end < this.currentOffset);
|
|
31881
|
-
const sizePurged = chunksToClear.reduce((acc, meta) => acc + meta.chunk.blob.size, 0);
|
|
31882
|
-
await this.chunkStorage.clearUploadedChunks(this.proctoringId);
|
|
31883
|
-
if (sizePurged > 0) {
|
|
31884
|
-
this.totalBytesPurged += sizePurged;
|
|
31885
|
-
this.saveSessionState();
|
|
31886
|
-
console.log(`[BackgroundUpload] ${sizePurged} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`);
|
|
31887
|
-
}
|
|
31888
|
-
}
|
|
31889
|
-
if (isFinal) {
|
|
31890
|
-
this.clearSessionState();
|
|
31891
|
-
}
|
|
31892
|
-
} catch (error) {
|
|
31893
|
-
console.error("[BackgroundUpload] Falha no upload:", error);
|
|
31894
|
-
(_b = this.onUploadError) == null ? void 0 : _b.call(this, lastProcessedChunkId || 0, error);
|
|
31895
|
-
}
|
|
31896
|
-
} catch (error) {
|
|
31897
|
-
console.error("[BackgroundUpload] Erro ao processar fila:", error);
|
|
31898
|
-
} finally {
|
|
31899
|
-
this.isProcessing = false;
|
|
31900
|
-
}
|
|
31901
|
-
}
|
|
31902
|
-
/**
|
|
31903
|
-
* Faz o upload bruto de dados para a sessão GCS.
|
|
31904
|
-
*/
|
|
31905
|
-
async uploadData(blob, mimeType, chunkIndex, totalSize) {
|
|
31906
|
-
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
31907
|
-
if (!this.sessionUrl) {
|
|
31908
|
-
const initiateUrl = await this.backend.initiateUpload(this.token, `${this.proctoringId}/${fileName}`, mimeType);
|
|
31909
|
-
const startResponse = await fetch(initiateUrl, {
|
|
31910
|
-
method: "POST",
|
|
31911
|
-
headers: { "x-goog-resumable": "start", "Content-Type": mimeType }
|
|
31912
|
-
});
|
|
31913
|
-
if (!startResponse.ok) throw new Error(`Falha ao iniciar: ${startResponse.status}`);
|
|
31914
|
-
this.sessionUrl = startResponse.headers.get("Location");
|
|
31915
|
-
if (!this.sessionUrl) throw new Error("Location header ausente");
|
|
31916
|
-
try {
|
|
31917
|
-
const urlObj = new URL(this.sessionUrl);
|
|
31918
|
-
const pathParts = urlObj.pathname.split("/");
|
|
31919
|
-
let bucket = pathParts[1];
|
|
31920
|
-
let object = decodeURIComponent(pathParts.slice(2).join("/"));
|
|
31921
|
-
if (pathParts.includes("b") && pathParts.includes("o")) {
|
|
31922
|
-
const bIdx = pathParts.indexOf("b") + 1;
|
|
31923
|
-
const oIdx = pathParts.indexOf("o") + 1;
|
|
31924
|
-
bucket = pathParts[bIdx];
|
|
31925
|
-
object = decodeURIComponent(pathParts.slice(oIdx).join("/"));
|
|
31926
|
-
}
|
|
31927
|
-
console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${bucket}, Objeto: ${object}`);
|
|
31928
|
-
} catch (e3) {
|
|
31929
|
-
console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`);
|
|
31930
|
-
}
|
|
31931
|
-
this.currentOffset = 0;
|
|
31932
|
-
this.saveSessionState();
|
|
31933
|
-
} else {
|
|
31934
|
-
console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);
|
|
31935
|
-
}
|
|
31936
|
-
const start = this.currentOffset;
|
|
31937
|
-
const end = start + blob.size - 1;
|
|
31938
|
-
const totalHeader = totalSize !== void 0 ? totalSize.toString() : "*";
|
|
31939
|
-
const contentRangeHeader = blob.size === 0 && totalSize !== void 0 ? `bytes */${totalHeader}` : `bytes ${start}-${end}/${totalHeader}`;
|
|
31940
|
-
console.log(`[BackgroundUpload] Enviando ${blob.size > 0 ? "dados" : "finaliza\xE7\xE3o"}: ${contentRangeHeader} (Size: ${blob.size})`);
|
|
31941
|
-
const response = await fetch(this.sessionUrl, {
|
|
31942
|
-
method: "PUT",
|
|
31943
|
-
headers: { "Content-Range": contentRangeHeader },
|
|
31944
|
-
body: blob.size > 0 ? blob : null
|
|
31945
|
-
// Usa null para garantir corpo vazio se necessário
|
|
31946
|
-
});
|
|
31947
|
-
console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${response.status}`);
|
|
31948
|
-
if (response.status !== 200 && response.status !== 201 && response.status !== 308) {
|
|
31949
|
-
const errorText = await response.text();
|
|
31950
|
-
console.error(`[BackgroundUpload] Erro GCS: ${errorText}`);
|
|
31951
|
-
throw new Error(`Status HTTP inesperado: ${response.status}`);
|
|
31952
|
-
}
|
|
31953
|
-
const rangeHeader = response.headers.get("Range");
|
|
31954
|
-
if (rangeHeader) {
|
|
31955
|
-
const lastByte = parseInt(rangeHeader.split("-")[1], 10);
|
|
31956
|
-
this.currentOffset = lastByte + 1;
|
|
31957
|
-
} else {
|
|
31958
|
-
this.currentOffset += blob.size;
|
|
31959
|
-
}
|
|
31960
|
-
this.saveSessionState();
|
|
31961
|
-
trackers.registerUploadFile(
|
|
31962
|
-
this.proctoringId,
|
|
31963
|
-
`GCS Stream Upload
|
|
31964
|
-
Size: ${blob.size}
|
|
31965
|
-
Range: ${start}-${end}
|
|
31966
|
-
Last Index: ${chunkIndex}`,
|
|
31967
|
-
"CameraChunk"
|
|
31968
|
-
);
|
|
31969
|
-
}
|
|
31970
|
-
/**
|
|
31971
|
-
* Método estático para recuperação pós-crash.
|
|
31972
|
-
* Verifica o IndexedDB em busca de chunks pendentes de qualquer sessão
|
|
31973
|
-
* e tenta enviar.
|
|
31974
|
-
*/
|
|
31975
|
-
static async recoverPendingUploads(backend, token) {
|
|
31976
|
-
const chunkStorage = new ChunkStorageService();
|
|
31977
|
-
const recoveredIds = [];
|
|
31978
|
-
try {
|
|
31979
|
-
const pendingIds = await chunkStorage.getPendingProctoringIds();
|
|
31980
|
-
if (pendingIds.length === 0) {
|
|
31981
|
-
console.log("[BackgroundUpload] Nenhum chunk pendente encontrado para recupera\xE7\xE3o.");
|
|
31982
|
-
return recoveredIds;
|
|
31983
|
-
}
|
|
31984
|
-
console.log(
|
|
31985
|
-
`[BackgroundUpload] Recupera\xE7\xE3o p\xF3s-crash: ${pendingIds.length} sess\xE3o(\xF5es) com chunks pendentes.`
|
|
31986
|
-
);
|
|
31987
|
-
for (const proctoringId2 of pendingIds) {
|
|
31988
|
-
try {
|
|
31989
|
-
const service = new _BackgroundUploadService(
|
|
31990
|
-
proctoringId2,
|
|
31991
|
-
token,
|
|
31992
|
-
backend,
|
|
31993
|
-
chunkStorage,
|
|
31994
|
-
{ cleanAfterUpload: true }
|
|
31995
|
-
);
|
|
31996
|
-
await service.flush();
|
|
31997
|
-
recoveredIds.push(proctoringId2);
|
|
31998
|
-
console.log(
|
|
31999
|
-
`[BackgroundUpload] Chunks da sess\xE3o ${proctoringId2} recuperados com sucesso.`
|
|
32000
|
-
);
|
|
32001
|
-
} catch (error) {
|
|
32002
|
-
console.error(
|
|
32003
|
-
`[BackgroundUpload] Erro ao recuperar chunks da sess\xE3o ${proctoringId2}:`,
|
|
32004
|
-
error
|
|
32005
|
-
);
|
|
32006
|
-
}
|
|
32007
|
-
}
|
|
32008
|
-
} catch (error) {
|
|
32009
|
-
console.error("[BackgroundUpload] Erro geral na recupera\xE7\xE3o:", error);
|
|
32010
|
-
}
|
|
32011
|
-
return recoveredIds;
|
|
32012
|
-
}
|
|
32013
|
-
sleep(ms2) {
|
|
32014
|
-
return new Promise((resolve) => setTimeout(resolve, ms2));
|
|
32015
|
-
}
|
|
32016
|
-
};
|
|
32017
|
-
|
|
32018
31401
|
// src/new-flow/recorders/CameraRecorder.ts
|
|
32019
31402
|
var import_jszip_min = __toESM(require_jszip_min());
|
|
32020
31403
|
var pkg = require_fix_webm_duration();
|
|
32021
31404
|
var fixWebmDuration = pkg.default || pkg;
|
|
32022
|
-
var
|
|
31405
|
+
var CameraRecorder = class {
|
|
32023
31406
|
constructor(options, videoOptions, paramsConfig, backend, backendToken) {
|
|
32024
31407
|
this.blobs = [];
|
|
32025
31408
|
this.paramsConfig = {
|
|
@@ -32072,13 +31455,6 @@ var _CameraRecorder = class _CameraRecorder {
|
|
|
32072
31455
|
this.videoElement = null;
|
|
32073
31456
|
this.duration = 0;
|
|
32074
31457
|
this.stopped = false;
|
|
32075
|
-
this.backgroundUpload = null;
|
|
32076
|
-
this.chunkIndex = 0;
|
|
32077
|
-
/** Lista de promises de chunks sendo salvos no IndexedDB para evitar race conditions no stop */
|
|
32078
|
-
this.pendingChunkSaves = [];
|
|
32079
|
-
// Handlers bound para poder remover os listeners depois
|
|
32080
|
-
this.boundVisibilityHandler = null;
|
|
32081
|
-
this.boundPageHideHandler = null;
|
|
32082
31458
|
this.currentRetries = 0;
|
|
32083
31459
|
this.packageCount = 0;
|
|
32084
31460
|
this.failedUploads = 0;
|
|
@@ -32089,122 +31465,10 @@ var _CameraRecorder = class _CameraRecorder {
|
|
|
32089
31465
|
this.backendToken = backendToken;
|
|
32090
31466
|
paramsConfig && (this.paramsConfig = paramsConfig);
|
|
32091
31467
|
}
|
|
32092
|
-
/**
|
|
32093
|
-
* Determina se o fluxo de chunks e lifecycle deve estar ativo.
|
|
32094
|
-
* Retorna true se:
|
|
32095
|
-
* 1. O proctoringId já foi definido (ou seja, estamos em uma sessão real, NÃO no checkDevices)
|
|
32096
|
-
* 2. E (`useChunkRecording` foi explicitamente setado como true OU o dispositivo é mobile)
|
|
32097
|
-
*/
|
|
32098
|
-
get isChunkEnabled() {
|
|
32099
|
-
return false;
|
|
32100
|
-
}
|
|
32101
31468
|
setProctoringId(proctoringId2) {
|
|
32102
31469
|
this.proctoringId = proctoringId2;
|
|
32103
31470
|
this.proctoringId && this.backend && (this.upload = new UploadService(this.proctoringId, this.backend));
|
|
32104
31471
|
setRecorderProctoringId(proctoringId2);
|
|
32105
|
-
if (this.isChunkEnabled) {
|
|
32106
|
-
this.chunkStorage = new ChunkStorageService();
|
|
32107
|
-
if (this.backend && this.backendToken) {
|
|
32108
|
-
this.backgroundUpload = new BackgroundUploadService(
|
|
32109
|
-
this.proctoringId,
|
|
32110
|
-
this.backendToken,
|
|
32111
|
-
this.backend,
|
|
32112
|
-
this.chunkStorage,
|
|
32113
|
-
{ pollInterval: 5e3, maxRetries: 5, cleanAfterUpload: true }
|
|
32114
|
-
);
|
|
32115
|
-
}
|
|
32116
|
-
this.persistSessionState("IN_PROGRESS");
|
|
32117
|
-
console.log(
|
|
32118
|
-
`[CameraRecorder] Chunk recording ATIVO (type: ${this.options.proctoringType}, mobile: ${isMobileDevice()})`
|
|
32119
|
-
);
|
|
32120
|
-
} else {
|
|
32121
|
-
console.log(
|
|
32122
|
-
`[CameraRecorder] Chunk recording INATIVO (type: ${this.options.proctoringType}) \u2014 modo cl\xE1ssico.`
|
|
32123
|
-
);
|
|
32124
|
-
}
|
|
32125
|
-
}
|
|
32126
|
-
// ========================
|
|
32127
|
-
// Session State Persistence (localStorage)
|
|
32128
|
-
// ========================
|
|
32129
|
-
persistSessionState(status) {
|
|
32130
|
-
try {
|
|
32131
|
-
const data = {
|
|
32132
|
-
proctoringId: this.proctoringId,
|
|
32133
|
-
status,
|
|
32134
|
-
timestamp: Date.now()
|
|
32135
|
-
};
|
|
32136
|
-
localStorage.setItem(_CameraRecorder.LS_SESSION_KEY, JSON.stringify(data));
|
|
32137
|
-
} catch (e3) {
|
|
32138
|
-
console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel salvar estado no localStorage:", e3);
|
|
32139
|
-
}
|
|
32140
|
-
}
|
|
32141
|
-
clearSessionState() {
|
|
32142
|
-
try {
|
|
32143
|
-
localStorage.removeItem(_CameraRecorder.LS_SESSION_KEY);
|
|
32144
|
-
} catch (e3) {
|
|
32145
|
-
console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel limpar estado do localStorage:", e3);
|
|
32146
|
-
}
|
|
32147
|
-
}
|
|
32148
|
-
/**
|
|
32149
|
-
* Verifica se existe uma sessão ativa anterior no localStorage.
|
|
32150
|
-
* Retorna os dados da sessão se ela estiver em IN_PROGRESS, null caso contrário.
|
|
32151
|
-
*/
|
|
32152
|
-
static checkForActiveSession() {
|
|
32153
|
-
try {
|
|
32154
|
-
const raw = localStorage.getItem(_CameraRecorder.LS_SESSION_KEY);
|
|
32155
|
-
if (!raw) return null;
|
|
32156
|
-
const data = JSON.parse(raw);
|
|
32157
|
-
if (data.status === "IN_PROGRESS") return data;
|
|
32158
|
-
return null;
|
|
32159
|
-
} catch {
|
|
32160
|
-
return null;
|
|
32161
|
-
}
|
|
32162
|
-
}
|
|
32163
|
-
// ========================
|
|
32164
|
-
// Page Lifecycle Management
|
|
32165
|
-
// ========================
|
|
32166
|
-
setupLifecycleListeners() {
|
|
32167
|
-
this.boundVisibilityHandler = () => this.handleVisibilityChange();
|
|
32168
|
-
this.boundPageHideHandler = () => this.handlePageHide();
|
|
32169
|
-
document.addEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
32170
|
-
window.addEventListener("pagehide", this.boundPageHideHandler);
|
|
32171
|
-
}
|
|
32172
|
-
removeLifecycleListeners() {
|
|
32173
|
-
if (this.boundVisibilityHandler) {
|
|
32174
|
-
document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
32175
|
-
this.boundVisibilityHandler = null;
|
|
32176
|
-
}
|
|
32177
|
-
if (this.boundPageHideHandler) {
|
|
32178
|
-
window.removeEventListener("pagehide", this.boundPageHideHandler);
|
|
32179
|
-
this.boundPageHideHandler = null;
|
|
32180
|
-
}
|
|
32181
|
-
}
|
|
32182
|
-
handleVisibilityChange() {
|
|
32183
|
-
var _a2;
|
|
32184
|
-
if (document.visibilityState === "hidden") {
|
|
32185
|
-
console.log("[CameraRecorder] P\xE1gina ficou invis\xEDvel \u2014 sess\xE3o potencialmente interrompida.");
|
|
32186
|
-
this.persistSessionState("INTERRUPTED");
|
|
32187
|
-
this.proctoringId && trackers.registerError(
|
|
32188
|
-
this.proctoringId,
|
|
32189
|
-
"Visibility API: P\xE1gina ficou oculta (hidden). Poss\xEDvel troca de app ou minimiza\xE7\xE3o."
|
|
32190
|
-
);
|
|
32191
|
-
} else if (document.visibilityState === "visible") {
|
|
32192
|
-
console.log("[CameraRecorder] P\xE1gina vis\xEDvel novamente \u2014 verificando estado da grava\xE7\xE3o.");
|
|
32193
|
-
this.persistSessionState("IN_PROGRESS");
|
|
32194
|
-
this.proctoringId && trackers.registerError(
|
|
32195
|
-
this.proctoringId,
|
|
32196
|
-
"Visibility API: P\xE1gina voltou a ficar vis\xEDvel. Usu\xE1rio retornou."
|
|
32197
|
-
);
|
|
32198
|
-
(_a2 = this.onVisibilityRestored) == null ? void 0 : _a2.call(this);
|
|
32199
|
-
}
|
|
32200
|
-
}
|
|
32201
|
-
handlePageHide() {
|
|
32202
|
-
console.log("[CameraRecorder] pagehide detectado \u2014 persistindo estado.");
|
|
32203
|
-
this.persistSessionState("INTERRUPTED");
|
|
32204
|
-
this.proctoringId && trackers.registerError(
|
|
32205
|
-
this.proctoringId,
|
|
32206
|
-
"Page Lifecycle: pagehide event detectado. P\xE1gina est\xE1 sendo descarregada."
|
|
32207
|
-
);
|
|
32208
31472
|
}
|
|
32209
31473
|
async initializeDetectors() {
|
|
32210
31474
|
var _a2, _b, _c2;
|
|
@@ -32357,15 +31621,9 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
32357
31621
|
await new Promise((r3) => setTimeout(r3, 300));
|
|
32358
31622
|
}
|
|
32359
31623
|
async startRecording() {
|
|
32360
|
-
var _a2, _b, _c2, _d, _e3, _f, _g
|
|
31624
|
+
var _a2, _b, _c2, _d, _e3, _f, _g;
|
|
32361
31625
|
await this.startStream();
|
|
32362
31626
|
await this.attachAndWarmup(this.cameraStream);
|
|
32363
|
-
const recorderOpts = this.isChunkEnabled ? {
|
|
32364
|
-
timeslice: _CameraRecorder.CHUNK_TIMESLICE_MS,
|
|
32365
|
-
onChunkAvailable: (blob, idx) => {
|
|
32366
|
-
this.handleNewChunk(blob, idx);
|
|
32367
|
-
}
|
|
32368
|
-
} : {};
|
|
32369
31627
|
const {
|
|
32370
31628
|
startRecording,
|
|
32371
31629
|
stopRecording,
|
|
@@ -32381,8 +31639,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
32381
31639
|
this.blobs,
|
|
32382
31640
|
this.options.onBufferSizeError,
|
|
32383
31641
|
(e3) => this.bufferError(e3),
|
|
32384
|
-
false
|
|
32385
|
-
recorderOpts
|
|
31642
|
+
false
|
|
32386
31643
|
);
|
|
32387
31644
|
this.recordingStart = startRecording;
|
|
32388
31645
|
this.recordingStop = stopRecording;
|
|
@@ -32392,18 +31649,13 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
32392
31649
|
this.getBufferSize = getBufferSize;
|
|
32393
31650
|
this.getStartTime = getStartTime;
|
|
32394
31651
|
this.getDuration = getDuration;
|
|
32395
|
-
this.chunkIndex = 0;
|
|
32396
|
-
if (this.isChunkEnabled) {
|
|
32397
|
-
(_a2 = this.backgroundUpload) == null ? void 0 : _a2.start();
|
|
32398
|
-
this.setupLifecycleListeners();
|
|
32399
|
-
}
|
|
32400
31652
|
try {
|
|
32401
31653
|
await new Promise((r3) => setTimeout(r3, 500));
|
|
32402
31654
|
await this.recordingStart();
|
|
32403
31655
|
} catch (error) {
|
|
32404
31656
|
console.log("Camera Recorder error", error);
|
|
32405
31657
|
this.stopRecording();
|
|
32406
|
-
const maxRetries = ((
|
|
31658
|
+
const maxRetries = ((_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.maxRetries) || 3;
|
|
32407
31659
|
if (this.currentRetries < maxRetries) {
|
|
32408
31660
|
console.log("Camera Recorder retry", this.currentRetries);
|
|
32409
31661
|
this.currentRetries++;
|
|
@@ -32413,13 +31665,13 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
32413
31665
|
}
|
|
32414
31666
|
}
|
|
32415
31667
|
this.stopped = false;
|
|
32416
|
-
if (((
|
|
31668
|
+
if (((_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.detectPerson) || ((_c2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _c2.detectCellPhone) || ((_d = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _d.detectFace)) {
|
|
32417
31669
|
await this.initializeDetectors();
|
|
32418
31670
|
}
|
|
32419
|
-
if ((
|
|
31671
|
+
if ((_e3 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _e3.detectFace) {
|
|
32420
31672
|
await this.faceDetection.enableCam(this.cameraStream);
|
|
32421
31673
|
}
|
|
32422
|
-
if (((
|
|
31674
|
+
if (((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectPerson) || ((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectCellPhone)) {
|
|
32423
31675
|
await this.objectDetection.enableCam(this.cameraStream);
|
|
32424
31676
|
}
|
|
32425
31677
|
this.filesToUpload = [];
|
|
@@ -32478,50 +31730,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
32478
31730
|
await this.sendPackage();
|
|
32479
31731
|
await this.filesToUpload.splice(0, this.filesToUpload.length);
|
|
32480
31732
|
}
|
|
32481
|
-
if (this.isChunkEnabled) {
|
|
32482
|
-
if (this.backgroundUpload) {
|
|
32483
|
-
try {
|
|
32484
|
-
if (this.pendingChunkSaves.length > 0) {
|
|
32485
|
-
console.log(`[CameraRecorder] Aguardando ${this.pendingChunkSaves.length} salvamentos de chunks pendentes...`);
|
|
32486
|
-
await Promise.all(this.pendingChunkSaves);
|
|
32487
|
-
}
|
|
32488
|
-
await this.backgroundUpload.flush();
|
|
32489
|
-
} catch (e3) {
|
|
32490
|
-
console.warn("[CameraRecorder] Erro ao fazer flush dos chunks:", e3);
|
|
32491
|
-
}
|
|
32492
|
-
this.backgroundUpload.stop();
|
|
32493
|
-
}
|
|
32494
|
-
this.removeLifecycleListeners();
|
|
32495
|
-
this.persistSessionState("FINISHED");
|
|
32496
|
-
}
|
|
32497
|
-
}
|
|
32498
|
-
/**
|
|
32499
|
-
* Callback chamado pelo recorder a cada novo chunk de vídeo disponível.
|
|
32500
|
-
* Salva o chunk no IndexedDB para persistência e recuperação.
|
|
32501
|
-
*/
|
|
32502
|
-
async handleNewChunk(blob, idx) {
|
|
32503
|
-
if (!this.proctoringId || !this.chunkStorage) return;
|
|
32504
|
-
const savePromise = (async () => {
|
|
32505
|
-
var _a2;
|
|
32506
|
-
try {
|
|
32507
|
-
await this.chunkStorage.saveChunk({
|
|
32508
|
-
proctoringId: this.proctoringId,
|
|
32509
|
-
chunkIndex: this.chunkIndex,
|
|
32510
|
-
blob,
|
|
32511
|
-
timestamp: Date.now(),
|
|
32512
|
-
uploaded: 0,
|
|
32513
|
-
mimeType: ((_a2 = this.recorderOptions) == null ? void 0 : _a2.mimeType) || "video/webm"
|
|
32514
|
-
});
|
|
32515
|
-
this.chunkIndex++;
|
|
32516
|
-
console.log(`[CameraRecorder] Chunk ${this.chunkIndex - 1} salvo no IndexedDB.`);
|
|
32517
|
-
} catch (error) {
|
|
32518
|
-
console.error("[CameraRecorder] Erro ao salvar chunk no IndexedDB:", error);
|
|
32519
|
-
}
|
|
32520
|
-
})();
|
|
32521
|
-
this.pendingChunkSaves.push(savePromise);
|
|
32522
|
-
savePromise.finally(() => {
|
|
32523
|
-
this.pendingChunkSaves = this.pendingChunkSaves.filter((p3) => p3 !== savePromise);
|
|
32524
|
-
});
|
|
32525
31733
|
}
|
|
32526
31734
|
async pauseRecording() {
|
|
32527
31735
|
await this.recordingPause();
|
|
@@ -32657,32 +31865,16 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
32657
31865
|
if (this.blobs != null)
|
|
32658
31866
|
trackers.registerSaveOnSession(
|
|
32659
31867
|
this.proctoringId,
|
|
32660
|
-
`Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()}
|
|
31868
|
+
`Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} `
|
|
32661
31869
|
);
|
|
32662
31870
|
const settings = this.cameraStream.getVideoTracks()[0].getSettings();
|
|
32663
31871
|
const settingsAudio = this.cameraStream.getAudioTracks()[0].getSettings();
|
|
32664
31872
|
if (this.options.proctoringType == "VIDEO" || this.options.proctoringType == "REALTIME" || this.options.proctoringType == "IMAGE" && ((_a2 = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _a2.saveVideo)) {
|
|
32665
|
-
let videoFile;
|
|
32666
|
-
if (this.isChunkEnabled) {
|
|
32667
|
-
const isStable = await this.checkInternetStability();
|
|
32668
|
-
if (isStable) {
|
|
32669
|
-
} else {
|
|
32670
|
-
if (this.backend && this.backendToken && this.proctoringId) {
|
|
32671
|
-
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
32672
|
-
const objectName = `${this.proctoringId}/${fileName}`;
|
|
32673
|
-
const isUploaded = await this.backend.checkUpload(this.backendToken, objectName, "video/webm");
|
|
32674
|
-
if (isUploaded) {
|
|
32675
|
-
this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
|
|
32676
|
-
return;
|
|
32677
|
-
}
|
|
32678
|
-
}
|
|
32679
|
-
}
|
|
32680
|
-
}
|
|
32681
31873
|
const rawBlob = new Blob(this.blobs, {
|
|
32682
31874
|
type: ((_b = this.recorderOptions) == null ? void 0 : _b.mimeType) || "video/webm"
|
|
32683
31875
|
});
|
|
32684
31876
|
const fixedBlob = await fixWebmDuration(rawBlob, this.duration);
|
|
32685
|
-
|
|
31877
|
+
const fileWithDuration = new File(
|
|
32686
31878
|
[fixedBlob],
|
|
32687
31879
|
`EP_${session.id}_camera_0.webm`,
|
|
32688
31880
|
{ type: rawBlob.type }
|
|
@@ -32694,7 +31886,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
32694
31886
|
|
|
32695
31887
|
Video:
|
|
32696
31888
|
${JSON.stringify(this.recorderOptions)}`,
|
|
32697
|
-
file:
|
|
31889
|
+
file: fileWithDuration,
|
|
32698
31890
|
origin: "Camera" /* Camera */
|
|
32699
31891
|
});
|
|
32700
31892
|
}
|
|
@@ -32709,28 +31901,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
32709
31901
|
});
|
|
32710
31902
|
});
|
|
32711
31903
|
}
|
|
32712
|
-
/**
|
|
32713
|
-
* Verifica se a internet está estável para realizar o upload do vídeo na íntegra.
|
|
32714
|
-
*/
|
|
32715
|
-
async checkInternetStability() {
|
|
32716
|
-
var _a2;
|
|
32717
|
-
if (!navigator.onLine) return false;
|
|
32718
|
-
try {
|
|
32719
|
-
const controller = new AbortController();
|
|
32720
|
-
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
32721
|
-
const baseUrl = (_a2 = this.backend) == null ? void 0 : _a2.getBaseUrl();
|
|
32722
|
-
if (!baseUrl) return true;
|
|
32723
|
-
const response = await fetch(`${baseUrl}/Client/health`, {
|
|
32724
|
-
method: "GET",
|
|
32725
|
-
signal: controller.signal
|
|
32726
|
-
});
|
|
32727
|
-
clearTimeout(timeoutId);
|
|
32728
|
-
return response.status < 500;
|
|
32729
|
-
} catch (e3) {
|
|
32730
|
-
console.warn("[CameraRecorder] Internet inst\xE1vel ou lenta detectada para upload integral.");
|
|
32731
|
-
return false;
|
|
32732
|
-
}
|
|
32733
|
-
}
|
|
32734
31904
|
onNoiseDetected() {
|
|
32735
31905
|
var _a2, _b, _c2;
|
|
32736
31906
|
if (this.options.proctoringType === "REALTIME") return;
|
|
@@ -32755,14 +31925,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
32755
31925
|
this.noiseWait++;
|
|
32756
31926
|
}
|
|
32757
31927
|
};
|
|
32758
|
-
// ========================
|
|
32759
|
-
// Chunk & Lifecycle
|
|
32760
|
-
// ========================
|
|
32761
|
-
/** Intervalo de cada chunk em ms (padrão: 60 segundos) */
|
|
32762
|
-
_CameraRecorder.CHUNK_TIMESLICE_MS = 6e4;
|
|
32763
|
-
/** Chave do localStorage para persistir estado da sessão */
|
|
32764
|
-
_CameraRecorder.LS_SESSION_KEY = "ep_proctoring_session";
|
|
32765
|
-
var CameraRecorder = _CameraRecorder;
|
|
32766
31928
|
|
|
32767
31929
|
// src/new-flow/checkers/DeviceCheckerUI.ts
|
|
32768
31930
|
var DeviceCheckerUI = class {
|
|
@@ -38887,20 +38049,6 @@ var Proctoring = class {
|
|
|
38887
38049
|
} catch (error) {
|
|
38888
38050
|
throw SAFE_BROWSER_API_NOT_FOUND;
|
|
38889
38051
|
}
|
|
38890
|
-
this.allRecorders.cameraRecorder.onVisibilityRestored = () => {
|
|
38891
|
-
console.log("[Proctoring] Usu\xE1rio retornou ao browser.");
|
|
38892
|
-
this.onVisibilityRestoredCallback();
|
|
38893
|
-
};
|
|
38894
|
-
if (this.sessionOptions.proctoringType === "REALTIME" && !isSafeBrowser()) {
|
|
38895
|
-
try {
|
|
38896
|
-
await BackgroundUploadService.recoverPendingUploads(
|
|
38897
|
-
this.backend,
|
|
38898
|
-
this.context.token
|
|
38899
|
-
);
|
|
38900
|
-
} catch (e3) {
|
|
38901
|
-
console.warn("[Proctoring] Erro ao recuperar chunks de sess\xE3o anterior:", e3);
|
|
38902
|
-
}
|
|
38903
|
-
}
|
|
38904
38052
|
try {
|
|
38905
38053
|
console.log("Starting recorders");
|
|
38906
38054
|
await this.recorder.startAll();
|