Skip to main content

audio-hotplug

Cross-platform audio device hotplug detection with debouncing for Windows, macOS, and Linux.

PyPI version Python versions License: MIT

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) via pycaw
  • macOS: Uses CoreAudio property listeners via pyobjc-framework-CoreAudio
  • Linux: Uses udev subsystem monitoring via pyudev

When devices are added, removed, or change state, the library:

  1. Detects the OS notification
  2. Triggers the debouncer
  3. Coalesces multiple rapid changes
  4. 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 scheduling
  • debounce_ms (optional): int - Milliseconds to wait before invoking callback (default: 200)
  • logger (optional): logging.Logger - Custom logger instance

Returns:

  • AudioDeviceMonitor instance or None if platform unsupported

AudioDeviceMonitor

Abstract base class for platform monitors.

Methods:

  • start(on_change: Callback) - Start monitoring, call on_change when devices change
  • stop() - 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:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. 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.

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)

Source distribution for audio-hotplug 0.2.0
File Size Uploaded
audio_hotplug-0.2.0.tar.gz 13.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for audio-hotplug 0.2.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page