Skip to content

Commit

Permalink
Merge branch 'master' into parse-spotify-urls
Browse files Browse the repository at this point in the history
  • Loading branch information
fcusson authored Oct 17, 2024
2 parents dac61bd + 31d7a11 commit fcc472b
Show file tree
Hide file tree
Showing 16 changed files with 642 additions and 235 deletions.
4 changes: 2 additions & 2 deletions config/bumpversion.cfg → .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[bumpversion]
current_version = 3.7.1
current_version = 3.8.2
commit = True

[bumpversion:file:Makefile]

[bumpversion:file:setup.cfg]

[bumpversion:manifest.json]
[bumpversion:file:custom_components/spotcast/manifest.json]

[bumpversion:file:custom_components/spotcast/__init__.py]
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,6 @@ venv.bak/

# mypy
.mypy_cache/
.vscode/
.vscode/

config/*
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__VERSION__ = "3.7.1"
__VERSION__ = "3.8.2"

bump:
bump2version --allow-dirty --current-version $(__VERSION__) patch Makefile custom_components/spotcast/manifest.json
Expand Down
11 changes: 2 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,8 @@ To use the Spotcast service with a Spotify Connect device, you need the `spotify
#### With Spotify developer portal

1. Go to [Spotify developer console](https://developer.spotify.com/console/get-users-available-devices/)
2. Click `GET TOKEN` <br/>
![get_token](./images/get_token.png)
3. Select `user-read-playback-state` as a scope<br/>
![select_scope](./images/select_scope.png)
4. If prompt give permission to your Spotify profile
5. For chromecast devices, make sure to play media on the device prior to checking the logs as they will not show unless active
6. Press the option `Try it`
7. Read the result in the console in the right.<br/>
![device_id](./images/device_id.png)
2. Click `TRY IT` <br/>
3. Extract the ID (there could be more than one if you have more than one speaker) from the 'RESPONSE SAMPLE' on the right

#### Through Spotcast log

Expand Down
10 changes: 10 additions & 0 deletions container/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
homeassistant:
container_name: homeassistant
image: ghcr.io/home-assistant/home-assistant:beta
privileged: true
network_mode: host
volumes:
- ../custom_components/spotcast:/config/custom_components/spotcast
- ../config/:/config/

91 changes: 72 additions & 19 deletions custom_components/spotcast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@

from __future__ import annotations

__version__ = "3.7.1"
__version__ = "3.8.2"

import collections
import logging
import time
import homeassistant

import homeassistant.core as ha_core
from homeassistant.components import websocket_api
from homeassistant.const import CONF_ENTITY_ID, CONF_OFFSET, CONF_REPEAT
from homeassistant.core import callback
import homeassistant.core as ha_core

from .const import (
CONF_ACCOUNTS,
Expand All @@ -24,19 +23,27 @@
CONF_SP_DC,
CONF_SP_KEY,
CONF_SPOTIFY_ACCOUNT,
CONF_SPOTIFY_DEVICE_ID,
CONF_SPOTIFY_URI,
CONF_SPOTIFY_SEARCH,
CONF_SPOTIFY_ALBUM_NAME,
CONF_SPOTIFY_ARTIST_NAME,
CONF_SPOTIFY_AUDIOBOOK_NAME,
CONF_SPOTIFY_CATEGORY,
CONF_SPOTIFY_COUNTRY,
CONF_SPOTIFY_DEVICE_ID,
CONF_SPOTIFY_EPISODE_NAME,
CONF_SPOTIFY_GENRE_NAME,
CONF_SPOTIFY_LIMIT,
CONF_SPOTIFY_PLAYLIST_NAME,
CONF_SPOTIFY_SHOW_NAME,
CONF_SPOTIFY_TRACK_NAME,
CONF_SPOTIFY_URI,
CONF_START_VOL,
DOMAIN,
SCHEMA_PLAYLISTS,
SCHEMA_WS_ACCOUNTS,
SCHEMA_WS_CASTDEVICES,
SCHEMA_WS_DEVICES,
SCHEMA_WS_PLAYER,
CONF_START_POSITION,
SERVICE_START_COMMAND_SCHEMA,
SPOTCAST_CONFIG_SCHEMA,
WS_TYPE_SPOTCAST_ACCOUNTS,
Expand All @@ -45,20 +52,19 @@
WS_TYPE_SPOTCAST_PLAYER,
WS_TYPE_SPOTCAST_PLAYLISTS,
)

from .helpers import (
add_tracks_to_queue,
async_wrap,
get_cast_devices,
get_random_playlist_from_category,
get_search_results,
get_spotify_devices,
get_spotify_install_status,
get_spotify_media_player,
is_empty_str,
get_random_playlist_from_category,
get_search_results,
is_valid_uri,
url_to_spotify_uri,
)

from .spotcast_controller import SpotcastController

CONFIG_SCHEMA = SPOTCAST_CONFIG_SCHEMA
Expand Down Expand Up @@ -139,7 +145,7 @@ def get_devices():
me_resp = client._get("me") # pylint: disable=W0212
spotify_media_player = get_spotify_media_player(
hass, me_resp["id"])
resp = get_spotify_devices(spotify_media_player)
resp = get_spotify_devices(spotify_media_player, hass)
connection.send_message(
websocket_api.result_message(msg["id"], resp))

Expand Down Expand Up @@ -202,13 +208,21 @@ def start_casting(call: ha_core.ServiceCall):
category = call.data.get(CONF_SPOTIFY_CATEGORY)
country = call.data.get(CONF_SPOTIFY_COUNTRY)
limit = call.data.get(CONF_SPOTIFY_LIMIT)
search = call.data.get(CONF_SPOTIFY_SEARCH)
artistName = call.data.get(CONF_SPOTIFY_ARTIST_NAME)
albumName = call.data.get(CONF_SPOTIFY_ALBUM_NAME)
playlistName = call.data.get(CONF_SPOTIFY_PLAYLIST_NAME)
trackName = call.data.get(CONF_SPOTIFY_TRACK_NAME)
showName = call.data.get(CONF_SPOTIFY_SHOW_NAME)
episodeName = call.data.get(CONF_SPOTIFY_EPISODE_NAME)
audiobookName = call.data.get(CONF_SPOTIFY_AUDIOBOOK_NAME)
genreName = call.data.get(CONF_SPOTIFY_GENRE_NAME)
random_song = call.data.get(CONF_RANDOM, False)
repeat = call.data.get(CONF_REPEAT, False)
shuffle = call.data.get(CONF_SHUFFLE, False)
start_volume = call.data.get(CONF_START_VOL)
spotify_device_id = call.data.get(CONF_SPOTIFY_DEVICE_ID)
position = call.data.get(CONF_OFFSET)
start_position = call.data.get(CONF_START_POSITION)
force_playback = call.data.get(CONF_FORCE_PLAYBACK)
account = call.data.get(CONF_SPOTIFY_ACCOUNT)
ignore_fully_played = call.data.get(CONF_IGNORE_FULLY_PLAYED)
Expand Down Expand Up @@ -246,18 +260,37 @@ def start_casting(call: ha_core.ServiceCall):
uri = uri.split(":")
uri[0] = uri[0].lower()
uri[1] = uri[1].lower()
uri = ':'.join(uri)
uri = ":".join(uri)

# first, rely on spotify id given in config otherwise get one
if not spotify_device_id:
spotify_device_id = spotcast_controller.get_spotify_device_id(
account, spotify_device_id, device_name, entity_id
)

if start_position is not None:
start_position *= 1000

if (
is_empty_str(uri)
and is_empty_str(search)
and is_empty_str(category)
is_empty_str(uri)
and len(
list(
filter(
lambda x: not is_empty_str(x),
[
artistName,
playlistName,
trackName,
showName,
episodeName,
audiobookName,
genreName,
category,
],
)
)
)
== 0
):
_LOGGER.debug("Transfering playback")
current_playback = client.current_playback()
Expand All @@ -269,7 +302,7 @@ def start_casting(call: ha_core.ServiceCall):
client.transfer_playback(
device_id=spotify_device_id, force_play=force_playback
)
elif category:
elif not is_empty_str(category):
uri = get_random_playlist_from_category(
client, category, country, limit)

Expand All @@ -284,12 +317,28 @@ def start_casting(call: ha_core.ServiceCall):
random_song,
position,
ignore_fully_played,
start_position,
)
else:

searchResults = []
if is_empty_str(uri):
# get uri from search request
uri = get_search_results(search, client, country)
searchResults = get_search_results(
spotify_client=client,
limit=limit,
artistName=artistName,
country=country,
albumName=albumName,
playlistName=playlistName,
trackName=trackName,
showName=showName,
episodeName=episodeName,
audiobookName=audiobookName,
genreName=genreName,
)
# play the first track
if len(searchResults) > 0:
uri = searchResults[0]["uri"]

spotcast_controller.play(
client,
Expand All @@ -298,8 +347,12 @@ def start_casting(call: ha_core.ServiceCall):
random_song,
position,
ignore_fully_played,
start_position,
)

if len(searchResults) > 1:
add_tracks_to_queue(client, searchResults[1:])

if start_volume <= 100:
_LOGGER.debug("Setting volume to %d", start_volume)
time.sleep(2)
Expand Down
23 changes: 5 additions & 18 deletions custom_components/spotcast/cast.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@

import logging

import homeassistant.core as ha_core
from homeassistant.components import spotify as ha_spotify
from homeassistant.components.media_player import BrowseMedia
from homeassistant.components.media_player.const import MEDIA_CLASS_APP
import homeassistant.core as ha_core
from pychromecast import Chromecast

_LOGGER = logging.getLogger(__name__)


async def async_get_media_browser_root_object(
hass: ha_core.HomeAssistant,
cast_type: str
hass: ha_core.HomeAssistant, cast_type: str
) -> list[BrowseMedia]:
"""Create a root object for media browsing."""
try:
Expand All @@ -35,22 +33,15 @@ async def async_browse_media(
cast_type: str,
) -> BrowseMedia | None:
"""Browse media."""
_LOGGER.debug(
"async_browse_media %s, %s",
media_content_type,
media_content_id
)
_LOGGER.debug("async_browse_media %s, %s", media_content_type, media_content_id)
result = None
# Check if this media is handled by Spotify, if it isn't just return None.
if ha_spotify.is_spotify_media_type(media_content_type):
# Browse deeper in the tree
result = await ha_spotify.async_browse_media(
hass, media_content_type, media_content_id, can_play_artist=False
)
_LOGGER.debug(
"async_browse_media return: %s",
result
)
_LOGGER.debug("async_browse_media return: %s", result)
return result


Expand All @@ -62,11 +53,7 @@ async def async_play_media(
media_id: str,
) -> bool:
"""Play media."""
_LOGGER.debug(
"async_browse_media %s, %s",
media_type,
media_id
)
_LOGGER.debug("async_browse_media %s, %s", media_type, media_id)
# If this is a spotify URI, forward to the the spotcast.start service, if not return
# False
if media_id and media_id.startswith("spotify:"):
Expand Down
20 changes: 18 additions & 2 deletions custom_components/spotcast/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@
CONF_SPOTIFY_DEVICE_ID = "spotify_device_id"
CONF_DEVICE_NAME = "device_name"
CONF_SPOTIFY_URI = "uri"
CONF_SPOTIFY_SEARCH = "search"
CONF_SPOTIFY_TRACK_NAME = "track_name"
CONF_SPOTIFY_ARTIST_NAME = "artist_name"
CONF_SPOTIFY_ALBUM_NAME = "album_name"
CONF_SPOTIFY_PLAYLIST_NAME = "playlist_name"
CONF_SPOTIFY_SHOW_NAME = "show_name"
CONF_SPOTIFY_EPISODE_NAME = "episode_name"
CONF_SPOTIFY_AUDIOBOOK_NAME = "audiobook_name"
CONF_SPOTIFY_GENRE_NAME = "genre_name"
CONF_SPOTIFY_CATEGORY = "category"
CONF_SPOTIFY_COUNTRY = "country"
CONF_SPOTIFY_LIMIT = "limit"
Expand All @@ -21,6 +28,7 @@
CONF_FORCE_PLAYBACK = "force_playback"
CONF_RANDOM = "random_song"
CONF_SHUFFLE = "shuffle"
CONF_START_POSITION = "start_position"
CONF_SP_DC = "sp_dc"
CONF_SP_KEY = "sp_key"
CONF_START_VOL = "start_volume"
Expand Down Expand Up @@ -75,7 +83,14 @@
vol.Optional(CONF_SPOTIFY_DEVICE_ID): cv.string,
vol.Optional(CONF_ENTITY_ID): cv.string,
vol.Optional(CONF_SPOTIFY_URI): cv.string,
vol.Optional(CONF_SPOTIFY_SEARCH): cv.string,
vol.Optional(CONF_SPOTIFY_TRACK_NAME): cv.string,
vol.Optional(CONF_SPOTIFY_ALBUM_NAME): cv.string,
vol.Optional(CONF_SPOTIFY_ARTIST_NAME): cv.string,
vol.Optional(CONF_SPOTIFY_PLAYLIST_NAME): cv.string,
vol.Optional(CONF_SPOTIFY_SHOW_NAME): cv.string,
vol.Optional(CONF_SPOTIFY_EPISODE_NAME): cv.string,
vol.Optional(CONF_SPOTIFY_AUDIOBOOK_NAME): cv.string,
vol.Optional(CONF_SPOTIFY_GENRE_NAME): cv.string,
vol.Optional(CONF_SPOTIFY_CATEGORY): cv.string,
vol.Optional(CONF_SPOTIFY_COUNTRY): cv.string,
vol.Optional(CONF_SPOTIFY_LIMIT, default=20): cv.positive_int,
Expand All @@ -85,6 +100,7 @@
vol.Optional(CONF_REPEAT, default="off"): cv.string,
vol.Optional(CONF_SHUFFLE, default=False): cv.boolean,
vol.Optional(CONF_OFFSET, default=0): cv.string,
vol.Optional(CONF_START_POSITION): cv.positive_int,
vol.Optional(CONF_START_VOL, default=101): cv.positive_int,
vol.Optional(CONF_IGNORE_FULLY_PLAYED, default=False): cv.boolean,
}
Expand Down
Loading

0 comments on commit fcc472b

Please sign in to comment.