GOTV+ / playcast relay on Cloudflare Workers and Durable Objects (one object per match token, so ingest stays consistent worldwide). Hono routes /gotv/* to the object.
-
Prerequisites: Node.js 20+, a Cloudflare account,
wranglervia npm. -
Clone and install:
git clone <your-fork-url> cd cstv-cloudflare npm ci
-
Log in and deploy:
npx wrangler login npx wrangler deploy
-
Set ingest secret (required for
POSTfrom the game server):npx wrangler secret put ORIGIN_AUTH
For local dev, copy
.dev.vars.example→.dev.varsand setORIGIN_AUTHthere (never commit.dev.vars). -
Point the game server at your worker (HTTPS, no trailing slash on the base URL):
tv_enable 1 tv_broadcast_url "https://<your-worker>.workers.dev/gotv" tv_broadcast_origin_auth "<same value as ORIGIN_AUTH>" tv_broadcast 1
-
Viewers use
playcastwith the same base you publish (again, no trailing slash):playcast "https://<your-worker>.workers.dev/gotv/<MATCH_OR_TOKEN>"The token in the URL is whatever the game server sends (Valve documents formats like
s<steamid>t<cookie>; your public alias can differ — see VDC). -
Dashboard (optional): open
https://<your-worker>.workers.dev/for a small UI that lists active matches (when KV is configured) and copiesplaycastcommands. See Dashboard below.
| Method | Path | Notes |
|---|---|---|
GET |
/ |
HTML dashboard (SSR via Hono JSX: match rows from KV on each request). |
GET |
/assets/dashboard.css |
Dashboard styles (public/assets/dashboard.css), via ASSETS binding. Public (no DASHBOARD_KEY). |
GET |
/api/matches |
JSON: { origin, indexEnabled, matches[] } — same data as the dashboard table. |
POST |
/gotv/:token/:fragment/start |
Header X-Origin-Auth required. Query: tick, tps, map, keyframe_interval, protocol. |
POST |
/gotv/:token/:fragment/full |
Query: tick. |
POST |
/gotv/:token/:fragment/delta |
Query: endtick, optional final. |
GET |
/gotv/:token/sync |
JSON; short CDN-friendly cache (see vars below). |
GET |
`/gotv/:token/:fragment/start | full |
If /full or /delta arrives before a successful /start, the worker responds with 205 Reset Content (per VDC).
Set in wrangler.toml [vars] or in the dashboard:
| Variable | Default | Purpose |
|---|---|---|
SYNC_FRAGMENT_DELAY |
8 |
“Live edge” offset: sync without ?fragment= picks roughly latestFull - delay (Valve reference relay uses 8; VDC mentions ~N−4 for buffering experiments). |
SYNC_CACHE_MAX_AGE |
3 |
Cache-Control: max-age for /sync (seconds). VDC suggests a few seconds; tune with your CDN. |
SYNC_NOT_READY_USE_404 |
unset | Set to true to return 404 when the chosen fragment is not ready (closer to gotv-plus-go); default is 405 (Valve relay style). |
TOKEN_REDIRECT |
unset | Optional global token_redirect string in /sync JSON (VDC: path segment for generic → match-specific playcast URLs). |
DASHBOARD_KEY |
unset | If set, GET / and GET /api/matches require ?key=<value> or header X-Dashboard-Key: <value> (tokens in the list stay non-public). |
Secrets: ORIGIN_AUTH — shared secret for ingest (tv_broadcast_origin_auth).
Core relay: Durable Objects + ORIGIN_AUTH + optional [vars] — no database is required for GOTV+ itself.
GET / is server-rendered: the Worker reads the match index from KV and emits the table in HTML. Styles live in public/assets/dashboard.css and load with <link rel="stylesheet"> (not inline CSS). No client-side JavaScript — users copy playcast / URL text from readonly fields manually.
Active matches are stored inside Durable Objects; to list them you need a Workers KV binding so each ingest can update a small index entry.
- Create a namespace:
npx wrangler kv namespace create CSTV_MATCH_INDEX - Uncomment
[[kv_namespaces]]forMATCH_INDEXinwrangler.tomland paste the IDs from the command output. - Redeploy. After the game server sends
POST .../start(and ongoingfull/delta), rows appear on/and/api/matches.
KV keys are match:<token> with JSON metadata (map, protocol, tps, last update, etc.) and a 7-day TTL refreshed on each write.
- Bypass cache for
*/sync(or very short TTL) so viewers stay near live. - Cache
GETstart/full/deltaonly when you are sure a given URL is immutable after the fragment is complete (see VDC + gotv-plus-go README).
Hidden playcast options (client-side)
Documented in gotv-plus-go README: F (start at fragment N → sync?fragment=N), A (sync?fragment=1), C (skips /sync, hits fragments directly — your GET routes must still serve data), B (client behaviour; server stores blobs transparently).
- Worker request body size limits apply (documentation).
- Durable Object memory applies for all retained fragments; very long matches may need a future R2 offload (not implemented here).
See .github/workflows/deploy.yml. Use repository secrets CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID. Set ORIGIN_AUTH in the dashboard or via wrangler secret put.
MIT — see LICENSE.