Skip to content

feat: pause media playback while recording#187

Open
cwillison94 wants to merge 2 commits into
Starmel:masterfrom
cwillison94:feat/pause-media-while-recording
Open

feat: pause media playback while recording#187
cwillison94 wants to merge 2 commits into
Starmel:masterfrom
cwillison94:feat/pause-media-while-recording

Conversation

@cwillison94

@cwillison94 cwillison94 commented Jul 15, 2026

Copy link
Copy Markdown

Why

When using push-to-talk dictation, any music or video already playing keeps going during the recording.
It bleeds into the mic and is distracting, and there is currently no way to have it pause automatically.

This adds an opt-in setting that pauses whatever is playing when a recording starts and resumes it when the recording ends.
It is off by default, so existing behavior is unchanged unless a user turns it on in Settings > Recording.

How it works

Pause and resume use discrete commands, never a play/pause toggle.
This matters because the obvious toggle-based approach (simulating the hardware media key, gated on a CoreAudio "output device is running" check) starts paused audio on record-start: browsers and YouTube keep the output device running even while paused, so the check false-positives and the toggle resumes playback the user had deliberately paused.

Discrete pause/play needs the private MediaRemote framework, which macOS 15.4+ restricts to Apple platform binaries.
An app calling it directly just gets empty/false state back (verified on macOS 26).
So a small helper dylib (MediaRemoteHelper/OSWMediaRemote.swift, ~90 lines, no third-party dependency) is loaded through /usr/bin/perl (a platform binary) via osw-media-remote.pl; it reads the real Now Playing state and sends kMRPlay / kMRPause.
Because the helper runs inside perl's entitled process, it gets accurate state and its commands take effect.
A discrete pause is a no-op when nothing is playing, so recording can never start audio that was not already playing, and play is only ever sent to resume media this feature paused.

It stays player-agnostic (Spotify, Apple Music, Safari/Chrome/YouTube, etc.) with no per-app scripting.
The helper is built by run.sh / notarize_app.sh and embedded in Contents/Frameworks (loaded out-of-process by perl, never linked into the app), with the launcher bundled in Contents/Resources.
It hooks into AudioRecorder, the single choke point for every trigger mode (keyboard shortcut, single modifier, mouse button, hold-to-record, double-tap), with all subprocess work on a dedicated serial queue so it never delays audio capture, guarded by a timeout, and failing safe (doing nothing) if the helper is ever unavailable.

Testing

Built and run on macOS 26 (Apple Silicon).
Verified the helper end to end through perl (reads playing/paused state, sends discrete pause/play, and pausing an already-paused player is a no-op), and verified in-app that recording pauses playback on start and resumes on release when media is playing, while starting a recording with media already paused issues no command and leaves it paused.

Known limitation

If the user manually resumes playback during a recording, stopping the recording will still issue a resume.
Discrete commands make misbehavior far less likely than the old toggle, but true fade in/out is out of scope.

🤖 Generated with Claude Code

Optionally pause whatever audio is playing (music, video, browser) when a
recording starts, and resume it when recording ends, so playback does not
bleed into the dictation. Off by default; toggle in Settings > Recording.

Playback is detected via CoreAudio so it only pauses when something is
actually playing, and pause/resume simulates the media Play/Pause key so it
works with any player without per-app scripting. Wired into AudioRecorder,
the single choke point for every trigger mode (shortcut, modifier, mouse,
hold-to-record, double-tap).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AI-Model: claude-sonnet-4.6
@cwillison94

Copy link
Copy Markdown
Author

It introduces a new setting which is currently defaulting to off (though arguable should be on)

The original approach simulated the hardware Play/Pause media key (a toggle)
and gated it on a CoreAudio "output device is running" check. Browsers and
YouTube keep that device running even while paused, so the check
false-positived and the toggle *started* paused audio the moment a recording
began - the opposite of what the feature should do.

Switch to discrete pause/play. macOS 15.4+ only lets Apple platform binaries
use the private MediaRemote framework, so the app itself gets empty/false state
back. Instead we ship a tiny helper dylib (MediaRemoteHelper/OSWMediaRemote.swift)
that calls MediaRemote's IsPlaying / SendCommand, and load it through
/usr/bin/perl (a platform binary) via osw-media-remote.pl. get reports real
playing/paused state and send issues kMRPlay / kMRPause. A discrete pause is a
no-op when nothing is playing, so recording can never start audio that was
paused; play is only sent to resume media we ourselves paused.

The helper is our own ~90-line Swift (no third-party dependency, no submodule),
built by run.sh/notarize_app.sh and embedded in Contents/Frameworks (loaded
out-of-process by perl, never linked), with the launcher bundled in Resources.
Runtime work is on the controller's own serial queue with a timeout and fails
safe (does nothing) if the helper is missing or errors. Public API and
AudioRecorder call sites are unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AI-Model: claude-sonnet-4.6
@cwillison94
cwillison94 force-pushed the feat/pause-media-while-recording branch from 02c2488 to 313f750 Compare July 17, 2026 23:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants