Prevent using pulseaudio on event loop (#1536)

* Prevent using pulseaudio on event loop

* Fix name overwrite

* Fix value
This commit is contained in:
Pascal Vizeli 2020-02-27 22:01:20 +01:00 committed by GitHub
parent 8a6ea7ab50
commit 19ca836b78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 122 additions and 103 deletions

View File

@ -29,7 +29,7 @@ from ..const import (
)
from ..coresys import CoreSysAttributes
from ..exceptions import APIError
from ..host.sound import SourceType
from ..host.sound import StreamType
from .utils import api_process, api_process_raw, api_validate
_LOGGER: logging.Logger = logging.getLogger(__name__)
@ -115,7 +115,7 @@ class APIAudio(CoreSysAttributes):
@api_process
async def set_volume(self, request: web.Request) -> None:
"""Set audio volume on stream."""
source: SourceType = SourceType(request.match_info.get("source"))
source: StreamType = StreamType(request.match_info.get("source"))
body = await api_validate(SCHEMA_VOLUME, request)
await asyncio.shield(
@ -125,7 +125,7 @@ class APIAudio(CoreSysAttributes):
@api_process
async def set_default(self, request: web.Request) -> None:
"""Set audio default stream."""
source: SourceType = SourceType(request.match_info.get("source"))
source: StreamType = StreamType(request.match_info.get("source"))
body = await api_validate(SCHEMA_DEFAULT, request)
await asyncio.shield(self.sys_host.sound.set_default(source, body[ATTR_NAME]))

View File

@ -1,4 +1,5 @@
"""Pulse host control."""
from datetime import timedelta
from enum import Enum
import logging
from typing import List
@ -8,13 +9,14 @@ from pulsectl import Pulse, PulseError, PulseIndexError, PulseOperationFailed
from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import PulseAudioError
from ..utils import AsyncThrottle
_LOGGER: logging.Logger = logging.getLogger(__name__)
PULSE_NAME = "supervisor"
class SourceType(str, Enum):
class StreamType(str, Enum):
"""INPUT/OUTPUT type of source."""
INPUT = "input"
@ -74,126 +76,143 @@ class SoundControl(CoreSysAttributes):
"""Return a list of available output streams."""
return self._outputs
async def set_default(self, source: SourceType, name: str) -> None:
async def set_default(self, stream_type: StreamType, name: str) -> None:
"""Set a stream to default input/output."""
try:
with Pulse(PULSE_NAME) as pulse:
if source == SourceType.OUTPUT:
# Get source and set it as default
source = pulse.get_source_by_name(name)
pulse.source_default_set(source)
else:
# Get sink and set it as default
sink = pulse.get_sink_by_name(name)
pulse.sink_default_set(sink)
except PulseIndexError:
_LOGGER.error("Can't find %s profile %s", source, name)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.error("Can't set %s as default: %s", name, err)
raise PulseAudioError() from None
# Reload data
def _set_default():
try:
with Pulse(PULSE_NAME) as pulse:
if stream_type == StreamType.INPUT:
# Get source and set it as default
source = pulse.get_source_by_name(name)
pulse.source_default_set(source)
else:
# Get sink and set it as default
sink = pulse.get_sink_by_name(name)
pulse.sink_default_set(sink)
except PulseIndexError:
_LOGGER.error("Can't find %s profile %s", source, name)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.error("Can't set %s as default: %s", name, err)
raise PulseAudioError() from None
# Run and Reload data
await self.sys_run_in_executor(_set_default)
await self.update()
async def set_volume(self, source: SourceType, name: str, volume: float) -> None:
async def set_volume(
self, stream_type: StreamType, name: str, volume: float
) -> None:
"""Set a stream to volume input/output."""
try:
with Pulse(PULSE_NAME) as pulse:
if source == SourceType.OUTPUT:
# Get source and set it as default
source = pulse.get_source_by_name(name)
else:
# Get sink and set it as default
source = pulse.get_sink_by_name(name)
pulse.volume_set_all_chans(source, volume)
except PulseIndexError:
_LOGGER.error("Can't find %s profile %s", source, name)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.error("Can't set %s volume: %s", name, err)
raise PulseAudioError() from None
def _set_volume():
try:
with Pulse(PULSE_NAME) as pulse:
if stream_type == StreamType.INPUT:
# Get source and set it as default
stream = pulse.get_source_by_name(name)
else:
# Get sink and set it as default
stream = pulse.get_sink_by_name(name)
# Reload data
pulse.volume_set_all_chans(stream, volume)
except PulseIndexError:
_LOGGER.error("Can't find %s profile %s", stream_type, name)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.error("Can't set %s volume: %s", name, err)
raise PulseAudioError() from None
# Run and Reload data
await self.sys_run_in_executor(_set_volume)
await self.update()
async def ativate_profile(self, card_name: str, profile_name: str) -> None:
"""Set a profile to volume input/output."""
try:
with Pulse(PULSE_NAME) as pulse:
card = pulse.get_sink_by_name(card_name)
pulse.card_profile_set(card, profile_name)
except PulseIndexError:
_LOGGER.error("Can't find %s profile %s", card_name, profile_name)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.error(
"Can't activate %s profile %s: %s", card_name, profile_name, err
)
raise PulseAudioError() from None
def _activate_profile():
try:
with Pulse(PULSE_NAME) as pulse:
card = pulse.get_sink_by_name(card_name)
pulse.card_profile_set(card, profile_name)
# Reload data
except PulseIndexError:
_LOGGER.error("Can't find %s profile %s", card_name, profile_name)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.error(
"Can't activate %s profile %s: %s", card_name, profile_name, err
)
raise PulseAudioError() from None
# Run and Reload data
await self.sys_run_in_executor(_activate_profile)
await self.update()
@AsyncThrottle(timedelta(seconds=10))
async def update(self):
"""Update properties over dbus."""
_LOGGER.info("Update PulseAudio information")
try:
with Pulse(PULSE_NAME) as pulse:
server = pulse.server_info()
# Update output
self._outputs.clear()
for sink in pulse.sink_list():
self._outputs.append(
AudioStream(
sink.name,
sink.description,
sink.volume.value_flat,
sink.name == server.default_sink_name,
)
)
def _update():
try:
with Pulse(PULSE_NAME) as pulse:
server = pulse.server_info()
# Update input
self._inputs.clear()
for source in pulse.source_list():
# Filter monitor devices out because we did not use it now
if source.name.endswith(".monitor"):
continue
self._inputs.append(
AudioStream(
source.name,
source.description,
source.volume.value_flat,
source.name == server.default_source_name,
)
)
# Update Sound Card
self._cards.clear()
for card in pulse.card_list():
sound_profiles: List[SoundProfile] = []
# Generate profiles
for profile in card.profile_list:
if not profile.available:
continue
sound_profiles.append(
SoundProfile(
profile.name,
profile.description,
profile.name == card.profile_active.name,
# Update output
self._outputs.clear()
for sink in pulse.sink_list():
self._outputs.append(
AudioStream(
sink.name,
sink.description,
sink.volume.value_flat,
sink.name == server.default_sink_name,
)
)
self._cards.append(
SoundCard(card.name, card.driver, sound_profiles)
)
# Update input
self._inputs.clear()
for source in pulse.source_list():
# Filter monitor devices out because we did not use it now
if source.name.endswith(".monitor"):
continue
self._inputs.append(
AudioStream(
source.name,
source.description,
source.volume.value_flat,
source.name == server.default_source_name,
)
)
except PulseOperationFailed as err:
_LOGGER.error("Error while processing pulse update: %s", err)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.debug("Can't update PulseAudio data: %s", err)
# Update Sound Card
self._cards.clear()
for card in pulse.card_list():
sound_profiles: List[SoundProfile] = []
# Generate profiles
for profile in card.profile_list:
if not profile.available:
continue
sound_profiles.append(
SoundProfile(
profile.name,
profile.description,
profile.name == card.profile_active.name,
)
)
self._cards.append(
SoundCard(card.name, card.driver, sound_profiles)
)
except PulseOperationFailed as err:
_LOGGER.error("Error while processing pulse update: %s", err)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.debug("Can't update PulseAudio data: %s", err)
# Run update from pulse server
await self.sys_run_in_executor(_update)