LogicalTrue commited on
Commit
cea73c0
·
1 Parent(s): 9adc2d0

feat: add live WebRTC camera stream viewfinder modal with capture trigger for mobile

Browse files
Files changed (1) hide show
  1. frontend/viewer.html +75 -4
frontend/viewer.html CHANGED
@@ -578,6 +578,25 @@
578
  </div>
579
  </form>
580
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
  </div>
582
 
583
  <!-- Privacy & Security Modal (Mobile) -->
@@ -1387,15 +1406,67 @@
1387
  });
1388
  }
1389
 
1390
- // Upload & Camera handlers
1391
  const cameraInputMobile = document.getElementById('cameraInputMobile');
1392
  const btnTriggerCameraMobile = document.getElementById('btnTriggerCameraMobile');
1393
  const btnTriggerFileMobile = document.getElementById('btnTriggerFileMobile');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1394
 
1395
- if (btnTriggerCameraMobile && cameraInputMobile) {
1396
- btnTriggerCameraMobile.addEventListener('click', (e) => {
1397
  e.stopPropagation();
1398
- cameraInputMobile.click();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1399
  });
1400
  }
1401
 
 
578
  </div>
579
  </form>
580
  </div>
581
+ <!-- Native Live Camera Stream Modal (Mobile WebRTC) -->
582
+ <div id="liveCameraModal" style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 999999; background: #000000; flex-direction: column; align-items: center; justify-content: space-between; padding: 16px; box-sizing: border-box;">
583
+ <div style="width: 100%; max-width: 600px; display: flex; justify-content: space-between; align-items: center; padding-bottom: 8px;">
584
+ <div style="font-weight: 700; font-size: 1rem; color: #fff; display: flex; align-items: center; gap: 8px;">
585
+ 📸 Tomar Foto
586
+ </div>
587
+ <button id="btnCloseLiveCamera" style="background: rgba(255,255,255,0.2); border: none; color: #fff; width: 36px; height: 36px; border-radius: 50%; cursor: pointer; font-weight: bold; font-size: 1rem; display: flex; align-items: center; justify-content: center;">✕</button>
588
+ </div>
589
+
590
+ <div style="width: 100%; max-width: 600px; height: calc(100vh - 160px); display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; background: #121214; border-radius: 16px;">
591
+ <video id="liveCameraVideo" autoplay playsinline style="width: 100%; height: 100%; object-fit: cover; border-radius: 16px;"></video>
592
+ <canvas id="liveCameraCanvas" style="display: none;"></canvas>
593
+ </div>
594
+
595
+ <div style="width: 100%; max-width: 600px; display: flex; justify-content: center; padding: 10px 0;">
596
+ <button type="button" id="btnCaptureLivePhoto" style="background: #ffffff; color: #121214; border: 4px solid #7c3aed; width: 70px; height: 70px; border-radius: 50%; font-size: 1.5rem; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 20px rgba(124, 58, 237, 0.6); transition: transform 0.1s active;">
597
+ 📸
598
+ </button>
599
+ </div>
600
  </div>
601
 
602
  <!-- Privacy & Security Modal (Mobile) -->
 
1406
  });
1407
  }
1408
 
1409
+ // Upload & Live Camera WebRTC handlers
1410
  const cameraInputMobile = document.getElementById('cameraInputMobile');
1411
  const btnTriggerCameraMobile = document.getElementById('btnTriggerCameraMobile');
1412
  const btnTriggerFileMobile = document.getElementById('btnTriggerFileMobile');
1413
+ const liveCameraModal = document.getElementById('liveCameraModal');
1414
+ const liveCameraVideo = document.getElementById('liveCameraVideo');
1415
+ const liveCameraCanvas = document.getElementById('liveCameraCanvas');
1416
+ const btnCloseLiveCamera = document.getElementById('btnCloseLiveCamera');
1417
+ const btnCaptureLivePhoto = document.getElementById('btnCaptureLivePhoto');
1418
+ let cameraStream = null;
1419
+
1420
+ function stopCameraStream() {
1421
+ if (cameraStream) {
1422
+ cameraStream.getTracks().forEach(track => track.stop());
1423
+ cameraStream = null;
1424
+ }
1425
+ if (liveCameraModal) liveCameraModal.style.display = 'none';
1426
+ }
1427
 
1428
+ if (btnTriggerCameraMobile) {
1429
+ btnTriggerCameraMobile.addEventListener('click', async (e) => {
1430
  e.stopPropagation();
1431
+ // Intentar abrir cámara en vivo mediante MediaDevices WebRTC
1432
+ if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
1433
+ try {
1434
+ cameraStream = await navigator.mediaDevices.getUserMedia({
1435
+ video: { facingMode: { ideal: 'environment' } },
1436
+ audio: false
1437
+ });
1438
+ liveCameraVideo.srcObject = cameraStream;
1439
+ liveCameraModal.style.display = 'flex';
1440
+ return;
1441
+ } catch (err) {
1442
+ console.warn("[Camera WebRTC Fallback] Direct camera access fallback to input capture:", err);
1443
+ }
1444
+ }
1445
+ // Fallback si no hay soporte WebRTC directo o permiso denegado: disparar input capture
1446
+ if (cameraInputMobile) cameraInputMobile.click();
1447
+ });
1448
+ }
1449
+
1450
+ if (btnCloseLiveCamera) {
1451
+ btnCloseLiveCamera.addEventListener('click', stopCameraStream);
1452
+ }
1453
+
1454
+ if (btnCaptureLivePhoto && liveCameraVideo && liveCameraCanvas) {
1455
+ btnCaptureLivePhoto.addEventListener('click', () => {
1456
+ const width = liveCameraVideo.videoWidth || 1024;
1457
+ const height = liveCameraVideo.videoHeight || 1024;
1458
+ liveCameraCanvas.width = width;
1459
+ liveCameraCanvas.height = height;
1460
+ const ctx = liveCameraCanvas.getContext('2d');
1461
+ ctx.drawImage(liveCameraVideo, 0, 0, width, height);
1462
+
1463
+ base64Image = liveCameraCanvas.toDataURL('image/png');
1464
+ imagePreviewMobile.src = base64Image;
1465
+ dropzone.style.display = 'none';
1466
+ imagePreviewContainer.style.display = 'block';
1467
+ btnCreate3DMobile.style.display = 'flex';
1468
+
1469
+ stopCameraStream();
1470
  });
1471
  }
1472