audio-hotplug
Cross-platform audio device hotplug detection with debouncing for Windows, macOS, and Linux.
Features
- 🔌 Cross-platform: Works on Windows, macOS, and Linux
- ⚡ Debouncing: Coalesces rapid device changes to prevent callback storms
- 🔄 Async-ready: Supports both sync and async callbacks
- 🎯 Focused: Does one thing well - detects audio device topology changes
- 🪶 Lightweight: Minimal dependencies, platform-specific imports only when needed
Installation
# Install with uv
uv pip install audio-hotplug
# Or with pip
pip install audio-hotplug
Platform-specific dependencies are installed automatically based on your OS.
Quick Start
import asyncio
from audio_hotplug import create_monitor
def on_audio_devices_changed():
print("Audio devices changed!")
async def main():
loop = asyncio.get_running_loop()
# Create monitor with 200ms debounce
monitor = create_monitor(loop=loop, debounce_ms=200)
if monitor:
monitor.start(on_audio_devices_changed)
# Your application code here...
await asyncio.sleep(60)
monitor.stop()
asyncio.run(main())
Usage
Basic Usage (Sync Callback)
from audio_hotplug import create_monitor
def handle_change():
# Refresh your audio device list here
print("Device list changed!")
monitor = create_monitor()
if monitor:
monitor.start(handle_change)
Async Callback
import asyncio
from audio_hotplug import create_monitor
async def handle_change():
# Async operations supported
await notify_websocket_clients()
print("Device list changed!")
async def main():
loop = asyncio.get_running_loop()
monitor = create_monitor(loop=loop)
if monitor:
monitor.start(handle_change)
await asyncio.sleep(3600)
monitor.stop()
asyncio.run(main())
Custom Debouncing
# Adjust debounce time (milliseconds)
monitor = create_monitor(debounce_ms=500) # Wait 500ms after last change
Custom Logging
import logging
logger = logging.getLogger("my_app.audio")
monitor = create_monitor(logger=logger)
How It Works
The library monitors OS-level audio device notifications:
- Windows: Uses Core Audio API (
IMMNotificationClient) viapycaw - macOS: Uses CoreAudio property listeners via
pyobjc-framework-CoreAudio - Linux: Uses
udevsubsystem monitoring viapyudev
When devices are added, removed, or change state, the library:
- Detects the OS notification
- Triggers the debouncer
- Coalesces multiple rapid changes
- Invokes your callback once after the debounce period
API Reference
create_monitor()
Creates a platform-specific audio device monitor.
Parameters:
loop(optional):asyncio.AbstractEventLoop- Event loop for callback schedulingdebounce_ms(optional):int- Milliseconds to wait before invoking callback (default: 200)logger(optional):logging.Logger- Custom logger instance
Returns:
AudioDeviceMonitorinstance orNoneif platform unsupported
AudioDeviceMonitor
Abstract base class for platform monitors.
Methods:
start(on_change: Callback)- Start monitoring, callon_changewhen devices changestop()- Stop monitoring (safe to call multiple times)
Call stop() before starting an existing monitor again. start() reports native
initialization failures to the caller; Linux and Windows wait up to five seconds
for their worker to initialize. stop() disables pending notifications and waits
up to two seconds for a Linux or Windows worker to exit. A callback already
executing is allowed to finish.
If native cleanup fails or a worker has not exited, the monitor logs a warning
and retains the resources needed for safe cleanup. Call stop() again to retry;
restarting is rejected until the previous registration or worker is gone.
Callback signature:
# Sync callback
def on_change() -> None: ...
# Or async callback
async def on_change() -> None: ...
Platform Support
| Platform | Dependency | Auto-installed |
|---|---|---|
| Windows | pycaw, comtypes |
✅ |
| macOS | pyobjc-framework-CoreAudio |
✅ |
| Linux | pyudev |
✅ |
Platform-specific dependencies are only installed on the relevant OS using package markers.
Development
This project uses uv for development:
# Clone the repository
git clone https://github.com/LedFx/audio-hotplug.git
cd audio-hotplug
# Install with dev dependencies
uv sync --locked
# Run strict type checking for all platform modules
uv run pyrefly check
# Run tests
uv run pytest
# Run example
uv run python examples/monitor_print.py
# Build package
uv build
Testing
# Run all tests
uv run pytest -v
# Run with coverage
uv run pytest --cov=audio_hotplug --cov-report=html
# Run specific test
uv run pytest tests/test_debounce.py
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
MIT License - see LICENSE file for details.
Acknowledgments
Extracted from LedFx to provide a reusable, focused library for audio device hotplug detection.
Related Projects
- LedFx - Real-time LED visualization system
- sounddevice - Audio I/O library
- PortAudio - Cross-platform audio I/O library
Development and releases
Python 3.10 and newer is supported. CI tests Linux, Windows, and macOS on Python 3.10–3.15 (including the current 3.15 prerelease). Native audio hardware notifications still need manual platform testing; the automated suite covers callback scheduling, debouncing, and monitor construction.
Run uv sync --locked, uv run pytest, uv run pyrefly check, and
uvx prek run --all-files.
Pyrefly uses its strict preset, checks Python 3.10 compatibility and every
platform backend, and rejects explicit Any and missing return annotations.
The focused stubs in typings/ describe only the native APIs used here; keep
them aligned with upstream signatures when updating native dependencies. They
are development-only and are not shipped in the wheel.
Development tools live in dependency groups: dev includes test and typing;
build contains the distribution validator. CI installs only the group each
job needs. The platform extras remain available as compatibility aliases,
with OS markers preventing installation of another OS's native packages.
The same lint and strict type checks run before CI tests and distribution builds. Renovate uses the shared LedFx configuration, and autofix.ci applies supported lint fixes. Use a Conventional Commit PR title so release-please can generate the changelog.
Releases use the LedFx automation app (AUTOMATION_APP_CLIENT_ID and
AUTOMATION_APP_PRIVATE_KEY organisation secrets, accessible to this repository).
Merging the release-please PR updates package and lockfile versions and creates
a version tag plus draft GitHub release. Tag CI checks version consistency,
runs the full test matrix, and builds the distributions before publishing to
PyPI, then attaches those distributions and publishes the GitHub draft.
Before the first automated release, configure PyPI Trusted Publishing for
owner LedFx, repository audio-hotplug, workflow publish.yml, and environment
pypi. Keep the GitHub pypi environment approval rules in place. The previous
API-token publishing path is replaced by OIDC; a manual workflow run validates
CI without publishing. Set the branch's required check to CI passed (and
Conventional PR title) instead of the retired individual workflows.
Metadata
Release files for audio-hotplug 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| audio_hotplug-0.2.0.tar.gz | 13.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| audio_hotplug-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.6 kB
Release files / audio_hotplug-0.2.0.tar.gz
| Download URL | audio_hotplug-0.2.0.tar.gz |
|---|---|
| Size | 13.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a16a9d92b8ec3eec08d009f0ac1832b75795d16976911960c4c992cab53459e3
|
|
BLAKE2b-256 checksum How to use checksums |
194bcbdceb48fb252000ddebfd33aa542ee4c5d87e2ca5bd82ea56d094ce78c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / audio_hotplug-0.2.0-py3-none-any.whl
| Download URL | audio_hotplug-0.2.0-py3-none-any.whl |
|---|---|
| Size | 16.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e65129c69305e18a140dd37b3cffc61e0636c6d3c3681731291d55ab421cc35e
|
|
BLAKE2b-256 checksum How to use checksums |
9828d8421c874590ae7452819f010e894cd25e4a6d19a47020df41cfbc45dd9a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency log