How it started #
It started on March 18th.
That was the day I published Fugue proxy ready to be used other than me. Nothing flashy, just bytes flowing through the right pipes, in the right shape, at the right time. But it was enough. Enough to know that the thing in my head was actually buildable.
So I did what you do when something clicks: I brought it to my friends. Not the developer ones, the other ones. The group chat. I pitched the idea of reviving Winamp on top of this proxy, using the Navidrome API as the backend. A proper music player. Skins. The visualizer. That whole feeling.
The response was, more or less: “sounds like it’s on your table, then.”
And they weren’t wrong. Nobody else was going to build it. That’s the quiet truth behind most side projects that actually ship, there’s a thing you want to exist in the world, you look around, and the chair where someone else might build it is empty.
But underneath the pitch was something I hadn’t really said out loud yet. It wasn’t just nostalgia for Winamp, though there was plenty of that, late nights, custom skins, the orange glow of the equalizer, the llama. It was more that I miss what listening to music felt like back then. Intentional. Owned. Something you picked and put on, not something an algorithm drip-fed you between ads.
What we have today is mostly noise. Playlists of AI-generated filler designed to be metabolized without being heard. Music that passes through your ears without ever arriving. I want the other thing back, the thing where you sat down, queued up an album, and listened. I can’t fix the industry, but I can build the player I want to use. And if someone else wants to use it too, even better.
That player became Moosic.
The technical story #
From here on, this gets technical. If you came for the vibes, the vibes are above. Below is the actual architecture, the choices, the dead ends, and the reasoning.
The problem space #
The Subsonic API is, at this point, the closest thing the self-hosted music world has to a lingua franca. Navidrome is the most pleasant server implementation of it I’ve used, fast, opinionated in the right ways, low-drama to operate. So the server side was, effectively, a solved problem. I didn’t want to build another server. I wanted to build the client I kept wishing existed.
The existing Subsonic client landscape has good options, but none of them scratched the specific itch: a native desktop player with a deliberate aesthetic, built for people who are nostaldic about their mp3 usage.
Why a proxy at all: Fugue #
Here’s the part of the story that gets out of order if you’re not careful: Fugue came first. Moosic came out of Fugue.
I had multiple Navidrome instances, different libraries on different boxes, for different reasons, and no good way to make a single client see them as one. As far as I could find, nothing in the Subsonic ecosystem merged libraries across servers. So I built Fugue: a Subsonic-compatible proxy that sits in front of one or more upstream servers, presents a unified library to whatever client connects, routes stream requests back to whichever server actually owns each track, and handles auth and a caching layer along the way.
To the client, Fugue looks like a regular Subsonic server. Underneath, it can be fronting a single Navidrome or a fleet of them, and the client doesn’t have to know or care.
Fugue is its own project, separate repository, fully open source, useful with any Subsonic client. Moosic does not require it. You can point Moosic straight at a Navidrome and it will work fine. But the two have a love story: when Moosic talks to Fugue specifically, they can establish a QUIC connection with NAT traversal via Iroh, which means you can run Fugue at home and reach it from anywhere without port-forwarding, dynamic DNS, or a VPN. The underlying API is still Subsonic, just delivered over a much friendlier transport.
The March 18th proof of concept was scrappier than it sounds. Winamp skins rendering, music actually streaming through the proxy, and roughly as many bugs as you’d expect from a one-day prototype. But all the pieces were in the same room for the first time, and that’s when I knew it was a real thing.
The exploration with Fugue is what led to Moosic. Once I had a clean library layer to point a client at, the question of what the ideal client on top of it looked like turned into a retro-inspired desktop player, and that question wouldn’t leave me alone.
Why Rust #
Rust was not a foregone conclusion. The honest reasons it won:
- It’s the language I want to maintain code in five years from now. A commercial music player has to be something I can still build, ship, and debug long after the initial burst of enthusiasm fades. Rust’s compile-time guarantees are a gift to future-me, who will have forgotten everything.
- Distribution. A single statically-linked binary per platform, no runtime for the user to install, no “please install the correct .NET version” support emails.
- Audio and concurrency. The ecosystem (
cpal,symphonia,rodio, async runtimes) is genuinely good, and the borrow checker keeps the audio thread honest.
Why Skia for the GUI #
I went back and forth between GPUI and Skia before landing on Skia. The decision came down to:
- Control over the aesthetic. A Winamp-inspired player needs pixel-level control, custom widgets, and the freedom to ignore platform conventions when that’s the right call. Immediate-mode-ish rendering on top of Skia gives me that without fighting a framework.
- Cross-platform consistency. The same renderer on macOS (Metal backend), Windows, and Linux means the player looks and feels identical everywhere, which matters when the look is a significant part of the product.
- Maturity. Skia is what Chrome, Flutter, and a lot of other serious software render with. It is not going away.
What I didn’t expect was how pleasant Skia would feel coming back from years of web stacks. You’re handed a canvas and a pile of pixels, and that’s it. No component framework with the wrong primitive for what you’re trying to build. No escape hatch into a workaround for a workaround. There’s more boilerplate, sure, text shaping, hit testing, layout, you write all of it, but the trade is that nothing is in your way. I had forgotten how good that felt.
The Winamp question: aesthetic, not clone #
I started this as a pure Winamp clone for Navidrome streaming, and within a few days I could feel myself fighting the design constraints. It stopped being fun. So I stopped, and asked the question I should have asked at the start: what would I want a music player to be today, and what am I missing from the streaming services I actually use? Things like having visiable queue without a ceremony. A queue that’s always one click away, not buried two screens deep. Something that lives on your desktop instead of in a tab.
Reviving “the feeling of Winamp” is not the same as cloning Winamp. I looked carefully at the trade dress question early on, skin format compatibility, iconography, layout, and drew a line. Moosic takes inspiration from that era of music software (compact, dense, present on your desktop rather than hidden in a browser tab), but it is its own product with its own visual identity.
That identity, concretely:
- Two UIs in one app. A modern UI as the default, and a Retro UI for the people who want the dense, skinnable, era-correct experience. Anything skin-related lives in the Retro UI; the modern UI deliberately does not support skin formats.
- Compact, never fullscreen. Fullscreen is the one mode Moosic does not have. It is meant to live in the corner of your screen, ready when you want it. There is a mini player and an always-on-top mode for the times you don’t want loose it.
- Typography. Inter, with Helvetica Neue as fallback.
- Default palette. Warm yellows on a dark background, with a light mode for people who prefer it. Paid users can change the palette to whatever they like.
- No fancy visualizations. No plugin system. I don’t think we need either today. Prove me wrong.
Licensing: offline-verifiable signed license blobs #
Moosic is commercial software, but I refuse to ship a player that phones home every time it starts. That would betray the entire premise. So the licensing model is fully offline-verifiable:
- The user receives a license as a signed JSON blob.
- The blob is signed with an Ed25519 private key. The corresponding public key is hardcoded into the Moosic binary.
- Verification at runtime is a pure function: parse the blob, check the signature against the embedded public key, done. No network call. No server dependency. If my business disappears tomorrow, every existing license keeps working forever.
The blob itself is small and boring on purpose:
schema_versionnameemailtier, defaults to lifetimefeatures, part of the signed payload, so it’s tamper-proofissued_at, Unix timeexpires_at, defaults to “never”nonce, used for refund handling
I don’t store the license blob itself anywhere on my side. The signing service generates it, sends it, and forgets it. The only things I keep are the Polar order id and the nonce, enough to process a refund and nothing more.
The license is not bound to a specific machine. Once the blob leaves my service, it’s a piece of paper the user owns. Run it on your laptop and your desktop. Reinstall the OS. I don’t care, and I don’t want to know.
If my signing key is ever lost or compromised, the existing licenses keep working, they’re already signed and verify against the embedded public key in their corresponding Moosic version. I’d generate a new keypair, ship a new version of Moosic that embeds the new public key, and re-issue licenses for that version going forward. The price of that is a one-time inconvenience for me; the benefit is that no user ever has a license stop working because of something on my end.
I’m aiming for v1 to be feature-complete on its own. If a v2 ever happens, it will be something genuinely radical, likely with a different financing model, and not a way to charge people again for the thing they already bought. Hopefully we never get there.
Checkout: Polar.sh as merchant of record, but not as license service #
For payments I’m using Polar.sh as the merchant of record. The reason is boring and correct: I do not want to be in the business of collecting VAT across the World, handling chargebacks, or arguing with Stripe about dispute fees. Polar handles the commercial surface and I stay focused on the product.
What I am specifically not using is Polar’s license issuing service, because that path involves a phone-home check and that is the line I am not crossing. Instead:
- Polar processes the purchase and fires a webhook.
- A serverless worker receives the webhook, generates the signed license blob, and sends the email via Resend.
- The user’s inbox is the only place the license lives. They can forward it to themselves, save it, archive it, it’s theirs.
The whole thing is end-to-end automated, runs in a serverless worker, and has no piece of persistent infrastructure I have to babysit.
Distribution #
- macOS: bundled with
cargo-bundle, thenproductbuild, signed and notarized. Mac App Store submission is on the roadmap once there’s revenue to justify the time, sandboxing constraints and the reviewer-testability problem (you need a Subsonic server to meaningfully test the app) are the two open questions there. - Windows: unsigned binaries today. A code signing certificate is on the list as soon as there’s revenue from the app to pay for it. Until then, SmartScreen will be unhappy and that’s a tradeoff I’m taking knowingly.
- Linux: package format is intentionally unsettled. AppImage, Flatpak, AUR, I’m waiting to see what users actually ask for before committing to one.
What I learned from PoC → product #
A few honest ones:
- The last 10% takes 90% of the time, and it isn’t code. Quirks, CI/CD, a website that doesn’t embarrass you, figuring out tax across jurisdictions, that’s what was actually hard until i found out about the magic word MoS. The audio pipeline was the easy part, Mac OS handling of sampling was kind of interesting and worth it’s own post.
- Skia after web stacks is a holiday. I’ve already said this above but it bears repeating. Pixels and a canvas. Some boilerplate. Nothing in your way.
- The Subsonic API is mostly complete, but its extensibility is the weak point. I’d love for it to be a bit more agnostic. For things like podcasts, OpenSubsonic is where I’m looking next.
- There are an absurd number of turn-key licensing and subscription SDKs in 2026. You can be selling software in an afternoon. I deliberately chose not to use any of them, because every single one would have created either a phone-home dependency or a “this app stops working when the vendor disappears” failure mode. Both are betrayals of what I’m trying to build.
Where it’s going #
Short list of what’s next:
- Casting and AirPlay. First on the list. People have phones and speakers; the player should meet them there.
- Party mode, with Fugue. Friends listening to the same library and contributing to the same queue, riding on Fugue’s NAT-traversed connection. Still an idea, not yet a plan.
- A SHOUTcast-shaped thought. I keep turning over what a modern, opinionated take on SHOUTcast could look like in the Moosic + Fugue world. No promises, just curiosity.
If you want to try it, you can. Download Moosic and play your first 10 tracks for free, that’s enough to know whether it’s the player you’ve been wanting. If it is, it’s $19, lifetime, at buy.moosic.now. I’d be genuinely happy to have you as a customer. Try it first.
A note on what Moosic is not: it is not a player for arbitrary files on your disk. It is a streaming client for Navidrome and other Subsonic-compatible servers. If that’s the gap you’ve been waiting to be filled, same.