Hold a key to pause whatever's playing on your Mac — release it to resume right where you left off. A Hammerspoon config that hooks the macOS Now Playing system, so it works with Music, Spotify, Podcasts, Safari, Chrome, Arc, YouTube, Netflix, VLC — anything that shows up in Control Center's media widget.
Built as a companion to Wispr Flow, an AI dictation tool you trigger by holding a hotkey (right Control by default). Wispr Flow has a "duck" feature that lowers the volume of other media while you dictate — but lowering the volume isn't enough. You can still miss content in a podcast or video while you're talking, especially if you dictate frequently.
The goal here is for dictation to act as a clean interruption: media pauses the moment you start talking, and resumes the instant you stop. You miss nothing.
Same key, two behaviors, layered:
- 🎤 Wispr Flow sees right Control held → starts listening to your voice
- ⏸️ This script sees right Control held → pauses Now Playing
- 🔓 Release the key → Wispr Flow stops listening, this script resumes playback
The script doesn't replace Wispr Flow's hotkey — it runs alongside it. The keypress passes through to both.
- 🎬 Hold right Control > 100ms while something is playing → pauses it
▶️ Release → resumes- 🔇 Hold while nothing is playing → no-op (won't accidentally start playback)
- 👆 Quick tap (< 100ms) → ignored, no flicker
- 🔄 Right Control still passes through to whatever else is bound to it (Wispr Flow's push-to-talk, etc.)
Shortcuts.app has no key-down/key-up event model — it triggers on chord press, not on hold/release. Can't express "while held" behavior at all.
Karabiner-Elements can detect hold/release, but its model is JSON-driven static rules — so dynamic logic (check if media is playing, remember state, conditionally pause) gets shelled out to separate scripts in a separate directory. Three files across two directories, more surface area to maintain.
Hammerspoon hooks flagsChanged events directly via Lua and keeps press detection, media check, state, and pause/resume logic in one ~45-line file in one place. Easier to share, easier to maintain, easier to extend.
brew install --cask hammerspoon
brew install nowplaying-cli
mkdir -p ~/.hammerspoon
# Save init.lua from this gist to ~/.hammerspoon/init.luaThen:
- 🚀 Launch Hammerspoon (it'll auto-load
~/.hammerspoon/init.lua) - 🔐 Grant both permissions in System Settings → Privacy & Security:
- Accessibility — macOS will prompt for this on first launch
- Input Monitoring — macOS often will NOT prompt for this. You must add Hammerspoon manually. Without it, the eventtap silently fails and nothing happens.
- 🔄 From Hammerspoon's menu bar 🔨 icon → Reload Config
- 🚀 Recommended: 🔨 → Preferences → check Launch Hammerspoon at login
Play something — Spotify, a YouTube tab, anything. Run this to confirm Now Playing sees it:
nowplaying-cli get playbackRate # should print "1"
nowplaying-cli get title # should print the song/video titleThen hold right Control past 100ms. Should pause. Release. Should resume.
In init.lua:
HOLD_THRESHOLD = 0.1— tap-vs-hold threshold in seconds. Lower = more sensitive, higher = harder to trigger accidentally.RIGHT_CONTROL_KEYCODE = 62— change to a different modifier. Runhs.keycodes.mapin the Hammerspoon Console (🔨 → Console) to see all keycodes. Common ones:right_command= 54,right_option= 61,right_shift= 60,fn= 63.
- 🔇 Nothing happens when I hold the key. Almost always Input Monitoring permission. Verify Hammerspoon is checked in System Settings → Privacy & Security → Input Monitoring. Restart Hammerspoon after granting.
- 🤐
nowplaying-cli get playbackRatereturns nothing. The browser/app you're playing from isn't registering with Now Playing. Safari, recent Chrome, and recent Firefox all do. Older Chrome may needchrome://flags/#hardware-media-key-handlingenabled. - 🔁 Hold pauses but release doesn't resume. Likely Hammerspoon was reloaded mid-hold and lost state. Just press and release again.