Spaces:
Sleeping
Sleeping
| import pygame | |
| import time | |
| import threading | |
| import os | |
| from flask import Flask, Response, render_template_string | |
| os.environ['SDL_VIDEODRIVER'] = 'dummy' | |
| pygame.init() | |
| print("✅ PyGame initialized") | |
| WIDTH, HEIGHT = 400, 300 | |
| shared = { | |
| "streaming": True, | |
| "x": 200, | |
| "y": 150, | |
| "frame_count": 0 | |
| } | |
| def game_loop(): | |
| """Generate frames continuously""" | |
| screen = pygame.Surface((WIDTH, HEIGHT)) | |
| x, speed = 200, 5 | |
| while shared["streaming"]: | |
| start_time = time.time() | |
| # Update position | |
| x += speed | |
| if x < 30 or x > WIDTH-30: | |
| speed *= -1 | |
| # Draw frame | |
| screen.fill((25, 25, 45)) | |
| pygame.draw.circle(screen, (255, 80, 80), (int(x), int(shared["y"])), 20) | |
| pygame.draw.circle(screen, (255, 255, 255), (int(x), int(shared["y"])), 20, 2) | |
| # Store latest frame as RGB bytes | |
| shared["current_frame"] = pygame.image.tostring(screen, 'RGB') | |
| shared["x"] = x | |
| shared["frame_count"] += 1 | |
| # Log every 30 frames | |
| if shared["frame_count"] % 30 == 0: | |
| print(f"Frame {shared['frame_count']}: X={x}") | |
| # Maintain 30 FPS | |
| elapsed = time.time() - start_time | |
| if elapsed < 1.0/30: | |
| time.sleep(1.0/30 - elapsed) | |
| app = Flask(__name__) | |
| HTML = '''<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <style> | |
| body { | |
| background: #0f172a; | |
| color: white; | |
| text-align: center; | |
| padding: 20px; | |
| font-family: monospace; | |
| margin: 0; | |
| overflow: hidden; | |
| } | |
| canvas { | |
| border: 3px solid #60a5fa; | |
| image-rendering: pixelated; | |
| background: black; | |
| } | |
| .stats { | |
| background: #1e293b; | |
| padding: 10px; | |
| border-radius: 5px; | |
| margin-top: 10px; | |
| display: inline-block; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🎮 Binary Stream</h1> | |
| <canvas id="canvas" width="400" height="300"></canvas> | |
| <div class="stats"> | |
| Frame: <span id="counter">0</span> | | |
| Position: X=<span id="pos">200</span> | | |
| FPS: <span id="fps">0</span> | |
| </div> | |
| <div style="margin-top: 10px; color: #94a3b8;"> | |
| Streaming raw RGB data • 30 FPS • No caching | |
| </div> | |
| <script> | |
| const canvas = document.getElementById('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| let frameCounter = 0; | |
| let fpsCounter = 0; | |
| let lastFpsUpdate = Date.now(); | |
| let connectionActive = false; | |
| // Connect to binary stream | |
| function connectStream() { | |
| const eventSource = new EventSource('/stream'); | |
| eventSource.onopen = () => { | |
| console.log('✅ Connected to stream'); | |
| connectionActive = true; | |
| document.title = '🎮 Stream • Connected'; | |
| }; | |
| eventSource.onmessage = (event) => { | |
| if (!connectionActive) return; | |
| try { | |
| // Parse the binary frame data | |
| const data = JSON.parse(event.data); | |
| // Convert base64 to binary | |
| const binaryString = atob(data.frame); | |
| const bytes = new Uint8Array(binaryString.length); | |
| for (let i = 0; i < binaryString.length; i++) { | |
| bytes[i] = binaryString.charCodeAt(i); | |
| } | |
| // Create ImageData from RGB bytes | |
| const imageData = new ImageData(400, 300); | |
| for (let i = 0; i < bytes.length; i++) { | |
| imageData.data[i] = bytes[i]; | |
| } | |
| // Draw to canvas | |
| ctx.putImageData(imageData, 0, 0); | |
| // Update stats | |
| frameCounter++; | |
| fpsCounter++; | |
| document.getElementById('counter').textContent = data.frame_count; | |
| document.getElementById('pos').textContent = data.x; | |
| // Calculate FPS | |
| const now = Date.now(); | |
| if (now - lastFpsUpdate >= 1000) { | |
| const fps = fpsCounter / ((now - lastFpsUpdate) / 1000); | |
| document.getElementById('fps').textContent = fps.toFixed(1); | |
| fpsCounter = 0; | |
| lastFpsUpdate = now; | |
| } | |
| } catch (err) { | |
| console.error('Frame error:', err); | |
| } | |
| }; | |
| eventSource.onerror = (err) => { | |
| console.log('❌ Stream error, reconnecting...'); | |
| connectionActive = false; | |
| document.title = '🎮 Stream • Reconnecting...'; | |
| eventSource.close(); | |
| // Show error on canvas | |
| ctx.fillStyle = '#5c1a1a'; | |
| ctx.fillRect(0, 0, 400, 300); | |
| ctx.fillStyle = 'white'; | |
| ctx.font = '20px monospace'; | |
| ctx.fillText('Reconnecting...', 120, 150); | |
| // Reconnect after 1 second | |
| setTimeout(connectStream, 1000); | |
| }; | |
| return eventSource; | |
| } | |
| // Start connection | |
| let streamConnection = connectStream(); | |
| // Manual reconnect button (optional) | |
| window.reconnect = function() { | |
| if (streamConnection) streamConnection.close(); | |
| streamConnection = connectStream(); | |
| }; | |
| console.log('Binary stream client started'); | |
| </script> | |
| </body> | |
| </html>''' | |
| def index(): | |
| return render_template_string(HTML) | |
| def stream(): | |
| """Server-Sent Events stream with base64 encoded frames""" | |
| import json | |
| import base64 | |
| def generate(): | |
| last_sent = 0 | |
| while shared.get("streaming", True): | |
| # Only send if frame has updated | |
| if shared.get("frame_count", 0) > last_sent and "current_frame" in shared: | |
| frame_data = { | |
| 'frame': base64.b64encode(shared["current_frame"]).decode('utf-8'), | |
| 'frame_count': shared["frame_count"], | |
| 'x': shared["x"], | |
| 'timestamp': time.time() | |
| } | |
| yield f"data: {json.dumps(frame_data)}\n\n" | |
| last_sent = shared["frame_count"] | |
| # Check for updates 30 times per second | |
| time.sleep(1.0/30) | |
| return Response( | |
| generate(), | |
| mimetype='text/event-stream', | |
| headers={ | |
| 'Cache-Control': 'no-cache', | |
| 'Connection': 'keep-alive', | |
| 'X-Accel-Buffering': 'no' | |
| } | |
| ) | |
| if __name__ == "__main__": | |
| print("🚀 Starting binary stream server...") | |
| print("Expecting 30 FPS updates via SSE") | |
| # Create initial frame | |
| screen = pygame.Surface((WIDTH, HEIGHT)) | |
| screen.fill((25, 25, 45)) | |
| pygame.draw.circle(screen, (255, 80, 80), (200, 150), 20) | |
| shared["current_frame"] = pygame.image.tostring(screen, 'RGB') | |
| # Start game thread | |
| thread = threading.Thread(target=game_loop, daemon=True) | |
| thread.start() | |
| # Give it a moment | |
| time.sleep(0.5) | |
| # Start server | |
| app.run( | |
| host='0.0.0.0', | |
| port=int(os.environ.get('PORT', 7860)), | |
| debug=False, | |
| threaded=True | |
| ) |