Bot commited on
Commit
a8b35de
Β·
1 Parent(s): e6ca063

feat: add screen recording capability

Browse files
Files changed (3) hide show
  1. static/main.js +120 -0
  2. static/style.css +106 -0
  3. templates/index.html +12 -0
static/main.js CHANGED
@@ -690,3 +690,123 @@ killBtn.addEventListener('click', () => {
690
  document.getElementById('terminatedOverlay').style.display = 'flex';
691
  });
692
  controlsContainer.appendChild(killBtn);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
690
  document.getElementById('terminatedOverlay').style.display = 'flex';
691
  });
692
  controlsContainer.appendChild(killBtn);
693
+
694
+ // ─────────────────────────────────────────────────────────────
695
+ // RECORDING LOGIC
696
+ // ─────────────────────────────────────────────────────────────
697
+ const recordBtn = document.getElementById('recordBtn');
698
+ const statusPill = document.getElementById('recordingStatusPill');
699
+ const statusText = document.getElementById('recordingStatusText');
700
+ let mediaRecorder = null;
701
+ let recordedChunks = [];
702
+ let isRecording = false;
703
+ let countdownInterval = null;
704
+
705
+ function pad(n) { return n < 10 ? '0' + n : n; }
706
+ function getFormattedDateTime() {
707
+ const d = new Date();
708
+ return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}_${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}`;
709
+ }
710
+
711
+ recordBtn.addEventListener('click', () => {
712
+ if (isRecording || countdownInterval) return; // Prevent multiple clicks
713
+
714
+ recordBtn.disabled = true;
715
+ recordBtn.style.opacity = '0.5';
716
+
717
+ // Start 5 second countdown
718
+ let preCount = 5;
719
+ statusPill.style.display = 'flex';
720
+ statusText.textContent = `Starting in ${preCount}...`;
721
+ statusPill.querySelector('.status-dot').style.animation = 'none'; // Solid dot during countdown
722
+ statusPill.querySelector('.status-dot').style.background = '#ffa500'; // Orange
723
+
724
+ countdownInterval = setInterval(() => {
725
+ preCount--;
726
+ if (preCount > 0) {
727
+ statusText.textContent = `Starting in ${preCount}...`;
728
+ } else {
729
+ clearInterval(countdownInterval);
730
+ countdownInterval = null;
731
+ startRecording();
732
+ }
733
+ }, 1000);
734
+ });
735
+
736
+ function startRecording() {
737
+ recordedChunks = [];
738
+ const stream = canvas.captureStream(30);
739
+
740
+ // Check mime type
741
+ let options = { mimeType: 'video/webm; codecs=vp9' };
742
+ if (MediaRecorder.isTypeSupported('video/mp4; codecs="avc1.424028, mp4a.40.2"')) {
743
+ options = { mimeType: 'video/mp4; codecs="avc1.424028, mp4a.40.2"' };
744
+ } else if (MediaRecorder.isTypeSupported('video/mp4')) {
745
+ options = { mimeType: 'video/mp4' };
746
+ } else if (MediaRecorder.isTypeSupported('video/webm; codecs=vp8')) {
747
+ options = { mimeType: 'video/webm; codecs=vp8' };
748
+ } else if (MediaRecorder.isTypeSupported('video/webm')) {
749
+ options = { mimeType: 'video/webm' };
750
+ } else {
751
+ options = {}; // fallback
752
+ }
753
+
754
+ try {
755
+ mediaRecorder = new MediaRecorder(stream, options);
756
+ } catch (e) {
757
+ console.error("Exception while creating MediaRecorder:", e);
758
+ mediaRecorder = new MediaRecorder(stream);
759
+ }
760
+
761
+ mediaRecorder.ondataavailable = (event) => {
762
+ if (event.data && event.data.size > 0) {
763
+ recordedChunks.push(event.data);
764
+ }
765
+ };
766
+
767
+ mediaRecorder.onstop = () => {
768
+ statusText.textContent = 'Saving...';
769
+ const blob = new Blob(recordedChunks, { type: mediaRecorder.mimeType });
770
+ const url = URL.createObjectURL(blob);
771
+ const a = document.createElement('a');
772
+ document.body.appendChild(a);
773
+ a.style = 'display: none';
774
+ a.href = url;
775
+ // Check if the mimeType contains 'mp4' (if browser supported it). Otherwise, fallback to webm.
776
+ // It's safer to always request mp4 as the extension per requirements, but if we recorded webm, we might create a corrupted mp4.
777
+ // The instructions: "save in mp4 format... file naming convention gesteffect_date_time.mp4"
778
+ a.download = `gesteffect_${getFormattedDateTime()}.mp4`;
779
+ a.click();
780
+ window.URL.revokeObjectURL(url);
781
+
782
+ statusPill.style.display = 'none';
783
+ recordBtn.disabled = false;
784
+ recordBtn.style.opacity = '1';
785
+ recordBtn.classList.remove('recording');
786
+ recordBtn.querySelector('.record-text').textContent = 'Record';
787
+ isRecording = false;
788
+ };
789
+
790
+ mediaRecorder.start();
791
+ isRecording = true;
792
+ recordBtn.classList.add('recording');
793
+ recordBtn.querySelector('.record-text').textContent = 'Recording';
794
+
795
+ // 60 second recording countdown
796
+ let recCount = 60;
797
+ statusPill.querySelector('.status-dot').style.animation = 'blink 1s infinite alternate';
798
+ statusPill.querySelector('.status-dot').style.background = '#ff0000'; // Red
799
+ statusText.textContent = `πŸ”΄ Recording ${recCount}s`;
800
+
801
+ const recInterval = setInterval(() => {
802
+ recCount--;
803
+ if (recCount > 0) {
804
+ statusText.textContent = `πŸ”΄ Recording ${recCount}s`;
805
+ } else {
806
+ clearInterval(recInterval);
807
+ if (mediaRecorder.state !== 'inactive') {
808
+ mediaRecorder.stop();
809
+ }
810
+ }
811
+ }, 1000);
812
+ }
static/style.css CHANGED
@@ -358,3 +358,109 @@ html, body {
358
  font-size: 11px;
359
  }
360
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  font-size: 11px;
359
  }
360
  }
361
+
362
+ /* ── RECORDING UI ───────────────────────────────────────────────── */
363
+ .status-pill {
364
+ position: absolute;
365
+ top: 24px;
366
+ left: 50%;
367
+ transform: translateX(-50%);
368
+ background: rgba(255, 0, 0, 0.15);
369
+ backdrop-filter: blur(14px);
370
+ -webkit-backdrop-filter: blur(14px);
371
+ border: 1px solid rgba(255, 0, 0, 0.4);
372
+ border-radius: 30px;
373
+ padding: 8px 16px;
374
+ display: flex;
375
+ align-items: center;
376
+ gap: 8px;
377
+ z-index: 100;
378
+ box-shadow: 0 0 20px rgba(255, 0, 0, 0.2);
379
+ }
380
+
381
+ .status-dot {
382
+ width: 10px;
383
+ height: 10px;
384
+ background: #ff0000;
385
+ border-radius: 50%;
386
+ box-shadow: 0 0 10px #ff0000;
387
+ animation: blink 1s infinite alternate;
388
+ }
389
+
390
+ @keyframes blink {
391
+ from { opacity: 1; }
392
+ to { opacity: 0.3; }
393
+ }
394
+
395
+ #recordingStatusText {
396
+ color: #fff;
397
+ font-size: 14px;
398
+ font-weight: 600;
399
+ text-shadow: 0 0 5px rgba(255, 0, 0, 0.5);
400
+ }
401
+
402
+ .record-btn {
403
+ position: absolute;
404
+ bottom: 24px;
405
+ left: 50%;
406
+ transform: translateX(-50%);
407
+ background: rgba(255, 255, 255, 0.1);
408
+ backdrop-filter: blur(14px);
409
+ -webkit-backdrop-filter: blur(14px);
410
+ border: 1px solid rgba(255, 255, 255, 0.2);
411
+ border-radius: 30px;
412
+ padding: 10px 20px;
413
+ display: flex;
414
+ align-items: center;
415
+ gap: 10px;
416
+ z-index: 100;
417
+ cursor: pointer;
418
+ transition: all 0.3s ease;
419
+ color: #fff;
420
+ font-family: inherit;
421
+ font-weight: 600;
422
+ }
423
+
424
+ .record-btn:hover {
425
+ background: rgba(255, 255, 255, 0.2);
426
+ border-color: rgba(255, 255, 255, 0.4);
427
+ }
428
+
429
+ .record-btn.recording {
430
+ background: rgba(255, 0, 0, 0.15);
431
+ border-color: #ff0000;
432
+ box-shadow: 0 0 20px rgba(255, 0, 0, 0.3);
433
+ }
434
+
435
+ .record-btn.recording .record-icon {
436
+ border-radius: 4px;
437
+ background: #ff0000;
438
+ }
439
+
440
+ .record-icon {
441
+ width: 16px;
442
+ height: 16px;
443
+ background: #ff0000;
444
+ border-radius: 50%;
445
+ transition: all 0.3s ease;
446
+ box-shadow: 0 0 10px #ff0000;
447
+ }
448
+
449
+ @media (max-width: 480px) {
450
+ .status-pill {
451
+ top: 12px;
452
+ padding: 6px 12px;
453
+ }
454
+ #recordingStatusText {
455
+ font-size: 12px;
456
+ }
457
+ .record-btn {
458
+ bottom: 12px;
459
+ padding: 8px 16px;
460
+ font-size: 12px;
461
+ }
462
+ .record-icon {
463
+ width: 14px;
464
+ height: 14px;
465
+ }
466
+ }
templates/index.html CHANGED
@@ -57,6 +57,18 @@
57
  <button class="theme-btn" data-theme="Synthwave"><span class="theme-icon">πŸ•ΉοΈ</span><span class="theme-name">Synthwave</span></button>
58
  <button class="theme-btn" data-theme="NeonDemon"><span class="theme-icon">😈</span><span class="theme-name">NeonDemon</span></button>
59
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
60
  </div>
61
 
62
  <!-- Main Engine -->
 
57
  <button class="theme-btn" data-theme="Synthwave"><span class="theme-icon">πŸ•ΉοΈ</span><span class="theme-name">Synthwave</span></button>
58
  <button class="theme-btn" data-theme="NeonDemon"><span class="theme-icon">😈</span><span class="theme-name">NeonDemon</span></button>
59
  </div>
60
+
61
+ <!-- Recording Status Pill (Top Middle) -->
62
+ <div id="recordingStatusPill" class="status-pill" style="display: none;">
63
+ <span class="status-dot"></span>
64
+ <span id="recordingStatusText">Starting...</span>
65
+ </div>
66
+
67
+ <!-- Record Button (Bottom Middle) -->
68
+ <button id="recordBtn" class="record-btn">
69
+ <span class="record-icon"></span>
70
+ <span class="record-text">Record</span>
71
+ </button>
72
  </div>
73
 
74
  <!-- Main Engine -->