Browser Screencast
Watch your cloud browsers in real-time. Screencast uses Chrome DevTools Protocol to stream the browser screen over WebSocket - view-only, no interaction.
Best for embedding, dashboards, session monitoring, and recording. For debugging, use DevTools instead.
Getting Started
When you start a profile, the response includes a screencast URL inside the inspector object:
{
"internal_uuid": "a8fb62f90611456aa75422b01c385a62",
"ws_url": "wss://api-public.surfsky.io/proxy/a8fb62f90611456aa75422b01c385a62",
"inspector": {
"list": "https://api-public.surfsky.io/proxy/.../inspector",
"pages": [{ ... }],
"screencast": "wss://api-public.surfsky.io/screencast/a8fb62f90611456aa75422b01c385a62/devtools/page/ABC123"
},
"success": true
}
Connect to inspector.screencast with any WebSocket client to receive frames.
Examples
Demo code - copy and use as-is, or build your own.
How it works: connects to WebSocket, receives base64 JPEG frames, decodes them as images, and draws to canvas.
- HTML
- JavaScript
- Python
Standalone HTML viewer. Pass the WebSocket URL as ?ws= parameter.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Browser screencast</title>
<style>
* { margin: 0; padding: 0; }
body {
background: #1a1a1a;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
position: relative;
}
#screen {
width: 1280px;
height: 720px;
background: #000;
border-radius: 4px;
}
.status {
position: absolute;
top: 10px;
right: 10px;
padding: 6px 12px;
border-radius: 4px;
font: 12px sans-serif;
color: white;
}
.status.live { background: #e53935; }
.status.connecting { background: #fb8c00; }
.status.error { background: #666; }
</style>
</head>
<body>
<div class="container">
<canvas id="screen" width="1280" height="720"></canvas>
<div class="status connecting" id="status">Connecting...</div>
</div>
<script>
const params = new URLSearchParams(location.search);
const wsUrl = params.get('ws');
if (!wsUrl) {
document.getElementById('status').textContent = 'Missing ?ws= parameter';
document.getElementById('status').className = 'status error';
} else {
const canvas = document.getElementById('screen');
const ctx = canvas.getContext('2d');
const status = document.getElementById('status');
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
status.textContent = 'LIVE';
status.className = 'status live';
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === 'frame') {
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0, 1280, 720);
};
img.src = 'data:image/jpeg;base64,' + data.data;
}
};
ws.onclose = () => {
status.textContent = 'Disconnected';
status.className = 'status error';
};
ws.onerror = () => {
status.textContent = 'Error';
status.className = 'status error';
};
}
</script>
</body>
</html>
const ws = new WebSocket(screencastUrl);
const canvas = document.getElementById('screen');
const ctx = canvas.getContext('2d');
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === 'frame') {
const img = new Image();
img.onload = () => ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
img.src = 'data:image/jpeg;base64,' + data.data;
}
};
import asyncio
import json
import websockets
async def watch_screencast(screencast_url):
async with websockets.connect(screencast_url) as ws:
async for message in ws:
data = json.loads(message)
if data["type"] == "frame":
# data["data"] contains base64 JPEG
print(f"Received frame, size: {len(data['data'])} bytes")
asyncio.run(watch_screencast("wss://api-public.surfsky.io/screencast/..."))