termcall
chat rooms and video calls that live in your terminal. the video is characters.
what it is
a terminal is a grid of characters, and a webcam frame is a grid of pixels. termcall is what happens when you take that correspondence seriously: a video call where every participant is drawn into your terminal, in colour, at whatever size your window happens to be.
one binary. nothing to install alongside it — ffmpeg rides along inside.
$ tc termcall 1 chat room talk to up to 10 people 2 video call ASCII video, up to 4 people 3 camera check see yourself, try the render modes
how it works
one cloudflare worker, one durable object per room, and a go client. no database, no build step, no framework. the whole design is one split: chat goes through the durable object; video does not.
a mesh, not a server
everyone connects to everyone. at four people that's three connections each, which is exactly why video rooms stop at four: the cost of a mesh climbs as the square, and a fifth person makes it worse for everyone already there.
what crosses the wire is a jpeg
not characters. each terminal renders locally, which is why you can change mode mid-call and why every tile fits whatever size your window happens to be. it's also smaller: ansi colour codes are bulky enough that a screenful compresses no better than a photograph of the same scene. a frame is about 1.5 kb, so a full four-way call sends roughly 45 kb/s upstream.
the three render modes
m cycles them mid-call, and it applies to everyone on your screen.
▀ carrying two colours, the foreground painting the top half and the background the bottom, so one cell shows two pixels in full 24-bit colour. the most picture per character..:-=+*#%@ by brightness and tinted with the colour it stands for. the classic look, half the vertical detail.setup · install it
one command. release binaries carry ffmpeg inside, so there's nothing to install alongside.
-
install the binary
curl -fsSL https://call.dharun.dev/install.sh | sh
irm https://call.dharun.dev/install.ps1 | iex
or take one from the releases page.
-
run it, and try the camera first
tc
option
3is a camera check — you see yourself and can cycle the render modes before anyone else does. -
create a room, or join one
creating gives you a six-character code; anyone with the code and
tccan join. codes avoid0,O,1,IandL, so one read down a phone line survives the trip. rooms aren't stored — when the last person leaves, the room is deleted and its code is free again.
keys, in a room
| key | what it does |
|---|---|
| enter | send a message |
| ↑ ↓ | scroll back through the transcript |
| m | change the video render mode |
| esc | leave |
setup · build it yourself
go build -o tc ./cmd/tc # needs a system ffmpeg for video go test ./... # unit tests; no server needed cd worker && npm run dev # wrangler dev on 127.0.0.1:8787 TC_HOST=http://127.0.0.1:8787 go test ./... # integration too
the integration tests are skipped unless TC_HOST is set, because what they check — that create refuses a live code, that a room fills up, that two peers complete a webrtc handshake — are decisions the real server makes. testing them against a mock would only prove the mock agrees with itself.
to build a release binary carrying ffmpeg:
tools/fetch-ffmpeg.sh linux amd64 GOOS=linux GOARCH=amd64 go build -tags embedffmpeg -ldflags "-s -w" ./cmd/tc
that takes the binary from about 7 mb to about 40 mb. it's worth it: a person downloads one file and it works. a system ffmpeg on PATH is still preferred at runtime when there is one, so the unpacking is skipped for anyone who already has it.
| environment variable | what it sets |
|---|---|
| TC_HOST | the server to use, default call.dharun.dev |
| TC_FFMPEG | a specific ffmpeg to use instead of searching |
staying inside the free tier
everything runs on cloudflare's free plan, and it isn't close.
| resource | free allowance | what termcall uses |
|---|---|---|
| worker requests | 100,000/day | ~1,000 for ten people chatting a hundred messages each |
| durable objects | free plan, sqlite-backed | one per live room, hibernating when idle |
| video relay | — | none; peer-to-peer after the handshake |
idle rooms hibernate, so holding a room open bills no duration. keepalives are protocol-level websocket pings, which the runtime answers at the edge without waking the durable object — an application-level ping would bill a request every time.
the one thing that may eventually need something else is turn, the relay for networks too strict for peer-to-peer to cross. /ice serves the list, so adding one is three secrets on the worker and no client update:
npx wrangler secret put TURN_URL # turn:host:3478
npx wrangler secret put TURN_USER
npx wrangler secret put TURN_PASS
mit licensed. release binaries bundle a gplv3 build of ffmpeg, run as a separate program rather than linked — see notice.
built by dharun ashokkumar · all projects