Music for Coding, Without the Browser Tab
Part 4 of a series on the tools I actually run.
I’ve got a YouTube live stream I put on while I work. Same one, most days. It’s not a sophisticated setup.
The browser tab was the problem. Not a big problem. Just a small recurring one. It’s a tab I have to remember not to close during a cleanup, it’s decoding video I’m not looking at, and it doesn’t stop the laptop dozing off and taking the music with it when I get up to make coffee.
So now it’s this:
alias codemusic='echo "PLAYING Claude Code Music...." && caffeinate -i mpv --no-video --no-resume-playback --ytdl-format=bestaudio --msg-level=lavf=error,demux=error --user-agent="com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12)" "https://www.youtube.com/watch?v=tRsQsTMvPNg"'
Type codemusic. Music plays. Press q. Music stops.
It didn’t start out that long. Every flag in there got added because something annoyed me, and one or two of them took me an embarrassingly long time to figure out. So, in order.
First, two installs
brew install mpv yt-dlp
mpv plays it. yt-dlp is what lets mpv understand a YouTube link in the first place.
Worth spelling that second part out, because it confused me at first. mpv doesn’t know anything about YouTube. What it has is a little Lua script called ytdl_hook that comes bundled with it, and when mpv sees a URL it can’t open on its own, that script quietly hands the job to yt-dlp. yt-dlp figures out the real stream and passes it back.
If yt-dlp isn’t there, nothing works, and the error you get won’t mention yt-dlp at all. It’ll mumble something about not being able to open the file. Not much to go on.
Now the flags
echo "PLAYING Claude Code Music...."
Completely cosmetic. There’s a few seconds of silence after you hit enter while yt-dlp goes off and resolves the stream, and without something on screen I’d assume it hadn’t taken and hit enter again.
The && there does nothing meaningful. A semicolon would be the same. It’s just how I typed it.
caffeinate -i
This one I’m quite fond of.
caffeinate is an Apple utility that keeps the machine awake. The -i means idle sleep specifically. But the good bit is what it does when you hand it a command to run: it holds the machine awake for exactly as long as that command lives, and lets go the second it exits. Nothing to remember, nothing to turn back off.
Which solved a genuinely irritating thing. I’d start music, wander off, come back to a sleeping laptop and silence. Which is precisely the moment I wanted music still going.
Couple of things it won’t do. The screen still sleeps, because that’s -d and I like the screen sleeping. And shutting the lid puts the machine down no matter what caffeinate thinks, because that’s a whole other mechanism.
It’s also the only bit of this that’s Mac-only. More on that below.
--no-video
Don’t decode the video. Don’t open a window. Don’t do any of it.
This is a fanless laptop. Decoding a picture nobody is looking at is exactly the sort of thing I bought a fanless laptop to avoid.
--ytdl-format=bestaudio
Just give me the audio.
I went round in circles here for a while. bestaudio was the obvious thing to try and it matched nothing at all, which was baffling. Turns out YouTube live streams come over HLS, and the HLS variants are all video and audio muxed together. There’s no audio-only option to pick. So there’s nothing for bestaudio to grab.
What I did for months instead was run yt-dlp -F against the URL, find the cheapest muxed variant, and hardcode that number into the alias. Then throw the video away with --no-video.
Which worked, and which I never liked. Those format numbers are per-stream and they aren’t stable. It was a thing sitting in my shell config waiting to break one day for no visible reason.
The next flag is what let me delete it.
--user-agent="com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12)"
Yes, that’s the YouTube app on an Oculus headset. And it’s carrying more weight than anything else in this alias.
YouTube hands out different format ladders depending on who it thinks you are. The web client gets the muxed HLS ladder I just complained about. Some of the app clients get a proper DASH ladder with real audio-only streams sitting on it, and fewer of the throttling and signature games the web player has to play. Say you’re one of those clients and you get the better menu. Which is why bestaudio suddenly had something to match, and why the hardcoded number could go.
I’ll be straight about what this is: a workaround. It leans on how YouTube behaves right now, and one day it’ll stop working. When it does, what you’ll see is a format selection error that says nothing about user agents, so write yourself a note. There’s also --extractor-args in yt-dlp for picking a client properly, which is the grown-up version of the same trick if spoofing a UA string makes you itch.
--no-resume-playback
mpv likes to remember where you left off. Great for a film. Meaningless for something with no end, and it drops a little state file in your watch_later folder every single time you quit, forever.
--msg-level=lavf=error,demux=error
Shutting mpv up, but only partly.
The heavy-handed version is --msg-level=all=warn, which mutes everything. This only turns down libavformat and the demuxer, which are the two doing all the shouting on a live HLS stream. That includes printing the resolved googlevideo URL, which runs to about a thousand characters of signed nonsense and wipes out your scrollback.
Everything else stays where it was. So if mpv has something worth telling me, I still hear it. Muting a program completely is one of those decisions you regret three months later when it’s failing quietly and you’ve forgotten you did it.
Will it run on Linux?
Yes, with one swap.
caffeinate isn’t there. It’s Apple’s. On systemd:
systemd-inhibit --what=idle --why="music" mpv --no-video ...
Same behaviour, held for the life of the command. On a box that never sleeps anyway, just drop the wrapper.
Do not install yt-dlp from apt. I mean it. This is the number one reason a working alias falls over on a Linux machine. The Debian and Ubuntu packages run months behind, and yt-dlp is in a constant back-and-forth with YouTube’s extractors, so months behind means broken in ways nothing in the error message will explain. Use pip, pipx, uv, or grab the standalone binary off the releases page. Arch’s package is close enough to upstream to be fine.
Sound sorts itself out. CoreAudio on the Mac, PipeWire or PulseAudio on Linux. I have never once had to set --ao=.
Shell file is ~/.zshrc on macOS and probably ~/.bashrc on Linux. The alias itself doesn’t change.
Media keys work on the Mac. On Linux it depends on your desktop environment and I gave up.
It still cuts out sometimes
Every few hours you might get a pile of 403s and silence.
Nothing’s broken. YouTube’s segment URLs expire, and a live stream’s DVR window eventually rolls past whatever mpv is holding onto. Quit, type codemusic, get on with your day. The VR client route seems to have made this rarer than it used to be, but it hasn’t gone away.
If it really bothers you, yt-dlp -f bestaudio -o - "URL" | mpv --no-video - keeps yt-dlp in the loop so it can refresh those URLs itself. I don’t bother. Piping forces a remux on the way out and shoves the video down the pipe just so mpv can throw it away at the other end. That’s a lot of wasted work to save typing nine characters occasionally.
Getting your prompt back
As written it sits in the foreground, which I want, because that’s what makes q work.
If you’d rather have the terminal back, tack this on the end:
&>/dev/null & disown
Output binned, process backgrounded, detached from the shell so closing the window doesn’t kill the music.
But you’ve just given up keyboard control. Nothing’s listening for q anymore, so stopping it means pkill mpv, which is a bit of a hammer if something else is playing. At least caffeinate tidies up after itself, since it only holds the assertion as long as its child is alive.
If you want it done properly, add an IPC socket:
--input-ipc-server=/tmp/mpv-codemusic
and then:
alias codemusic-stop='echo quit | socat - /tmp/mpv-codemusic'
socat is one brew install away. You can send pause and volume over that socket too. I never have.
One URL is never enough
Sooner or later you want a second one, so make it a function:
ytaudio() {
caffeinate -i mpv --no-video --no-resume-playback --ytdl-format=bestaudio \
--msg-level=lavf=error,demux=error \
--user-agent="com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12)" \
"$1"
}
Same flags, URL as an argument. I’ve got a small pile of these now. codemusic for working, a couple of news channels for background. All the same shape.
So was it worth it
Six flags, to save maybe fifteen seconds a day over opening a tab. Put like that, obviously not.
But the tab was never costing me fifteen seconds. It was costing me a tab I had to look after, a video decoder running for nothing, a laptop that fell asleep and took the music with it, and the mild ongoing risk of clicking something and ending up twenty minutes deep in a video about lathe restoration.
The point isn’t that terminals beat browsers. It’s that I was running a video player in order to listen to audio, and babysitting a tab to do it.
Now I type a word and music comes out.
If you actually want the video, or the chat, or you skip between things constantly, keep using the browser. It’s the right tool for that. This is for the one stream you leave on for six hours and never touch.
Next in the series: the dotfiles repo that keeps the Mac and the servers in sync.