mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Fix pandora.media_player to not sleep during event loop (#141957)
* Fix pandora.media_player to not sleep during event loop * factor out pianobar spawn * linting cleanup --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
c023f610dd
commit
2fdda91cb8
@ -94,18 +94,22 @@ class PandoraMediaPlayer(MediaPlayerEntity):
|
|||||||
self._attr_media_duration = 0
|
self._attr_media_duration = 0
|
||||||
self._pianobar: pexpect.spawn[str] | None = None
|
self._pianobar: pexpect.spawn[str] | None = None
|
||||||
|
|
||||||
def turn_on(self) -> None:
|
async def _start_pianobar(self) -> bool:
|
||||||
"""Turn the media player on."""
|
pianobar = pexpect.spawn("pianobar", encoding="utf-8")
|
||||||
if self.state != MediaPlayerState.OFF:
|
pianobar.delaybeforesend = None
|
||||||
return
|
# mypy thinks delayafterread must be a float but that is not what pexpect says
|
||||||
self._pianobar = pexpect.spawn("pianobar", encoding="utf-8")
|
# https://github.com/pexpect/pexpect/blob/4.9/pexpect/expect.py#L170
|
||||||
|
pianobar.delayafterread = None # type: ignore[assignment]
|
||||||
|
pianobar.delayafterclose = 0
|
||||||
|
pianobar.delayafterterminate = 0
|
||||||
_LOGGER.debug("Started pianobar subprocess")
|
_LOGGER.debug("Started pianobar subprocess")
|
||||||
mode = self._pianobar.expect(
|
mode = await pianobar.expect(
|
||||||
["Receiving new playlist", "Select station:", "Email:"]
|
["Receiving new playlist", "Select station:", "Email:"],
|
||||||
|
async_=True,
|
||||||
)
|
)
|
||||||
if mode == 1:
|
if mode == 1:
|
||||||
# station list was presented. dismiss it.
|
# station list was presented. dismiss it.
|
||||||
self._pianobar.sendcontrol("m")
|
pianobar.sendcontrol("m")
|
||||||
elif mode == 2:
|
elif mode == 2:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"The pianobar client is not configured to log in. "
|
"The pianobar client is not configured to log in. "
|
||||||
@ -113,16 +117,20 @@ class PandoraMediaPlayer(MediaPlayerEntity):
|
|||||||
"https://www.home-assistant.io/integrations/pandora/"
|
"https://www.home-assistant.io/integrations/pandora/"
|
||||||
)
|
)
|
||||||
# pass through the email/password prompts to quit cleanly
|
# pass through the email/password prompts to quit cleanly
|
||||||
self._pianobar.sendcontrol("m")
|
pianobar.sendcontrol("m")
|
||||||
self._pianobar.sendcontrol("m")
|
pianobar.sendcontrol("m")
|
||||||
self._pianobar.terminate()
|
pianobar.terminate()
|
||||||
self._pianobar = None
|
return False
|
||||||
return
|
self._pianobar = pianobar
|
||||||
self._update_stations()
|
return True
|
||||||
self.update_playing_status()
|
|
||||||
|
|
||||||
self._attr_state = MediaPlayerState.IDLE
|
async def async_turn_on(self) -> None:
|
||||||
self.schedule_update_ha_state()
|
"""Turn the media player on."""
|
||||||
|
if self.state == MediaPlayerState.OFF and await self._start_pianobar():
|
||||||
|
await self._update_stations()
|
||||||
|
await self.update_playing_status()
|
||||||
|
self._attr_state = MediaPlayerState.IDLE
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
def turn_off(self) -> None:
|
def turn_off(self) -> None:
|
||||||
"""Turn the media player off."""
|
"""Turn the media player off."""
|
||||||
@ -142,30 +150,24 @@ class PandoraMediaPlayer(MediaPlayerEntity):
|
|||||||
self._attr_state = MediaPlayerState.OFF
|
self._attr_state = MediaPlayerState.OFF
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
def media_play(self) -> None:
|
async def async_media_play(self) -> None:
|
||||||
"""Send play command."""
|
"""Send play command."""
|
||||||
self._send_pianobar_command(SERVICE_MEDIA_PLAY_PAUSE)
|
await self._send_pianobar_command(SERVICE_MEDIA_PLAY_PAUSE)
|
||||||
self._attr_state = MediaPlayerState.PLAYING
|
self._attr_state = MediaPlayerState.PLAYING
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
def media_pause(self) -> None:
|
async def async_media_pause(self) -> None:
|
||||||
"""Send pause command."""
|
"""Send pause command."""
|
||||||
self._send_pianobar_command(SERVICE_MEDIA_PLAY_PAUSE)
|
await self._send_pianobar_command(SERVICE_MEDIA_PLAY_PAUSE)
|
||||||
self._attr_state = MediaPlayerState.PAUSED
|
self._attr_state = MediaPlayerState.PAUSED
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
def media_next_track(self) -> None:
|
async def async_media_next_track(self) -> None:
|
||||||
"""Go to next track."""
|
"""Go to next track."""
|
||||||
self._send_pianobar_command(SERVICE_MEDIA_NEXT_TRACK)
|
await self._send_pianobar_command(SERVICE_MEDIA_NEXT_TRACK)
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
@property
|
async def async_select_source(self, source: str) -> None:
|
||||||
def media_title(self) -> str | None:
|
|
||||||
"""Title of current playing media."""
|
|
||||||
self.update_playing_status()
|
|
||||||
return self._attr_media_title
|
|
||||||
|
|
||||||
def select_source(self, source: str) -> None:
|
|
||||||
"""Choose a different Pandora station and play it."""
|
"""Choose a different Pandora station and play it."""
|
||||||
if self.source_list is None:
|
if self.source_list is None:
|
||||||
return
|
return
|
||||||
@ -176,45 +178,46 @@ class PandoraMediaPlayer(MediaPlayerEntity):
|
|||||||
return
|
return
|
||||||
_LOGGER.debug("Setting station %s, %d", source, station_index)
|
_LOGGER.debug("Setting station %s, %d", source, station_index)
|
||||||
assert self._pianobar is not None
|
assert self._pianobar is not None
|
||||||
self._send_station_list_command()
|
await self._send_station_list_command()
|
||||||
self._pianobar.sendline(f"{station_index}")
|
self._pianobar.sendline(f"{station_index}")
|
||||||
self._pianobar.expect("\r\n")
|
await self._pianobar.expect("\r\n", async_=True)
|
||||||
self._attr_state = MediaPlayerState.PLAYING
|
self._attr_state = MediaPlayerState.PLAYING
|
||||||
|
|
||||||
def _send_station_list_command(self) -> None:
|
async def _send_station_list_command(self) -> None:
|
||||||
"""Send a station list command."""
|
"""Send a station list command."""
|
||||||
assert self._pianobar is not None
|
assert self._pianobar is not None
|
||||||
self._pianobar.send("s")
|
self._pianobar.send("s")
|
||||||
try:
|
try:
|
||||||
self._pianobar.expect("Select station:", timeout=1)
|
await self._pianobar.expect("Select station:", async_=True, timeout=1)
|
||||||
except pexpect.exceptions.TIMEOUT:
|
except pexpect.exceptions.TIMEOUT:
|
||||||
# try again. Buffer was contaminated.
|
# try again. Buffer was contaminated.
|
||||||
self._clear_buffer()
|
await self._clear_buffer()
|
||||||
self._pianobar.send("s")
|
self._pianobar.send("s")
|
||||||
self._pianobar.expect("Select station:")
|
await self._pianobar.expect("Select station:", async_=True)
|
||||||
|
|
||||||
def update_playing_status(self) -> None:
|
async def update_playing_status(self) -> None:
|
||||||
"""Query pianobar for info about current media_title, station."""
|
"""Query pianobar for info about current media_title, station."""
|
||||||
response = self._query_for_playing_status()
|
response = await self._query_for_playing_status()
|
||||||
if not response:
|
if not response:
|
||||||
return
|
return
|
||||||
self._update_current_station(response)
|
self._update_current_station(response)
|
||||||
self._update_current_song(response)
|
self._update_current_song(response)
|
||||||
self._update_song_position()
|
self._update_song_position()
|
||||||
|
|
||||||
def _query_for_playing_status(self) -> str | None:
|
async def _query_for_playing_status(self) -> str | None:
|
||||||
"""Query system for info about current track."""
|
"""Query system for info about current track."""
|
||||||
assert self._pianobar is not None
|
assert self._pianobar is not None
|
||||||
self._clear_buffer()
|
await self._clear_buffer()
|
||||||
self._pianobar.send("i")
|
self._pianobar.send("i")
|
||||||
try:
|
try:
|
||||||
match_idx = self._pianobar.expect(
|
match_idx = await self._pianobar.expect(
|
||||||
[
|
[
|
||||||
r"(\d\d):(\d\d)/(\d\d):(\d\d)",
|
r"(\d\d):(\d\d)/(\d\d):(\d\d)",
|
||||||
"No song playing",
|
"No song playing",
|
||||||
"Select station",
|
"Select station",
|
||||||
"Receiving new playlist",
|
"Receiving new playlist",
|
||||||
]
|
],
|
||||||
|
async_=True,
|
||||||
)
|
)
|
||||||
except pexpect.exceptions.EOF:
|
except pexpect.exceptions.EOF:
|
||||||
_LOGGER.warning("Pianobar process already exited")
|
_LOGGER.warning("Pianobar process already exited")
|
||||||
@ -229,11 +232,11 @@ class PandoraMediaPlayer(MediaPlayerEntity):
|
|||||||
_LOGGER.warning("On unexpected station list page")
|
_LOGGER.warning("On unexpected station list page")
|
||||||
self._pianobar.sendcontrol("m") # press enter
|
self._pianobar.sendcontrol("m") # press enter
|
||||||
self._pianobar.sendcontrol("m") # do it again b/c an 'i' got in
|
self._pianobar.sendcontrol("m") # do it again b/c an 'i' got in
|
||||||
self.update_playing_status()
|
await self.update_playing_status()
|
||||||
return None
|
return None
|
||||||
if match_idx == 3:
|
if match_idx == 3:
|
||||||
_LOGGER.debug("Received new playlist list")
|
_LOGGER.debug("Received new playlist list")
|
||||||
self.update_playing_status()
|
await self.update_playing_status()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self._pianobar.before
|
return self._pianobar.before
|
||||||
@ -292,7 +295,7 @@ class PandoraMediaPlayer(MediaPlayerEntity):
|
|||||||
repr(self._pianobar.after),
|
repr(self._pianobar.after),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _send_pianobar_command(self, service_cmd: str) -> None:
|
async def _send_pianobar_command(self, service_cmd: str) -> None:
|
||||||
"""Send a command to Pianobar."""
|
"""Send a command to Pianobar."""
|
||||||
assert self._pianobar is not None
|
assert self._pianobar is not None
|
||||||
command = CMD_MAP.get(service_cmd)
|
command = CMD_MAP.get(service_cmd)
|
||||||
@ -300,13 +303,13 @@ class PandoraMediaPlayer(MediaPlayerEntity):
|
|||||||
if command is None:
|
if command is None:
|
||||||
_LOGGER.warning("Command %s not supported yet", service_cmd)
|
_LOGGER.warning("Command %s not supported yet", service_cmd)
|
||||||
return
|
return
|
||||||
self._clear_buffer()
|
await self._clear_buffer()
|
||||||
self._pianobar.sendline(command)
|
self._pianobar.sendline(command)
|
||||||
|
|
||||||
def _update_stations(self) -> None:
|
async def _update_stations(self) -> None:
|
||||||
"""List defined Pandora stations."""
|
"""List defined Pandora stations."""
|
||||||
assert self._pianobar is not None
|
assert self._pianobar is not None
|
||||||
self._send_station_list_command()
|
await self._send_station_list_command()
|
||||||
station_lines = self._pianobar.before or ""
|
station_lines = self._pianobar.before or ""
|
||||||
_LOGGER.debug("Getting stations: %s", station_lines)
|
_LOGGER.debug("Getting stations: %s", station_lines)
|
||||||
self._attr_source_list = []
|
self._attr_source_list = []
|
||||||
@ -320,7 +323,7 @@ class PandoraMediaPlayer(MediaPlayerEntity):
|
|||||||
self._pianobar.sendcontrol("m") # press enter with blank line
|
self._pianobar.sendcontrol("m") # press enter with blank line
|
||||||
self._pianobar.sendcontrol("m") # do it twice in case an 'i' got in
|
self._pianobar.sendcontrol("m") # do it twice in case an 'i' got in
|
||||||
|
|
||||||
def _clear_buffer(self) -> None:
|
async def _clear_buffer(self) -> None:
|
||||||
"""Clear buffer from pexpect.
|
"""Clear buffer from pexpect.
|
||||||
|
|
||||||
This is necessary because there are a bunch of 00:00 in the buffer
|
This is necessary because there are a bunch of 00:00 in the buffer
|
||||||
@ -328,7 +331,7 @@ class PandoraMediaPlayer(MediaPlayerEntity):
|
|||||||
"""
|
"""
|
||||||
assert self._pianobar is not None
|
assert self._pianobar is not None
|
||||||
try:
|
try:
|
||||||
while not self._pianobar.expect(".+", timeout=0.1):
|
while not await self._pianobar.expect(".+", async_=True, timeout=0.1):
|
||||||
pass
|
pass
|
||||||
except pexpect.exceptions.TIMEOUT:
|
except pexpect.exceptions.TIMEOUT:
|
||||||
pass
|
pass
|
||||||
|
Loading…
x
Reference in New Issue
Block a user