Skip to content

Reaction: Pause media when AirPods detect sleep#547

Merged
d4rken merged 2 commits into
mainfrom
feat/sleep-reaction
Apr 25, 2026
Merged

Reaction: Pause media when AirPods detect sleep#547
d4rken merged 2 commits into
mainfrom
feat/sleep-reaction

Conversation

@d4rken

@d4rken d4rken commented Apr 24, 2026

Copy link
Copy Markdown
Member

What changed

Adds a new Sleep Detection reaction for Pro users. When compatible AirPods firmware detects the wearer has been still for a while, CAPod pauses whatever is currently playing and posts a transient notification naming the device. The Sleep Detection toggle already existed but previously did nothing in the app — enabling it now requires Pro, matching the existing Auto-Pause / Auto-Play reactions. Disabling stays free so a lapsed subscriber can still turn it off.

Technical Context

  • The AAP 0x57 "Sleep Detection Update" opcode was already being decoded at the engine layer but the event went nowhere. New wiring mirrors the stem-press path: engine SharedFlow → connection pass-through → address-only fan-out in the session manager → a new SleepReaction launched on the monitor scope.
  • The reaction gates with three filters in order: a 5-minute per-device cooldown (keyed by BluetoothAddress, monotonic elapsedRealtime), a primary-device match (prevents a paired pair-in-a-drawer from pausing audio routed to a different pair), and sendPause()'s return value. The last forced MediaControl.sendPause() to return Boolean instead of Unit — a separate isPlaying check would race with the audio system, so sendPause() is now the atomic check+act. The cooldown is only burned and the notification only shown when a pause actually dispatched; no-op calls log at DEBUG and leave the cooldown untouched, so the next genuine event still fires.
  • Observed payload across multiple cupping sessions on Pro 2 USB-C (firmware 81.26750000750000.6814) is a stable 07 00 02 00 00 64 64 00 00 regardless of ear-detection state. Treated as opaque — the cooldown absorbs repeats. If future firmware encodes sub-state in the payload, the degradation is "over-pause slightly with user-visible feedback" rather than silent misbehavior.
  • Pro-gating is at toggle-time, not trigger-time (matching setAutoPause): non-pro users tapping the toggle hit the upgrade screen. No pro check lives inside the reaction itself — if the reaction fires, the user is pro by construction.
  • Known deferred coupling: sendPause() still marks the 15-second wasRecentlyPausedByCap window that PlayPause.evaluateNormalMode uses to auto-resume when pods return to ears. For the sleep case this means a groggy user who wakes, removes pods, and reinserts within 15s will auto-resume music. That's likely desired on balance; a non-window-setting pause variant can be added later if it proves noisy in practice.

@d4rken d4rken added enhancement Add a new feature of improve an existing feature coms/AAP Uses Apples AirPod Protocol. Requires Android ROM with fixed L2CAP support on the Bluetooth sockets. labels Apr 24, 2026
@d4rken d4rken merged commit c2ff1f1 into main Apr 25, 2026
10 checks passed
@d4rken d4rken deleted the feat/sleep-reaction branch April 25, 2026 08:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

coms/AAP Uses Apples AirPod Protocol. Requires Android ROM with fixed L2CAP support on the Bluetooth sockets. enhancement Add a new feature of improve an existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant