Zero-dependency macOS media controller. 55KB binary. Works everywhere.
Control any media player from the command line or Claude Code—pause music when you focus, resume when you're done. No window switching, no external dependencies, no configuration hassle.
mediakey pause # Pause any media player
mediakey next # Skip to next track
mediakey enable # Auto-pause during Claude Code sessionsCompatible with: YouTube, Spotify, Apple Music, Safari, Chrome, and any application that responds to system media keys.
The Problem: You're coding with Claude, listening to music. Claude asks a question—you switch to pause Spotify. You answer—switch back to resume. Claude finishes—pause again. Repeat all day.
The Solution: mediakey automates this. Music plays while Claude works (you're waiting anyway), pauses when you need to focus (reading responses, typing). No window switching, no manual control, just natural focus cycles.
Perfect for:
- Long coding sessions with background music
- Podcast listeners who want silence during focus moments
- Anyone tired of CMD+Tab → Pause → CMD+Tab back to Claude
- Teams running multiple Claude Code sessions across different projects (per-directory state means no interference)
- Why Use This?
- Installation
- Usage
- How It Works
- Configuration
- Troubleshooting
- Development
- Contributing
- License
Install as a Claude Code plugin for automatic pause/resume during coding sessions:
/plugin install fntune/claude-mediakeyWhat this does:
- Builds the
mediakeybinary in the plugin directory - Configures hooks to pause media when you submit prompts
- Adds the
/mediaslash command for control - Everything stays self-contained—no system-wide changes
First-time setup:
/media enable # Activate auto-pause/resumeYou'll see a macOS notification confirming the state change.
Install as a standalone command-line utility:
git clone https://github.com/fntune/claude-mediakey
cd claude-mediakey
make buildAdd to PATH (optional):
sudo cp mediakey /usr/local/bin/Or use directly from the build directory:
./mediakey playpause- macOS 10.13+ (High Sierra or later)
- Swift compiler (Xcode Command Line Tools)
xcode-select --install
| Command | Description |
|---|---|
mediakey play |
Resume playback |
mediakey pause |
Pause playback |
mediakey playpause |
Toggle play/pause |
mediakey next |
Skip to next track |
mediakey prev |
Go to previous track |
mediakey volup |
Increase volume |
mediakey voldown |
Decrease volume |
| Command | Description |
|---|---|
mediakey enable |
Enable automation hooks (Claude Code) |
mediakey disable |
Disable automation hooks |
mediakey status |
Show current state (enabled/disabled) |
Note: By default, mediakey starts disabled in each directory. Run mediakey enable in your project directory to activate automatic media control for that specific project.
Per-Directory State: Each project has its own enabled/disabled state (stored in .mediakey_enabled). This means:
- You can enable mediakey in project A but not project B
- Parallel Claude Code sessions in different directories won't interfere
- State persists per-project, not globally
When installed as a plugin, mediakey automatically:
- Resumes media when you submit a prompt (Claude is working, you can relax)
- Pauses media when Claude needs your input or finishes (so you can focus)
- Shows macOS notifications for state changes
The plugin uses hooks to create a seamless focus management experience:
┌─────────────────────────────────────────────────────────────┐
│ YOU: Reading docs, thinking about code │
│ 🔇 Media: PAUSED (you need focus) │
└─────────────────────────────────────────────────────────────┘
│
[You hit submit]
↓
┌─────────────────────────────────────────────────────────────┐
│ CLAUDE: Processing your request, running tools │
│ 🎵 Media: PLAYING (you can relax, listen to music) │
└─────────────────────────────────────────────────────────────┘
│
[Claude finishes or needs input]
↓
┌─────────────────────────────────────────────────────────────┐
│ YOU: Reading response, deciding next action │
│ 🔇 Media: PAUSED (you need focus) │
└─────────────────────────────────────────────────────────────┘
│
[You hit submit]
↓
(cycle repeats)
The Philosophy:
- You're working (reading, typing, thinking) → Music distracts → Pause
- Claude is working (processing, running tools) → You're waiting → Play
This creates natural focus cycles without manual media control.
# Check status and get options
/media
# Toggle automation
/media enable
/media disable
# Manual playback control
/media next
/media pause
/media play# Enable automatic media control
/media enable
# Work on your code, chat with Claude
# Media plays while Claude processes your requests
# Media pauses when Claude needs input or finishes (so you can focus)
# Disable when you want manual control
/media disable# Pause media before long-running tasks
mediakey pause && npm run build
# Resume after completion
npm test && mediakey play#!/bin/bash
# deploy.sh - Pause music during deployment
mediakey pause
echo "Deploying application..."
./deploy-script.sh
if [ $? -eq 0 ]; then
echo "✓ Deployment successful"
mediakey play
else
echo "✗ Deployment failed"
fiUse with automation tools:
{
"type": "basic",
"from": {"key_code": "f13"},
"to": [{"shell_command": "/usr/local/bin/mediakey next"}]
}macOS Shortcuts.app:
- Create new shortcut
- Add "Run Shell Script" action
- Enter:
mediakey next - Assign keyboard shortcut
# .git/hooks/pre-push
#!/bin/bash
mediakey pause
# Run your tests...mediakey sends NSEvent.systemDefined events directly to kCGHIDEventTap, the same low-level system event tap that physical keyboard media keys use. This bypasses application-specific controls and works universally with any media player.
Event Structure:
- Type:
NSEventTypeSystemDefined - Subtype:
8(NX_SUBTYPE_AUX_CONTROL_BUTTONS) - data1:
(keyCode << 16) | (pressFlag << 8) - Event Tap:
.cghidEventTap(system-level HID event tap)
Media Key Codes (from IOKit's ev_keymap.h):
| Key Code | Function | Constant |
|---|---|---|
16 |
Play/Pause | NX_KEYTYPE_PLAY |
17 |
Next Track | NX_KEYTYPE_NEXT |
18 |
Previous Track | NX_KEYTYPE_PREVIOUS |
0 |
Volume Up | NX_KEYTYPE_SOUND_UP |
1 |
Volume Down | NX_KEYTYPE_SOUND_DOWN |
Event Sequence:
- Create key-down event (
pressFlag = 0xa) - Post to system event tap
- Create key-up event (
pressFlag = 0xb) - Post to system event tap
- Wait 10ms (
usleep(10000)) to ensure processing
The 10ms delay prevents events from being dropped when the program exits immediately after posting.
By posting events to the HID system tap, mediakey simulates actual hardware media key presses. The operating system routes these events to the active media session, regardless of which application is playing media.
mediakey stores its enabled/disabled state in .mediakey_enabled in the current working directory (per-project).
Location: .mediakey_enabled in each project directory where you run mediakey enable
Content:
1= enabled (automation active in this directory)0= disabled (automation inactive in this directory)
Per-Directory Behavior:
- Each project directory has its own independent state
- Run
mediakey enablein project A → only affects project A - Run
mediakey enablein project B → only affects project B - Parallel Claude Code sessions in different directories won't interfere with each other
Gitignore: Add .mediakey_enabled to your .gitignore since it's a local preference file
When installed as a plugin, hooks are automatically configured in hooks/hooks.json:
{
"hooks": {
"UserPromptSubmit": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/mediakey play"
}]
}],
"Notification": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/mediakey pause"
}]
}],
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/mediakey pause"
}]
}]
}
}Hook Behavior:
- UserPromptSubmit → Resume media (Claude is working)
- Notification → Pause media (you need to focus/read)
- Stop → Pause media (you need to read the response)
The hooks only fire when mediakey is enabled. When disabled, the commands exit silently without affecting playback.
Check if enabled:
mediakey status
# or
/mediaEnable automation:
mediakey enable
# or
/media enableClaude Code plugin:
# Check if binary exists
ls -la ~/.claude/plugins/marketplaces/claude-mediakey/mediakey
# Rebuild if missing
cd ~/.claude/plugins/marketplaces/claude-mediakey
make buildStandalone:
# Rebuild
make clean && make buildCheck macOS notification settings:
- System Settings → Notifications
- Find "Script Editor" or "osascript"
- Enable "Allow Notifications"
-
Verify plugin installation:
/plugin list
-
Check hooks are registered:
ls ~/.claude/plugins/marketplaces/claude-mediakey/hooks/ -
Restart Claude Code to reload plugins
mediakey/
├── .claude-plugin/ # Plugin metadata
│ ├── marketplace.json # Marketplace configuration
│ └── plugin.json # Plugin manifest
├── commands/
│ └── media.md # /media slash command
├── hooks/
│ └── hooks.json # Hook configuration
├── mediakey.swift # Core implementation
├── session-start.sh # Auto-build on first session
├── install.sh # Build script
├── Makefile # Build automation
├── CLAUDE.md # Claude Code instructions
├── PLUGIN.md # Plugin developer docs
└── README.md # This file
# Standard build
make build
# Manual build (no Makefile)
swiftc mediakey.swift -o mediakey
# Clean build artifacts
make clean# Test playback control (requires media playing)
make test
# Manual testing
./mediakey playpause # Should pause/resume media
./mediakey status # Should show current state
./mediakey enable # Should show notificationSee PLUGIN.md for detailed plugin development documentation.
The implementation is a single Swift file with three main components:
postMediaKey(key:)– Creates and posts system-level media key events- State management –
isEnabled(),setEnabled()for automation control - Command parser – Maps CLI arguments to key codes
All dependencies are system frameworks (AppKit, Foundation), ensuring zero external dependencies.
PyObjC: Requires Python runtime + PyObjC package installation
mediakey: Single 55KB binary, no runtime dependencies
AppleScript: Cannot send system-level media key events, limited to app-specific controls
mediakey: Uses system HID event tap, works with any media player
BetterTouchTool/Karabiner: Requires installing and configuring third-party GUI applications
mediakey: Lightweight CLI tool, scriptable, integrates with existing workflows
NPM media key packages: Require Node.js runtime, often use native bindings
mediakey: Pure Swift, no runtime, smaller footprint
Contributions welcome! Please:
- Open an issue for bugs or feature requests
- Submit PRs with clear descriptions and test results
- Test on your system before submitting
- Follow Swift conventions for code style
# Fork and clone
git clone https://github.com/YOUR-USERNAME/claude-mediakey
cd claude-mediakey
# Make changes
vim mediakey.swift
# Test
make build
./mediakey playpause
# Commit
git add .
git commit -m "Add feature X"
git push
# Open PR on GitHubSee PLUGIN.md for detailed documentation on:
- Plugin architecture
- Hook configuration
- Slash command development
- Testing strategies
/plugin uninstall claude-mediakeyThis removes everything automatically:
- Binary
- State file
- Hooks
- Slash commands
No manual cleanup required.
# Remove binary
rm /usr/local/bin/mediakey # If installed to PATH
# or
make clean # If using from project directory
# Remove state file
rm .mediakey_enabledMIT License – See LICENSE for details.
- Based on Stack Overflow discussion on media key emulation
- Built for the Claude Code plugin ecosystem
- Inspired by the need for lightweight, dependency-free developer tools
- GitHub: https://github.com/fntune/claude-mediakey
- Issues: https://github.com/fntune/claude-mediakey/issues
- Claude Code Docs: https://docs.claude.com/en/docs/claude-code
Star ⭐ this repo if you find it useful!