Expand description
Pause and resume external media around breaks (issue #77).
When the user enables “Pause media while a break is showing”, the
scheduler calls on_break_start as a break overlay opens and
on_break_end when it closes. Notification-only breaks don’t block
the screen and have no defined end, so they intentionally don’t reach
here — only the overlay path (fire_break) does.
Platform behaviour differs by what each OS lets us inspect:
- Linux is precise. We enumerate MPRIS players on the session bus
via the
gdbusCLI (the same dependency-free approach the DnD probe uses), pause only the players currently reportingPlaying, remember them, and resume exactly those when the break ends. - macOS / Windows have no portable way to enumerate players, so we synthesise the system Play/Pause media key — a best-effort toggle. Because the key is a toggle (there’s no separate “pause” key), we only send it when an “is media actually playing?” probe says yes, so we don’t accidentally start media that was paused: a real audio-output probe that tells a paused player apart from one merely holding the audio device open — a CoreAudio process tap on macOS (#233) and a WASAPI endpoint peak meter on Windows (#234). The matching resume sends the same key again.
The testable core is pure and lives at module scope so it compiles and
is unit-tested on every OS, mirroring crate::video: the gdbus output
parsers and the “which players are Playing” decision (Linux), and the
“may the blind toggle fire?” guards (media_key_pause_allowed /
media_key_resume_allowed, macOS/Windows). The guards keep the toggle
from ever starting media the user had paused (#104): pause only when the
platform probe says something is playing, and resume only a toggle we
ourselves sent.
Modules§
- linux 🔒
Enums§
- Playback
Status 🔒 - Playback state from an MPRIS player’s
PlaybackStatusproperty. - Resume
Token 🔒 - Records the action a break-start pause took.
Constants§
- MPRIS_
DBUS_ 🔒DEST - MPRIS_
DBUS_ 🔒PATH - MPRIS_
PLAYER_ 🔒IFACE - MPRIS_
PLAYER_ 🔒PATH - SILENCE_
THRESHOLD 🔒 - A measured output peak above this counts as real audio. Digital silence is ~0.0, so any small positive floor cleanly separates a paused player (no output) from an active one; the margin ignores denormal/dither noise.
Statics§
- ENABLED 🔒
- Mirrors
Settings::pause_media_during_breaks. The scheduler refreshes this each tick so the synchronous overlay path (on_break_start) can read it without locking the async settings mutex. - RESUME 🔒
- What
on_break_startdid, soon_break_endreverses exactly that and never blindly toggles media that was already paused.
Functions§
- is_
audible 🔒 - Pure decision shared by both audio-output probes: is a measured peak
amplitude loud enough to be real playback? One threshold so the macOS tap
(#233) and the Windows peak meter (#234) judge “audible” identically instead
of drifting apart behind two copies. Pure, so it’s unit-tested without FFI
on every OS (like
media_key_pause_allowedjust above). - lock_
resume 🔒 - A poisoned lock only means a previous holder panicked; the media state is best-effort, so recover the guard and carry on rather than panic.
- media_
key_ 🔒pause_ allowed - Decide whether the macOS/Windows blind Play/Pause toggle may fire on break start. The toggle has no separate “pause” key, so sending it when nothing is playing would start media the user had paused (issue #104). We therefore only allow it when the platform’s “is media actually playing?” probe says yes — a real audio-output probe on both macOS (a CoreAudio tap, #233) and Windows (a WASAPI peak meter, #234). Pure so it’s unit-tested without FFI on every OS.
- media_
key_ 🔒resume_ allowed - Decide whether the resume toggle may fire on break end. Only reverse a toggle we actually sent — never blindly hit the media key for a break we did not pause, so we can’t start media the user left paused (#104). Pure so it’s unit-tested without FFI on every OS.
- on_
break_ end - Called as a break overlay closes. Resumes whatever
on_break_startpaused. Deliberately NOT gated onENABLED: if the user toggled the feature off mid-break, we still resume what we paused. - on_
break_ 🔒end_ with - Testable core of
on_break_end: always drains the stored token and hands it to the injected resume action (seeon_break_start_withfor why the action is injected rather than called directly). - on_
break_ start - Called as a break overlay opens. No-op unless the feature is enabled. Performs the (fast, infrequent) platform media-pause inline.
- on_
break_ 🔒start_ with - Testable core of
on_break_start: the enabled-gate and token bookkeeping, with the platform pause action injected. The injection keeps unit tests off the real key-send —platform_pauseposts a genuine system Play/Pause media key on macOS/Windows, which would toggle whatever the developer is playing every time the suite runs. - parse_
mpris_ 🔒names - Parse
gdbus call … org.freedesktop.DBus.ListNamesoutput into the MPRIS player bus names. gdbus prints one GVariant tuple, e.g.([... 'org.mpris.MediaPlayer2.vlc', 'org.freedesktop.DBus', ...],); we collect every single-quoted token with the MPRIS prefix, de-duped. - parse_
playback_ 🔒status - Parse
gdbus call … Properties.Get … PlaybackStatusoutput. gdbus prints the variant-wrapped value, e.g.(<'Playing'>,). - plan_
and_ 🔒pause - List MPRIS players, pause the ones currently
Playing, and return a token naming exactly those (so resume reverses only what we paused). - platform_
pause 🔒 - platform_
resume 🔒 - player_
method 🔒 - players_
to_ 🔒pause - Given each player’s status, the bus names to pause: only those
actively
Playing. Pure, so the pause set is testable without a session bus. - resume_
all 🔒 - Resume the named players (those a prior
plan_and_pausepaused). - set_
enabled - Mirror the current setting into the process-wide flag. Called by the scheduler run loop once per tick.
Type Aliases§
- Dbus
Call 🔒 - A session-bus call:
(dest, object_path, method, args) -> stdout, orNoneon failure. The production impl shells out togdbus; tests pass a fake.