← Back to Blog
· Joshua

Velvet Radio Is Live

This week Velvet Radio shipped a working audio player and real radio streaming infrastructure — Icecast, Liquidsoap, a live stream deployed to EC2 and verified in production. Lunar Blood got a proper mail system and full test coverage to match.

Two projects shipped real features this week. The kind that change what the sites actually do, not just how they look. That felt good.


Velvet Radio — Going Live

Velvet Radio now has a working audio player. I built it as a native HTML5 component in React — play and pause, a seek bar, current time and total duration, volume control and mute. It sits as a fixed bar at the bottom of the page so it stays visible while browsing. The episodes page was wired up at the same time: each episode now carries an audio_url, and clicking Play loads it directly into the player. If an episode has no audio file attached, the Play button is disabled rather than broken.

That was the frontend half. The infrastructure side was more involved.

I set up Icecast as the streaming server — configured with a /live mount point, environment-substituted passwords, and a listener cap. Behind it, Liquidsoap handles what actually gets streamed: a fallback playlist that pulls .mp3 files from storage/audio/fallback/ when nothing is live, and a live input on port 8001 that takes over when a source connects. Liquidsoap runs as a systemd service with auto-restart on failure. The whole thing sits behind an nginx reverse proxy.

The AudioPlayer component got a streamUrl prop added — when it is set, a LIVE badge appears and the player streams from that URL instead of a file. The new listen.tsx page uses this to point directly at /stream.

All of it is deployed to EC2. The /stream endpoint returns 200. The Icecast admin shows both /live and /fallback active. Velvet Radio is a working internet radio station now.


Lunar Blood — Mail and Tests

Lunar Blood had a few rough edges in its error handling. The shows delete action and the checkout page were both managing error state inline — local component state that would get out of sync or silently fail. Both are now wired into the toast system: errors surface as dismissible notifications instead of state that has to be manually cleared.

The bigger addition was the mail system. Two new Mailables: ContactFormMail, which captures sender name, email address, and message body from the contact form, and OrderConfirmationMail, which goes out after a successful payment with customer name, order ID, product name, and total. Both have Blade text templates. The API routes on /api/contact and /api/process-payment now dispatch the appropriate Mailable after processing.

Four new mail tests were added to cover the send paths and template rendering. The full test suite is at 63/63.


What Is Next

Hollow Press has an RSS feed queued — it is the next feature up. On the Noteleks side, the spear now fires a projectile; the visual sprite attachment is what comes next.

More next week.

— Joshua, Graveyard Jokes Studios