File size: 13,523 Bytes
da97741
38d4239
da97741
 
 
 
 
 
 
 
 
 
 
 
 
 
38d4239
da97741
38d4239
 
 
 
 
 
 
 
 
 
 
 
 
da97741
 
 
 
38d4239
da97741
 
 
38d4239
da97741
38d4239
 
 
 
 
 
 
 
 
15deff9
38d4239
15deff9
da97741
 
 
38d4239
 
 
 
 
 
 
 
 
da97741
 
 
38d4239
 
 
 
15deff9
38d4239
15deff9
38d4239
 
 
 
 
 
 
 
15deff9
da97741
 
38d4239
 
 
 
 
da97741
15deff9
 
38d4239
15deff9
38d4239
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15deff9
 
 
da97741
15deff9
38d4239
 
 
 
 
 
 
 
 
15deff9
38d4239
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15deff9
 
38d4239
 
 
 
 
 
 
 
 
 
 
15deff9
38d4239
 
 
 
15deff9
 
 
 
 
 
38d4239
15deff9
 
 
38d4239
 
 
 
 
 
 
 
 
 
 
 
 
 
da97741
 
 
38d4239
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da97741
 
 
 
 
 
 
 
38d4239
 
 
 
 
 
 
 
 
 
da97741
15deff9
 
38d4239
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da97741
38d4239
 
 
 
 
 
 
 
 
 
 
15deff9
 
da97741
 
38d4239
da97741
 
38d4239
 
 
da97741
38d4239
15deff9
38d4239
 
 
 
 
 
 
 
15deff9
38d4239
 
 
15deff9
38d4239
 
 
15deff9
38d4239
 
 
15deff9
38d4239
 
 
15deff9
38d4239
 
 
 
 
 
 
 
15deff9
38d4239
15deff9
 
38d4239
 
15deff9
38d4239
 
15deff9
38d4239
 
 
15deff9
38d4239
15deff9
 
38d4239
 
 
 
15deff9
38d4239
 
 
 
 
 
 
 
15deff9
38d4239
da97741
38d4239
da97741
15deff9
38d4239
15deff9
38d4239
 
 
15deff9
da97741
 
38d4239
 
da97741
15deff9
38d4239
15deff9
 
 
 
 
 
 
 
 
 
 
 
38d4239
15deff9
38d4239
 
 
da97741
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
#!/usr/bin/env python3
# shared_memory_working.py - Working shared memory
import pygame
import numpy as np
import time
import threading
import os
from flask import Flask, Response

# ===== 1. SETUP =====
os.environ['SDL_VIDEODRIVER'] = 'dummy'
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'

PORT = int(os.getenv('PORT', 7860))
WIDTH, HEIGHT = 400, 300

print(f"πŸš€ Starting on port {PORT}")

# ===== 2. SHARED MEMORY WITH DEFAULT DATA =====
# Create initial frame data immediately (BEFORE any threads start)
initial_frame = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8)
initial_frame[:, :] = [100, 150, 200]  # Light blue
initial_bytes = initial_frame.tobytes()

shared_state = {
    "raw_pixels": initial_bytes,  # ALWAYS has data from the start
    "frame_count": 0,
    "streaming": True,
    "lock": threading.Lock(),
    "ready": True  # Mark as ready immediately
}

# ===== 3. FLASK APP =====
app = Flask(__name__)

# ===== 4. SIMPLE HTML WITH VISIBLE TEST =====
HTML = '''<!DOCTYPE html>
<html>
<head>
    <title>Shared Memory WORKING</title>
    <style>
        body { margin: 0; padding: 20px; background: #111; color: white; font-family: Arial; }
        h1 { color: #4CAF50; text-align: center; }
        .test-area {
            text-align: center;
            margin: 30px 0;
            padding: 20px;
            background: #222;
            border-radius: 10px;
        }
        #streamCanvas {
            border: 5px solid #4CAF50;
            background: black;
            display: block;
            margin: 20px auto;
        }
        .stats {
            background: #333;
            padding: 15px;
            border-radius: 8px;
            display: inline-block;
            margin: 10px;
        }
        .success { color: #4CAF50; font-weight: bold; }
        .error { color: #f44336; font-weight: bold; }
    </style>
</head>
<body>
    <h1>🎯 SHARED MEMORY TEST - MUST SHOW IMAGE</h1>
    
    <div class="test-area">
        <h2>Canvas should show colors below:</h2>
        <canvas id="streamCanvas" width="400" height="300"></canvas>
        
        <div class="stats">
            <div>Server Status: <span id="serverStatus" class="success">Connected</span></div>
            <div>Frames Received: <span id="framesReceived">0</span></div>
            <div>Last Update: <span id="lastUpdate">Never</span></div>
        </div>
        
        <div style="margin-top: 20px;">
            <button onclick="testConnection()">Test Connection</button>
            <button onclick="forceRefresh()">Force Refresh</button>
        </div>
    </div>
    
    <div style="text-align: center; color: #888; margin-top: 30px;">
        If you see a colored canvas above, it's working!<br>
        If it's black, check browser console (F12) for errors.
    </div>
    
    <script>
        const canvas = document.getElementById('streamCanvas');
        const ctx = canvas.getContext('2d');
        let receivedFrames = 0;
        
        // Initialize with a test pattern immediately
        function drawTestPattern() {
            const testImage = new ImageData(400, 300);
            const data = testImage.data;
            
            // Create a red/gradient test pattern
            for (let y = 0; y < 300; y++) {
                for (let x = 0; x < 400; x++) {
                    const i = (y * 400 + x) * 4;
                    data[i] = x % 256;      // Red gradient
                    data[i + 1] = y % 256;  // Green gradient  
                    data[i + 2] = 128;      // Constant blue
                    data[i + 3] = 255;      // Alpha
                }
            }
            
            ctx.putImageData(testImage, 0, 0);
            console.log('βœ… Drawn test pattern');
        }
        
        // Draw test pattern initially
        drawTestPattern();
        
        function updateFrame() {
            const startTime = Date.now();
            
            fetch('/frame')
                .then(response => {
                    console.log('Response status:', response.status, 'type:', response.headers.get('content-type'));
                    
                    if (!response.ok) {
                        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                    }
                    
                    return response.arrayBuffer();
                })
                .then(buffer => {
                    receivedFrames++;
                    const now = new Date().toLocaleTimeString();
                    
                    // Update stats
                    document.getElementById('framesReceived').textContent = receivedFrames;
                    document.getElementById('lastUpdate').textContent = now;
                    
                    // Check buffer size
                    console.log(`πŸ“¦ Received ${buffer.byteLength} bytes`);
                    
                    if (buffer.byteLength === 0) {
                        console.warn('⚠️ Empty buffer received');
                        return;
                    }
                    
                    // Create ImageData
                    const imageData = new ImageData(400, 300);
                    const rgba = imageData.data;
                    
                    // Convert RGB to RGBA
                    const rgb = new Uint8Array(buffer);
                    
                    // Verify size
                    const expectedSize = 400 * 300 * 3;
                    if (rgb.length !== expectedSize) {
                        console.error(`❌ Wrong size: ${rgb.length} bytes, expected ${expectedSize}`);
                        return;
                    }
                    
                    // Fast conversion
                    for (let i = 0, j = 0; i < rgba.length; i += 4, j += 3) {
                        rgba[i] = rgb[j];
                        rgba[i + 1] = rgb[j + 1];
                        rgba[i + 2] = rgb[j + 2];
                        rgba[i + 3] = 255;
                    }
                    
                    // Draw to canvas
                    ctx.putImageData(imageData, 0, 0);
                    
                    const latency = Date.now() - startTime;
                    console.log(`βœ… Frame ${receivedFrames} drawn, latency: ${latency}ms`);
                    
                })
                .catch(error => {
                    console.error('❌ Frame fetch error:', error);
                    document.getElementById('serverStatus').textContent = 'Error: ' + error.message;
                    document.getElementById('serverStatus').className = 'error';
                    
                    // Draw error pattern
                    const errorImage = new ImageData(400, 300);
                    const data = errorImage.data;
                    for (let i = 0; i < data.length; i += 4) {
                        data[i] = 100;     // R
                        data[i + 1] = 0;   // G
                        data[i + 2] = 0;   // B
                        data[i + 3] = 255; // A
                    }
                    ctx.putImageData(errorImage, 0, 0);
                });
        }
        
        function testConnection() {
            console.log('πŸ” Testing connection...');
            fetch('/test')
                .then(r => r.json())
                .then(data => {
                    console.log('Server test response:', data);
                    alert('Server OK: ' + JSON.stringify(data));
                })
                .catch(err => {
                    console.error('Connection test failed:', err);
                    alert('Connection failed: ' + err);
                });
        }
        
        function forceRefresh() {
            console.log('πŸ”„ Forcing refresh...');
            updateFrame();
        }
        
        // Start updating every second (1 FPS for testing)
        setInterval(updateFrame, 1000);
        
        // Initial update
        setTimeout(updateFrame, 500);
        
        console.log('🟒 Page loaded and ready');
    </script>
</body>
</html>'''

@app.route('/')
def index():
    return HTML

@app.route('/test')
def test():
    """Simple test endpoint"""
    with shared_state['lock']:
        return {
            'status': 'ok',
            'frame_count': shared_state['frame_count'],
            'data_size': len(shared_state['raw_pixels']) if shared_state['raw_pixels'] else 0,
            'time': time.time()
        }

@app.route('/frame')
def get_frame():
    """Return frame data - ALWAYS returns data"""
    with shared_state['lock']:
        # ALWAYS return data, even if it's the initial frame
        data = shared_state['raw_pixels']
        frame_num = shared_state['frame_count']
        
        print(f"πŸ“€ Serving frame {frame_num}, {len(data) if data else 0} bytes")
        
        if data is None:
            # This should never happen, but just in case
            print("⚠️ WARNING: No frame data, sending fallback")
            fallback = np.ones((HEIGHT, WIDTH, 3), dtype=np.uint8) * [255, 0, 0]  # Red
            data = fallback.tobytes()
        
        return Response(
            data,
            mimetype='application/octet-stream',
            headers={
                'Cache-Control': 'no-cache, no-store, must-revalidate',
                'Pragma': 'no-cache',
                'Expires': '0',
                'Content-Type': 'application/octet-stream',
                'X-Frame-Number': str(frame_num)
            }
        )

# ===== 5. RENDER THREAD - UPDATES SHARED MEMORY =====
def render_thread():
    """Update the shared memory with new frames"""
    print("🎬 Starting render thread...")
    
    try:
        pygame.init()
        print("βœ… PyGame initialized")
    except Exception as e:
        print(f"❌ PyGame init failed: {e}")
        # Continue anyway - we already have initial frame
    
    # Create surface
    surface = pygame.Surface((WIDTH, HEIGHT))
    
    # Get pixel array
    pixels_3d = pygame.surfarray.pixels3d(surface)
    
    # Simple animation
    x, y = WIDTH // 2, HEIGHT // 2
    dx, dy = 3, 2
    
    color_cycle = 0
    
    while shared_state['streaming']:
        try:
            # Clear with gradient
            for y_pos in range(HEIGHT):
                gradient = 50 + int(y_pos / HEIGHT * 100)
                pixels_3d[y_pos, :, 0] = gradient  # Red
                pixels_3d[y_pos, :, 1] = gradient // 2  # Green
                pixels_3d[y_pos, :, 2] = 100  # Blue
            
            # Update position
            x += dx
            y += dy
            
            if x < 20 or x > WIDTH - 20:
                dx = -dx
                color_cycle = (color_cycle + 1) % 3
            
            if y < 20 or y > HEIGHT - 20:
                dy = -dy
                color_cycle = (color_cycle + 1) % 3
            
            # Draw ball
            colors = [[255, 100, 100], [100, 255, 100], [100, 100, 255]]
            current_color = colors[color_cycle]
            
            # Simple circle drawing
            for dy_circle in range(-20, 21):
                for dx_circle in range(-20, 21):
                    if dx_circle*dx_circle + dy_circle*dy_circle <= 20*20:
                        px = x + dx_circle
                        py = y + dy_circle
                        if 0 <= px < WIDTH and 0 <= py < HEIGHT:
                            pixels_3d[py, px] = current_color
            
            # Add text
            font = pygame.font.Font(None, 24)
            
            with shared_state['lock']:
                frame_num = shared_state['frame_count']
            
            time_text = font.render(time.strftime("%H:%M:%S"), True, (255, 255, 255))
            frame_text = font.render(f"Frame: {frame_num}", True, (255, 255, 255))
            
            # Blit text
            surface.blit(time_text, (10, 10))
            surface.blit(frame_text, (10, 40))
            
            # Convert to bytes
            raw_bytes = pixels_3d.tobytes()
            
            # Update shared memory
            with shared_state['lock']:
                shared_state['raw_pixels'] = raw_bytes
                shared_state['frame_count'] += 1
            
            if frame_num % 30 == 0:
                print(f"πŸ“Έ Rendered frame {frame_num}")
            
            time.sleep(1/30)  # 30 FPS
            
        except Exception as e:
            print(f"⚠️ Render error: {e}")
            time.sleep(0.1)
    
    print("πŸ›‘ Render thread stopped")

# ===== 6. MAIN =====
def main():
    print("="*60)
    print("🎯 SHARED MEMORY SERVER STARTING")
    print("="*60)
    print(f"πŸ“‘ Port: {PORT}")
    print(f"🎨 Frame size: {WIDTH}x{HEIGHT}x3 = {WIDTH*HEIGHT*3} bytes")
    print(f"πŸ“Š Initial data ready: {len(shared_state['raw_pixels'])} bytes")
    print("="*60)
    
    # Start render thread
    renderer = threading.Thread(target=render_thread, daemon=True)
    renderer.start()
    
    # Give renderer time to start
    time.sleep(0.5)
    
    print("🌐 Starting Flask server...")
    
    try:
        app.run(
            host='0.0.0.0',
            port=PORT,
            debug=False,
            threaded=True,
            use_reloader=False
        )
    except Exception as e:
        print(f"❌ Server error: {e}")
    finally:
        shared_state['streaming'] = False
        renderer.join(timeout=1)
        print("βœ… Server stopped")

if __name__ == "__main__":
    main()