The Project Was Already Finished
About a year ago, I wrote a blog on how I built a terminal music player called Sparklines TUI. At that point, the project was pretty much complete. It did exactly what I wanted: I could search songs, stream music without opening a browser, control playback via keyboard shortcuts, and stay inside the terminal for way longer than any sane person should.
It was one of those curiosity-driven projects that taught me a lot and eventually found its resting place on GitHub and npm. I never planned for it to become anything more. Usually, you build these things, get a few stars, and move on.
But then I started wondering about something absolutely nobody asked for: What if I could run the entire TUI inside a browser? Not rewrite it as a React app. Not build a simplified web version. The actual, remote terminal application, running inside a browser. At first, it sounded relatively simple.
I was very wrong.
The Idea That Started The Mess
The goal was straightforward: open a website and instantly get a Sparklines TUI session. The experience should feel identical to running it locally, from searching songs to hitting keyboard shortcuts. The browser would just be a window into the terminal.
In my head, this was a weekend project. In reality, it became a multi-month rabbit hole involving Docker, WebSockets, WebRTC, Linux audio systems, FFmpeg, PulseAudio, FIFO pipes, and enough debugging to make me question my life choices.
Solving The Terminal Problem
The first hurdle was multi-user chaos. If several people opened the site, they couldn't share the same terminal session, one person would be searching while another randomly triggered keyboard shortcuts.
Docker was the obvious answer. Every user would get their own isolated container where Sparklines TUI ran exactly as it would on a normal Linux machine. Everyone gets their own environment, their own session, and their own problems.
Transporting that terminal to the browser was surprisingly easy. Docker exposes container streams, Node.js attaches to them, WebSockets transports them, and xterm.js renders them in the browser.
For a brief moment, I genuinely thought I was done. Then I remembered that music players need to actually play music.
The Audio Problem
Terminal output is easy. Audio is a nightmare.
Inside the container, MPV was doing its job perfectly. The challenge was getting that audio out of the isolated container and into a browser. This is where it stopped feeling like a terminal app and became a hardcore Linux systems project.
I found myself deep in documentation for PulseAudio, experimenting with FFmpeg capture devices, and debugging FIFO pipes. Every time I fixed one thing, another broke. Permissions broke. PulseAudio broke. Everything broke together. A classic Linux experience.
The Rabbit Hole Gets Deeper
Eventually, I stopped assuming things worked and tested every component individually:
- Could MPV play music? Yes.
- Could PulseAudio capture it? Yes.
- Could FFmpeg record it to raw PCM data? Yes.
- Could FIFO pipes transport it outside the container to the host? Yes.
- Could I convert it to a WAV and play it back? Also yes.
I was literally streaming music inside a Docker container, capturing it, piping it to the host, and saving it successfully. It was encouraging because the audio existed, but incredibly frustrating because the browser still refused to cooperate.
Enter WebRTC
WebSockets are great for text, but terrible for real-time media. That meant I had to learn WebRTC.
This introduced an entirely new world of ICE candidates, peer connections, and media tracks. At one point, I had terminal windows everywhere, browser dev tools open, Docker logs scrolling endlessly, and FFmpeg output buried underneath it all.
The good news? The WebRTC connection eventually worked. Tracks appeared. Media streams existed. Everything looked perfectly healthy.
The only thing missing was the actual sound. Which, as it turns out, is an important detail for a music player.
What Actually Works Today
Right now, the project is in a weird, fascinating state.
The terminal side is flawless. Browser sessions, automatic container launches, session management, and terminal streaming all work perfectly. The audio pipeline is also largely functional, music successfully travels from MPV to PulseAudio, through FFmpeg, into FIFO pipes, to the host machine, and into the WebRTC layer.
The final boss remaining is the browser playback itself. The project is simultaneously incredibly close to completion and somehow still incomplete.
What I Learned
The best part about this project is that none of this was planned. I just wanted a terminal music player.
I wasn't trying to learn Docker container lifecycle management. I wasn't trying to debug WebRTC media transport. I certainly wasn't trying to permanently burn Linux FIFO pipe mechanics into my brain. But that’s how the best side projects work, they force you into territories you’d never explore otherwise.
Final Thoughts
Objectively speaking, there are much easier ways to stream music. There are easier ways to build web apps, and there are definitely easier ways to spend a weekend.
But that’s never the point.
The value isn't the final result; it’s the journey through the systems and problems. Sparklines TUI started as a music player, morphed into a Docker project, became a browser streaming experiment, and evolved into a platform for running isolated remote terminals.
Somewhere along the way, the architecture became larger than the application itself. And honestly, that’s my favorite part of the story. Sometimes the best projects are the ones that completely escape their original purpose.
And there you have it, my friends, that was Sparklines TUI from my side.


Top comments (0)