diff --git a/quick-play.html b/quick-play.html
index 8ffbaf1..24de716 100644
--- a/quick-play.html
+++ b/quick-play.html
@@ -169,7 +169,41 @@
-
+
+
+
đš Webcam Recording
+
+
+
+
+
+
+ Records your session with a small webcam viewer. All recordings are stored locally on your device.
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1164,12 +1198,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
đš
+
No Session Videos Yet
+
Enable session recording in Quick Play setup to start recording your training sessions.
+
+
+
+
+
+
+
+
+
+
@@ -1214,6 +1283,10 @@
enableVideoSound: true,
enableVideoControls: false,
videoOpacity: 0.7,
+ // Webcam recording settings
+ enableSessionRecording: false,
+ webcamPosition: 'bottom-right',
+ webcamSize: 'small',
// Task management
disabledTasks: {
main: [],
@@ -1735,6 +1808,17 @@
}
}
+ function updateWebcamOptionsVisibility() {
+ const webcamSubOptions = document.getElementById('webcam-sub-options');
+ const enableRecording = document.getElementById('enable-session-recording').checked;
+
+ if (enableRecording) {
+ webcamSubOptions.style.display = 'block';
+ } else {
+ webcamSubOptions.style.display = 'none';
+ }
+ }
+
async function initializeVideoLibrary() {
try {
console.log('đŦ Attempting to initialize video library for Quick Play...');
@@ -1972,6 +2056,38 @@
exitToHome();
});
}
+
+ // Session videos button
+ const sessionVideosBtn = document.getElementById('session-videos');
+ if (sessionVideosBtn) {
+ sessionVideosBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ console.log('Session videos button clicked');
+ showSessionVideosGallery();
+ });
+ }
+
+ // Back to results from videos gallery
+ const backToResultsBtn = document.getElementById('back-to-results');
+ if (backToResultsBtn) {
+ backToResultsBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ console.log('Back to results button clicked');
+ hideSessionVideosGallery();
+ });
+ }
+
+ // Clear all videos button
+ const clearAllVideosBtn = document.getElementById('clear-all-videos');
+ if (clearAllVideosBtn) {
+ clearAllVideosBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ console.log('Clear all videos button clicked');
+ if (confirm('â ī¸ Are you sure you want to delete all recorded session videos? This cannot be undone.')) {
+ clearAllSessionVideos();
+ }
+ });
+ }
// Force exit button
const forceExitBtn = document.getElementById('force-exit');
@@ -2114,6 +2230,18 @@
quickPlaySettings.videoOpacity = parseFloat(e.target.value);
});
+ // Webcam recording settings
+ document.getElementById('enable-session-recording').addEventListener('change', (e) => {
+ quickPlaySettings.enableSessionRecording = e.target.checked;
+ updateWebcamOptionsVisibility();
+ });
+ document.getElementById('webcam-position').addEventListener('change', (e) => {
+ quickPlaySettings.webcamPosition = e.target.value;
+ });
+ document.getElementById('webcam-size').addEventListener('change', (e) => {
+ quickPlaySettings.webcamSize = e.target.value;
+ });
+
// Task type checkboxes
document.getElementById('include-standard-tasks').addEventListener('change', (e) => {
quickPlaySettings.includeStandardTasks = e.target.checked;
@@ -2226,6 +2354,11 @@
};
console.log('đ Session stats reset:', sessionStats);
+ // Initialize webcam recording if enabled
+ if (quickPlaySettings.enableSessionRecording) {
+ initializeWebcamRecording();
+ }
+
// Save current settings
localStorage.setItem('quickPlaySettings', JSON.stringify(quickPlaySettings));
@@ -3961,6 +4094,11 @@
console.log('đ Showing results screen with data:', results);
+ // Stop webcam recording if active
+ if (quickPlaySettings.enableSessionRecording) {
+ stopWebcamRecording();
+ }
+
// Hide game screen, show results
document.getElementById('quick-play-game').style.display = 'none';
document.getElementById('quick-play-results').style.display = 'block';
@@ -4126,6 +4264,11 @@
function exitToHome() {
console.log('Attempting to exit to home...');
+ // Stop webcam recording if active
+ if (quickPlaySettings.enableSessionRecording) {
+ stopWebcamRecording();
+ }
+
// Stop any running game
if (gameInstance) {
try {
@@ -4297,6 +4440,365 @@
}
}
+ // Webcam Recording Functions
+ let mediaRecorder = null;
+ let recordedChunks = [];
+ let webcamStream = null;
+
+ async function initializeWebcamRecording() {
+ console.log('đĨ Initializing webcam recording for session...');
+
+ try {
+ // Request camera access
+ webcamStream = await navigator.mediaDevices.getUserMedia({
+ video: true,
+ audio: false // Audio disabled for privacy
+ });
+
+ // Set up webcam viewer
+ const webcamViewer = document.getElementById('webcam-viewer');
+ const webcamPreview = document.getElementById('webcam-preview');
+
+ webcamPreview.srcObject = webcamStream;
+
+ // Apply user settings
+ webcamViewer.className = `webcam-viewer active ${quickPlaySettings.webcamSize} ${quickPlaySettings.webcamPosition}`;
+
+ // Set up recording
+ recordedChunks = [];
+ mediaRecorder = new MediaRecorder(webcamStream, {
+ mimeType: 'video/webm'
+ });
+
+ mediaRecorder.ondataavailable = (event) => {
+ if (event.data.size > 0) {
+ recordedChunks.push(event.data);
+ }
+ };
+
+ mediaRecorder.onstop = () => {
+ saveRecordedSession();
+ };
+
+ // Start recording
+ mediaRecorder.start();
+ console.log('â
Session recording started');
+
+ // Set up viewer controls
+ setupWebcamViewerControls();
+
+ } catch (error) {
+ console.error('â Failed to initialize webcam recording:', error);
+ // Don't show error to user - recording is optional
+ // Hide the viewer if it failed
+ const webcamViewer = document.getElementById('webcam-viewer');
+ webcamViewer.style.display = 'none';
+ }
+ }
+
+ function setupWebcamViewerControls() {
+ const toggleBtn = document.getElementById('toggle-webcam-viewer');
+ const stopBtn = document.getElementById('stop-recording');
+ const webcamViewer = document.getElementById('webcam-viewer');
+
+ // Toggle visibility
+ toggleBtn.addEventListener('click', () => {
+ const preview = document.getElementById('webcam-preview');
+ if (preview.style.display === 'none') {
+ preview.style.display = 'block';
+ toggleBtn.textContent = 'đī¸';
+ toggleBtn.title = 'Hide Webcam';
+ } else {
+ preview.style.display = 'none';
+ toggleBtn.textContent = 'đī¸âđ¨ī¸';
+ toggleBtn.title = 'Show Webcam';
+ }
+ });
+
+ // Stop recording
+ stopBtn.addEventListener('click', () => {
+ stopWebcamRecording();
+ });
+
+ // Make viewer draggable
+ makeWebcamViewerDraggable();
+ }
+
+ function makeWebcamViewerDraggable() {
+ const webcamViewer = document.getElementById('webcam-viewer');
+ let isDragging = false;
+ let startX, startY, startLeft, startTop;
+
+ webcamViewer.addEventListener('mousedown', (e) => {
+ // Only drag when clicking on the viewer itself, not controls
+ if (e.target.classList.contains('control-btn')) return;
+
+ isDragging = true;
+ startX = e.clientX;
+ startY = e.clientY;
+ startLeft = webcamViewer.offsetLeft;
+ startTop = webcamViewer.offsetTop;
+
+ webcamViewer.style.position = 'fixed';
+ webcamViewer.style.left = startLeft + 'px';
+ webcamViewer.style.top = startTop + 'px';
+ webcamViewer.classList.remove('bottom-right', 'bottom-left', 'top-right', 'top-left');
+ });
+
+ document.addEventListener('mousemove', (e) => {
+ if (!isDragging) return;
+
+ const deltaX = e.clientX - startX;
+ const deltaY = e.clientY - startY;
+
+ webcamViewer.style.left = (startLeft + deltaX) + 'px';
+ webcamViewer.style.top = (startTop + deltaY) + 'px';
+ });
+
+ document.addEventListener('mouseup', () => {
+ isDragging = false;
+ });
+ }
+
+ function stopWebcamRecording() {
+ console.log('âšī¸ Stopping webcam recording...');
+
+ if (mediaRecorder && mediaRecorder.state === 'recording') {
+ mediaRecorder.stop();
+ }
+
+ if (webcamStream) {
+ webcamStream.getTracks().forEach(track => track.stop());
+ webcamStream = null;
+ }
+
+ const webcamViewer = document.getElementById('webcam-viewer');
+ webcamViewer.classList.remove('active');
+
+ console.log('â
Webcam recording stopped');
+ }
+
+ function saveRecordedSession() {
+ console.log('đž Saving recorded session to gallery...');
+
+ if (recordedChunks.length === 0) {
+ console.warn('No recorded data to save');
+ return;
+ }
+
+ const blob = new Blob(recordedChunks, { type: 'video/webm' });
+ const reader = new FileReader();
+
+ reader.onload = function(event) {
+ const videoData = {
+ id: `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ dataURL: event.target.result,
+ timestamp: Date.now(),
+ sessionType: 'quick-play-recording',
+ duration: Date.now() - sessionStats.started, // Session duration
+ metadata: {
+ type: 'session-recording',
+ format: 'webm',
+ source: 'quick-play',
+ settings: {
+ position: quickPlaySettings.webcamPosition,
+ size: quickPlaySettings.webcamSize
+ }
+ }
+ };
+
+ // Save to localStorage gallery
+ let savedVideos = JSON.parse(localStorage.getItem('savedSessionVideos') || '[]');
+ savedVideos.push(videoData);
+
+ // Keep only last 10 recordings to manage storage
+ if (savedVideos.length > 10) {
+ savedVideos = savedVideos.slice(-10);
+ }
+
+ localStorage.setItem('savedSessionVideos', JSON.stringify(savedVideos));
+
+ console.log('â
Session recording saved to gallery:', videoData.id);
+
+ // Show completion message
+ if (window.flashMessageManager) {
+ window.flashMessageManager.show('đš Session recording saved to gallery! Check your session videos to download.', 'positive');
+ }
+
+ // Clean up
+ recordedChunks = [];
+ };
+
+ reader.readAsDataURL(blob);
+ }
+
+ // Session Videos Gallery Functions
+ function showSessionVideosGallery() {
+ console.log('đš Opening session videos gallery');
+
+ // Hide results screen, show videos gallery
+ document.getElementById('quick-play-results').style.display = 'none';
+ document.getElementById('session-videos-gallery').style.display = 'block';
+
+ // Load and display videos
+ loadSessionVideos();
+ }
+
+ function hideSessionVideosGallery() {
+ console.log('đš Closing session videos gallery');
+
+ // Hide videos gallery, show results screen
+ document.getElementById('session-videos-gallery').style.display = 'none';
+ document.getElementById('quick-play-results').style.display = 'block';
+ }
+
+ function loadSessionVideos() {
+ const savedVideos = JSON.parse(localStorage.getItem('savedSessionVideos') || '[]');
+ const videosGrid = document.getElementById('videos-grid');
+ const noVideosMessage = document.getElementById('no-videos-message');
+
+ if (savedVideos.length === 0) {
+ videosGrid.innerHTML = '';
+ noVideosMessage.style.display = 'block';
+ return;
+ }
+
+ noVideosMessage.style.display = 'none';
+
+ // Sort by timestamp (newest first)
+ savedVideos.sort((a, b) => b.timestamp - a.timestamp);
+
+ videosGrid.innerHTML = savedVideos.map(video => {
+ const date = new Date(video.timestamp);
+ const duration = formatDuration(video.duration);
+
+ return `
+
+
+
+
Session Recording
+
+ ${date.toLocaleDateString()} ${date.toLocaleTimeString()}
+ ${duration}
+
+
+ Position: ${video.metadata.settings.position} | Size: ${video.metadata.settings.size}
+
+
+
+
+
+
+
+ `;
+ }).join('');
+ }
+
+ function formatDuration(ms) {
+ const seconds = Math.floor(ms / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const remainingSeconds = seconds % 60;
+ return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
+ }
+
+ function playVideo(videoId) {
+ const savedVideos = JSON.parse(localStorage.getItem('savedSessionVideos') || '[]');
+ const video = savedVideos.find(v => v.id === videoId);
+
+ if (!video) {
+ console.warn('Video not found:', videoId);
+ return;
+ }
+
+ // Create fullscreen video player
+ const overlay = document.createElement('div');
+ overlay.className = 'video-player-overlay';
+ overlay.innerHTML = `
+
+
+
+
+ `;
+
+ document.body.appendChild(overlay);
+ window.currentVideoOverlay = overlay;
+ }
+
+ function closeVideoPlayer() {
+ if (window.currentVideoOverlay) {
+ document.body.removeChild(window.currentVideoOverlay);
+ window.currentVideoOverlay = null;
+ }
+ }
+
+ function downloadVideo(videoId) {
+ const savedVideos = JSON.parse(localStorage.getItem('savedSessionVideos') || '[]');
+ const video = savedVideos.find(v => v.id === videoId);
+
+ if (!video) {
+ console.warn('Video not found:', videoId);
+ return;
+ }
+
+ const a = document.createElement('a');
+ a.href = video.dataURL;
+ a.download = `quick-play-session-${new Date(video.timestamp).toISOString().slice(0, 19)}.webm`;
+
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+
+ console.log('đš Video downloaded:', videoId);
+
+ if (window.flashMessageManager) {
+ window.flashMessageManager.show('đš Video downloaded successfully!', 'positive');
+ }
+ }
+
+ function deleteVideo(videoId) {
+ if (!confirm('â ī¸ Are you sure you want to delete this video? This cannot be undone.')) {
+ return;
+ }
+
+ let savedVideos = JSON.parse(localStorage.getItem('savedSessionVideos') || '[]');
+ savedVideos = savedVideos.filter(v => v.id !== videoId);
+
+ localStorage.setItem('savedSessionVideos', JSON.stringify(savedVideos));
+
+ console.log('đī¸ Video deleted:', videoId);
+
+ // Reload the gallery
+ loadSessionVideos();
+
+ if (window.flashMessageManager) {
+ window.flashMessageManager.show('đī¸ Video deleted successfully!', 'positive');
+ }
+ }
+
+ function clearAllSessionVideos() {
+ localStorage.removeItem('savedSessionVideos');
+ loadSessionVideos();
+
+ console.log('đī¸ All session videos cleared');
+
+ if (window.flashMessageManager) {
+ window.flashMessageManager.show('đī¸ All videos cleared successfully!', 'positive');
+ }
+ }
+
function showErrorDialog(message) {
alert(`â Error: ${message}`);
}
@@ -8927,6 +9429,351 @@
border-color: rgba(255, 193, 7, 0.3);
background: rgba(255, 193, 7, 0.05);
}
+
+ /* Session Videos Gallery Styles */
+ .session-videos-gallery {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100vh;
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
+ overflow-y: auto;
+ z-index: 100;
+ }
+
+ .videos-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+ }
+
+ .videos-header {
+ text-align: center;
+ margin-bottom: 30px;
+ }
+
+ .videos-header h2 {
+ color: #ffffff;
+ font-size: 2rem;
+ margin-bottom: 10px;
+ }
+
+ .videos-header p {
+ color: #b8b8b8;
+ font-size: 1.1rem;
+ }
+
+ .videos-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
+ gap: 20px;
+ margin-bottom: 30px;
+ }
+
+ .video-item {
+ background: rgba(255, 255, 255, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 15px;
+ padding: 15px;
+ backdrop-filter: blur(10px);
+ transition: all 0.3s ease;
+ }
+
+ .video-item:hover {
+ transform: translateY(-5px);
+ border-color: rgba(0, 255, 255, 0.5);
+ box-shadow: 0 10px 30px rgba(0, 255, 255, 0.2);
+ }
+
+ .video-preview {
+ position: relative;
+ width: 100%;
+ height: 200px;
+ border-radius: 10px;
+ overflow: hidden;
+ margin-bottom: 15px;
+ background: #000;
+ }
+
+ .video-preview video {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+
+ .video-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ }
+
+ .video-preview:hover .video-overlay {
+ opacity: 1;
+ }
+
+ .play-btn {
+ background: rgba(0, 255, 255, 0.9);
+ border: none;
+ border-radius: 50%;
+ width: 60px;
+ height: 60px;
+ font-size: 1.2rem;
+ color: #000;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ }
+
+ .play-btn:hover {
+ background: rgba(0, 255, 255, 1);
+ transform: scale(1.1);
+ }
+
+ .video-info {
+ margin-bottom: 15px;
+ }
+
+ .video-title {
+ color: #ffffff;
+ font-size: 1.1rem;
+ font-weight: bold;
+ margin-bottom: 5px;
+ }
+
+ .video-meta {
+ color: #b8b8b8;
+ font-size: 0.9rem;
+ margin-bottom: 5px;
+ }
+
+ .video-meta span {
+ margin-right: 15px;
+ }
+
+ .video-settings {
+ color: #888;
+ font-size: 0.8rem;
+ }
+
+ .video-actions {
+ display: flex;
+ gap: 10px;
+ }
+
+ .video-actions .btn {
+ flex: 1;
+ padding: 8px 12px;
+ font-size: 0.9rem;
+ }
+
+ .videos-actions {
+ display: flex;
+ justify-content: center;
+ gap: 20px;
+ margin-top: 30px;
+ }
+
+ .no-videos-message {
+ text-align: center;
+ padding: 60px 20px;
+ }
+
+ .empty-state {
+ max-width: 400px;
+ margin: 0 auto;
+ }
+
+ .empty-icon {
+ font-size: 4rem;
+ margin-bottom: 20px;
+ }
+
+ .empty-state h3 {
+ color: #ffffff;
+ font-size: 1.5rem;
+ margin-bottom: 15px;
+ }
+
+ .empty-state p {
+ color: #b8b8b8;
+ line-height: 1.6;
+ }
+
+ /* Video Player Overlay */
+ .video-player-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100vh;
+ background: rgba(0, 0, 0, 0.95);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 2000;
+ }
+
+ .video-player-container {
+ position: relative;
+ max-width: 90vw;
+ max-height: 90vh;
+ }
+
+ .video-player-container video {
+ width: 100%;
+ height: auto;
+ max-height: 90vh;
+ border-radius: 10px;
+ }
+
+ .close-player {
+ position: absolute;
+ top: -40px;
+ right: 0;
+ background: rgba(255, 255, 255, 0.2);
+ border: none;
+ border-radius: 50%;
+ width: 35px;
+ height: 35px;
+ color: #fff;
+ font-size: 1.2rem;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ }
+
+ .close-player:hover {
+ background: rgba(255, 255, 255, 0.4);
+ transform: scale(1.1);
+ }
+
+ /* Webcam Viewer Styles */
+ .webcam-viewer {
+ position: fixed;
+ z-index: 1000;
+ border: 2px solid rgba(0, 255, 255, 0.6);
+ border-radius: 10px;
+ background: rgba(0, 20, 40, 0.9);
+ backdrop-filter: blur(5px);
+ box-shadow: 0 4px 20px rgba(0, 255, 255, 0.3);
+ transition: all 0.3s ease;
+ cursor: move;
+ display: none;
+ overflow: hidden;
+ }
+
+ .webcam-viewer.active {
+ display: block;
+ }
+
+ .webcam-viewer video {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ border-radius: 8px;
+ }
+
+ .webcam-viewer .viewer-controls {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ display: flex;
+ gap: 5px;
+ opacity: 0.7;
+ transition: opacity 0.3s;
+ }
+
+ .webcam-viewer:hover .viewer-controls {
+ opacity: 1;
+ }
+
+ .webcam-viewer .control-btn {
+ background: rgba(0, 0, 0, 0.7);
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ color: white;
+ border-radius: 3px;
+ padding: 2px 5px;
+ font-size: 10px;
+ cursor: pointer;
+ transition: background 0.3s;
+ }
+
+ .webcam-viewer .control-btn:hover {
+ background: rgba(255, 255, 255, 0.2);
+ }
+
+ .webcam-viewer .recording-indicator {
+ position: absolute;
+ top: 5px;
+ left: 5px;
+ background: #ff4444;
+ color: white;
+ padding: 2px 6px;
+ border-radius: 10px;
+ font-size: 10px;
+ font-weight: bold;
+ animation: pulse 2s infinite;
+ }
+
+ @keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+ }
+
+ /* Size variants */
+ .webcam-viewer.small {
+ width: 150px;
+ height: 113px; /* 4:3 aspect ratio */
+ }
+
+ .webcam-viewer.medium {
+ width: 200px;
+ height: 150px;
+ }
+
+ .webcam-viewer.large {
+ width: 250px;
+ height: 188px;
+ }
+
+ /* Position variants */
+ .webcam-viewer.bottom-right {
+ bottom: 20px;
+ right: 20px;
+ }
+
+ .webcam-viewer.bottom-left {
+ bottom: 20px;
+ left: 20px;
+ }
+
+ .webcam-viewer.top-right {
+ top: 20px;
+ right: 20px;
+ }
+
+ .webcam-viewer.top-left {
+ top: 20px;
+ left: 20px;
+ }
+
+
+
+
â REC
+
+
+
+
+
+
+