vol_spotter
A Flutter plugin for detecting physical button presses on Android, iOS, macOS, and Windows.
Detect Volume Up, Volume Down, and Power button events with optional interception (consume the hardware event so the system volume doesn't change).
Platform Support
| Feature | Android | iOS | macOS | Windows |
|---|---|---|---|---|
| Volume Up detection | Yes | Yes | Yes | Yes |
| Volume Down detection | Yes | Yes | Yes | Yes |
| Power button detection | Yes | Yes | Yes | Yes |
| Volume interception (consume event) | Yes | Yes | Yes | Yes |
pressed and released actions |
Yes | pressed only |
Yes | Yes |
| Minimum OS version | API 19 | iOS 13.0 | 10.14 | Windows 10 |
Getting Started
flutter pub add vol_spotter
Usage
Listen for button events
import 'package:vol_spotter/vol_spotter.dart';
final volSpotter = const VolSpotter();
// Start listening (call once)
await volSpotter.startListening();
// React to events
volSpotter.buttonEvents.listen((event) {
if (event.action is! ButtonPressed) return;
switch (event.button) {
case VolumeUpButton():
print('Volume Up pressed');
case VolumeDownButton():
print('Volume Down pressed');
case PowerButton():
print('Power button pressed');
}
});
// Stop when done
await volSpotter.stopListening();
Intercept volume events
When interceptVolumeEvents is true, hardware volume changes are consumed so the system volume stays unchanged and the app receives the raw button press instead.
await volSpotter.startListening(
config: const VolSpotterConfig(interceptVolumeEvents: true),
);
Full counter example
The plugin ships with a counter demo where Volume Up increments, Volume Down decrements, and Power resets. See example/lib/main.dart.
Models
The plugin uses Dart 3 sealed classes for type-safe pattern matching.
| Class | Variants |
|---|---|
PhysicalButton |
VolumeUpButton, VolumeDownButton, PowerButton |
ButtonAction |
ButtonPressed, ButtonReleased |
ButtonEvent |
Combines a PhysicalButton + ButtonAction |
VolSpotterConfig |
interceptVolumeEvents, interceptPowerEvents |
Architecture
vol_spotter follows the federated plugin pattern:
| Package | Purpose |
|---|---|
vol_spotter |
App-facing API |
vol_spotter_platform_interface |
Shared interface and models |
vol_spotter_android |
Android implementation |
vol_spotter_ios |
iOS implementation |
vol_spotter_macos |
macOS implementation |
vol_spotter_windows |
Windows implementation |
vol_spotter_linux |
Linux stub |
vol_spotter_web |
Web stub |
Platform Details
Android
- Volume: intercepts
KEYCODE_VOLUME_UP/KEYCODE_VOLUME_DOWNvia aWindow.Callbackproxy. Emits bothpressedandreleasedactions. - Power: listens for
ACTION_SCREEN_OFFviaBroadcastReceiver. Emitspressedonly. - Requires an active
Activity(works automatically when the Flutter engine is attached).
iOS
- Volume: observes
AVAudioSession.outputVolumevia KVO. A hiddenMPVolumeViewsuppresses the system HUD and resets the slider when interception is enabled. - Power: observes
protectedDataWillBecomeUnavailableNotificationfor screen lock detection. - Only
ButtonPressedis emitted (iOS KVO fires once per press with no separate release event). - Volume boundary detection uses a
1/128offset to keep the volume away from0.0/1.0so subsequent presses are always detected.
macOS
- Volume: uses
CGEventtaps to intercept media key events (NX_KEYTYPE_SOUND_UP/NX_KEYTYPE_SOUND_DOWN). Emits bothpressedandreleasedactions. - Power: observes
NSWorkspace.willSleepNotificationfor sleep/power button detection. - Requires the Accessibility permission for event tap interception (macOS will prompt the user).
Windows
- Volume: uses a low-level keyboard hook (
WH_KEYBOARD_LL) to interceptVK_VOLUME_UP/VK_VOLUME_DOWN. Emits bothpressedandreleasedactions. - Power: listens for
WM_POWERBROADCASTmessages for suspend/power button detection.
Known Limitations
- Power button cannot be consumed on any platform.
interceptPowerEventsis observe-only. - iOS volume interception may cause a brief visual flicker as the hidden slider resets.
- iOS simulator: volume buttons are available under Hardware > Volume Up / Volume Down, but behavior may differ from a physical device.
- macOS: requires Accessibility permission to be granted by the user for event tap to work.
- Linux / Web: stub implementations only.
startListening()throwsUnsupportedError.
Requirements
| Minimum | |
|---|---|
| Flutter | 3.10.0 |
| Dart | 3.0.0 |
| Android | API 19 |
| iOS | 13.0 |
| macOS | 10.14 |
| Windows | 10 |
Credits
Created and maintained by @Crdzbird.
License
This project is licensed under the MIT License. See the LICENSE file for details.