pjxcharya commited on
Commit
067e5d2
·
verified ·
1 Parent(s): b5c92d5

Create live_test.html

Browse files
Files changed (1) hide show
  1. live_test.html +243 -0
live_test.html ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Live Fitness Trainer Test</title>
7
+ <style>
8
+ body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; margin: 0; padding: 20px; background-color: #f4f4f4; }
9
+ #controls { margin-bottom: 20px; padding: 15px; background-color: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
10
+ label, select, button { font-size: 1em; margin: 5px; }
11
+ button { padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
12
+ button:disabled { background-color: #ccc; }
13
+ button:hover:not(:disabled) { background-color: #0056b3; }
14
+ #videoContainer { display: flex; flex-wrap: wrap; justify-content: center; gap: 20px; margin-bottom: 20px; }
15
+ video { border: 2px solid #007bff; transform: scaleX(-1); border-radius: 8px; background-color: #000; } /* Flip video for mirror effect */
16
+ #feedbackArea { border: 1px solid #ccc; padding: 15px; width: 320px; background-color: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
17
+ #feedbackArea h3 { margin-top: 0; color: #007bff; }
18
+ #feedbackArea p { margin: 8px 0; }
19
+ #feedbackArea span { font-weight: bold; color: #333; }
20
+ .hidden { display: none; }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <h1>Live Fitness Trainer Test</h1>
25
+
26
+ <div id="controls">
27
+ <label for="exerciseType">Exercise:</label>
28
+ <select id="exerciseType">
29
+ <option value="squat">Squat</option>
30
+ <option value="push_up">Push Up</option>
31
+ <option value="hammer_curl">Hammer Curl</option>
32
+ </select>
33
+ <button id="startTrainerBtn">Start Trainer</button>
34
+ <button id="stopTrainerBtn" disabled>Stop Trainer</button>
35
+ </div>
36
+
37
+ <div id="videoContainer">
38
+ <div>
39
+ <h3>Your Webcam</h3>
40
+ <video id="userVideo" width="320" height="240" autoplay playsinline></video>
41
+ </div>
42
+ <div id="feedbackArea">
43
+ <h3>Feedback & Status</h3>
44
+ <p>Session ID: <span id="sessionIdDisplay">-</span></p>
45
+ <p>API Status: <span id="apiStatus">Idle</span></p>
46
+ <hr>
47
+ <p>Reps: <span id="reps">0</span></p>
48
+ <p>Stage: <span id="stage">N/A</span></p>
49
+ <p>Feedback: <span id="feedback">N/A</span></p>
50
+ </div>
51
+ </div>
52
+
53
+ <script>
54
+ const videoElement = document.getElementById('userVideo');
55
+ // It's good practice to create the canvas in memory if it's not needed in the DOM
56
+ const canvasElement = document.createElement('canvas');
57
+ const context = canvasElement.getContext('2d', { willReadFrequently: true }); // Added willReadFrequently for performance
58
+
59
+ const startBtn = document.getElementById('startTrainerBtn');
60
+ const stopBtn = document.getElementById('stopTrainerBtn');
61
+ const exerciseTypeSelect = document.getElementById('exerciseType');
62
+
63
+ const sessionIdDisplay = document.getElementById('sessionIdDisplay');
64
+ const repsDisplay = document.getElementById('reps');
65
+ const stageDisplay = document.getElementById('stage');
66
+ const feedbackDisplay = document.getElementById('feedback');
67
+ const apiStatusDisplay = document.getElementById('apiStatus');
68
+
69
+ let currentSessionId = null;
70
+ let streamActive = false;
71
+ let animationFrameId = null; // To control the loop
72
+
73
+ // API Endpoints - ensure your Flask app is running on this address
74
+ const API_URL_TRACK = 'http://127.0.0.1:5000/api/track_exercise_stream';
75
+ const API_URL_END_SESSION = 'http://127.0.0.1:5000/api/end_exercise_session';
76
+
77
+ function generateSessionId() {
78
+ if (typeof crypto.randomUUID === 'function') {
79
+ return crypto.randomUUID();
80
+ }
81
+ // Basic fallback for older browsers
82
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
83
+ var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
84
+ return v.toString(16);
85
+ });
86
+ }
87
+
88
+ startBtn.addEventListener('click', async () => {
89
+ currentSessionId = generateSessionId();
90
+ sessionIdDisplay.textContent = currentSessionId;
91
+ const selectedExerciseType = exerciseTypeSelect.value;
92
+ console.log(`Starting session: ${currentSessionId} for ${selectedExerciseType}`);
93
+ apiStatusDisplay.textContent = "Initializing camera...";
94
+ feedbackDisplay.textContent = "N/A"; // Reset feedback
95
+ repsDisplay.textContent = "0";
96
+ stageDisplay.textContent = "N/A";
97
+
98
+
99
+ try {
100
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
101
+ videoElement.srcObject = stream;
102
+ // Ensure onloadedmetadata is used, as it waits for video dimensions
103
+ videoElement.onloadedmetadata = () => {
104
+ videoElement.play().then(() => { // Ensure play is successful
105
+ canvasElement.width = videoElement.videoWidth;
106
+ canvasElement.height = videoElement.videoHeight;
107
+ streamActive = true;
108
+ startBtn.disabled = true;
109
+ stopBtn.disabled = false;
110
+ exerciseTypeSelect.disabled = true;
111
+ apiStatusDisplay.textContent = "Camera active. Starting stream...";
112
+ sendFrameLoop();
113
+ }).catch(playError => {
114
+ console.error("Error playing video:", playError);
115
+ apiStatusDisplay.textContent = "Error playing video.";
116
+ alert("Error playing video: " + playError.message);
117
+ });
118
+ };
119
+ } catch (err) {
120
+ console.error("Error accessing webcam:", err);
121
+ apiStatusDisplay.textContent = "Error accessing webcam.";
122
+ alert("Could not access webcam: " + err.message);
123
+ }
124
+ });
125
+
126
+ stopBtn.addEventListener('click', async () => {
127
+ streamActive = false; // Signal to stop the loop
128
+ if (animationFrameId) {
129
+ cancelAnimationFrame(animationFrameId);
130
+ animationFrameId = null;
131
+ }
132
+ apiStatusDisplay.textContent = "Stopping session...";
133
+
134
+ if (videoElement.srcObject) {
135
+ videoElement.srcObject.getTracks().forEach(track => track.stop());
136
+ videoElement.srcObject = null; // Release camera
137
+ }
138
+
139
+ if (currentSessionId) {
140
+ console.log(`Ending session: ${currentSessionId}`);
141
+ try {
142
+ const response = await fetch(API_URL_END_SESSION, {
143
+ method: 'POST',
144
+ headers: { 'Content-Type': 'application/json' },
145
+ body: JSON.stringify({ session_id: currentSessionId })
146
+ });
147
+ const result = await response.json();
148
+ console.log('End session response:', result.message);
149
+ apiStatusDisplay.textContent = "Session ended.";
150
+ } catch (error) {
151
+ console.error('Error ending session:', error);
152
+ apiStatusDisplay.textContent = "Error ending session.";
153
+ }
154
+ }
155
+
156
+ currentSessionId = null; // Reset session ID
157
+ sessionIdDisplay.textContent = "-";
158
+ startBtn.disabled = false;
159
+ stopBtn.disabled = true;
160
+ exerciseTypeSelect.disabled = false;
161
+ // Optionally reset feedback displays here or leave them with last known state
162
+ });
163
+
164
+ async function sendFrameLoop() {
165
+ if (!streamActive || !currentSessionId || !videoElement.srcObject || videoElement.paused || videoElement.ended || videoElement.readyState < 3) {
166
+ // video.readyState < 3 means not enough data to play the current frame
167
+ if(streamActive) { // If we intended to stream, try again shortly
168
+ animationFrameId = requestAnimationFrame(sendFrameLoop);
169
+ } else {
170
+ apiStatusDisplay.textContent = "Stream stopped or video not ready.";
171
+ }
172
+ return;
173
+ }
174
+
175
+ context.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
176
+ // Use a lower quality for faster encoding if network is slow, e.g., 0.5 or 0.6
177
+ const imageDataBase64 = canvasElement.toDataURL('image/jpeg', 0.7).split(',')[1];
178
+ const selectedExerciseType = exerciseTypeSelect.value;
179
+
180
+ try {
181
+ // apiStatusDisplay.textContent = "Sending frame..."; // This can be too flashy
182
+ const response = await fetch(API_URL_TRACK, {
183
+ method: 'POST',
184
+ headers: { 'Content-Type': 'application/json' },
185
+ body: JSON.stringify({
186
+ session_id: currentSessionId,
187
+ exercise_type: selectedExerciseType,
188
+ image: imageDataBase64,
189
+ frame_width: canvasElement.width,
190
+ frame_height: canvasElement.height
191
+ })
192
+ });
193
+
194
+ if (!response.ok) {
195
+ const errorData = await response.json();
196
+ console.error('API Error:', errorData.error);
197
+ feedbackDisplay.textContent = `API Error: ${errorData.error}`;
198
+ apiStatusDisplay.textContent = `API Error.`;
199
+ } else {
200
+ const result = await response.json();
201
+ apiStatusDisplay.textContent = "Frame processed."; // Reset status on success
202
+
203
+ if (result.success && result.landmarks_detected) {
204
+ const data = result.data;
205
+ // console.log('API Data:', data); // For debugging
206
+ repsDisplay.textContent = data.counter !== undefined ? data.counter : `${data.counter_left || 0} (L) / ${data.counter_right || 0} (R)`;
207
+ stageDisplay.textContent = data.stage !== undefined ? data.stage : `${data.stage_left || 'N/A'} (L) / ${data.stage_right || 'N/A'} (R)`;
208
+ feedbackDisplay.textContent = data.feedback !== undefined ? data.feedback : `${data.feedback_left || ''} ${data.feedback_right || ''}`.trim() || "Processing...";
209
+ } else if (result.success && !result.landmarks_detected) {
210
+ console.log('No landmarks detected in this frame.');
211
+ feedbackDisplay.textContent = 'No landmarks detected. Adjust position?';
212
+ } else {
213
+ feedbackDisplay.textContent = 'Error processing frame or unexpected response.';
214
+ }
215
+ }
216
+ } catch (error) {
217
+ console.error('Network or other error sending frame:', error);
218
+ feedbackDisplay.textContent = "Network error. Is Flask server running?";
219
+ apiStatusDisplay.textContent = "Network error.";
220
+ // Consider stopping the loop on persistent network errors
221
+ // For now, we'll let it keep trying if streamActive is true
222
+ }
223
+
224
+ if (streamActive) { // Continue the loop only if stream is supposed to be active
225
+ animationFrameId = requestAnimationFrame(sendFrameLoop);
226
+ }
227
+ }
228
+
229
+ // Gracefully handle page unload
230
+ window.addEventListener('beforeunload', () => {
231
+ if (currentSessionId) { // If a session is active
232
+ // This is a synchronous request, not ideal, but best effort for cleanup
233
+ // Modern browsers might block this or ignore it.
234
+ // A more robust solution would be a server-side timeout for inactive sessions.
235
+ const payload = JSON.stringify({ session_id: currentSessionId });
236
+ navigator.sendBeacon(API_URL_END_SESSION, payload);
237
+ console.log('Attempted to end session on page unload via Beacon API.');
238
+ }
239
+ });
240
+
241
+ </script>
242
+ </body>
243
+ </html>