easyproctor-hml 2.7.10 → 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/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, recorderOpts) {
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
- chunkIndex = 0;
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,686 +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
- * WebKit pode falhar em IDB.add/put com "Error preparing Blob/File data..." se o
31419
- * ArrayBuffer ainda estiver associado ao Blob (ex.: blob.arrayBuffer()) ou no
31420
- * round-trip get→put do mesmo registro. Esta cópia remove qualquer vínculo.
31421
- */
31422
- static detachedArrayBufferCopy(src) {
31423
- const next = new Uint8Array(src.byteLength);
31424
- next.set(new Uint8Array(src));
31425
- return next.buffer;
31426
- }
31427
- /**
31428
- * Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
31429
- */
31430
- async connect() {
31431
- if (this.db) return this.db;
31432
- return new Promise((resolve, reject) => {
31433
- const request = window.indexedDB.open(
31434
- _ChunkStorageService.DB_NAME,
31435
- _ChunkStorageService.DB_VERSION
31436
- );
31437
- request.onerror = () => {
31438
- reject(new Error("N\xE3o foi poss\xEDvel conectar ao IndexedDB para chunks."));
31439
- };
31440
- request.onupgradeneeded = () => {
31441
- const db = request.result;
31442
- if (db.objectStoreNames.contains(_ChunkStorageService.STORE_NAME)) {
31443
- db.deleteObjectStore(_ChunkStorageService.STORE_NAME);
31444
- }
31445
- const store = db.createObjectStore(_ChunkStorageService.STORE_NAME, {
31446
- keyPath: "id",
31447
- autoIncrement: true
31448
- });
31449
- store.createIndex("proctoringId", "proctoringId", { unique: false });
31450
- store.createIndex("uploaded", "uploaded", { unique: false });
31451
- store.createIndex("proctoringId_uploaded", ["proctoringId", "uploaded"], {
31452
- unique: false
31453
- });
31454
- };
31455
- request.onsuccess = () => {
31456
- this.db = request.result;
31457
- resolve(this.db);
31458
- };
31459
- });
31460
- }
31461
- /**
31462
- * Salva um chunk de vídeo no IndexedDB.
31463
- */
31464
- async saveChunk(chunk) {
31465
- const db = await this.connect();
31466
- const record = {
31467
- proctoringId: chunk.proctoringId,
31468
- chunkIndex: chunk.chunkIndex,
31469
- arrayBuffer: _ChunkStorageService.detachedArrayBufferCopy(chunk.arrayBuffer),
31470
- timestamp: chunk.timestamp,
31471
- uploaded: chunk.uploaded,
31472
- mimeType: chunk.mimeType
31473
- };
31474
- return new Promise((resolve, reject) => {
31475
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
31476
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31477
- const request = store.add(record);
31478
- request.onsuccess = () => {
31479
- resolve(request.result);
31480
- };
31481
- request.onerror = () => {
31482
- var _a2;
31483
- reject(new Error(`Erro ao salvar chunk no IndexedDB: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31484
- };
31485
- });
31486
- }
31487
- /**
31488
- * Retorna todos os chunks pendentes (não enviados) de um proctoringId específico.
31489
- */
31490
- async getPendingChunks(proctoringId2) {
31491
- const db = await this.connect();
31492
- return new Promise((resolve, reject) => {
31493
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
31494
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31495
- const index = store.index("proctoringId_uploaded");
31496
- const range = IDBKeyRange.only([proctoringId2, 0]);
31497
- const request = index.getAll(range);
31498
- request.onsuccess = () => {
31499
- const chunks = request.result.sort(
31500
- (a3, b3) => a3.chunkIndex - b3.chunkIndex
31501
- );
31502
- resolve(chunks);
31503
- };
31504
- request.onerror = () => {
31505
- var _a2;
31506
- reject(new Error(`Erro ao buscar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31507
- };
31508
- });
31509
- }
31510
- /**
31511
- * Retorna todos os chunks (enviados ou não) de um proctoringId específico.
31512
- */
31513
- async getAllChunks(proctoringId2) {
31514
- const db = await this.connect();
31515
- return new Promise((resolve, reject) => {
31516
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
31517
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31518
- const index = store.index("proctoringId");
31519
- const range = IDBKeyRange.only(proctoringId2);
31520
- const request = index.getAll(range);
31521
- request.onsuccess = () => {
31522
- const chunks = request.result.sort(
31523
- (a3, b3) => a3.chunkIndex - b3.chunkIndex
31524
- );
31525
- resolve(chunks);
31526
- };
31527
- request.onerror = () => {
31528
- var _a2;
31529
- reject(new Error(`Erro ao buscar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31530
- };
31531
- });
31532
- }
31533
- /**
31534
- * Remove vários chunks numa única transação (delete por chave — sem put com buffer).
31535
- */
31536
- async deleteChunkIds(chunkIds) {
31537
- if (chunkIds.length === 0) return;
31538
- const db = await this.connect();
31539
- return new Promise((resolve, reject) => {
31540
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
31541
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31542
- transaction.oncomplete = () => resolve();
31543
- transaction.onerror = () => {
31544
- var _a2, _b;
31545
- return reject(
31546
- new Error(
31547
- `Erro ao remover chunks em lote: ${(_b = (_a2 = transaction.error) == null ? void 0 : _a2.message) != null ? _b : "unknown"}`
31548
- )
31549
- );
31550
- };
31551
- transaction.onabort = () => {
31552
- var _a2, _b;
31553
- return reject(new Error(`Remo\xE7\xE3o em lote abortada: ${(_b = (_a2 = transaction.error) == null ? void 0 : _a2.message) != null ? _b : "unknown"}`));
31554
- };
31555
- for (const id of chunkIds) {
31556
- store.delete(id);
31557
- }
31558
- });
31559
- }
31560
- /**
31561
- * Remove registros com uploaded = 1 (vestígio de versões antigas que faziam put).
31562
- * Usa openKeyCursor + delete por primaryKey para não materializar o valor (WebKit/iOS).
31563
- */
31564
- async clearUploadedChunks(proctoringId2) {
31565
- const db = await this.connect();
31566
- return new Promise((resolve, reject) => {
31567
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
31568
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31569
- const index = store.index("proctoringId_uploaded");
31570
- const range = IDBKeyRange.only([proctoringId2, 1]);
31571
- const request = index.openKeyCursor(range);
31572
- request.onsuccess = () => {
31573
- const cursor = request.result;
31574
- if (cursor) {
31575
- store.delete(cursor.primaryKey);
31576
- cursor.continue();
31577
- } else {
31578
- resolve();
31579
- }
31580
- };
31581
- request.onerror = () => {
31582
- var _a2;
31583
- return reject(new Error(`Erro ao limpar chunks enviados: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31584
- };
31585
- });
31586
- }
31587
- /**
31588
- * Remove TODOS os chunks de um proctoringId (limpeza completa pós-finalização).
31589
- */
31590
- async clearAllChunks(proctoringId2) {
31591
- const db = await this.connect();
31592
- return new Promise((resolve, reject) => {
31593
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
31594
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31595
- const index = store.index("proctoringId");
31596
- const range = IDBKeyRange.only(proctoringId2);
31597
- const request = index.openKeyCursor(range);
31598
- request.onsuccess = () => {
31599
- const cursor = request.result;
31600
- if (cursor) {
31601
- store.delete(cursor.primaryKey);
31602
- cursor.continue();
31603
- } else {
31604
- resolve();
31605
- }
31606
- };
31607
- request.onerror = () => {
31608
- var _a2;
31609
- return reject(new Error(`Erro ao limpar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31610
- };
31611
- });
31612
- }
31613
- /**
31614
- * Verifica se existem chunks pendentes para qualquer proctoringId.
31615
- * Útil na recuperação pós-crash.
31616
- */
31617
- async hasAnyPendingChunks() {
31618
- const db = await this.connect();
31619
- return new Promise((resolve, reject) => {
31620
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
31621
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31622
- const index = store.index("uploaded");
31623
- const range = IDBKeyRange.only(0);
31624
- const request = index.count(range);
31625
- request.onsuccess = () => {
31626
- resolve(request.result > 0);
31627
- };
31628
- request.onerror = () => {
31629
- var _a2;
31630
- return reject(new Error(`Erro ao verificar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31631
- };
31632
- });
31633
- }
31634
- /**
31635
- * Retorna todos os proctoringIds que possuem chunks pendentes.
31636
- * Útil na recuperação pós-crash para saber quais sessões precisam ser finalizadas.
31637
- */
31638
- async getPendingProctoringIds() {
31639
- const db = await this.connect();
31640
- return new Promise((resolve, reject) => {
31641
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
31642
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31643
- const index = store.index("uploaded");
31644
- const range = IDBKeyRange.only(0);
31645
- const request = index.getAll(range);
31646
- request.onsuccess = () => {
31647
- const chunks = request.result;
31648
- const ids = [...new Set(chunks.map((c3) => c3.proctoringId))];
31649
- resolve(ids);
31650
- };
31651
- request.onerror = () => {
31652
- var _a2;
31653
- return reject(new Error(`Erro ao buscar proctoringIds pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31654
- };
31655
- });
31656
- }
31657
- /**
31658
- * Fecha a conexão com o banco.
31659
- */
31660
- close() {
31661
- if (this.db) {
31662
- this.db.close();
31663
- this.db = null;
31664
- }
31665
- }
31666
- };
31667
- _ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
31668
- /** v2: índices numéricos; v3: ArrayBuffer no payload; v4: cópia explícita de bytes (WebKit/iOS) */
31669
- _ChunkStorageService.DB_VERSION = 4;
31670
- _ChunkStorageService.STORE_NAME = "chunks";
31671
- var ChunkStorageService = _ChunkStorageService;
31672
-
31673
- // src/new-flow/chunk/BackgroundUploadService.ts
31674
- var DEFAULT_CONFIG = {
31675
- pollInterval: 5e3,
31676
- maxRetries: 5,
31677
- baseRetryDelay: 2e3,
31678
- cleanAfterUpload: true
31679
- };
31680
- var BackgroundUploadService = class _BackgroundUploadService {
31681
- constructor(proctoringId2, token, backend, chunkStorage, config) {
31682
- this.pollTimer = null;
31683
- this.isProcessing = false;
31684
- this.isRunning = false;
31685
- /** Mapa de chunkId -> número de tentativas já feitas */
31686
- this.retryCount = /* @__PURE__ */ new Map();
31687
- /** GCS Resumable Upload State */
31688
- this.sessionUrl = null;
31689
- this.currentOffset = 0;
31690
- this.totalBytesPurged = 0;
31691
- this.STORAGE_KEY_PREFIX = "ep_upload_session_";
31692
- this.GCS_CHUNK_SIZE = 256 * 1024;
31693
- this.proctoringId = proctoringId2.trim();
31694
- this.token = token;
31695
- this.backend = backend;
31696
- this.chunkStorage = chunkStorage;
31697
- this.config = { ...DEFAULT_CONFIG, ...config };
31698
- this.loadSessionState();
31699
- }
31700
- loadSessionState() {
31701
- try {
31702
- const stored = localStorage.getItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
31703
- if (stored) {
31704
- const { sessionUrl, currentOffset, totalBytesPurged } = JSON.parse(stored);
31705
- this.sessionUrl = sessionUrl;
31706
- this.currentOffset = currentOffset;
31707
- this.totalBytesPurged = totalBytesPurged || 0;
31708
- }
31709
- } catch (e3) {
31710
- console.warn("[BackgroundUpload] Erro ao carregar estado da sess\xE3o:", e3);
31711
- }
31712
- }
31713
- saveSessionState() {
31714
- try {
31715
- localStorage.setItem(
31716
- `${this.STORAGE_KEY_PREFIX}${this.proctoringId}`,
31717
- JSON.stringify({
31718
- sessionUrl: this.sessionUrl,
31719
- currentOffset: this.currentOffset,
31720
- totalBytesPurged: this.totalBytesPurged
31721
- })
31722
- );
31723
- } catch (e3) {
31724
- console.warn("[BackgroundUpload] Erro ao salvar estado da sess\xE3o:", e3);
31725
- }
31726
- }
31727
- clearSessionState() {
31728
- try {
31729
- localStorage.removeItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
31730
- this.sessionUrl = null;
31731
- this.currentOffset = 0;
31732
- this.totalBytesPurged = 0;
31733
- } catch (e3) {
31734
- console.warn("[BackgroundUpload] Erro ao limpar estado da sess\xE3o:", e3);
31735
- }
31736
- }
31737
- /**
31738
- * Inicia o serviço de upload em background. Faz polling periódico no IndexedDB
31739
- * para enviar chunks pendentes.
31740
- */
31741
- start() {
31742
- if (this.isRunning) return;
31743
- this.isRunning = true;
31744
- console.log(`[BackgroundUpload] Iniciando servi\xE7o para proctoringId: ${this.proctoringId}`);
31745
- this.processQueue();
31746
- this.pollTimer = setInterval(() => {
31747
- this.processQueue(false);
31748
- }, this.config.pollInterval);
31749
- }
31750
- /**
31751
- * Para o serviço de upload em background.
31752
- */
31753
- stop() {
31754
- this.isRunning = false;
31755
- if (this.pollTimer) {
31756
- clearInterval(this.pollTimer);
31757
- this.pollTimer = null;
31758
- }
31759
- console.log(`[BackgroundUpload] Servi\xE7o parado para proctoringId: ${this.proctoringId}`);
31760
- }
31761
- /**
31762
- * Força o processamento de todos os chunks pendentes e encerra a sessão GCS.
31763
- * Útil quando a gravação é finalizada.
31764
- */
31765
- async flush() {
31766
- console.log(`[BackgroundUpload] Flush: enviando todos os chunks pendentes e finalizando...`);
31767
- let waitAttempts = 0;
31768
- while (this.isProcessing && waitAttempts < 10) {
31769
- await this.sleep(1e3);
31770
- waitAttempts++;
31771
- }
31772
- let flushRetries = 0;
31773
- const maxFlushRetries = 3;
31774
- while (flushRetries < maxFlushRetries) {
31775
- try {
31776
- await this.processQueue(true);
31777
- console.log(`[BackgroundUpload] Flush completado com sucesso.`);
31778
- return;
31779
- } catch (error) {
31780
- flushRetries++;
31781
- console.error(`[BackgroundUpload] Erro no flush (tentativa ${flushRetries}/${maxFlushRetries}):`, error);
31782
- if (flushRetries < maxFlushRetries) {
31783
- await this.sleep(2e3);
31784
- }
31785
- }
31786
- }
31787
- throw new Error(`[BackgroundUpload] Falha ao finalizar upload ap\xF3s ${maxFlushRetries} tentativas.`);
31788
- }
31789
- /**
31790
- * Sincroniza o offset local com o estado real no Google Cloud Storage.
31791
- */
31792
- async syncOffset() {
31793
- if (!this.sessionUrl) return 0;
31794
- try {
31795
- console.log(`[BackgroundUpload] Sincronizando offset com GCS...`);
31796
- const response = await fetch(this.sessionUrl, {
31797
- method: "PUT",
31798
- headers: {
31799
- "Content-Range": "bytes */*"
31800
- }
31801
- });
31802
- console.log(`[BackgroundUpload] Status da sincroniza\xE7\xE3o (syncOffset): ${response.status}`);
31803
- if (response.status === 308) {
31804
- const range = response.headers.get("Range");
31805
- if (range) {
31806
- const lastByte = parseInt(range.split("-")[1], 10);
31807
- this.currentOffset = lastByte + 1;
31808
- this.saveSessionState();
31809
- console.log(`[BackgroundUpload] Offset sincronizado: ${this.currentOffset}`);
31810
- } else {
31811
- this.currentOffset = 0;
31812
- }
31813
- } else if (response.ok || response.status === 201) {
31814
- console.log("[BackgroundUpload] Sincroniza\xE7\xE3o indicou upload JA FINALIZADO.");
31815
- this.currentOffset = -1;
31816
- } else {
31817
- console.warn(`[BackgroundUpload] Status inesperado na sincroniza\xE7\xE3o: ${response.status}`);
31818
- }
31819
- } catch (error) {
31820
- console.warn("[BackgroundUpload] Erro ao sincronizar offset:", error);
31821
- }
31822
- return this.currentOffset;
31823
- }
31824
- /**
31825
- * Verifica e envia chunks pendentes para o backend.
31826
- * @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
31827
- */
31828
- async processQueue(isFinal = false) {
31829
- var _a2, _b, _c2, _d, _e3, _f, _g, _h, _i3;
31830
- console.log(`[BackgroundUpload] processQueue init`);
31831
- if (this.isProcessing) return;
31832
- this.isProcessing = true;
31833
- try {
31834
- if (this.sessionUrl) {
31835
- await this.syncOffset();
31836
- if (this.currentOffset === -1) {
31837
- console.log("[BackgroundUpload] Sess\xE3o j\xE1 finalizada no servidor.");
31838
- this.clearSessionState();
31839
- this.isProcessing = false;
31840
- return;
31841
- }
31842
- }
31843
- console.log(`[BackgroundUpload] processQueue syncOffset ok`);
31844
- const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
31845
- const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
31846
- console.log(`[BackgroundUpload] processQueue getAllChunks ok`);
31847
- if (pendingChunks.length === 0 && !isFinal) {
31848
- this.isProcessing = false;
31849
- return;
31850
- }
31851
- if (pendingChunks.length === 0 && isFinal) {
31852
- const mimeTypeFallback = (_b = (_a2 = allChunks[allChunks.length - 1]) == null ? void 0 : _a2.mimeType) != null ? _b : "video/webm";
31853
- try {
31854
- if (!this.sessionUrl) {
31855
- this.clearSessionState();
31856
- return;
31857
- }
31858
- const totalBytes = this.currentOffset;
31859
- await this.uploadData(new ArrayBuffer(0), mimeTypeFallback, 0, totalBytes);
31860
- await this.chunkStorage.clearUploadedChunks(this.proctoringId);
31861
- this.clearSessionState();
31862
- } catch (error) {
31863
- console.error(
31864
- "[BackgroundUpload] Falha ao finalizar upload (flush sem pendentes):",
31865
- error
31866
- );
31867
- (_c2 = this.onUploadError) == null ? void 0 : _c2.call(this, 0, error);
31868
- }
31869
- return;
31870
- }
31871
- console.log(`[BackgroundUpload] ${pendingChunks.length} chunks pendentes encontrados. Modo final: ${isFinal}`);
31872
- let virtualStart = this.totalBytesPurged;
31873
- const chunksWithMeta = allChunks.map((c3) => {
31874
- const start = virtualStart;
31875
- const end = start + c3.arrayBuffer.byteLength - 1;
31876
- virtualStart += c3.arrayBuffer.byteLength;
31877
- return { chunk: c3, start, end };
31878
- });
31879
- const combinedBufferParts = [];
31880
- let lastProcessedChunkId = null;
31881
- let finalChunkIndex = 0;
31882
- const mimeType = (_g = (_f = (_d = pendingChunks[0]) == null ? void 0 : _d.mimeType) != null ? _f : (_e3 = allChunks[allChunks.length - 1]) == null ? void 0 : _e3.mimeType) != null ? _g : "video/webm";
31883
- console.log(`[BackgroundUpload] passo 1 ok`);
31884
- for (const meta of chunksWithMeta) {
31885
- if (this.currentOffset > meta.end) continue;
31886
- const sliceStart = Math.max(0, this.currentOffset - meta.start);
31887
- const chunkSlice = meta.chunk.arrayBuffer.slice(sliceStart);
31888
- combinedBufferParts.push(chunkSlice);
31889
- lastProcessedChunkId = meta.chunk.id;
31890
- finalChunkIndex = meta.chunk.chunkIndex;
31891
- }
31892
- console.log(`[BackgroundUpload] passo 2 ok`);
31893
- if (combinedBufferParts.length === 0 && !isFinal) {
31894
- this.isProcessing = false;
31895
- return;
31896
- }
31897
- const fullBuffer = _BackgroundUploadService.concatArrayBuffers(combinedBufferParts);
31898
- console.log(`[BackgroundUpload] passo 3 ok`);
31899
- let sendableSize = fullBuffer.byteLength;
31900
- let totalSizeForHeader = void 0;
31901
- if (!isFinal) {
31902
- sendableSize = Math.floor(fullBuffer.byteLength / this.GCS_CHUNK_SIZE) * this.GCS_CHUNK_SIZE;
31903
- if (sendableSize === 0) {
31904
- console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk...");
31905
- this.isProcessing = false;
31906
- return;
31907
- }
31908
- } else {
31909
- totalSizeForHeader = fullBuffer.byteLength > 0 ? virtualStart : Math.max(virtualStart, this.currentOffset);
31910
- }
31911
- console.log(`[BackgroundUpload] passo 4 ok`);
31912
- const bufferToSend = sendableSize < fullBuffer.byteLength ? fullBuffer.slice(0, sendableSize) : fullBuffer;
31913
- try {
31914
- await this.uploadData(bufferToSend, mimeType, finalChunkIndex, totalSizeForHeader);
31915
- console.log(`[BackgroundUpload] passo 5 ok`);
31916
- const fullySent = chunksWithMeta.filter(
31917
- (meta) => meta.chunk.uploaded === 0 && meta.end < this.currentOffset && meta.chunk.id != null
31918
- );
31919
- const sizePurged = fullySent.reduce(
31920
- (acc, meta) => acc + meta.chunk.arrayBuffer.byteLength,
31921
- 0
31922
- );
31923
- const idsToDelete = fullySent.map((m3) => m3.chunk.id);
31924
- if (idsToDelete.length > 0) {
31925
- await this.chunkStorage.deleteChunkIds(idsToDelete);
31926
- for (const meta of fullySent) {
31927
- this.retryCount.delete(meta.chunk.id);
31928
- (_h = this.onChunkUploaded) == null ? void 0 : _h.call(this, meta.chunk.id, meta.chunk.chunkIndex);
31929
- console.log(
31930
- `[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} removido do IndexedDB ap\xF3s upload.`
31931
- );
31932
- }
31933
- }
31934
- console.log(`[BackgroundUpload] passo 6 ok`);
31935
- await this.chunkStorage.clearUploadedChunks(this.proctoringId);
31936
- console.log(`[BackgroundUpload] passo 7 ok`);
31937
- if (this.config.cleanAfterUpload && sizePurged > 0) {
31938
- this.totalBytesPurged += sizePurged;
31939
- this.saveSessionState();
31940
- console.log(
31941
- `[BackgroundUpload] ${sizePurged} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`
31942
- );
31943
- }
31944
- console.log(`[BackgroundUpload] passo 8 ok`);
31945
- if (isFinal) {
31946
- this.clearSessionState();
31947
- }
31948
- console.log(`[BackgroundUpload] passo 9 ok`);
31949
- } catch (error) {
31950
- console.error("[BackgroundUpload] Falha no upload:", error);
31951
- (_i3 = this.onUploadError) == null ? void 0 : _i3.call(this, lastProcessedChunkId || 0, error);
31952
- }
31953
- } catch (error) {
31954
- console.error("[BackgroundUpload] Erro ao processar fila:", error);
31955
- } finally {
31956
- this.isProcessing = false;
31957
- }
31958
- }
31959
- /**
31960
- * Faz o upload bruto de dados para a sessão GCS.
31961
- */
31962
- static concatArrayBuffers(parts) {
31963
- if (parts.length === 0) return new ArrayBuffer(0);
31964
- const total = parts.reduce((sum, p3) => sum + p3.byteLength, 0);
31965
- const merged = new Uint8Array(total);
31966
- let offset = 0;
31967
- for (const p3 of parts) {
31968
- merged.set(new Uint8Array(p3), offset);
31969
- offset += p3.byteLength;
31970
- }
31971
- return merged.buffer.byteLength === total ? merged.buffer : merged.buffer.slice(merged.byteOffset, merged.byteOffset + merged.byteLength);
31972
- }
31973
- async uploadData(data, mimeType, chunkIndex, totalSize) {
31974
- const fileName = `EP_${this.proctoringId}_camera_0.webm`;
31975
- if (!this.sessionUrl) {
31976
- const initiateUrl = await this.backend.initiateUpload(this.token, `${this.proctoringId}/${fileName}`, mimeType);
31977
- const startResponse = await fetch(initiateUrl, {
31978
- method: "POST",
31979
- headers: { "x-goog-resumable": "start", "Content-Type": mimeType }
31980
- });
31981
- if (!startResponse.ok) throw new Error(`Falha ao iniciar: ${startResponse.status}`);
31982
- this.sessionUrl = startResponse.headers.get("Location");
31983
- if (!this.sessionUrl) throw new Error("Location header ausente");
31984
- try {
31985
- const urlObj = new URL(this.sessionUrl);
31986
- const pathParts = urlObj.pathname.split("/");
31987
- let bucket = pathParts[1];
31988
- let object = decodeURIComponent(pathParts.slice(2).join("/"));
31989
- if (pathParts.includes("b") && pathParts.includes("o")) {
31990
- const bIdx = pathParts.indexOf("b") + 1;
31991
- const oIdx = pathParts.indexOf("o") + 1;
31992
- bucket = pathParts[bIdx];
31993
- object = decodeURIComponent(pathParts.slice(oIdx).join("/"));
31994
- }
31995
- console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${bucket}, Objeto: ${object}`);
31996
- } catch (e3) {
31997
- console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`);
31998
- }
31999
- this.currentOffset = 0;
32000
- this.saveSessionState();
32001
- } else {
32002
- console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);
32003
- }
32004
- const start = this.currentOffset;
32005
- const end = start + data.byteLength - 1;
32006
- const totalHeader = totalSize !== void 0 ? totalSize.toString() : "*";
32007
- const contentRangeHeader = data.byteLength === 0 && totalSize !== void 0 ? `bytes */${totalHeader}` : `bytes ${start}-${end}/${totalHeader}`;
32008
- console.log(
32009
- `[BackgroundUpload] Enviando ${data.byteLength > 0 ? "dados" : "finaliza\xE7\xE3o"}: ${contentRangeHeader} (Size: ${data.byteLength})`
32010
- );
32011
- const response = await fetch(this.sessionUrl, {
32012
- method: "PUT",
32013
- headers: { "Content-Range": contentRangeHeader },
32014
- body: data.byteLength > 0 ? data : null
32015
- });
32016
- console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${response.status}`);
32017
- if (response.status !== 200 && response.status !== 201 && response.status !== 308) {
32018
- const errorText = await response.text();
32019
- console.error(`[BackgroundUpload] Erro GCS: ${errorText}`);
32020
- throw new Error(`Status HTTP inesperado: ${response.status}`);
32021
- }
32022
- const rangeHeader = response.headers.get("Range");
32023
- if (rangeHeader) {
32024
- const lastByte = parseInt(rangeHeader.split("-")[1], 10);
32025
- this.currentOffset = lastByte + 1;
32026
- } else {
32027
- this.currentOffset += data.byteLength;
32028
- }
32029
- this.saveSessionState();
32030
- trackers.registerUploadFile(
32031
- this.proctoringId,
32032
- `GCS Stream Upload
32033
- Size: ${data.byteLength}
32034
- Range: ${start}-${end}
32035
- Last Index: ${chunkIndex}`,
32036
- "CameraChunk"
32037
- );
32038
- }
32039
- /**
32040
- * Método estático para recuperação pós-crash.
32041
- * Verifica o IndexedDB em busca de chunks pendentes de qualquer sessão
32042
- * e tenta enviar.
32043
- */
32044
- static async recoverPendingUploads(backend, token) {
32045
- const chunkStorage = new ChunkStorageService();
32046
- const recoveredIds = [];
32047
- try {
32048
- const pendingIds = await chunkStorage.getPendingProctoringIds();
32049
- if (pendingIds.length === 0) {
32050
- console.log("[BackgroundUpload] Nenhum chunk pendente encontrado para recupera\xE7\xE3o.");
32051
- return recoveredIds;
32052
- }
32053
- console.log(
32054
- `[BackgroundUpload] Recupera\xE7\xE3o p\xF3s-crash: ${pendingIds.length} sess\xE3o(\xF5es) com chunks pendentes.`
32055
- );
32056
- for (const proctoringId2 of pendingIds) {
32057
- try {
32058
- const service = new _BackgroundUploadService(
32059
- proctoringId2,
32060
- token,
32061
- backend,
32062
- chunkStorage,
32063
- { cleanAfterUpload: true }
32064
- );
32065
- await service.flush();
32066
- recoveredIds.push(proctoringId2);
32067
- console.log(
32068
- `[BackgroundUpload] Chunks da sess\xE3o ${proctoringId2} recuperados com sucesso.`
32069
- );
32070
- } catch (error) {
32071
- console.error(
32072
- `[BackgroundUpload] Erro ao recuperar chunks da sess\xE3o ${proctoringId2}:`,
32073
- error
32074
- );
32075
- }
32076
- }
32077
- } catch (error) {
32078
- console.error("[BackgroundUpload] Erro geral na recupera\xE7\xE3o:", error);
32079
- }
32080
- return recoveredIds;
32081
- }
32082
- sleep(ms2) {
32083
- return new Promise((resolve) => setTimeout(resolve, ms2));
32084
- }
32085
- };
32086
-
32087
31401
  // src/new-flow/recorders/CameraRecorder.ts
32088
31402
  var import_jszip_min = __toESM(require_jszip_min());
32089
31403
  var pkg = require_fix_webm_duration();
32090
31404
  var fixWebmDuration = pkg.default || pkg;
32091
- var _CameraRecorder = class _CameraRecorder {
31405
+ var CameraRecorder = class {
32092
31406
  constructor(options, videoOptions, paramsConfig, backend, backendToken) {
32093
31407
  this.blobs = [];
32094
31408
  this.paramsConfig = {
@@ -32141,13 +31455,6 @@ var _CameraRecorder = class _CameraRecorder {
32141
31455
  this.videoElement = null;
32142
31456
  this.duration = 0;
32143
31457
  this.stopped = false;
32144
- this.backgroundUpload = null;
32145
- this.chunkIndex = 0;
32146
- /** Lista de promises de chunks sendo salvos no IndexedDB para evitar race conditions no stop */
32147
- this.pendingChunkSaves = [];
32148
- // Handlers bound para poder remover os listeners depois
32149
- this.boundVisibilityHandler = null;
32150
- this.boundPageHideHandler = null;
32151
31458
  this.currentRetries = 0;
32152
31459
  this.packageCount = 0;
32153
31460
  this.failedUploads = 0;
@@ -32158,122 +31465,10 @@ var _CameraRecorder = class _CameraRecorder {
32158
31465
  this.backendToken = backendToken;
32159
31466
  paramsConfig && (this.paramsConfig = paramsConfig);
32160
31467
  }
32161
- /**
32162
- * Determina se o fluxo de chunks e lifecycle deve estar ativo.
32163
- * Retorna true se:
32164
- * 1. O proctoringId já foi definido (ou seja, estamos em uma sessão real, NÃO no checkDevices)
32165
- * 2. E (`useChunkRecording` foi explicitamente setado como true OU o dispositivo é mobile)
32166
- */
32167
- get isChunkEnabled() {
32168
- return !!this.proctoringId && this.options.proctoringType === "REALTIME" && !isSafeBrowser();
32169
- }
32170
31468
  setProctoringId(proctoringId2) {
32171
31469
  this.proctoringId = proctoringId2;
32172
31470
  this.proctoringId && this.backend && (this.upload = new UploadService(this.proctoringId, this.backend));
32173
31471
  setRecorderProctoringId(proctoringId2);
32174
- if (this.isChunkEnabled) {
32175
- this.chunkStorage = new ChunkStorageService();
32176
- if (this.backend && this.backendToken) {
32177
- this.backgroundUpload = new BackgroundUploadService(
32178
- this.proctoringId,
32179
- this.backendToken,
32180
- this.backend,
32181
- this.chunkStorage,
32182
- { pollInterval: 5e3, maxRetries: 5, cleanAfterUpload: true }
32183
- );
32184
- }
32185
- this.persistSessionState("IN_PROGRESS");
32186
- console.log(
32187
- `[CameraRecorder] Chunk recording ATIVO (type: ${this.options.proctoringType}, mobile: ${isMobileDevice()})`
32188
- );
32189
- } else {
32190
- console.log(
32191
- `[CameraRecorder] Chunk recording INATIVO (type: ${this.options.proctoringType}) \u2014 modo cl\xE1ssico.`
32192
- );
32193
- }
32194
- }
32195
- // ========================
32196
- // Session State Persistence (localStorage)
32197
- // ========================
32198
- persistSessionState(status) {
32199
- try {
32200
- const data = {
32201
- proctoringId: this.proctoringId,
32202
- status,
32203
- timestamp: Date.now()
32204
- };
32205
- localStorage.setItem(_CameraRecorder.LS_SESSION_KEY, JSON.stringify(data));
32206
- } catch (e3) {
32207
- console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel salvar estado no localStorage:", e3);
32208
- }
32209
- }
32210
- clearSessionState() {
32211
- try {
32212
- localStorage.removeItem(_CameraRecorder.LS_SESSION_KEY);
32213
- } catch (e3) {
32214
- console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel limpar estado do localStorage:", e3);
32215
- }
32216
- }
32217
- /**
32218
- * Verifica se existe uma sessão ativa anterior no localStorage.
32219
- * Retorna os dados da sessão se ela estiver em IN_PROGRESS, null caso contrário.
32220
- */
32221
- static checkForActiveSession() {
32222
- try {
32223
- const raw = localStorage.getItem(_CameraRecorder.LS_SESSION_KEY);
32224
- if (!raw) return null;
32225
- const data = JSON.parse(raw);
32226
- if (data.status === "IN_PROGRESS") return data;
32227
- return null;
32228
- } catch {
32229
- return null;
32230
- }
32231
- }
32232
- // ========================
32233
- // Page Lifecycle Management
32234
- // ========================
32235
- setupLifecycleListeners() {
32236
- this.boundVisibilityHandler = () => this.handleVisibilityChange();
32237
- this.boundPageHideHandler = () => this.handlePageHide();
32238
- document.addEventListener("visibilitychange", this.boundVisibilityHandler);
32239
- window.addEventListener("pagehide", this.boundPageHideHandler);
32240
- }
32241
- removeLifecycleListeners() {
32242
- if (this.boundVisibilityHandler) {
32243
- document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
32244
- this.boundVisibilityHandler = null;
32245
- }
32246
- if (this.boundPageHideHandler) {
32247
- window.removeEventListener("pagehide", this.boundPageHideHandler);
32248
- this.boundPageHideHandler = null;
32249
- }
32250
- }
32251
- handleVisibilityChange() {
32252
- var _a2;
32253
- if (document.visibilityState === "hidden") {
32254
- console.log("[CameraRecorder] P\xE1gina ficou invis\xEDvel \u2014 sess\xE3o potencialmente interrompida.");
32255
- this.persistSessionState("INTERRUPTED");
32256
- this.proctoringId && trackers.registerError(
32257
- this.proctoringId,
32258
- "Visibility API: P\xE1gina ficou oculta (hidden). Poss\xEDvel troca de app ou minimiza\xE7\xE3o."
32259
- );
32260
- } else if (document.visibilityState === "visible") {
32261
- console.log("[CameraRecorder] P\xE1gina vis\xEDvel novamente \u2014 verificando estado da grava\xE7\xE3o.");
32262
- this.persistSessionState("IN_PROGRESS");
32263
- this.proctoringId && trackers.registerError(
32264
- this.proctoringId,
32265
- "Visibility API: P\xE1gina voltou a ficar vis\xEDvel. Usu\xE1rio retornou."
32266
- );
32267
- (_a2 = this.onVisibilityRestored) == null ? void 0 : _a2.call(this);
32268
- }
32269
- }
32270
- handlePageHide() {
32271
- console.log("[CameraRecorder] pagehide detectado \u2014 persistindo estado.");
32272
- this.persistSessionState("INTERRUPTED");
32273
- this.proctoringId && trackers.registerError(
32274
- this.proctoringId,
32275
- "Page Lifecycle: pagehide event detectado. P\xE1gina est\xE1 sendo descarregada."
32276
- );
32277
31472
  }
32278
31473
  async initializeDetectors() {
32279
31474
  var _a2, _b, _c2;
@@ -32426,15 +31621,9 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32426
31621
  await new Promise((r3) => setTimeout(r3, 300));
32427
31622
  }
32428
31623
  async startRecording() {
32429
- var _a2, _b, _c2, _d, _e3, _f, _g, _h;
31624
+ var _a2, _b, _c2, _d, _e3, _f, _g;
32430
31625
  await this.startStream();
32431
31626
  await this.attachAndWarmup(this.cameraStream);
32432
- const recorderOpts = this.isChunkEnabled ? {
32433
- timeslice: _CameraRecorder.CHUNK_TIMESLICE_MS,
32434
- onChunkAvailable: (blob, idx) => {
32435
- this.handleNewChunk(blob, idx);
32436
- }
32437
- } : {};
32438
31627
  const {
32439
31628
  startRecording,
32440
31629
  stopRecording,
@@ -32450,8 +31639,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32450
31639
  this.blobs,
32451
31640
  this.options.onBufferSizeError,
32452
31641
  (e3) => this.bufferError(e3),
32453
- false,
32454
- recorderOpts
31642
+ false
32455
31643
  );
32456
31644
  this.recordingStart = startRecording;
32457
31645
  this.recordingStop = stopRecording;
@@ -32461,18 +31649,13 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32461
31649
  this.getBufferSize = getBufferSize;
32462
31650
  this.getStartTime = getStartTime;
32463
31651
  this.getDuration = getDuration;
32464
- this.chunkIndex = 0;
32465
- if (this.isChunkEnabled) {
32466
- (_a2 = this.backgroundUpload) == null ? void 0 : _a2.start();
32467
- this.setupLifecycleListeners();
32468
- }
32469
31652
  try {
32470
31653
  await new Promise((r3) => setTimeout(r3, 500));
32471
31654
  await this.recordingStart();
32472
31655
  } catch (error) {
32473
31656
  console.log("Camera Recorder error", error);
32474
31657
  this.stopRecording();
32475
- const maxRetries = ((_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.maxRetries) || 3;
31658
+ const maxRetries = ((_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.maxRetries) || 3;
32476
31659
  if (this.currentRetries < maxRetries) {
32477
31660
  console.log("Camera Recorder retry", this.currentRetries);
32478
31661
  this.currentRetries++;
@@ -32482,13 +31665,13 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32482
31665
  }
32483
31666
  }
32484
31667
  this.stopped = false;
32485
- if (((_c2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _c2.detectPerson) || ((_d = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _d.detectCellPhone) || ((_e3 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _e3.detectFace)) {
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)) {
32486
31669
  await this.initializeDetectors();
32487
31670
  }
32488
- if ((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectFace) {
31671
+ if ((_e3 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _e3.detectFace) {
32489
31672
  await this.faceDetection.enableCam(this.cameraStream);
32490
31673
  }
32491
- if (((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectPerson) || ((_h = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _h.detectCellPhone)) {
31674
+ if (((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectPerson) || ((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectCellPhone)) {
32492
31675
  await this.objectDetection.enableCam(this.cameraStream);
32493
31676
  }
32494
31677
  this.filesToUpload = [];
@@ -32547,51 +31730,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32547
31730
  await this.sendPackage();
32548
31731
  await this.filesToUpload.splice(0, this.filesToUpload.length);
32549
31732
  }
32550
- if (this.isChunkEnabled) {
32551
- if (this.backgroundUpload) {
32552
- try {
32553
- if (this.pendingChunkSaves.length > 0) {
32554
- console.log(`[CameraRecorder] Aguardando ${this.pendingChunkSaves.length} salvamentos de chunks pendentes...`);
32555
- await Promise.all(this.pendingChunkSaves);
32556
- }
32557
- await this.backgroundUpload.flush();
32558
- } catch (e3) {
32559
- console.warn("[CameraRecorder] Erro ao fazer flush dos chunks:", e3);
32560
- }
32561
- this.backgroundUpload.stop();
32562
- }
32563
- this.removeLifecycleListeners();
32564
- this.persistSessionState("FINISHED");
32565
- }
32566
- }
32567
- /**
32568
- * Callback chamado pelo recorder a cada novo chunk de vídeo disponível.
32569
- * Salva o chunk no IndexedDB para persistência e recuperação.
32570
- */
32571
- async handleNewChunk(blob, idx) {
32572
- if (!this.proctoringId || !this.chunkStorage || blob.size == 0) return;
32573
- const savePromise = (async () => {
32574
- var _a2;
32575
- try {
32576
- const arrayBuffer = await blob.arrayBuffer();
32577
- await this.chunkStorage.saveChunk({
32578
- proctoringId: this.proctoringId,
32579
- chunkIndex: this.chunkIndex,
32580
- arrayBuffer,
32581
- timestamp: Date.now(),
32582
- uploaded: 0,
32583
- mimeType: ((_a2 = this.recorderOptions) == null ? void 0 : _a2.mimeType) || "video/webm"
32584
- });
32585
- this.chunkIndex++;
32586
- console.log(`[CameraRecorder] Chunk ${this.chunkIndex - 1} salvo no IndexedDB.`);
32587
- } catch (error) {
32588
- console.error("[CameraRecorder] Erro ao salvar chunk no IndexedDB:", error);
32589
- }
32590
- })();
32591
- this.pendingChunkSaves.push(savePromise);
32592
- savePromise.finally(() => {
32593
- this.pendingChunkSaves = this.pendingChunkSaves.filter((p3) => p3 !== savePromise);
32594
- });
32595
31733
  }
32596
31734
  async pauseRecording() {
32597
31735
  await this.recordingPause();
@@ -32727,32 +31865,16 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32727
31865
  if (this.blobs != null)
32728
31866
  trackers.registerSaveOnSession(
32729
31867
  this.proctoringId,
32730
- `Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} ChunkEnabled: ${this.isChunkEnabled}`
31868
+ `Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} `
32731
31869
  );
32732
31870
  const settings = this.cameraStream.getVideoTracks()[0].getSettings();
32733
31871
  const settingsAudio = this.cameraStream.getAudioTracks()[0].getSettings();
32734
31872
  if (this.options.proctoringType == "VIDEO" || this.options.proctoringType == "REALTIME" || this.options.proctoringType == "IMAGE" && ((_a2 = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _a2.saveVideo)) {
32735
- let videoFile;
32736
- if (this.isChunkEnabled) {
32737
- const isStable = await this.checkInternetStability();
32738
- if (isStable) {
32739
- } else {
32740
- if (this.backend && this.backendToken && this.proctoringId) {
32741
- const fileName = `EP_${this.proctoringId}_camera_0.webm`;
32742
- const objectName = `${this.proctoringId}/${fileName}`;
32743
- const isUploaded = await this.backend.checkUpload(this.backendToken, objectName, "video/webm");
32744
- if (isUploaded) {
32745
- this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
32746
- return;
32747
- }
32748
- }
32749
- }
32750
- }
32751
31873
  const rawBlob = new Blob(this.blobs, {
32752
31874
  type: ((_b = this.recorderOptions) == null ? void 0 : _b.mimeType) || "video/webm"
32753
31875
  });
32754
31876
  const fixedBlob = await fixWebmDuration(rawBlob, this.duration);
32755
- videoFile = new File(
31877
+ const fileWithDuration = new File(
32756
31878
  [fixedBlob],
32757
31879
  `EP_${session.id}_camera_0.webm`,
32758
31880
  { type: rawBlob.type }
@@ -32764,7 +31886,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32764
31886
 
32765
31887
  Video:
32766
31888
  ${JSON.stringify(this.recorderOptions)}`,
32767
- file: videoFile,
31889
+ file: fileWithDuration,
32768
31890
  origin: "Camera" /* Camera */
32769
31891
  });
32770
31892
  }
@@ -32779,28 +31901,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32779
31901
  });
32780
31902
  });
32781
31903
  }
32782
- /**
32783
- * Verifica se a internet está estável para realizar o upload do vídeo na íntegra.
32784
- */
32785
- async checkInternetStability() {
32786
- var _a2;
32787
- if (!navigator.onLine) return false;
32788
- try {
32789
- const controller = new AbortController();
32790
- const timeoutId = setTimeout(() => controller.abort(), 5e3);
32791
- const baseUrl = (_a2 = this.backend) == null ? void 0 : _a2.getBaseUrl();
32792
- if (!baseUrl) return true;
32793
- const response = await fetch(`${baseUrl}/Client/health`, {
32794
- method: "GET",
32795
- signal: controller.signal
32796
- });
32797
- clearTimeout(timeoutId);
32798
- return response.status < 500;
32799
- } catch (e3) {
32800
- console.warn("[CameraRecorder] Internet inst\xE1vel ou lenta detectada para upload integral.");
32801
- return false;
32802
- }
32803
- }
32804
31904
  onNoiseDetected() {
32805
31905
  var _a2, _b, _c2;
32806
31906
  if (this.options.proctoringType === "REALTIME") return;
@@ -32825,14 +31925,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32825
31925
  this.noiseWait++;
32826
31926
  }
32827
31927
  };
32828
- // ========================
32829
- // Chunk & Lifecycle
32830
- // ========================
32831
- /** Intervalo de cada chunk em ms (padrão: 60 segundos) */
32832
- _CameraRecorder.CHUNK_TIMESLICE_MS = 6e4;
32833
- /** Chave do localStorage para persistir estado da sessão */
32834
- _CameraRecorder.LS_SESSION_KEY = "ep_proctoring_session";
32835
- var CameraRecorder = _CameraRecorder;
32836
31928
 
32837
31929
  // src/new-flow/checkers/DeviceCheckerUI.ts
32838
31930
  var DeviceCheckerUI = class {
@@ -38957,20 +38049,6 @@ var Proctoring = class {
38957
38049
  } catch (error) {
38958
38050
  throw SAFE_BROWSER_API_NOT_FOUND;
38959
38051
  }
38960
- this.allRecorders.cameraRecorder.onVisibilityRestored = () => {
38961
- console.log("[Proctoring] Usu\xE1rio retornou ao browser.");
38962
- this.onVisibilityRestoredCallback();
38963
- };
38964
- if (this.sessionOptions.proctoringType === "REALTIME" && !isSafeBrowser()) {
38965
- try {
38966
- await BackgroundUploadService.recoverPendingUploads(
38967
- this.backend,
38968
- this.context.token
38969
- );
38970
- } catch (e3) {
38971
- console.warn("[Proctoring] Erro ao recuperar chunks de sess\xE3o anterior:", e3);
38972
- }
38973
- }
38974
38052
  try {
38975
38053
  console.log("Starting recorders");
38976
38054
  await this.recorder.startAll();