The Renderer service is a critical component of the OGS Casting system, responsible for running headless browsers, rendering game content, and streaming it to Chromecast devices via WebRTC. This service is designed to operate as a stateless API while maintaining long-running browser instances for high-quality game streaming.
The Renderer service is built on these core technologies:
- Google Cloud Run: Containerized, auto-scaling serverless platform
- Puppeteer: Headless Chrome automation for rendering games
- PeerJS: WebRTC signaling and streaming for TV casting
- Express.js: HTTP API for receiving commands from Cloudflare Workflows
graph TD
subgraph "OGS Casting System"
CastRouter[Cast Router - Cloudflare Worker]
Workflow[Cloudflare Workflow]
KV[Cloudflare KV]
end
subgraph "Renderer Service - Google Cloud Run"
API[Express API]
BrowserManager[Browser Manager]
PuppeteerPool[Puppeteer Pool]
subgraph "Browser Instances"
Browser1[Browser 1]
Browser2[Browser 2]
BrowserN[Browser N]
end
PeerJSClient[PeerJS Client]
end
subgraph "WebRTC Infrastructure"
PeerJSServer[PeerJS Server]
STUN[STUN/TURN Servers]
end
subgraph "User Devices"
Chromecast[Chromecast]
end
Workflow -->|HTTP API Calls| API
API -->|Manage| BrowserManager
BrowserManager -->|Launch/Control| PuppeteerPool
PuppeteerPool -->|Contains| Browser1
PuppeteerPool -->|Contains| Browser2
PuppeteerPool -->|Contains| BrowserN
Browser1 -->|Stream via| PeerJSClient
PeerJSClient -->|Connect to| PeerJSServer
PeerJSServer -->|Stream to| Chromecast
- Stateless API Design: No persistent connections required; operates via standard HTTP requests
- Long-Running Browser Management: Maintains browser instances for extended periods despite stateless API
- High-Quality Streaming: Captures and streams browser content at up to 1080p/60fps via WebRTC
- Efficient Resource Usage: Automatic cleanup of idle browsers to optimize resource consumption
- Scalable Architecture: Designed to handle multiple concurrent casting sessions
- Container-Based: Packaged as a Docker container for easy deployment and scaling
- Google Cloud account with Cloud Run access
- Docker installed for local development
- Node.js 18+ and npm/yarn
PORT=8080 # Port for the API server
MAX_BROWSERS=50 # Maximum number of concurrent browser instances
BROWSER_TIMEOUT_MS=3600000 # Browser idle timeout (1 hour default)
PEERJS_SERVER=peerjs.triviajam.tv # PeerJS server for WebRTC
PEERJS_PORT=443 # PeerJS server port
PEERJS_PATH=/peerjs # PeerJS server path
PEERJS_SECURE=true # Use secure WebRTC connections
ALLOWED_ORIGINS=triviajam.tv # CORS allowed origins
BROWSER_POOL_MIN_SIZE=5 # Minimum browsers to keep warm
# Clone the repository
git clone https://github.com/open-game-collective/cast-renderer.git
cd cast-renderer
# Install dependencies
npm install
# Start development server
npm run dev
# Build and run with Docker
docker build -t cast-renderer .
docker run -p 8080:8080 -e PORT=8080 cast-renderer# Build and push to Google Container Registry
gcloud builds submit --tag gcr.io/your-project/cast-renderer
# Deploy to Cloud Run
gcloud run deploy cast-renderer \
--image gcr.io/your-project/cast-renderer \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--memory 2Gi \
--cpu 2 \
--min-instances 1 \
--max-instances 10The Renderer service exposes several HTTP endpoints for managing browser instances and streaming:
POST /api/browser
Creates a new browser instance and loads the specified game URL.
Request Body:
{
"sessionId": "sess_abc123",
"gameUrl": "https://triviajam.tv/cast?gameId=123&roomCode=ABCD",
"options": {
"resolution": "1080p",
"frameRate": 60
}
}Response:
{
"browserId": "browser_xyz789",
"status": "launching",
"createdAt": "2023-05-15T12:34:56.789Z"
}GET /api/browser/{browserId}
Returns the current status of a browser instance.
Response:
{
"browserId": "browser_xyz789",
"status": "ready",
"createdAt": "2023-05-15T12:34:56.789Z",
"lastActiveAt": "2023-05-15T12:35:10.123Z"
}POST /api/browser/{browserId}/heartbeat
Sends a heartbeat to keep the browser instance alive. This endpoint should be called periodically by Cloudflare Workflows.
Response:
{
"browserId": "browser_xyz789",
"status": "ready",
"lastHeartbeatAt": "2023-05-15T12:40:15.678Z"
}POST /api/browser/{browserId}/receiver
Initiates WebRTC streaming from the browser to a Chromecast receiver.
Request Body:
{
"receiverPeerId": "peer_abc123"
}Response:
{
"browserId": "browser_xyz789",
"status": "streaming",
"senderPeerId": "peer_def456",
"streamStartedAt": "2023-05-15T12:42:30.456Z"
}DELETE /api/browser/{browserId}
Terminates a browser instance and cleans up resources.
Response:
{
"browserId": "browser_xyz789",
"status": "terminated",
"terminatedAt": "2023-05-15T13:15:45.789Z"
}GET /health
Health check endpoint for monitoring service status.
Response:
{
"status": "healthy",
"activeBrowsers": 5,
"availableSlots": 45,
"version": "1.0.0"
}The Renderer service uses a sophisticated browser management system to efficiently handle multiple concurrent sessions:
- Pre-initialized browser instances for fast startup times
- Automatic scaling based on demand
- Resource monitoring to prevent overallocation
- Initialization: Browser is launched with specified options
- Loading: Game URL is loaded in the browser
- Ready: Browser is ready for streaming
- Streaming: Browser is actively streaming to a receiver
- Idle: Browser is not actively streaming but remains available
- Terminated: Browser is closed and resources are cleaned up
Browsers are automatically terminated after a configurable idle period (default: 1 hour) to free up resources. The cleanup process:
- Checks for browser instances that have not received a heartbeat
- Gracefully closes these instances
- Releases associated resources
- Updates internal state
The Renderer service uses PeerJS to establish WebRTC connections for streaming game content:
// Example of WebRTC streaming implementation
async function startStreaming(browser, page, receiverPeerId) {
// Create a sender peer ID
const senderPeerId = `sender-${browser.id}-${Date.now()}`;
// Create PeerJS instance
const peer = new Peer(senderPeerId, {
host: config.peerjs.host,
port: config.peerjs.port,
path: config.peerjs.path,
secure: config.peerjs.secure,
debug: 3
});
return new Promise((resolve, reject) => {
peer.on('open', async (id) => {
try {
// Get media stream from the page
const stream = await page.evaluate(async () => {
const canvas = document.querySelector('canvas');
if (!canvas) {
throw new Error('No canvas element found for streaming');
}
// Get stream from canvas
return canvas.captureStream(60);
});
// Call the receiver with our stream
const call = peer.call(receiverPeerId, stream);
// Track connection state
call.on('stream', () => {
console.log(`Stream connected to receiver ${receiverPeerId}`);
});
call.on('close', () => {
console.log(`Stream to ${receiverPeerId} closed`);
});
resolve({
senderPeerId,
status: 'streaming'
});
} catch (error) {
peer.destroy();
reject(error);
}
});
peer.on('error', (error) => {
reject(error);
});
});
}The Renderer service is designed to work seamlessly with Cloudflare Workflows, which maintain the connection lifecycle:
- Browser Creation: Workflow calls the Renderer API to create a browser
- Heartbeat Mechanism: Workflow periodically sends heartbeats to keep browsers alive
- Command Execution: Workflow forwards commands like receiver connection requests
- Termination: Workflow explicitly terminates browsers when sessions end
// Example of Cloudflare Workflow integration
async function maintainBrowserInstance(browserId) {
// Send regular heartbeats to keep the browser alive
const sendHeartbeat = async () => {
try {
const response = await fetch(`${RENDERER_HOST}/api/browser/${browserId}/heartbeat`, {
method: 'POST'
});
if (!response.ok) {
throw new Error(`Failed to send heartbeat: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Heartbeat error:', error);
// Implement retry logic here
}
};
// Send heartbeats at regular intervals
while (true) {
const result = await sendHeartbeat();
// Check if we should continue
if (result.status === 'terminated') {
break;
}
// Wait before the next heartbeat
await new Promise(resolve => setTimeout(resolve, 30000)); // 30 seconds
}
}The Renderer loads game content from a URL like:
https://triviajam.tv/cast?gameId=123&roomCode=ABCD&mode=cast
This URL should load a specialized version of your game optimized for casting:
- Fullscreen layout suitable for TV display
- No interactive elements (controlled by the player's device)
- Connected to your game's state management system
- Optimized for rendering performance
For high-quality tab capture, the Renderer uses custom Chrome extensions:
- Tab Capture Extension: Enables access to tab content for streaming
- Media Optimization: Improves video encoding performance
- Audio Capture: Ensures game audio is properly captured
The Renderer service includes several optimizations for performance:
- Browser Resource Limits: CPU and memory limits for each browser
- Streaming Quality Adaptation: Adjusts quality based on network conditions
- Process Isolation: Each browser runs in an isolated process
- Garbage Collection: Regular cleanup of terminated browsers
Comprehensive monitoring and logging are built into the service:
- Browser Instance Metrics: Count, status, resource usage
- Streaming Performance: FPS, bitrate, latency
- API Usage: Request rates, errors, latency
- Resource Utilization: CPU, memory, network usage
Logs are structured in JSON format for easy integration with logging systems.
The Renderer service implements several security measures:
- Input Validation: All API requests are validated
- Resource Limits: Prevents resource exhaustion attacks
- CORS Protection: Restricts access to allowed origins
- Sanitized Browser Environment: Restricts browser capabilities
- Secure WebRTC: Encrypted media streams
Common issues and solutions:
- Check resource limits in Google Cloud Run
- Verify Puppeteer configuration
- Check for errors in container logs
- Verify PeerJS server configuration
- Check WebRTC compatibility
- Ensure game content is properly rendered
- Adjust browser pool size
- Decrease idle timeout
- Optimize Puppeteer memory usage
Contributions to the OGS Casting Renderer are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.