Reaction: Pause media when AirPods detect sleep#547
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
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 newSleepReactionlaunched on the monitor scope.BluetoothAddress, monotonicelapsedRealtime), a primary-device match (prevents a paired pair-in-a-drawer from pausing audio routed to a different pair), andsendPause()'s return value. The last forcedMediaControl.sendPause()to returnBooleaninstead ofUnit— a separateisPlayingcheck would race with the audio system, sosendPause()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.81.26750000750000.6814) is a stable07 00 02 00 00 64 64 00 00regardless 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.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.sendPause()still marks the 15-secondwasRecentlyPausedByCapwindow thatPlayPause.evaluateNormalModeuses 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.