Skip to main content

Asyncio frontend for the pulsectl Python bindings of libpulse

Project description

pulsectl-asyncio

This library provides an Python 3 asyncio interface on top of the pulsectl library for monitoring and controlling the PulseAudio sound server.

pulsectl is a Python ctypes wrapper of the PulseAudio client C library libpulse, providing a high-level interface to PulseAudio's source/sink/stream handling and volume mixing. It has originally been forked from the internal code of the pulsemixer command line application.

Although libpulse provides a callback-based asynchronous C API for the communication with the PulseAudio server, pulsectl only exposes a blocking Python interface, letting libpulse's internal event loop spin until a response is received for each request. In the README file and Issue #11 of pulsectl, different ways of integrating the library into asynchronous Python applications are discussed. However, none of these ways provides seamless integration into Python's asyncio event loop framework.

pulsectl-asyncio uses a ctypes-based Python implementation of the main_loop_api of libpulse to use a Python asyncio event loop for libpulse's asynchronous event handling. With this event handling in place, no blocking calls into libpulse are required, so an asynchronous version for the high-level API of pulsectl can be provided: The PulseAsync class, provided by pulsectl-asyncio, exactly mimics the Pulse class from pulsectl, except that all methods are declared async and asynchronously await the actions' results. Additionally, the API for subscribing to PulseAudio server events has been changed from a callback-based interface (event_callback_set() etc.) to a more asnycio-nic interface using an async generator.

pulsectl-asyncio depends on pulsectl to reuse its ctype wrappers of libpulse as well as the PulseObject classes, which are used for modelling the PulseAudio action result structures as Python objects. The high-level API class PulseAsync has been copied from pulsectl and modified for asynchronous control flow. Thus, its architecture and major parts of its code are still similar to pulsectl's code.

For more info about the API, the returned PulseObject objects and other value types, volume specification, etc., please refer to pulsectl's README file.

Usage Examples

(heavily inspired by pulsectl's README file)

Simple example:

import asyncio
import pulsectl_asyncio

async def main():
    async with pulsectl_asyncio.PulseAsync('volume-increaser') as pulse:
        for sink in await pulse.sink_list():
            await pulse.volume_change_all_chans(sink, 0.1)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Listening for server state change events:

import asyncio
import signal
from contextlib import suppress
import pulsectl_asyncio

# import pulsectl
# print('Event types:', pulsectl.PulseEventTypeEnum)
# print('Event facilities:', pulsectl.PulseEventFacilityEnum)
# print('Event masks:', pulsectl.PulseEventMaskEnum)

async def listen():
    async with pulsectl_asyncio.PulseAsync('event-printer') as pulse:
        async for event in pulse.subscribe_events('all'):
            print('Pulse event:', event)

async def main():
    # Run listen() coroutine in task to allow cancelling it
    listen_task = asyncio.create_task(listen())

    # register signal handlers to cancel listener when program is asked to terminate
    for sig in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT):
        loop.add_signal_handler(sig, listen_task.cancel)
    # Alternatively, the PulseAudio event subscription can be ended by breaking/returning from the `async for` loop

    with suppress(asyncio.CancelledError):
        await listen_task

# Run event loop until main_task finishes
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Misc other tinkering:

import asyncio
import pulsectl_asyncio

async def main():
    pulse = pulsectl_asyncio.PulseAsync('my-client-name')
    await pulse.connect()

    print(await pulse.sink_list())
    # [<PulseSinkInfo at 7f85cfd053d0 - desc='Built-in Audio', index=0L, mute=0, name='alsa-speakers', channels=2, volumes='44.0%, 44.0%'>]
    print(await pulse.sink_input_list())
    # [<PulseSinkInputInfo at 7fa06562d3d0 - index=181L, mute=0, name='mpv Media Player', channels=2, volumes='25.0%, 25.0%'>]

    print((await pulse.sink_input_list())[0].proplist)  # Note the parentheses around `await` and the method call
    # {'application.icon_name': 'mpv',
    #  'application.language': 'C',
    #  'application.name': 'mpv Media Player',
    #  ...
    #  'native-protocol.version': '30',
    #  'window.x11.display': ':1.0'}

    print(await pulse.source_list())
    # [<PulseSourceInfo at 7fcb0615d8d0 - desc='Monitor of Built-in Audio', index=0L, mute=0, name='alsa-speakers.monitor', channels=2, volumes='100.0%, 100.0%'>,
    #  <PulseSourceInfo at 7fcb0615da10 - desc='Built-in Audio', index=1L, mute=0, name='alsa-mic', channels=2, volumes='100.0%, 100.0%'>]

    sink = (await pulse.sink_list())[0]
    await pulse.volume_change_all_chans(sink, -0.1)
    await pulse.volume_set_all_chans(sink, 0.5)

    print((await pulse.server_info()).default_sink_name)
    # 'alsa_output.pci-0000_00_14.2.analog-stereo'
    await pulse.default_set(sink)

    card = (await pulse.card_list())[0]
    print(card.profile_list)
    # [<PulseCardProfileInfo at 7f02e7e88ac8 - description='Analog Stereo Input', n_sinks=0, n_sources=1, name='input:analog-stereo', priority=60>,
    #  <PulseCardProfileInfo at 7f02e7e88b70 - description='Analog Stereo Output', n_sinks=1, n_sources=0, name='output:analog-stereo', priority=6000>,
    #  ...
    #  <PulseCardProfileInfo at 7f02e7e9a4e0 - description='Off', n_sinks=0, n_sources=0, name='off', priority=0>]

    await pulse.card_profile_set(card, 'output:hdmi-stereo')

    pulse.close()  # No await here!

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pulsectl-asyncio-0.1.1.tar.gz (16.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pulsectl_asyncio-0.1.1-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

File details

Details for the file pulsectl-asyncio-0.1.1.tar.gz.

File metadata

  • Download URL: pulsectl-asyncio-0.1.1.tar.gz
  • Upload date:
  • Size: 16.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/53.0.0 requests-toolbelt/0.9.1 tqdm/4.58.0 CPython/3.9.2

File hashes

Hashes for pulsectl-asyncio-0.1.1.tar.gz
Algorithm Hash digest
SHA256 dd7222f5f0e493f1cdebceb93d00a0e7a2b8ee474862fc7952fb37e146cdb579
MD5 71ddaea5adac2939ce5af2898a3d1676
BLAKE2b-256 3c77cb9fac5025532e452d27e1380415c4c2b858bbb3b29560d99b7bf9e98e6a

See more details on using hashes here.

File details

Details for the file pulsectl_asyncio-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: pulsectl_asyncio-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 15.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/53.0.0 requests-toolbelt/0.9.1 tqdm/4.58.0 CPython/3.9.2

File hashes

Hashes for pulsectl_asyncio-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 90393f637c2cdca322469eceb48afd7513ebce523a728cd4917e235cd373a3aa
MD5 3a9086c1087bd8da833f20f940d31fb7
BLAKE2b-256 2462f36721e4b1812a2c03aba272db05dfe0a474d56d8e1d392ee049888b9c5c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page