Spaces:
Sleeping
Sleeping
| import pygame | |
| import time | |
| import threading | |
| import numpy as np | |
| import base64 | |
| import zlib | |
| import os | |
| from flask import Flask, render_template_string | |
| from flask_socketio import SocketIO, emit | |
| # ===== PYGAME SETUP ===== | |
| os.environ['SDL_VIDEODRIVER'] = 'dummy' | |
| pygame.init() | |
| print("β PyGame initialized with surfarray") | |
| WIDTH, HEIGHT = 400, 300 | |
| # Create surface | |
| screen = pygame.Surface((WIDTH, HEIGHT)) | |
| print(f"β Surface: {WIDTH}x{HEIGHT}") | |
| # Test surfarray | |
| print("π§ͺ Testing surfarray...") | |
| test_arr = pygame.surfarray.array3d(screen) | |
| print(f"β Surfarray works! Shape: {test_arr.shape}, Type: {test_arr.dtype}") | |
| # ===== FLASK/SOCKETIO ===== | |
| app = Flask(__name__) | |
| app.config['SECRET_KEY'] = 'surfarray_stream' | |
| socketio = SocketIO(app, async_mode='threading', cors_allowed_origins="*") | |
| # Shared state | |
| shared = { | |
| "running": True, | |
| "x": 200, | |
| "frame_count": 0, | |
| "clients": 0 | |
| } | |
| # ===== SURFARRAY THREAD ===== | |
| def surfarray_thread(): | |
| """Send raw pixel arrays via WebSocket""" | |
| print("π Surfarray thread STARTED") | |
| x, speed = 200, 5 | |
| fps_counter = 0 | |
| last_log = time.time() | |
| while shared["running"]: | |
| try: | |
| start_time = time.time() | |
| # 1. UPDATE POSITION | |
| x += speed | |
| if x < 30 or x > WIDTH-30: | |
| speed *= -1 | |
| # 2. DRAW | |
| screen.fill((25, 25, 45)) # Dark blue | |
| pygame.draw.circle(screen, (255, 80, 80), (int(x), 150), 20) | |
| pygame.draw.circle(screen, (255, 255, 255), (int(x), 150), 20, 2) | |
| # 3. GET PIXEL ARRAY (FAST!) | |
| pixel_array = pygame.surfarray.array3d(screen) # Shape: (400, 300, 3) | |
| # 4. COMPRESS (optional but recommended) | |
| # Flatten and compress the array | |
| flat_data = pixel_array.flatten().tobytes() | |
| compressed = zlib.compress(flat_data, level=1) # Fast compression | |
| encoded = base64.b64encode(compressed).decode('utf-8') | |
| # 5. BROADCAST | |
| shared["x"] = x | |
| shared["frame_count"] += 1 | |
| socketio.emit('pixel_data', { | |
| 'pixels': encoded, | |
| 'shape': [WIDTH, HEIGHT, 3], # Send dimensions | |
| 'frame': shared["frame_count"], | |
| 'x': x, | |
| 'compressed': True | |
| }) | |
| # 6. LOGGING | |
| fps_counter += 1 | |
| current_time = time.time() | |
| if current_time - last_log >= 1.0: | |
| print(f"π Sent {fps_counter} frames | Frame {shared['frame_count']} | X={x}") | |
| fps_counter = 0 | |
| last_log = current_time | |
| # 7. FRAME RATE | |
| elapsed = time.time() - start_time | |
| if elapsed < 1.0/30: | |
| time.sleep(1.0/30 - elapsed) | |
| except Exception as e: | |
| print(f"β Surfarray thread error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| break | |
| print("π Surfarray thread STOPPED") | |
| # ===== START THREAD ===== | |
| thread = threading.Thread(target=surfarray_thread, daemon=True) | |
| thread.start() | |
| print(f"β Thread alive: {thread.is_alive()}") | |
| # ===== FLASK ROUTES ===== | |
| def index(): | |
| return render_template_string(''' | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <script src="https://cdn.socket.io/4.5.0/socket.io.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako.min.js"></script> | |
| <style> | |
| body { background: #0f172a; color: white; text-align: center; padding: 20px; } | |
| canvas { | |
| border: 3px solid #60a5fa; | |
| width: 400px; | |
| height: 300px; | |
| image-rendering: pixelated; | |
| } | |
| .stats { | |
| background: #1e293b; | |
| padding: 15px; | |
| border-radius: 10px; | |
| margin: 20px auto; | |
| width: 400px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>π Surfarray Stream</h1> | |
| <canvas id="pixelCanvas" width="400" height="300"></canvas> | |
| <div class="stats"> | |
| <div>Frame: <span id="frame">0</span></div> | |
| <div>Circle X: <span id="pos">200</span></div> | |
| <div>Status: <span id="status">Connecting...</span></div> | |
| <div>FPS: <span id="fps">0</span></div> | |
| <div>Data size: <span id="size">0 KB</span></div> | |
| </div> | |
| <script> | |
| const socket = io(); | |
| const canvas = document.getElementById('pixelCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| let framesReceived = 0; | |
| let lastFpsTime = Date.now(); | |
| let totalBytes = 0; | |
| // WebSocket connection | |
| socket.on('connect', () => { | |
| console.log('β Connected to surfarray stream'); | |
| document.getElementById('status').textContent = 'Connected'; | |
| document.getElementById('status').style.color = '#10b981'; | |
| }); | |
| // PIXEL DATA RECEIVED | |
| socket.on('pixel_data', (data) => { | |
| framesReceived++; | |
| totalBytes += data.pixels.length; | |
| // Decode base64 | |
| const binaryString = atob(data.pixels); | |
| const bytes = new Uint8Array(binaryString.length); | |
| for (let i = 0; i < binaryString.length; i++) { | |
| bytes[i] = binaryString.charCodeAt(i); | |
| } | |
| // Decompress if needed | |
| let pixelData; | |
| if (data.compressed) { | |
| try { | |
| // Use pako for zlib decompression | |
| pixelData = pako.inflate(bytes); | |
| } catch (e) { | |
| console.error('Decompression failed:', e); | |
| return; | |
| } | |
| } else { | |
| pixelData = bytes; | |
| } | |
| // Create ImageData | |
| const [width, height] = data.shape; | |
| const imageData = new ImageData(width, height); | |
| // Copy pixel data (assuming RGB format) | |
| for (let i = 0; i < pixelData.length; i++) { | |
| imageData.data[i] = pixelData[i]; | |
| } | |
| // Draw to canvas | |
| ctx.putImageData(imageData, 0, 0); | |
| // Update stats | |
| document.getElementById('frame').textContent = data.frame; | |
| document.getElementById('pos').textContent = data.x; | |
| document.getElementById('size').textContent = | |
| Math.round(data.pixels.length / 1024) + ' KB'; | |
| // Calculate FPS | |
| const now = Date.now(); | |
| if (now - lastFpsTime >= 1000) { | |
| document.getElementById('fps').textContent = framesReceived; | |
| framesReceived = 0; | |
| lastFpsTime = now; | |
| } | |
| // Visual feedback | |
| canvas.style.borderColor = '#10b981'; | |
| setTimeout(() => canvas.style.borderColor = '#60a5fa', 50); | |
| }); | |
| socket.on('disconnect', () => { | |
| document.getElementById('status').textContent = 'Disconnected'; | |
| document.getElementById('status').style.color = '#ef4444'; | |
| }); | |
| console.log('Surfarray client ready'); | |
| </script> | |
| </body> | |
| </html> | |
| ''') | |
| def handle_connect(): | |
| shared['clients'] += 1 | |
| print(f"π€ Client connected. Total: {shared['clients']}") | |
| def handle_disconnect(): | |
| shared['clients'] -= 1 | |
| print(f"π€ Client disconnected. Total: {shared['clients']}") | |
| if __name__ == '__main__': | |
| print("π Starting surfarray server...") | |
| # Wait for thread | |
| time.sleep(1) | |
| print(f"π Initial: Thread alive={thread.is_alive()}, Frames={shared['frame_count']}") | |
| socketio.run( | |
| app, | |
| host='0.0.0.0', | |
| port=int(os.environ.get('PORT', 7860)), | |
| debug=False, | |
| use_reloader=False, | |
| allow_unsafe_werkzeug=True | |
| ) |