Skip to content

Commit

Permalink
adjustments for hass integration
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelveldt committed Sep 17, 2020
1 parent e45aa9b commit 1595f6a
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 256 deletions.
5 changes: 2 additions & 3 deletions music_assistant/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""All constants for Music Assistant."""

__version__ = "0.0.30"
__version__ = "0.0.31"
REQUIRED_PYTHON_VER = "3.7"

CONF_USERNAME = "username"
Expand All @@ -25,11 +25,10 @@
EVENT_STREAM_STARTED = "streaming started"
EVENT_STREAM_ENDED = "streaming ended"
EVENT_CONFIG_CHANGED = "config changed"
EVENT_PLAYBACK_STARTED = "playback started"
EVENT_PLAYBACK_STOPPED = "playback stopped"
EVENT_MUSIC_SYNC_STATUS = "music sync status"
EVENT_QUEUE_UPDATED = "queue updated"
EVENT_QUEUE_ITEMS_UPDATED = "queue items updated"
EVENT_QUEUE_TIME_UPDATED = "queue time updated"
EVENT_SHUTDOWN = "application shutdown"
EVENT_PROVIDER_REGISTERED = "provider registered"
EVENT_PLAYER_CONTROL_REGISTERED = "player control registered"
Expand Down
11 changes: 7 additions & 4 deletions music_assistant/http_streamer.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ def __get_audio_stream(
yield (True, b"")
return
# fire event that streaming has started for this track
streamdetails.path = "" # invalidate
self.mass.signal_event(EVENT_STREAM_STARTED, streamdetails)
# yield chunks from stdout
# we keep 1 chunk behind to detect end of stream properly
Expand All @@ -462,10 +463,12 @@ def __get_audio_stream(
yield (False, prev_chunk)
prev_chunk = chunk
# fire event that streaming has ended
self.mass.signal_event(EVENT_STREAM_ENDED, streamdetails)
# send task to background to analyse the audio
if queue_item.media_type == MediaType.Track:
self.mass.loop.run_in_executor(None, self.__analyze_audio, streamdetails)
if not cancelled.is_set():
streamdetails.seconds_played = queue_item.duration
self.mass.signal_event(EVENT_STREAM_ENDED, streamdetails)
# send task to background to analyse the audio
if queue_item.media_type == MediaType.Track:
self.mass.add_job(self.__analyze_audio, streamdetails)

def __get_player_sox_options(
self, player_id: str, streamdetails: StreamDetails
Expand Down
3 changes: 1 addition & 2 deletions music_assistant/models/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
class PlayerState(Enum):
"""Enum for the playstate of a player."""

Off = "off"
Stopped = "stopped"
Paused = "paused"
Playing = "playing"
Expand Down Expand Up @@ -45,7 +44,7 @@ class Player(DataClassDictMixin):
name: str = ""
powered: bool = False
elapsed_time: int = 0
state: PlayerState = PlayerState.Off
state: PlayerState = PlayerState.Stopped
available: bool = True
current_uri: str = ""
volume_level: int = 0
Expand Down
47 changes: 18 additions & 29 deletions music_assistant/models/player_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
from typing import List

from music_assistant.constants import (
EVENT_PLAYBACK_STARTED,
EVENT_PLAYBACK_STOPPED,
EVENT_QUEUE_ITEMS_UPDATED,
EVENT_QUEUE_TIME_UPDATED,
EVENT_QUEUE_UPDATED,
)
from music_assistant.models.media_types import Track
Expand Down Expand Up @@ -67,7 +66,6 @@ def __init__(self, mass, player_id):
self._last_item_time = 0
self._last_queue_startindex = 0
self._next_queue_startindex = 0
self._last_player_state = PlayerState.Stopped
self._last_track = None
# load previous queue settings from disk
self.mass.add_job(self.__async_restore_saved_state())
Expand Down Expand Up @@ -237,6 +235,7 @@ async def async_next(self):
return
if self.use_queue_stream:
return await self.async_play_index(self.cur_index + 1)
await self.mass.player_manager.async_cmd_power_on(self.player_id)
return await self.mass.player_manager.get_player_provider(
self.player_id
).async_cmd_next(self.player_id)
Expand All @@ -245,13 +244,15 @@ async def async_previous(self):
"""Play the previous track in the queue."""
if self.cur_index is None:
return
await self.mass.player_manager.async_cmd_power_on(self.player_id)
if self.use_queue_stream:
return await self.async_play_index(self.cur_index - 1)
return await self.mass.player_manager.async_cmd_previous(self.player_id)

async def async_resume(self):
"""Resume previous queue."""
if self.items:
await self.mass.player_manager.async_cmd_power_on(self.player_id)
prev_index = self.cur_index
supports_queue = PlayerFeature.QUEUE in self.player.features
if self.use_queue_stream or not supports_queue:
Expand All @@ -271,6 +272,7 @@ async def async_resume(self):

async def async_play_index(self, index):
"""Play item at index X in queue."""
await self.mass.player_manager.async_cmd_power_on(self.player_id)
player_prov = self.mass.player_manager.get_player_provider(self.player_id)
supports_queue = PlayerFeature.QUEUE in self.player.features
if not isinstance(index, int):
Expand Down Expand Up @@ -329,6 +331,7 @@ async def async_move_item(self, queue_item_id, pos_shift=1):

async def async_load(self, queue_items: List[QueueItem]):
"""Load (overwrite) queue with new items."""
await self.mass.player_manager.async_cmd_power_on(self.player_id)
supports_queue = PlayerFeature.QUEUE in self.player.features
for index, item in enumerate(queue_items):
item.sort_index = index
Expand Down Expand Up @@ -470,7 +473,6 @@ async def async_update_state(self):
break
# process new index
await self.async_process_queue_update(cur_index, track_time)
self.mass.signal_event(EVENT_QUEUE_UPDATED, self.to_dict())

async def async_start_queue_stream(self):
"""Call when queue_streamer starts playing the queue stream."""
Expand Down Expand Up @@ -521,34 +523,21 @@ def __get_queue_stream_index(self):
async def async_process_queue_update(self, new_index, track_time):
"""Compare the queue index to determine if playback changed."""
new_track = self.get_item(new_index)
if (not self._last_track and new_track) or self._last_track != new_track:
self._cur_item_time = track_time
self._cur_index = new_index
if self._last_track != new_track:
# queue track updated
# account for track changing state so trigger track change after 1 second
if self._last_track and self._last_track.streamdetails:
self._last_track.streamdetails.seconds_played = self._last_item_time
self.mass.signal_event(
EVENT_PLAYBACK_STOPPED, self._last_track.streamdetails
)
if new_track and new_track.streamdetails:
self.mass.signal_event(EVENT_PLAYBACK_STARTED, new_track.streamdetails)
self._last_track = new_track
if self._last_player_state != self.player.state:
self._last_player_state = self.player.state
if self.player.elapsed_time == 0 and self.player.state in [
PlayerState.Stopped,
PlayerState.Off,
]:
# player stopped playing
if self._last_track:
self.mass.signal_event(
EVENT_PLAYBACK_STOPPED, self._last_track.streamdetails
)
self._last_track = new_track
self.mass.signal_event(EVENT_QUEUE_UPDATED, self.to_dict())
if self._last_track:
self._last_track.streamdetails = None # invalidate streamdetails
# update vars
if track_time > 2:
# account for track changing state so keep this a few seconds behind
if self._last_item_time != track_time:
self._last_item_time = track_time
self._cur_item_time = track_time
self._cur_index = new_index
self.mass.signal_event(
EVENT_QUEUE_TIME_UPDATED,
{"player_id": self.player_id, "cur_item_time": track_time},
)

@staticmethod
def __shuffle_items(queue_items):
Expand Down
53 changes: 31 additions & 22 deletions music_assistant/music_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
)
from music_assistant.models.musicprovider import MusicProvider
from music_assistant.models.provider import ProviderType
from music_assistant.models.streamdetails import StreamDetails
from music_assistant.models.streamdetails import ContentType, StreamDetails, StreamType
from music_assistant.utils import compare_strings, run_periodic
from PIL import Image

Expand Down Expand Up @@ -1077,29 +1077,38 @@ async def async_get_stream_details(
param media_item: The MediaItem (track/radio) for which to request the streamdetails for.
param player_id: Optionally provide the player_id which will play this stream.
"""
if media_item.streamdetails:
media_item.streamdetails.player_id = player_id
return media_item.streamdetails # already present, no need to fetch again!
# always request the full db track as there might be other qualities available
# except for radio
if media_item.media_type == MediaType.Radio:
full_track = media_item
else:
full_track = await self.async_get_track(
media_item.item_id, media_item.provider, lazy=True, refresh=True
if media_item.provider == "uri":
# special type: a plain uri was added to the queue
streamdetails = StreamDetails(
type=StreamType.URL,
provider="uri",
item_id=media_item.item_id,
path=media_item.item_id,
content_type=ContentType(media_item.item_id.split(".")[-1]),
sample_rate=44100,
bit_depth=16,
)
# sort by quality and check track availability
for prov_media in sorted(
full_track.provider_ids, key=lambda x: x.quality, reverse=True
):
# get streamdetails from provider
music_prov = self.mass.get_provider(prov_media.provider)
if not music_prov:
continue # provider temporary unavailable ?
else:
# always request the full db track as there might be other qualities available
# except for radio
if media_item.media_type == MediaType.Radio:
full_track = media_item
else:
full_track = await self.async_get_track(
media_item.item_id, media_item.provider, lazy=True, refresh=True
)
# sort by quality and check track availability
for prov_media in sorted(
full_track.provider_ids, key=lambda x: x.quality, reverse=True
):
# get streamdetails from provider
music_prov = self.mass.get_provider(prov_media.provider)
if not music_prov:
continue # provider temporary unavailable ?

streamdetails = await music_prov.async_get_stream_details(
prov_media.item_id
)
streamdetails = await music_prov.async_get_stream_details(
prov_media.item_id
)

if streamdetails:
streamdetails.player_id = player_id
Expand Down
36 changes: 23 additions & 13 deletions music_assistant/player_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from music_assistant.models.player_queue import PlayerQueue, QueueItem, QueueOption
from music_assistant.models.playerprovider import PlayerProvider
from music_assistant.models.provider import ProviderType
from music_assistant.models.streamdetails import ContentType, StreamDetails, StreamType
from music_assistant.utils import (
async_iter_items,
callback,
Expand Down Expand Up @@ -272,18 +271,15 @@ async def async_cmd_play_uri(self, player_id: str, uri: str):
queue_item = QueueItem(
Track(
item_id=uri,
provider="",
name="uri",
provider="uri",
name=uri,
)
)
queue_item.streamdetails = StreamDetails(
type=StreamType.URL,
provider="",
item_id=uri,
path=uri,
content_type=ContentType(uri.split(".")[-1]),
sample_rate=44100,
bit_depth=16,
# generate uri for this queue item
queue_item.uri = "%s/stream/%s/%s" % (
self.mass.web.internal_url,
player_id,
queue_item.queue_item_id,
)
# turn on player
await self.async_cmd_power_on(player_id)
Expand Down Expand Up @@ -388,6 +384,22 @@ async def async_cmd_power_off(self, player_id: str) -> None:
for child_player_id in player.group_childs:
if self._players.get(child_player_id):
await self.async_cmd_power_off(child_player_id)
else:
# if this was the last powered player in the group, turn off group
for parent_player_id in player.group_parents:
parent_player = self._players.get(parent_player_id)
if not parent_player:
continue
has_powered_players = False
for child_player_id in parent_player.group_childs:
if child_player_id == player_id:
continue
child_player = self._players.get(child_player_id)
if child_player and child_player.powered:
has_powered_players = True
break
if not has_powered_players:
await self.async_cmd_power_off(parent_player_id)

async def async_cmd_power_toggle(self, player_id: str):
"""
Expand Down Expand Up @@ -591,8 +603,6 @@ def __get_player_volume_level(self, player: Player):
@callback
def __get_player_state(self, player: Player, active_parent: str):
"""Get final/calculated player's state."""
if not player.available or not player.powered:
return PlayerState.Off
if active_parent != player.player_id:
# use group state
return self._players[active_parent].state
Expand Down
2 changes: 0 additions & 2 deletions music_assistant/providers/chromecast/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ def should_poll(self):
@property
def state(self) -> PlayerState:
"""Return the state of the player."""
if not self._powered:
return PlayerState.Off
if self.media_status is None:
return PlayerState.Stopped
if self.media_status.player_is_playing:
Expand Down
7 changes: 6 additions & 1 deletion music_assistant/providers/demo/demo_playerprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,17 @@ async def async_cmd_play_uri(self, player_id: str, uri: str):
player.current_uri = uri
player.sox = subprocess.Popen(["play", uri])
player.state = PlayerState.Playing
player.powered = True
self.mass.add_job(self.mass.player_manager.async_update_player(player))

async def report_progress():
"""Report fake progress while sox is playing."""
player.elapsed_time = 0
while player.sox and not player.sox.poll():
while (
player.state == PlayerState.Playing
and player.sox
and not player.sox.poll()
):
await asyncio.sleep(1)
player.elapsed_time += 1
self.mass.add_job(self.mass.player_manager.async_update_player(player))
Expand Down
Loading

0 comments on commit 1595f6a

Please sign in to comment.