| # PixelStream π₯οΈβ‘οΈπ | |
| A mini "TeamViewer for a browser". You open a web UI, hit **Start Browser**, and a | |
| **headless Chromium running inside a Docker container** streams its screen back to | |
| your browser in real time. You can **click, scroll, type, and navigate** β and it | |
| all happens inside that remote headless browser........... | |
| --- | |
| ## How it works | |
| ``` | |
| ββββββββββββββββ click / scroll / type (WebSocket) βββββββββββββββββ CDP ββββββββββββββββββββ | |
| β Web UI β ββββββββββββββββββββββββββββββββββββΊ β Node backend β ββββββββΊ β Headless Chromium β | |
| β (Next.js) β β (ws + CDP) β β (Puppeteer) β | |
| β <canvas> β ββββββββββββββββββββββββββββββββββββ β β ββββββββ β β | |
| ββββββββββββββββ live JPEG frames (WebSocket) βββββββββββββββββ screencast ββββββββββββββββββββ | |
| ``` | |
| - **Frontend** (`/frontend`) β Next.js app. A `<canvas>` paints the live frames; | |
| mouse/keyboard/scroll events are captured, converted to real browser | |
| coordinates, and sent over a WebSocket. | |
| - **Backend** (`/backend`) β Node.js + `ws` + Puppeteer. Launches Chromium, uses the | |
| **Chrome DevTools Protocol (CDP)** `Page.startScreencast` to get a continuous | |
| stream of JPEG frames, and injects input via `Input.dispatchMouseEvent` / | |
| `Input.dispatchKeyEvent`. | |
| - **Docker** β both services run in containers via `docker compose`. The backend | |
| uses the official Puppeteer image (Chromium + system deps preinstalled). | |
| ### Why CDP screencast (not a screenshot loop)? | |
| `Page.startScreencast` only emits a frame when the page actually changes, and it's | |
| much smoother and lower-latency than taking `page.screenshot()` on a timer. | |
| --- | |
| ## Run it | |
| ### Option A β Docker (recommended) | |
| ```bash | |
| docker compose up --build | |
| ``` | |
| Then open **http://localhost:3000** and click **Start Browser**. | |
| ### Option B β Local (no Docker, for fast iteration) | |
| ```bash | |
| # terminal 1 | |
| cd backend && npm install && npm start # ws server on :8080 | |
| # terminal 2 | |
| cd frontend && npm install && npm run dev # UI on :3000 | |
| ``` | |
| --- | |
| ## Deploy (Frontend β Vercel, Backend β Render) | |
| > β οΈ The browser page is served over **HTTPS** (Vercel), so it can only open a | |
| > **secure** WebSocket (`wss://`). Render gives you an `https://β¦onrender.com` | |
| > URL, whose WebSocket endpoint is `wss://β¦onrender.com`. Plain `ws://` will be | |
| > blocked as mixed content. | |
| ### 1. Backend on Render | |
| - New **Web Service** β connect this repo. | |
| - Runtime: **Docker**, Dockerfile path: `backend/Dockerfile`, context: `backend` | |
| (or just use the included `render.yaml` Blueprint). | |
| - Deploy. Note the URL, e.g. `https://pixelstream-backend.onrender.com`. | |
| - Render injects `PORT` automatically β the server already reads it. | |
| ### 2. Frontend on Vercel | |
| - New Project β import this repo β set **Root Directory = `frontend`**. | |
| - Add an environment variable: | |
| | Key | Value | | |
| |-----|-------| | |
| | `NEXT_PUBLIC_WS_URL` | `wss://pixelstream-backend.onrender.com` | | |
| - Deploy. Open the Vercel URL β **Start Browser**. | |
| --- | |
| ## What works β | |
| - Start a headless Chromium in a Docker container from the web UI | |
| - Live screen streaming via CDP screencast (JPEG frames over WebSocket) | |
| - Mouse: move, left/right click, scroll | |
| - Keyboard: printable characters + common special keys (Enter, Backspace, Tab, | |
| arrows, Esc, Deleteβ¦) | |
| - URL bar to navigate the remote browser | |
| - Accurate click mapping (canvas coords β real viewport coords) | |
| ## Known limitations / where it gets hard β οΈ | |
| - **Keyboard coverage** β only common special keys are mapped; modifier | |
| combos (Ctrl+C, Shift+selection) aren't fully wired yet. | |
| - **Single session** β one browser per server; no multi-user / multi-tab. | |
| - **Streaming** β JPEG-over-WebSocket is simple but bandwidth-heavy; WebRTC or | |
| VP8/H.264 encoding would be smoother at scale. | |
| - **Chromium-in-Docker** β needs `--no-sandbox`, `--disable-dev-shm-usage`, and | |
| `shm_size: 1gb` or Chromium crashes. (These are already set.) | |
| ## Next steps π | |
| 1. Full keyboard model with modifiers + IME/composition events | |
| 2. Multiple isolated sessions (one container per user, spawned on demand) | |
| 3. WebRTC streaming for lower latency / adaptive quality | |
| 4. Reconnect handling + session timeouts + resource cleanup | |
| --- | |
| ## Project structure | |
| ``` | |
| Pixel/ | |
| βββ docker-compose.yml | |
| βββ backend/ | |
| β βββ Dockerfile | |
| β βββ package.json | |
| β βββ server.js # ws server + Puppeteer + CDP screencast/input | |
| βββ frontend/ | |
| βββ Dockerfile | |
| βββ package.json | |
| βββ next.config.js | |
| βββ app/ | |
| βββ layout.js | |
| βββ page.js # UI + canvas + input capture | |
| ``` | |