2026-03-20
people ask why this took so long. here’s the honest answer.
version one was really just a pile of bash scripts messing around with ffmpeg back in november 2019. I knew obs could do what I wanted but even then I knew it wasn’t a good solution for deployment. I wanted headless. the scripts were mostly just rtmp to hls and back again. used open streaming platform as my test viewer. so close to what I wanted and so far. tried gstreamer through voctomix. seemed promising but I could never get their live production switcher running headless in a docker container. so back to ffmpeg. then back to gstreamer. then back to ffmpeg. two years of this until I took a break.
version two. january 2023. had to try again. flask, postgres, admin panel, the works. called it neptune. tried to build the whole stack at once. way too much.
version three. 2024. stripped it down, went api-first. called it the neptune-engine. super fancy. getting closer to the root of the problem. first real attempt at pipes with ffmpeg.
version four. also 2024. restarted and added the full stack right back in.
version five. 2025. rewrote the whole thing async because I thought that was the problem. it wasn’t.
version six. april 2025. got so frustrated I deleted almost everything and started over with just ffmpeg and a flask app. got obsessed with ffplayout. it could do everything I wanted. and I figured out how. pipes. I just couldn’t figure out how to do it in my own app.
version seven. early 2026. named pipes, because that’s how ffplayout did it. still trying to force ffmpeg to be a server. the problem was always the switch. how do you go from one source to the next without bringing down the whole stream. ffplayout solved it with pipes so I tried pipes. just more headaches. collected every doc I’d written over six years and I was determined to solve it. gave up on ffmpeg not long after. switched to… you guessed it, gstreamer. now we were cooking. but the codebase was wrecked. too much debt from the rewrite. we knew it worked though.
version eight. february 2026. one last rewrite. fastapi, gstreamer, plugin system, docker. everything I’d learned crammed into one codebase. I finally had an mvp. the M stood for monolith. it worked. it actually worked. but I couldn’t ship it like that. everything was coupled to everything.
so I blew it up one more time. pulled the protocol out into its own spec. gave the server a name. gave the client a name. gave everything its own repo. I had all my documents from over the years so I knew exactly what worked and what didn’t.
turns out the thing I’d been building since 2015 was a protocol. I just kept accidentally piping ffmpeg to itself.
— pfarnsworth