Case file

Spinhrs

Somebody hosts a house, everybody else drops in and listens to whatever is already playing. A room is a place you are in rather than a file you operate, and that one constraint decides everything downstream.

Author
Henry CartoMSc · Software, Medical & eHealth Engineer
Role
Sole engineer. Streaming chain, product, ingest, rights position.
Stack
Liquidsoap · Icecast · Next.js App Router · Postgres · Vercel
State
Deployed, database and streaming box outstanding

Product thesis

Streaming won by giving everybody a pause button, and it took the room with it. Two people can hold the same album and never once be inside it together, because on demand means each of them is somewhere else in it.

Spinhrs puts the room back. A house is live or it is not, you join it where it is, and there are no controls, because a room is a place you are in rather than a file you operate. That single constraint decides everything downstream, and most of the work has been following it honestly rather than softening it the first time it makes something awkward.

The bet is twenty minutes. If somebody drops into a room they cannot steer and is still there twenty minutes later, the premise holds and the rest is distribution. If they leave at ninety seconds, no amount of design saves it. Every decision below was made against that number.

Listeners never make an account. Not as a growth tactic, but because an account is something you ask for before you have given anything.

Architecture

Two halves that share nothing but a contract, so either can be rebuilt without touching the other. A streaming chain on its own box, and a web app that knows nothing about audio.

The app never touches the stream. It renders what is on, who is in it and what is playing, and a listener's browser connects to the audio directly. A small agent on the streaming box reports the truth of what is happening back to the app on a fixed interval, and that one feed drives the live grid, the now playing line, the listener figure and the play log.

Keeping those halves apart is the decision the whole thing rests on. Audio is the part that has to be up at three in the morning and the part I would least like to redeploy in a hurry. Nothing in the product half can take it down.

Key engineering decisions

The listener figure is measured where the connection is, not where the listener is. It began as something the browser reported, which is to say something the browser could invent. During a security pass I put sixty listeners into an empty room without opening the app, and since that number also orders the chart it was a free route to the top of it. Moving the measurement to the one place that cannot be reached from a browser closed it, and the same attack now moves nothing.

Chasing that fix surfaced the worse bug underneath it. A room is served in two audio formats, and the ingest was matching them as if they were two different rooms, so an entire format's worth of listeners was being counted as belonging to nothing and discarded. Not just off the display: out of the listening totals a royalty statement would eventually be built from. The platform was under reporting plays to artists by roughly the share of its audience on the format that got dropped, which is most of it.

Public writes are limited at the edge rather than in the client. With no accounts there is no identity to attach a limit to, and the cooldown that existed was in the browser, which is not where a rule lives: forty spam messages went through in a row by changing one string. The fix is unglamorous and it is the difference between a loop being free and a loop being slow.

The play log pays for itself three times. It is the reporting any licence will ask for, the basis of anything ever paid to a rights holder, and the source of the now playing line in the interface. Building it once for all three is the only reason a product with no revenue can have that conversation at all.

The signature element

The room at minute twelve is where this is won or lost, and for a long time it was a beautifully typeset poster. Everything on the screen was fixed at the moment somebody walked in. The only things that moved were numbers changing, and a number changing is data rather than company.

The one thing a listener could give back was the worst of it. You tapped it, saw it register, and for everybody else in the room a counter moved with no more ceremony than a clock. The single interactive element in a shared space was invisible to the people you were sharing it with. Now it surfaces to everyone, carrying no name and no number, because neither is true to what happened. It keeps showing after you have spent your own, which is the point, since by minute twelve that is everybody.

Underneath it, the tracks you have sat through pile up. That list is built on the device from what this listener actually witnessed, so it is not a fact about the room at all. Nobody who arrived a minute ago has one. At minute one it is not on the screen, and at minute twelve it is four tracks long, which makes it the only thing there worth more the longer somebody stays.

Where it stands

Deployed, and the interface runs end to end: the grid, rooms, scheduled premieres, an artist control panel, spinner profiles, the flow for starting a house, share previews, offline and error states.

What is proven was proven by attacking the running application and reading what came back, not by a test suite. There is no test suite, and calling that anything other than a gap would be dishonest.

Not finished. The live path from a DJ's software into a room is written and has never been exercised, because the software it depends on does not run on the machine this was built on. There is no database behind the deployment yet, so nothing written survives, and no room plays audio.

The rights position is worked out and deliberately not written up here.