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/esm/index.js CHANGED
@@ -12758,16 +12758,13 @@ function isMobileDevice() {
12758
12758
  }
12759
12759
  return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
12760
12760
  }
12761
- function isSafeBrowser() {
12762
- return versionVerify() !== "1.0.0.0";
12763
- }
12764
12761
 
12765
12762
  // src/plugins/recorder.ts
12766
12763
  var proctoringId;
12767
12764
  function setRecorderProctoringId(id) {
12768
12765
  proctoringId = id;
12769
12766
  }
12770
- function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false, recorderOpts) {
12767
+ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false) {
12771
12768
  let resolvePromise;
12772
12769
  let onBufferSizeInterval;
12773
12770
  let lastEvent;
@@ -12775,7 +12772,6 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
12775
12772
  bufferSize = 0;
12776
12773
  let startTime;
12777
12774
  let duration = 0;
12778
- let chunkIndex = 0;
12779
12775
  let recorderOptions = {
12780
12776
  // eslint-disable-next-line no-useless-escape
12781
12777
  mimeType: "video/webm",
@@ -12810,10 +12806,6 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
12810
12806
  mediaRecorder2.ondataavailable = (e3) => {
12811
12807
  bufferSize = bufferSize + e3.data.size;
12812
12808
  if (e3.data.size > 0) {
12813
- if (recorderOpts == null ? void 0 : recorderOpts.onChunkAvailable) {
12814
- recorderOpts.onChunkAvailable(e3.data, chunkIndex);
12815
- chunkIndex++;
12816
- }
12817
12809
  buffer.push(e3.data);
12818
12810
  }
12819
12811
  };
@@ -12845,13 +12837,7 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
12845
12837
  };
12846
12838
  try {
12847
12839
  console.log("State antes do start:", recorder2.state);
12848
- chunkIndex = 0;
12849
- if ((recorderOpts == null ? void 0 : recorderOpts.timeslice) && (recorderOpts == null ? void 0 : recorderOpts.timeslice) > 0) {
12850
- recorder2.start(recorderOpts.timeslice);
12851
- } else {
12852
- recorder2.start(1e4);
12853
- }
12854
- bufferSize = 0;
12840
+ recorder2.start(1e4);
12855
12841
  startTime = new Date(Date.now());
12856
12842
  } catch (e3) {
12857
12843
  console.error("Recorder erro ao chamar start event:", e3);
@@ -12905,6 +12891,9 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
12905
12891
  console.log("stopRecording Recorder n\xE3o est\xE1 em estado recording");
12906
12892
  resolve();
12907
12893
  }
12894
+ stream.getTracks().forEach((el) => {
12895
+ el.stop();
12896
+ });
12908
12897
  });
12909
12898
  }
12910
12899
  function pauseRecording() {
@@ -13312,686 +13301,11 @@ var VolumeMeter = class {
13312
13301
  }
13313
13302
  };
13314
13303
 
13315
- // src/new-flow/chunk/ChunkStorageService.ts
13316
- var _ChunkStorageService = class _ChunkStorageService {
13317
- constructor() {
13318
- this.db = null;
13319
- }
13320
- /**
13321
- * WebKit pode falhar em IDB.add/put com "Error preparing Blob/File data..." se o
13322
- * ArrayBuffer ainda estiver associado ao Blob (ex.: blob.arrayBuffer()) ou no
13323
- * round-trip get→put do mesmo registro. Esta cópia remove qualquer vínculo.
13324
- */
13325
- static detachedArrayBufferCopy(src) {
13326
- const next = new Uint8Array(src.byteLength);
13327
- next.set(new Uint8Array(src));
13328
- return next.buffer;
13329
- }
13330
- /**
13331
- * Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
13332
- */
13333
- async connect() {
13334
- if (this.db) return this.db;
13335
- return new Promise((resolve, reject) => {
13336
- const request = window.indexedDB.open(
13337
- _ChunkStorageService.DB_NAME,
13338
- _ChunkStorageService.DB_VERSION
13339
- );
13340
- request.onerror = () => {
13341
- reject(new Error("N\xE3o foi poss\xEDvel conectar ao IndexedDB para chunks."));
13342
- };
13343
- request.onupgradeneeded = () => {
13344
- const db = request.result;
13345
- if (db.objectStoreNames.contains(_ChunkStorageService.STORE_NAME)) {
13346
- db.deleteObjectStore(_ChunkStorageService.STORE_NAME);
13347
- }
13348
- const store = db.createObjectStore(_ChunkStorageService.STORE_NAME, {
13349
- keyPath: "id",
13350
- autoIncrement: true
13351
- });
13352
- store.createIndex("proctoringId", "proctoringId", { unique: false });
13353
- store.createIndex("uploaded", "uploaded", { unique: false });
13354
- store.createIndex("proctoringId_uploaded", ["proctoringId", "uploaded"], {
13355
- unique: false
13356
- });
13357
- };
13358
- request.onsuccess = () => {
13359
- this.db = request.result;
13360
- resolve(this.db);
13361
- };
13362
- });
13363
- }
13364
- /**
13365
- * Salva um chunk de vídeo no IndexedDB.
13366
- */
13367
- async saveChunk(chunk) {
13368
- const db = await this.connect();
13369
- const record = {
13370
- proctoringId: chunk.proctoringId,
13371
- chunkIndex: chunk.chunkIndex,
13372
- arrayBuffer: _ChunkStorageService.detachedArrayBufferCopy(chunk.arrayBuffer),
13373
- timestamp: chunk.timestamp,
13374
- uploaded: chunk.uploaded,
13375
- mimeType: chunk.mimeType
13376
- };
13377
- return new Promise((resolve, reject) => {
13378
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
13379
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13380
- const request = store.add(record);
13381
- request.onsuccess = () => {
13382
- resolve(request.result);
13383
- };
13384
- request.onerror = () => {
13385
- var _a2;
13386
- reject(new Error(`Erro ao salvar chunk no IndexedDB: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13387
- };
13388
- });
13389
- }
13390
- /**
13391
- * Retorna todos os chunks pendentes (não enviados) de um proctoringId específico.
13392
- */
13393
- async getPendingChunks(proctoringId2) {
13394
- const db = await this.connect();
13395
- return new Promise((resolve, reject) => {
13396
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
13397
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13398
- const index = store.index("proctoringId_uploaded");
13399
- const range = IDBKeyRange.only([proctoringId2, 0]);
13400
- const request = index.getAll(range);
13401
- request.onsuccess = () => {
13402
- const chunks = request.result.sort(
13403
- (a3, b3) => a3.chunkIndex - b3.chunkIndex
13404
- );
13405
- resolve(chunks);
13406
- };
13407
- request.onerror = () => {
13408
- var _a2;
13409
- reject(new Error(`Erro ao buscar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13410
- };
13411
- });
13412
- }
13413
- /**
13414
- * Retorna todos os chunks (enviados ou não) de um proctoringId específico.
13415
- */
13416
- async getAllChunks(proctoringId2) {
13417
- const db = await this.connect();
13418
- return new Promise((resolve, reject) => {
13419
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
13420
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13421
- const index = store.index("proctoringId");
13422
- const range = IDBKeyRange.only(proctoringId2);
13423
- const request = index.getAll(range);
13424
- request.onsuccess = () => {
13425
- const chunks = request.result.sort(
13426
- (a3, b3) => a3.chunkIndex - b3.chunkIndex
13427
- );
13428
- resolve(chunks);
13429
- };
13430
- request.onerror = () => {
13431
- var _a2;
13432
- reject(new Error(`Erro ao buscar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13433
- };
13434
- });
13435
- }
13436
- /**
13437
- * Remove vários chunks numa única transação (delete por chave — sem put com buffer).
13438
- */
13439
- async deleteChunkIds(chunkIds) {
13440
- if (chunkIds.length === 0) return;
13441
- const db = await this.connect();
13442
- return new Promise((resolve, reject) => {
13443
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
13444
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13445
- transaction.oncomplete = () => resolve();
13446
- transaction.onerror = () => {
13447
- var _a2, _b;
13448
- return reject(
13449
- new Error(
13450
- `Erro ao remover chunks em lote: ${(_b = (_a2 = transaction.error) == null ? void 0 : _a2.message) != null ? _b : "unknown"}`
13451
- )
13452
- );
13453
- };
13454
- transaction.onabort = () => {
13455
- var _a2, _b;
13456
- return reject(new Error(`Remo\xE7\xE3o em lote abortada: ${(_b = (_a2 = transaction.error) == null ? void 0 : _a2.message) != null ? _b : "unknown"}`));
13457
- };
13458
- for (const id of chunkIds) {
13459
- store.delete(id);
13460
- }
13461
- });
13462
- }
13463
- /**
13464
- * Remove registros com uploaded = 1 (vestígio de versões antigas que faziam put).
13465
- * Usa openKeyCursor + delete por primaryKey para não materializar o valor (WebKit/iOS).
13466
- */
13467
- async clearUploadedChunks(proctoringId2) {
13468
- const db = await this.connect();
13469
- return new Promise((resolve, reject) => {
13470
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
13471
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13472
- const index = store.index("proctoringId_uploaded");
13473
- const range = IDBKeyRange.only([proctoringId2, 1]);
13474
- const request = index.openKeyCursor(range);
13475
- request.onsuccess = () => {
13476
- const cursor = request.result;
13477
- if (cursor) {
13478
- store.delete(cursor.primaryKey);
13479
- cursor.continue();
13480
- } else {
13481
- resolve();
13482
- }
13483
- };
13484
- request.onerror = () => {
13485
- var _a2;
13486
- return reject(new Error(`Erro ao limpar chunks enviados: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13487
- };
13488
- });
13489
- }
13490
- /**
13491
- * Remove TODOS os chunks de um proctoringId (limpeza completa pós-finalização).
13492
- */
13493
- async clearAllChunks(proctoringId2) {
13494
- const db = await this.connect();
13495
- return new Promise((resolve, reject) => {
13496
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
13497
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13498
- const index = store.index("proctoringId");
13499
- const range = IDBKeyRange.only(proctoringId2);
13500
- const request = index.openKeyCursor(range);
13501
- request.onsuccess = () => {
13502
- const cursor = request.result;
13503
- if (cursor) {
13504
- store.delete(cursor.primaryKey);
13505
- cursor.continue();
13506
- } else {
13507
- resolve();
13508
- }
13509
- };
13510
- request.onerror = () => {
13511
- var _a2;
13512
- return reject(new Error(`Erro ao limpar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13513
- };
13514
- });
13515
- }
13516
- /**
13517
- * Verifica se existem chunks pendentes para qualquer proctoringId.
13518
- * Útil na recuperação pós-crash.
13519
- */
13520
- async hasAnyPendingChunks() {
13521
- const db = await this.connect();
13522
- return new Promise((resolve, reject) => {
13523
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
13524
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13525
- const index = store.index("uploaded");
13526
- const range = IDBKeyRange.only(0);
13527
- const request = index.count(range);
13528
- request.onsuccess = () => {
13529
- resolve(request.result > 0);
13530
- };
13531
- request.onerror = () => {
13532
- var _a2;
13533
- return reject(new Error(`Erro ao verificar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13534
- };
13535
- });
13536
- }
13537
- /**
13538
- * Retorna todos os proctoringIds que possuem chunks pendentes.
13539
- * Útil na recuperação pós-crash para saber quais sessões precisam ser finalizadas.
13540
- */
13541
- async getPendingProctoringIds() {
13542
- const db = await this.connect();
13543
- return new Promise((resolve, reject) => {
13544
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
13545
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13546
- const index = store.index("uploaded");
13547
- const range = IDBKeyRange.only(0);
13548
- const request = index.getAll(range);
13549
- request.onsuccess = () => {
13550
- const chunks = request.result;
13551
- const ids = [...new Set(chunks.map((c3) => c3.proctoringId))];
13552
- resolve(ids);
13553
- };
13554
- request.onerror = () => {
13555
- var _a2;
13556
- return reject(new Error(`Erro ao buscar proctoringIds pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13557
- };
13558
- });
13559
- }
13560
- /**
13561
- * Fecha a conexão com o banco.
13562
- */
13563
- close() {
13564
- if (this.db) {
13565
- this.db.close();
13566
- this.db = null;
13567
- }
13568
- }
13569
- };
13570
- _ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
13571
- /** v2: índices numéricos; v3: ArrayBuffer no payload; v4: cópia explícita de bytes (WebKit/iOS) */
13572
- _ChunkStorageService.DB_VERSION = 4;
13573
- _ChunkStorageService.STORE_NAME = "chunks";
13574
- var ChunkStorageService = _ChunkStorageService;
13575
-
13576
- // src/new-flow/chunk/BackgroundUploadService.ts
13577
- var DEFAULT_CONFIG = {
13578
- pollInterval: 5e3,
13579
- maxRetries: 5,
13580
- baseRetryDelay: 2e3,
13581
- cleanAfterUpload: true
13582
- };
13583
- var BackgroundUploadService = class _BackgroundUploadService {
13584
- constructor(proctoringId2, token, backend, chunkStorage, config) {
13585
- this.pollTimer = null;
13586
- this.isProcessing = false;
13587
- this.isRunning = false;
13588
- /** Mapa de chunkId -> número de tentativas já feitas */
13589
- this.retryCount = /* @__PURE__ */ new Map();
13590
- /** GCS Resumable Upload State */
13591
- this.sessionUrl = null;
13592
- this.currentOffset = 0;
13593
- this.totalBytesPurged = 0;
13594
- this.STORAGE_KEY_PREFIX = "ep_upload_session_";
13595
- this.GCS_CHUNK_SIZE = 256 * 1024;
13596
- this.proctoringId = proctoringId2.trim();
13597
- this.token = token;
13598
- this.backend = backend;
13599
- this.chunkStorage = chunkStorage;
13600
- this.config = { ...DEFAULT_CONFIG, ...config };
13601
- this.loadSessionState();
13602
- }
13603
- loadSessionState() {
13604
- try {
13605
- const stored = localStorage.getItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
13606
- if (stored) {
13607
- const { sessionUrl, currentOffset, totalBytesPurged } = JSON.parse(stored);
13608
- this.sessionUrl = sessionUrl;
13609
- this.currentOffset = currentOffset;
13610
- this.totalBytesPurged = totalBytesPurged || 0;
13611
- }
13612
- } catch (e3) {
13613
- console.warn("[BackgroundUpload] Erro ao carregar estado da sess\xE3o:", e3);
13614
- }
13615
- }
13616
- saveSessionState() {
13617
- try {
13618
- localStorage.setItem(
13619
- `${this.STORAGE_KEY_PREFIX}${this.proctoringId}`,
13620
- JSON.stringify({
13621
- sessionUrl: this.sessionUrl,
13622
- currentOffset: this.currentOffset,
13623
- totalBytesPurged: this.totalBytesPurged
13624
- })
13625
- );
13626
- } catch (e3) {
13627
- console.warn("[BackgroundUpload] Erro ao salvar estado da sess\xE3o:", e3);
13628
- }
13629
- }
13630
- clearSessionState() {
13631
- try {
13632
- localStorage.removeItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
13633
- this.sessionUrl = null;
13634
- this.currentOffset = 0;
13635
- this.totalBytesPurged = 0;
13636
- } catch (e3) {
13637
- console.warn("[BackgroundUpload] Erro ao limpar estado da sess\xE3o:", e3);
13638
- }
13639
- }
13640
- /**
13641
- * Inicia o serviço de upload em background. Faz polling periódico no IndexedDB
13642
- * para enviar chunks pendentes.
13643
- */
13644
- start() {
13645
- if (this.isRunning) return;
13646
- this.isRunning = true;
13647
- console.log(`[BackgroundUpload] Iniciando servi\xE7o para proctoringId: ${this.proctoringId}`);
13648
- this.processQueue();
13649
- this.pollTimer = setInterval(() => {
13650
- this.processQueue(false);
13651
- }, this.config.pollInterval);
13652
- }
13653
- /**
13654
- * Para o serviço de upload em background.
13655
- */
13656
- stop() {
13657
- this.isRunning = false;
13658
- if (this.pollTimer) {
13659
- clearInterval(this.pollTimer);
13660
- this.pollTimer = null;
13661
- }
13662
- console.log(`[BackgroundUpload] Servi\xE7o parado para proctoringId: ${this.proctoringId}`);
13663
- }
13664
- /**
13665
- * Força o processamento de todos os chunks pendentes e encerra a sessão GCS.
13666
- * Útil quando a gravação é finalizada.
13667
- */
13668
- async flush() {
13669
- console.log(`[BackgroundUpload] Flush: enviando todos os chunks pendentes e finalizando...`);
13670
- let waitAttempts = 0;
13671
- while (this.isProcessing && waitAttempts < 10) {
13672
- await this.sleep(1e3);
13673
- waitAttempts++;
13674
- }
13675
- let flushRetries = 0;
13676
- const maxFlushRetries = 3;
13677
- while (flushRetries < maxFlushRetries) {
13678
- try {
13679
- await this.processQueue(true);
13680
- console.log(`[BackgroundUpload] Flush completado com sucesso.`);
13681
- return;
13682
- } catch (error) {
13683
- flushRetries++;
13684
- console.error(`[BackgroundUpload] Erro no flush (tentativa ${flushRetries}/${maxFlushRetries}):`, error);
13685
- if (flushRetries < maxFlushRetries) {
13686
- await this.sleep(2e3);
13687
- }
13688
- }
13689
- }
13690
- throw new Error(`[BackgroundUpload] Falha ao finalizar upload ap\xF3s ${maxFlushRetries} tentativas.`);
13691
- }
13692
- /**
13693
- * Sincroniza o offset local com o estado real no Google Cloud Storage.
13694
- */
13695
- async syncOffset() {
13696
- if (!this.sessionUrl) return 0;
13697
- try {
13698
- console.log(`[BackgroundUpload] Sincronizando offset com GCS...`);
13699
- const response = await fetch(this.sessionUrl, {
13700
- method: "PUT",
13701
- headers: {
13702
- "Content-Range": "bytes */*"
13703
- }
13704
- });
13705
- console.log(`[BackgroundUpload] Status da sincroniza\xE7\xE3o (syncOffset): ${response.status}`);
13706
- if (response.status === 308) {
13707
- const range = response.headers.get("Range");
13708
- if (range) {
13709
- const lastByte = parseInt(range.split("-")[1], 10);
13710
- this.currentOffset = lastByte + 1;
13711
- this.saveSessionState();
13712
- console.log(`[BackgroundUpload] Offset sincronizado: ${this.currentOffset}`);
13713
- } else {
13714
- this.currentOffset = 0;
13715
- }
13716
- } else if (response.ok || response.status === 201) {
13717
- console.log("[BackgroundUpload] Sincroniza\xE7\xE3o indicou upload JA FINALIZADO.");
13718
- this.currentOffset = -1;
13719
- } else {
13720
- console.warn(`[BackgroundUpload] Status inesperado na sincroniza\xE7\xE3o: ${response.status}`);
13721
- }
13722
- } catch (error) {
13723
- console.warn("[BackgroundUpload] Erro ao sincronizar offset:", error);
13724
- }
13725
- return this.currentOffset;
13726
- }
13727
- /**
13728
- * Verifica e envia chunks pendentes para o backend.
13729
- * @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
13730
- */
13731
- async processQueue(isFinal = false) {
13732
- var _a2, _b, _c2, _d, _e3, _f, _g, _h, _i3;
13733
- console.log(`[BackgroundUpload] processQueue init`);
13734
- if (this.isProcessing) return;
13735
- this.isProcessing = true;
13736
- try {
13737
- if (this.sessionUrl) {
13738
- await this.syncOffset();
13739
- if (this.currentOffset === -1) {
13740
- console.log("[BackgroundUpload] Sess\xE3o j\xE1 finalizada no servidor.");
13741
- this.clearSessionState();
13742
- this.isProcessing = false;
13743
- return;
13744
- }
13745
- }
13746
- console.log(`[BackgroundUpload] processQueue syncOffset ok`);
13747
- const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
13748
- const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
13749
- console.log(`[BackgroundUpload] processQueue getAllChunks ok`);
13750
- if (pendingChunks.length === 0 && !isFinal) {
13751
- this.isProcessing = false;
13752
- return;
13753
- }
13754
- if (pendingChunks.length === 0 && isFinal) {
13755
- const mimeTypeFallback = (_b = (_a2 = allChunks[allChunks.length - 1]) == null ? void 0 : _a2.mimeType) != null ? _b : "video/webm";
13756
- try {
13757
- if (!this.sessionUrl) {
13758
- this.clearSessionState();
13759
- return;
13760
- }
13761
- const totalBytes = this.currentOffset;
13762
- await this.uploadData(new ArrayBuffer(0), mimeTypeFallback, 0, totalBytes);
13763
- await this.chunkStorage.clearUploadedChunks(this.proctoringId);
13764
- this.clearSessionState();
13765
- } catch (error) {
13766
- console.error(
13767
- "[BackgroundUpload] Falha ao finalizar upload (flush sem pendentes):",
13768
- error
13769
- );
13770
- (_c2 = this.onUploadError) == null ? void 0 : _c2.call(this, 0, error);
13771
- }
13772
- return;
13773
- }
13774
- console.log(`[BackgroundUpload] ${pendingChunks.length} chunks pendentes encontrados. Modo final: ${isFinal}`);
13775
- let virtualStart = this.totalBytesPurged;
13776
- const chunksWithMeta = allChunks.map((c3) => {
13777
- const start = virtualStart;
13778
- const end = start + c3.arrayBuffer.byteLength - 1;
13779
- virtualStart += c3.arrayBuffer.byteLength;
13780
- return { chunk: c3, start, end };
13781
- });
13782
- const combinedBufferParts = [];
13783
- let lastProcessedChunkId = null;
13784
- let finalChunkIndex = 0;
13785
- 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";
13786
- console.log(`[BackgroundUpload] passo 1 ok`);
13787
- for (const meta of chunksWithMeta) {
13788
- if (this.currentOffset > meta.end) continue;
13789
- const sliceStart = Math.max(0, this.currentOffset - meta.start);
13790
- const chunkSlice = meta.chunk.arrayBuffer.slice(sliceStart);
13791
- combinedBufferParts.push(chunkSlice);
13792
- lastProcessedChunkId = meta.chunk.id;
13793
- finalChunkIndex = meta.chunk.chunkIndex;
13794
- }
13795
- console.log(`[BackgroundUpload] passo 2 ok`);
13796
- if (combinedBufferParts.length === 0 && !isFinal) {
13797
- this.isProcessing = false;
13798
- return;
13799
- }
13800
- const fullBuffer = _BackgroundUploadService.concatArrayBuffers(combinedBufferParts);
13801
- console.log(`[BackgroundUpload] passo 3 ok`);
13802
- let sendableSize = fullBuffer.byteLength;
13803
- let totalSizeForHeader = void 0;
13804
- if (!isFinal) {
13805
- sendableSize = Math.floor(fullBuffer.byteLength / this.GCS_CHUNK_SIZE) * this.GCS_CHUNK_SIZE;
13806
- if (sendableSize === 0) {
13807
- console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk...");
13808
- this.isProcessing = false;
13809
- return;
13810
- }
13811
- } else {
13812
- totalSizeForHeader = fullBuffer.byteLength > 0 ? virtualStart : Math.max(virtualStart, this.currentOffset);
13813
- }
13814
- console.log(`[BackgroundUpload] passo 4 ok`);
13815
- const bufferToSend = sendableSize < fullBuffer.byteLength ? fullBuffer.slice(0, sendableSize) : fullBuffer;
13816
- try {
13817
- await this.uploadData(bufferToSend, mimeType, finalChunkIndex, totalSizeForHeader);
13818
- console.log(`[BackgroundUpload] passo 5 ok`);
13819
- const fullySent = chunksWithMeta.filter(
13820
- (meta) => meta.chunk.uploaded === 0 && meta.end < this.currentOffset && meta.chunk.id != null
13821
- );
13822
- const sizePurged = fullySent.reduce(
13823
- (acc, meta) => acc + meta.chunk.arrayBuffer.byteLength,
13824
- 0
13825
- );
13826
- const idsToDelete = fullySent.map((m3) => m3.chunk.id);
13827
- if (idsToDelete.length > 0) {
13828
- await this.chunkStorage.deleteChunkIds(idsToDelete);
13829
- for (const meta of fullySent) {
13830
- this.retryCount.delete(meta.chunk.id);
13831
- (_h = this.onChunkUploaded) == null ? void 0 : _h.call(this, meta.chunk.id, meta.chunk.chunkIndex);
13832
- console.log(
13833
- `[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} removido do IndexedDB ap\xF3s upload.`
13834
- );
13835
- }
13836
- }
13837
- console.log(`[BackgroundUpload] passo 6 ok`);
13838
- await this.chunkStorage.clearUploadedChunks(this.proctoringId);
13839
- console.log(`[BackgroundUpload] passo 7 ok`);
13840
- if (this.config.cleanAfterUpload && sizePurged > 0) {
13841
- this.totalBytesPurged += sizePurged;
13842
- this.saveSessionState();
13843
- console.log(
13844
- `[BackgroundUpload] ${sizePurged} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`
13845
- );
13846
- }
13847
- console.log(`[BackgroundUpload] passo 8 ok`);
13848
- if (isFinal) {
13849
- this.clearSessionState();
13850
- }
13851
- console.log(`[BackgroundUpload] passo 9 ok`);
13852
- } catch (error) {
13853
- console.error("[BackgroundUpload] Falha no upload:", error);
13854
- (_i3 = this.onUploadError) == null ? void 0 : _i3.call(this, lastProcessedChunkId || 0, error);
13855
- }
13856
- } catch (error) {
13857
- console.error("[BackgroundUpload] Erro ao processar fila:", error);
13858
- } finally {
13859
- this.isProcessing = false;
13860
- }
13861
- }
13862
- /**
13863
- * Faz o upload bruto de dados para a sessão GCS.
13864
- */
13865
- static concatArrayBuffers(parts) {
13866
- if (parts.length === 0) return new ArrayBuffer(0);
13867
- const total = parts.reduce((sum, p3) => sum + p3.byteLength, 0);
13868
- const merged = new Uint8Array(total);
13869
- let offset = 0;
13870
- for (const p3 of parts) {
13871
- merged.set(new Uint8Array(p3), offset);
13872
- offset += p3.byteLength;
13873
- }
13874
- return merged.buffer.byteLength === total ? merged.buffer : merged.buffer.slice(merged.byteOffset, merged.byteOffset + merged.byteLength);
13875
- }
13876
- async uploadData(data, mimeType, chunkIndex, totalSize) {
13877
- const fileName = `EP_${this.proctoringId}_camera_0.webm`;
13878
- if (!this.sessionUrl) {
13879
- const initiateUrl = await this.backend.initiateUpload(this.token, `${this.proctoringId}/${fileName}`, mimeType);
13880
- const startResponse = await fetch(initiateUrl, {
13881
- method: "POST",
13882
- headers: { "x-goog-resumable": "start", "Content-Type": mimeType }
13883
- });
13884
- if (!startResponse.ok) throw new Error(`Falha ao iniciar: ${startResponse.status}`);
13885
- this.sessionUrl = startResponse.headers.get("Location");
13886
- if (!this.sessionUrl) throw new Error("Location header ausente");
13887
- try {
13888
- const urlObj = new URL(this.sessionUrl);
13889
- const pathParts = urlObj.pathname.split("/");
13890
- let bucket = pathParts[1];
13891
- let object = decodeURIComponent(pathParts.slice(2).join("/"));
13892
- if (pathParts.includes("b") && pathParts.includes("o")) {
13893
- const bIdx = pathParts.indexOf("b") + 1;
13894
- const oIdx = pathParts.indexOf("o") + 1;
13895
- bucket = pathParts[bIdx];
13896
- object = decodeURIComponent(pathParts.slice(oIdx).join("/"));
13897
- }
13898
- console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${bucket}, Objeto: ${object}`);
13899
- } catch (e3) {
13900
- console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`);
13901
- }
13902
- this.currentOffset = 0;
13903
- this.saveSessionState();
13904
- } else {
13905
- console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);
13906
- }
13907
- const start = this.currentOffset;
13908
- const end = start + data.byteLength - 1;
13909
- const totalHeader = totalSize !== void 0 ? totalSize.toString() : "*";
13910
- const contentRangeHeader = data.byteLength === 0 && totalSize !== void 0 ? `bytes */${totalHeader}` : `bytes ${start}-${end}/${totalHeader}`;
13911
- console.log(
13912
- `[BackgroundUpload] Enviando ${data.byteLength > 0 ? "dados" : "finaliza\xE7\xE3o"}: ${contentRangeHeader} (Size: ${data.byteLength})`
13913
- );
13914
- const response = await fetch(this.sessionUrl, {
13915
- method: "PUT",
13916
- headers: { "Content-Range": contentRangeHeader },
13917
- body: data.byteLength > 0 ? data : null
13918
- });
13919
- console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${response.status}`);
13920
- if (response.status !== 200 && response.status !== 201 && response.status !== 308) {
13921
- const errorText = await response.text();
13922
- console.error(`[BackgroundUpload] Erro GCS: ${errorText}`);
13923
- throw new Error(`Status HTTP inesperado: ${response.status}`);
13924
- }
13925
- const rangeHeader = response.headers.get("Range");
13926
- if (rangeHeader) {
13927
- const lastByte = parseInt(rangeHeader.split("-")[1], 10);
13928
- this.currentOffset = lastByte + 1;
13929
- } else {
13930
- this.currentOffset += data.byteLength;
13931
- }
13932
- this.saveSessionState();
13933
- trackers.registerUploadFile(
13934
- this.proctoringId,
13935
- `GCS Stream Upload
13936
- Size: ${data.byteLength}
13937
- Range: ${start}-${end}
13938
- Last Index: ${chunkIndex}`,
13939
- "CameraChunk"
13940
- );
13941
- }
13942
- /**
13943
- * Método estático para recuperação pós-crash.
13944
- * Verifica o IndexedDB em busca de chunks pendentes de qualquer sessão
13945
- * e tenta enviar.
13946
- */
13947
- static async recoverPendingUploads(backend, token) {
13948
- const chunkStorage = new ChunkStorageService();
13949
- const recoveredIds = [];
13950
- try {
13951
- const pendingIds = await chunkStorage.getPendingProctoringIds();
13952
- if (pendingIds.length === 0) {
13953
- console.log("[BackgroundUpload] Nenhum chunk pendente encontrado para recupera\xE7\xE3o.");
13954
- return recoveredIds;
13955
- }
13956
- console.log(
13957
- `[BackgroundUpload] Recupera\xE7\xE3o p\xF3s-crash: ${pendingIds.length} sess\xE3o(\xF5es) com chunks pendentes.`
13958
- );
13959
- for (const proctoringId2 of pendingIds) {
13960
- try {
13961
- const service = new _BackgroundUploadService(
13962
- proctoringId2,
13963
- token,
13964
- backend,
13965
- chunkStorage,
13966
- { cleanAfterUpload: true }
13967
- );
13968
- await service.flush();
13969
- recoveredIds.push(proctoringId2);
13970
- console.log(
13971
- `[BackgroundUpload] Chunks da sess\xE3o ${proctoringId2} recuperados com sucesso.`
13972
- );
13973
- } catch (error) {
13974
- console.error(
13975
- `[BackgroundUpload] Erro ao recuperar chunks da sess\xE3o ${proctoringId2}:`,
13976
- error
13977
- );
13978
- }
13979
- }
13980
- } catch (error) {
13981
- console.error("[BackgroundUpload] Erro geral na recupera\xE7\xE3o:", error);
13982
- }
13983
- return recoveredIds;
13984
- }
13985
- sleep(ms2) {
13986
- return new Promise((resolve) => setTimeout(resolve, ms2));
13987
- }
13988
- };
13989
-
13990
13304
  // src/new-flow/recorders/CameraRecorder.ts
13991
13305
  var import_jszip_min = __toESM(require_jszip_min());
13992
13306
  var pkg = require_fix_webm_duration();
13993
13307
  var fixWebmDuration = pkg.default || pkg;
13994
- var _CameraRecorder = class _CameraRecorder {
13308
+ var CameraRecorder = class {
13995
13309
  constructor(options, videoOptions, paramsConfig, backend, backendToken) {
13996
13310
  this.blobs = [];
13997
13311
  this.paramsConfig = {
@@ -14044,13 +13358,6 @@ var _CameraRecorder = class _CameraRecorder {
14044
13358
  this.videoElement = null;
14045
13359
  this.duration = 0;
14046
13360
  this.stopped = false;
14047
- this.backgroundUpload = null;
14048
- this.chunkIndex = 0;
14049
- /** Lista de promises de chunks sendo salvos no IndexedDB para evitar race conditions no stop */
14050
- this.pendingChunkSaves = [];
14051
- // Handlers bound para poder remover os listeners depois
14052
- this.boundVisibilityHandler = null;
14053
- this.boundPageHideHandler = null;
14054
13361
  this.currentRetries = 0;
14055
13362
  this.packageCount = 0;
14056
13363
  this.failedUploads = 0;
@@ -14061,122 +13368,10 @@ var _CameraRecorder = class _CameraRecorder {
14061
13368
  this.backendToken = backendToken;
14062
13369
  paramsConfig && (this.paramsConfig = paramsConfig);
14063
13370
  }
14064
- /**
14065
- * Determina se o fluxo de chunks e lifecycle deve estar ativo.
14066
- * Retorna true se:
14067
- * 1. O proctoringId já foi definido (ou seja, estamos em uma sessão real, NÃO no checkDevices)
14068
- * 2. E (`useChunkRecording` foi explicitamente setado como true OU o dispositivo é mobile)
14069
- */
14070
- get isChunkEnabled() {
14071
- return !!this.proctoringId && this.options.proctoringType === "REALTIME" && !isSafeBrowser();
14072
- }
14073
13371
  setProctoringId(proctoringId2) {
14074
13372
  this.proctoringId = proctoringId2;
14075
13373
  this.proctoringId && this.backend && (this.upload = new UploadService(this.proctoringId, this.backend));
14076
13374
  setRecorderProctoringId(proctoringId2);
14077
- if (this.isChunkEnabled) {
14078
- this.chunkStorage = new ChunkStorageService();
14079
- if (this.backend && this.backendToken) {
14080
- this.backgroundUpload = new BackgroundUploadService(
14081
- this.proctoringId,
14082
- this.backendToken,
14083
- this.backend,
14084
- this.chunkStorage,
14085
- { pollInterval: 5e3, maxRetries: 5, cleanAfterUpload: true }
14086
- );
14087
- }
14088
- this.persistSessionState("IN_PROGRESS");
14089
- console.log(
14090
- `[CameraRecorder] Chunk recording ATIVO (type: ${this.options.proctoringType}, mobile: ${isMobileDevice()})`
14091
- );
14092
- } else {
14093
- console.log(
14094
- `[CameraRecorder] Chunk recording INATIVO (type: ${this.options.proctoringType}) \u2014 modo cl\xE1ssico.`
14095
- );
14096
- }
14097
- }
14098
- // ========================
14099
- // Session State Persistence (localStorage)
14100
- // ========================
14101
- persistSessionState(status) {
14102
- try {
14103
- const data = {
14104
- proctoringId: this.proctoringId,
14105
- status,
14106
- timestamp: Date.now()
14107
- };
14108
- localStorage.setItem(_CameraRecorder.LS_SESSION_KEY, JSON.stringify(data));
14109
- } catch (e3) {
14110
- console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel salvar estado no localStorage:", e3);
14111
- }
14112
- }
14113
- clearSessionState() {
14114
- try {
14115
- localStorage.removeItem(_CameraRecorder.LS_SESSION_KEY);
14116
- } catch (e3) {
14117
- console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel limpar estado do localStorage:", e3);
14118
- }
14119
- }
14120
- /**
14121
- * Verifica se existe uma sessão ativa anterior no localStorage.
14122
- * Retorna os dados da sessão se ela estiver em IN_PROGRESS, null caso contrário.
14123
- */
14124
- static checkForActiveSession() {
14125
- try {
14126
- const raw = localStorage.getItem(_CameraRecorder.LS_SESSION_KEY);
14127
- if (!raw) return null;
14128
- const data = JSON.parse(raw);
14129
- if (data.status === "IN_PROGRESS") return data;
14130
- return null;
14131
- } catch (e3) {
14132
- return null;
14133
- }
14134
- }
14135
- // ========================
14136
- // Page Lifecycle Management
14137
- // ========================
14138
- setupLifecycleListeners() {
14139
- this.boundVisibilityHandler = () => this.handleVisibilityChange();
14140
- this.boundPageHideHandler = () => this.handlePageHide();
14141
- document.addEventListener("visibilitychange", this.boundVisibilityHandler);
14142
- window.addEventListener("pagehide", this.boundPageHideHandler);
14143
- }
14144
- removeLifecycleListeners() {
14145
- if (this.boundVisibilityHandler) {
14146
- document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
14147
- this.boundVisibilityHandler = null;
14148
- }
14149
- if (this.boundPageHideHandler) {
14150
- window.removeEventListener("pagehide", this.boundPageHideHandler);
14151
- this.boundPageHideHandler = null;
14152
- }
14153
- }
14154
- handleVisibilityChange() {
14155
- var _a2;
14156
- if (document.visibilityState === "hidden") {
14157
- console.log("[CameraRecorder] P\xE1gina ficou invis\xEDvel \u2014 sess\xE3o potencialmente interrompida.");
14158
- this.persistSessionState("INTERRUPTED");
14159
- this.proctoringId && trackers.registerError(
14160
- this.proctoringId,
14161
- "Visibility API: P\xE1gina ficou oculta (hidden). Poss\xEDvel troca de app ou minimiza\xE7\xE3o."
14162
- );
14163
- } else if (document.visibilityState === "visible") {
14164
- console.log("[CameraRecorder] P\xE1gina vis\xEDvel novamente \u2014 verificando estado da grava\xE7\xE3o.");
14165
- this.persistSessionState("IN_PROGRESS");
14166
- this.proctoringId && trackers.registerError(
14167
- this.proctoringId,
14168
- "Visibility API: P\xE1gina voltou a ficar vis\xEDvel. Usu\xE1rio retornou."
14169
- );
14170
- (_a2 = this.onVisibilityRestored) == null ? void 0 : _a2.call(this);
14171
- }
14172
- }
14173
- handlePageHide() {
14174
- console.log("[CameraRecorder] pagehide detectado \u2014 persistindo estado.");
14175
- this.persistSessionState("INTERRUPTED");
14176
- this.proctoringId && trackers.registerError(
14177
- this.proctoringId,
14178
- "Page Lifecycle: pagehide event detectado. P\xE1gina est\xE1 sendo descarregada."
14179
- );
14180
13375
  }
14181
13376
  async initializeDetectors() {
14182
13377
  var _a2, _b, _c2;
@@ -14329,15 +13524,9 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14329
13524
  await new Promise((r3) => setTimeout(r3, 300));
14330
13525
  }
14331
13526
  async startRecording() {
14332
- var _a2, _b, _c2, _d, _e3, _f, _g, _h;
13527
+ var _a2, _b, _c2, _d, _e3, _f, _g;
14333
13528
  await this.startStream();
14334
13529
  await this.attachAndWarmup(this.cameraStream);
14335
- const recorderOpts = this.isChunkEnabled ? {
14336
- timeslice: _CameraRecorder.CHUNK_TIMESLICE_MS,
14337
- onChunkAvailable: (blob, idx) => {
14338
- this.handleNewChunk(blob, idx);
14339
- }
14340
- } : {};
14341
13530
  const {
14342
13531
  startRecording,
14343
13532
  stopRecording,
@@ -14353,8 +13542,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14353
13542
  this.blobs,
14354
13543
  this.options.onBufferSizeError,
14355
13544
  (e3) => this.bufferError(e3),
14356
- false,
14357
- recorderOpts
13545
+ false
14358
13546
  );
14359
13547
  this.recordingStart = startRecording;
14360
13548
  this.recordingStop = stopRecording;
@@ -14364,18 +13552,13 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14364
13552
  this.getBufferSize = getBufferSize;
14365
13553
  this.getStartTime = getStartTime;
14366
13554
  this.getDuration = getDuration;
14367
- this.chunkIndex = 0;
14368
- if (this.isChunkEnabled) {
14369
- (_a2 = this.backgroundUpload) == null ? void 0 : _a2.start();
14370
- this.setupLifecycleListeners();
14371
- }
14372
13555
  try {
14373
13556
  await new Promise((r3) => setTimeout(r3, 500));
14374
13557
  await this.recordingStart();
14375
13558
  } catch (error) {
14376
13559
  console.log("Camera Recorder error", error);
14377
13560
  this.stopRecording();
14378
- const maxRetries = ((_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.maxRetries) || 3;
13561
+ const maxRetries = ((_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.maxRetries) || 3;
14379
13562
  if (this.currentRetries < maxRetries) {
14380
13563
  console.log("Camera Recorder retry", this.currentRetries);
14381
13564
  this.currentRetries++;
@@ -14385,13 +13568,13 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14385
13568
  }
14386
13569
  }
14387
13570
  this.stopped = false;
14388
- 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)) {
13571
+ 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)) {
14389
13572
  await this.initializeDetectors();
14390
13573
  }
14391
- if ((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectFace) {
13574
+ if ((_e3 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _e3.detectFace) {
14392
13575
  await this.faceDetection.enableCam(this.cameraStream);
14393
13576
  }
14394
- if (((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectPerson) || ((_h = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _h.detectCellPhone)) {
13577
+ if (((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectPerson) || ((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectCellPhone)) {
14395
13578
  await this.objectDetection.enableCam(this.cameraStream);
14396
13579
  }
14397
13580
  this.filesToUpload = [];
@@ -14450,51 +13633,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14450
13633
  await this.sendPackage();
14451
13634
  await this.filesToUpload.splice(0, this.filesToUpload.length);
14452
13635
  }
14453
- if (this.isChunkEnabled) {
14454
- if (this.backgroundUpload) {
14455
- try {
14456
- if (this.pendingChunkSaves.length > 0) {
14457
- console.log(`[CameraRecorder] Aguardando ${this.pendingChunkSaves.length} salvamentos de chunks pendentes...`);
14458
- await Promise.all(this.pendingChunkSaves);
14459
- }
14460
- await this.backgroundUpload.flush();
14461
- } catch (e3) {
14462
- console.warn("[CameraRecorder] Erro ao fazer flush dos chunks:", e3);
14463
- }
14464
- this.backgroundUpload.stop();
14465
- }
14466
- this.removeLifecycleListeners();
14467
- this.persistSessionState("FINISHED");
14468
- }
14469
- }
14470
- /**
14471
- * Callback chamado pelo recorder a cada novo chunk de vídeo disponível.
14472
- * Salva o chunk no IndexedDB para persistência e recuperação.
14473
- */
14474
- async handleNewChunk(blob, idx) {
14475
- if (!this.proctoringId || !this.chunkStorage || blob.size == 0) return;
14476
- const savePromise = (async () => {
14477
- var _a2;
14478
- try {
14479
- const arrayBuffer = await blob.arrayBuffer();
14480
- await this.chunkStorage.saveChunk({
14481
- proctoringId: this.proctoringId,
14482
- chunkIndex: this.chunkIndex,
14483
- arrayBuffer,
14484
- timestamp: Date.now(),
14485
- uploaded: 0,
14486
- mimeType: ((_a2 = this.recorderOptions) == null ? void 0 : _a2.mimeType) || "video/webm"
14487
- });
14488
- this.chunkIndex++;
14489
- console.log(`[CameraRecorder] Chunk ${this.chunkIndex - 1} salvo no IndexedDB.`);
14490
- } catch (error) {
14491
- console.error("[CameraRecorder] Erro ao salvar chunk no IndexedDB:", error);
14492
- }
14493
- })();
14494
- this.pendingChunkSaves.push(savePromise);
14495
- savePromise.finally(() => {
14496
- this.pendingChunkSaves = this.pendingChunkSaves.filter((p3) => p3 !== savePromise);
14497
- });
14498
13636
  }
14499
13637
  async pauseRecording() {
14500
13638
  await this.recordingPause();
@@ -14630,32 +13768,16 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14630
13768
  if (this.blobs != null)
14631
13769
  trackers.registerSaveOnSession(
14632
13770
  this.proctoringId,
14633
- `Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} ChunkEnabled: ${this.isChunkEnabled}`
13771
+ `Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} `
14634
13772
  );
14635
13773
  const settings = this.cameraStream.getVideoTracks()[0].getSettings();
14636
13774
  const settingsAudio = this.cameraStream.getAudioTracks()[0].getSettings();
14637
13775
  if (this.options.proctoringType == "VIDEO" || this.options.proctoringType == "REALTIME" || this.options.proctoringType == "IMAGE" && ((_a2 = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _a2.saveVideo)) {
14638
- let videoFile;
14639
- if (this.isChunkEnabled) {
14640
- const isStable = await this.checkInternetStability();
14641
- if (isStable) {
14642
- } else {
14643
- if (this.backend && this.backendToken && this.proctoringId) {
14644
- const fileName = `EP_${this.proctoringId}_camera_0.webm`;
14645
- const objectName = `${this.proctoringId}/${fileName}`;
14646
- const isUploaded = await this.backend.checkUpload(this.backendToken, objectName, "video/webm");
14647
- if (isUploaded) {
14648
- this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
14649
- return;
14650
- }
14651
- }
14652
- }
14653
- }
14654
13776
  const rawBlob = new Blob(this.blobs, {
14655
13777
  type: ((_b = this.recorderOptions) == null ? void 0 : _b.mimeType) || "video/webm"
14656
13778
  });
14657
13779
  const fixedBlob = await fixWebmDuration(rawBlob, this.duration);
14658
- videoFile = new File(
13780
+ const fileWithDuration = new File(
14659
13781
  [fixedBlob],
14660
13782
  `EP_${session.id}_camera_0.webm`,
14661
13783
  { type: rawBlob.type }
@@ -14667,7 +13789,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14667
13789
 
14668
13790
  Video:
14669
13791
  ${JSON.stringify(this.recorderOptions)}`,
14670
- file: videoFile,
13792
+ file: fileWithDuration,
14671
13793
  origin: "Camera" /* Camera */
14672
13794
  });
14673
13795
  }
@@ -14682,28 +13804,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14682
13804
  });
14683
13805
  });
14684
13806
  }
14685
- /**
14686
- * Verifica se a internet está estável para realizar o upload do vídeo na íntegra.
14687
- */
14688
- async checkInternetStability() {
14689
- var _a2;
14690
- if (!navigator.onLine) return false;
14691
- try {
14692
- const controller = new AbortController();
14693
- const timeoutId = setTimeout(() => controller.abort(), 5e3);
14694
- const baseUrl = (_a2 = this.backend) == null ? void 0 : _a2.getBaseUrl();
14695
- if (!baseUrl) return true;
14696
- const response = await fetch(`${baseUrl}/Client/health`, {
14697
- method: "GET",
14698
- signal: controller.signal
14699
- });
14700
- clearTimeout(timeoutId);
14701
- return response.status < 500;
14702
- } catch (e3) {
14703
- console.warn("[CameraRecorder] Internet inst\xE1vel ou lenta detectada para upload integral.");
14704
- return false;
14705
- }
14706
- }
14707
13807
  onNoiseDetected() {
14708
13808
  var _a2, _b, _c2;
14709
13809
  if (this.options.proctoringType === "REALTIME") return;
@@ -14728,14 +13828,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14728
13828
  this.noiseWait++;
14729
13829
  }
14730
13830
  };
14731
- // ========================
14732
- // Chunk & Lifecycle
14733
- // ========================
14734
- /** Intervalo de cada chunk em ms (padrão: 60 segundos) */
14735
- _CameraRecorder.CHUNK_TIMESLICE_MS = 6e4;
14736
- /** Chave do localStorage para persistir estado da sessão */
14737
- _CameraRecorder.LS_SESSION_KEY = "ep_proctoring_session";
14738
- var CameraRecorder = _CameraRecorder;
14739
13831
 
14740
13832
  // src/new-flow/checkers/DeviceCheckerUI.ts
14741
13833
  var DeviceCheckerUI = class {
@@ -23708,20 +22800,6 @@ var Proctoring = class {
23708
22800
  } catch (error) {
23709
22801
  throw SAFE_BROWSER_API_NOT_FOUND;
23710
22802
  }
23711
- this.allRecorders.cameraRecorder.onVisibilityRestored = () => {
23712
- console.log("[Proctoring] Usu\xE1rio retornou ao browser.");
23713
- this.onVisibilityRestoredCallback();
23714
- };
23715
- if (this.sessionOptions.proctoringType === "REALTIME" && !isSafeBrowser()) {
23716
- try {
23717
- await BackgroundUploadService.recoverPendingUploads(
23718
- this.backend,
23719
- this.context.token
23720
- );
23721
- } catch (e3) {
23722
- console.warn("[Proctoring] Erro ao recuperar chunks de sess\xE3o anterior:", e3);
23723
- }
23724
- }
23725
22803
  try {
23726
22804
  console.log("Starting recorders");
23727
22805
  await this.recorder.startAll();