menutube is a SwiftBar/xbar-compatible menu bar plugin that plays YouTube audio in the background, with macOS media-key support, a clickable library, repeat-on-end, and one-click yt-dlp updates from the menu.
Built on mpv + yt-dlp. The plugin is a thin UI; mpv handles playback and auto-registers with MPNowPlayingInfoCenter so the F7/F8/F9 keys and Bluetooth headphone play-pause buttons work without any native app.
- Plays any URL
yt-dlpcan resolve — YouTube videos, YouTube live streams, Twitch, SoundCloud, direct media URLs. - Audio-only (
mpv --no-video), runs in the background. - Macros media keys + Bluetooth headphone play/pause work out of the box (mpv registers with
MPRemoteCommandCenter). - Library stored as a plain JSON file you can hand-edit (
~/.config/menutube/library.json). - Add via menu: paste a URL → title auto-fetched via
yt-dlpand pre-filled in a confirmation dialog. - Repeat-on-end toggle: persists across plays, also flips the loop state on the currently-playing track via mpv IPC.
- Built-in
yt-dlpversion display and one-click update via Homebrew. - Built-in plugin version status: release installs show whether menutube is current or a newer release is available.
- Refetch-all-titles action for when YouTube changes something.
- No private data leaves your machine; the plugin only talks to local mpv IPC and (when invoked)
brew,osascript, the public GitHub release endpoint, and the public YouTube/Homebrew endpointsyt-dlpandbrewreach out to.
When you first install, the library is seeded with three known-good 24/7 audio streams (lofi, jazz piano, focus techno) so you can verify everything works in one click. Remove them via the menu or edit the library JSON.
- Install SwiftBar.
- Install runtime dependencies:
brew install mpv yt-dlp jq
- Download the latest release plugin into your SwiftBar plugin folder:
mkdir -p "$HOME/SwiftBarPlugins" curl -fsSL \ https://github.com/flamerged/menutube/releases/latest/download/menutube.5s.sh \ -o "$HOME/SwiftBarPlugins/menutube.5s.sh" chmod +x "$HOME/SwiftBarPlugins/menutube.5s.sh"
SwiftBar picks up menutube.5s.sh and refreshes every 5 seconds.
menutube uses the BitBar/xbar stdout menu format. Install the latest release asset into your xbar plugin folder:
mkdir -p "$HOME/Library/Application Support/xbar/plugins"
curl -fsSL \
https://github.com/flamerged/menutube/releases/latest/download/menutube.5s.sh \
-o "$HOME/Library/Application Support/xbar/plugins/menutube.5s.sh"
chmod +x "$HOME/Library/Application Support/xbar/plugins/menutube.5s.sh"Clone the repo only when you want a source checkout for development:
git clone https://github.com/flamerged/menutube.git
cd menutube
./scripts/install-dev-swiftbar.sh "$HOME/SwiftBarPlugins"Release installs show whether the installed plugin is current or whether a newer release is available. Update to latest release runs in the background, writes to MENUTUBE_UPDATE_LOG, and replaces the plugin with the latest release asset. Source checkout installs show the current branch and commit for diagnostics, but hide the menu updater to avoid overwriting checkout-managed files. Development updates should use normal git commands in the checkout.
The plugin uses macOS-specific bits: osascript for the "Add video" dialog, open -a to open the library/log, and mpv's MPRemoteCommandCenter integration for media keys. Linux menu bar runners like Argos can render the menu, but the macOS integrations won't work.
zshmpv(0.34+ recommended for macOS media-key support)yt-dlpjqnc(system/usr/bin/ncis fine — used for Unix-socket IPC to mpv)
macOS ships zsh, nc, and osascript. Install the rest via Homebrew:
brew install mpv yt-dlp jqThe menu bar icon is ♫ YT (idle), ♪ YT (playing), or ⏸︎ YT (paused).
- Click a library entry → starts playback. Existing playback is replaced.
- F8 / headphone play-pause → toggles pause/resume on the active mpv session.
- ➕ Add video… → AppleScript dialog asks for the URL; the plugin pre-fetches the title via yt-dlp and shows a confirmation dialog you can accept or edit.
- 🛠 Edit library file → opens
library.jsonin TextEdit for bulk cleanup or reordering. - 🔁 Repeat → toggles loop-on-end for the current and future plays. Persisted across restarts.
- 🔧 Tools → Update yt-dlp → runs
brew upgrade yt-dlpin the background and notifies you when it's done. - 🔧 Tools → Refetch all titles → re-resolves every title in the library. Useful after a yt-dlp update or YouTube format change.
menutube works without configuration. These environment variables can tailor it to your setup, declared as SwiftBar <xbar.var> entries so they appear in SwiftBar's plugin variable panel:
| Variable | Default | Purpose |
|---|---|---|
MENUTUBE_CONFIG_DIR |
~/.config/menutube |
Library and preferences directory |
MENUTUBE_REPO_DIR |
empty | Optional menutube git checkout for source metadata (hides the menu updater) |
MENUTUBE_REPO_URL |
https://github.com/flamerged/menutube |
Repository URL for the "Open project page" footer link |
MENUTUBE_RELEASE_ASSET_URL |
https://github.com/flamerged/menutube/releases/latest/download/menutube.5s.sh |
HTTPS latest release asset URL used by the "Update to latest release" action |
MENUTUBE_UPDATE_LOG |
$HOME/.cache/menutube/update.log |
Update log path |
MENUTUBE_CHECK_RELEASE_UPDATES |
1 |
Set to 0 to disable cached plugin release checks |
MENUTUBE_RELEASE_CHECK_TTL_SECONDS |
86400 |
Minimum seconds between automatic latest-release checks |
MENUTUBE_RELEASE_CHECK_CACHE |
$HOME/.cache/menutube/release-check.tsv |
Latest-release check cache path |
MENUTUBE_MPV |
auto-detected | Override path to mpv |
MENUTUBE_YTDLP |
auto-detected | Override path to yt-dlp |
MENUTUBE_USER_AGENT |
Safari 17 desktop UA | User-Agent for HLS segment fetches |
MENUTUBE_PLAYER_CLIENT |
android,web |
yt-dlp --extractor-args player_client list (anti-403 for YouTube live HLS) |
The library and preferences live at:
$MENUTUBE_CONFIG_DIR/library.json— JSON array of{title, url}objects.$MENUTUBE_CONFIG_DIR/repeat— file containingyesorno.
mpv runtime state lives at:
$TMPDIR/menutube-mpv.sock— Unix-socket IPC.$TMPDIR/menutube.current— title of the currently-playing entry.$TMPDIR/menutube-mpv.log— mpv log file (informational; openable from the menu).
A few non-obvious lessons baked into this plugin, in case you fork it:
- mpv
--no-terminalsilences all mpv logging, even with--msg-level=all=info. Use--no-input-terminalinstead and rely on--log-filefor diagnostics. - mpv's
ytdl_hookfinds whateveryt-dlpis in PATH first. On many machines this is a stale pip-installed module that predates current YouTube format changes. menutube pins to/opt/homebrew/opt/yt-dlp/bin/yt-dlp(the brew-prefix symlink, always current) via--script-opts=ytdl_hook-ytdl_path. - YouTube live-stream HLS segments return HTTP 403 when fetched with ffmpeg's default headers, even when yt-dlp's resolved URL is valid. The fix is
--extractor-args=youtube:player_client=android,web— yt-dlp returns URLs that ffmpeg can fetch unchallenged. - SwiftBar splits each menu line on the first ASCII
|between display text and attributes. A title likeMental Clarity & Logic | High-Energy Technobreaks the click handler entirely. menutube replaces|with the visually-identical fullwidth|(U+FF5C) at render time. - Library entries dispatch by INDEX, not URL.
bash=… param2=0is parser-safe;param2='https://www.youtube.com/watch?v=...'is not (the?/&characters confuse SwiftBar's attribute parser). yt-dlp -eperforms format resolution as part of its workflow and can fail with "Requested format is not available" on some videos. Use--print "%(title)s" --skip-downloadto get the title without format resolution.
- No audio: check
$TMPDIR/menutube-mpv.log— look forHTTP 403(YouTube anti-scraping; try aMENUTUBE_PLAYER_CLIENTvalue likeiosortv_simply) orRequested format is not available(yt-dlp out of date; click 🔧 Tools → Update yt-dlp). - Click does nothing: titles containing
|were a bug fixed in v0.1.0. If a title with another special char misbehaves, please open an issue with the offending string. - Media keys go to the wrong app: macOS gives them to the most recently active media app. Pause/resume in the menu once to make mpv the active one.
menutube only talks to:
- The local mpv process over its Unix socket
yt-dlpas a subprocess, which fetches from YouTube/Twitch/etc.brewas a subprocess for the optional yt-dlp update actionosascriptfor the Add Video dialog and notifications
No telemetry. No background network activity beyond yt-dlp and brew when you trigger them.
See SECURITY.md for vulnerability reporting.
See CONTRIBUTING.md.
MIT — see LICENSE.