Skip to content

Commit

Permalink
support known Spotify Connect devices which are undiscoverable (by ad…
Browse files Browse the repository at this point in the history
…ding their id to config) (#156)

* support known Spotify Connect devices which are undiscoverable (by adding their id to config)

* support default device for known devices
  • Loading branch information
oradsa authored Jul 25, 2021
1 parent 60b5444 commit f2ad5ab
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 102 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ You can specify filters in 2 ways:
- with only pattern, ex `.*rock.*`, then it will filter only on `name` attribute
- with field + pattern, ex `description:.*rock.*`, then it will filter on given field.

##### Known Spotify Connect devices

You can specify a pre-known Spotify Connect devices that are not showing up in the devices list although they are available in the network.
This is useful for smart WiFi speakers that are not connected specifically to your Spotify account and are available visible only inside the home WiFi network, such as Sonos speakers.

To add a device, start a playback on it and then click the add button in the card editor. You can also manually configure devices, if know the device id (see [spotcast guide for finding the device id](https://github.com/fondberg/spotcast#find-spotify-device-id)).

#### YAML mode

If you are not using the visual configuration of Lovelace you can add the card like this:
Expand All @@ -90,6 +97,10 @@ If you are not using the visual configuration of Lovelace you can add the card l
filter_devices: <optional Array> Hides devices which matches any array entry. The entries have to be regular expressions and are matched against the full device-name. The following are examples
- .*room <hides all devices ending with room>
- bath.*,kids.* <hides all devices which start with bath or kids>
known_connect_devices: <optional> List of known Spotify Connect devices, see [Known Spotify Connect devices](Known-Spotify-Connect-devices) section
- id: The Spotify Connect device id
name: The name of the device
entity_id: <optional> The Home Assistant media player entity id of this device (e.g. from Sonos integration)
```
### Screenshots
Expand Down
197 changes: 122 additions & 75 deletions dist/spotify-card.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/spotify-card.js.map

Large diffs are not rendered by default.

107 changes: 103 additions & 4 deletions src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import { HomeAssistant, LovelaceCardEditor, fireEvent } from 'custom-card-helper
import {
SpotifyCardConfig,
ConfigEntry,
CurrentPlayer,
DisplayStyle,
PlaylistType,
ChromecastDevice,
KnownConnectDevice,
ValueChangedEvent,
} from './types';
import { localize } from './localize/localize';
Expand Down Expand Up @@ -110,10 +112,11 @@ export class SpotifyCardEditor extends LitElement implements LovelaceCardEditor
delete clone[target.configValue];
this.config = clone;
} else {
let config_value = target.configValue;
let target_value = target.value;
if (target.configValue == 'height' || target.configValue == 'limit') {
if (config_value == 'height' || config_value == 'limit') {
target_value = Number(target_value);
} else if (target.configValue == 'filter_devices') {
} else if (config_value == 'filter_devices') {
target_value = target_value
.split(',')
.map((value: string) => {
Expand All @@ -122,7 +125,7 @@ export class SpotifyCardEditor extends LitElement implements LovelaceCardEditor
.filter((value: string) => {
return value != '';
});
} else if (target.configValue == 'include_playlists') {
} else if (config_value == 'include_playlists') {
target_value = target_value
.split(',')
.map((value: string) => {
Expand All @@ -131,16 +134,61 @@ export class SpotifyCardEditor extends LitElement implements LovelaceCardEditor
.filter((value: string) => {
return value != '';
});
} else if (config_value.startsWith('known_connect_devices')) {
const targetIndex = config_value.split(':')[1];
const targetProperty = config_value.split(':')[0].split('.')[1];
target_value = (this.config?.known_connect_devices ?? [])
.map((device: KnownConnectDevice, index: number) => {
return index == targetIndex ? {
...device,
[targetProperty]: target_value
} : device;
});
config_value = 'known_connect_devices';
}
this.config = {
...this.config,
[target.configValue]: target.checked !== undefined ? target.checked : target_value,
[config_value]: target.checked !== undefined ? target.checked : target_value,
};
}
}
fireEvent(this, 'config-changed', { config: this.config });
}

private async addKnownConnectDevice(): Promise<void> {
let currentPlayer: CurrentPlayer | undefined = undefined;
try {
currentPlayer = await this.hass.callWS({
type: 'spotcast/player',
account: this.config?.account,
});
} catch (e) {
console.error('Failed to fetch player', e);
}
if (this.config) {
this.config = {
...this.config,
known_connect_devices: (this.config.known_connect_devices ?? []).concat([{
id: currentPlayer?.device?.id ?? '',
name: currentPlayer?.device.name ?? ''
}])
};
fireEvent(this, 'config-changed', { config: this.config });
}
}

private removeKnownConnectDevice(index: number): void {
if (this.config) {
this.config = {
...this.config,
known_connect_devices: (this.config.known_connect_devices ?? []).filter((_, i: number) => {
return i != index;
})
};
fireEvent(this, 'config-changed', { config: this.config });
}
}

public getValue(value: ConfigEntry): any {
switch (value) {
case ConfigEntry.Name:
Expand Down Expand Up @@ -173,6 +221,8 @@ export class SpotifyCardEditor extends LitElement implements LovelaceCardEditor
return this.config?.default_device ?? '';
case ConfigEntry.Filter_Devices:
return this.config?.filter_devices?.toString() ?? '';
case ConfigEntry.Known_Connect_Devices:
return this.config?.known_connect_devices ?? [];
case ConfigEntry.Include_Playlists:
return this.config?.include_playlists?.toString() ?? '';
case ConfigEntry.Hide_Connect_Devices:
Expand Down Expand Up @@ -360,6 +410,7 @@ export class SpotifyCardEditor extends LitElement implements LovelaceCardEditor
}

private renderAdvanced(): TemplateResult {
const media_player_entities = this.getMediaPlayerEntities();
return html`
<div class="values">
<paper-input
Expand Down Expand Up @@ -393,6 +444,50 @@ export class SpotifyCardEditor extends LitElement implements LovelaceCardEditor
</ha-formfield>
</div>
</div>
<div>
<div class="side-by-side">
<p>${localize('settings.known_connect_devices')}</p>
<ha-icon-button
@click=${this.addKnownConnectDevice}
icon="hass:plus"
title=${localize('settings.known_connect_device_add')}>
</ha-icon-button>
</div>
${this.getValue(ConfigEntry.Known_Connect_Devices).map((device: KnownConnectDevice, index: number) => {
return html`<div class="side-by-side">
<paper-input
label=${localize('settings.known_connect_device_id')}
.value=${device.id}
.configValue=${`known_connect_devices.id:${index}`}
@value-changed=${this.valueChanged}
></paper-input>
<paper-input
label=${localize('settings.known_connect_device_name')}
.value=${device.name}
.configValue=${`known_connect_devices.name:${index}`}
@value-changed=${this.valueChanged}
></paper-input>
<paper-dropdown-menu
label=${localize('settings.known_connect_device_entity_id')}
@value-changed=${this.valueChanged}
.configValue=${`known_connect_devices.entity_id:${index}`}
class="dropdown"
>
<paper-listbox
slot="dropdown-content"
.selected=${device.entity_id ? media_player_entities.indexOf(device.entity_id) : undefined}
>
${media_player_entities.map((item) => html` <paper-item>${item}</paper-item> `)}
</paper-listbox>
</paper-dropdown-menu>
<ha-icon-button
@click=${() => this.removeKnownConnectDevice(index)}
icon="hass:close"
title=${localize('settings.known_connect_device_remove')}>
</ha-icon-button>
</div>`;
})}
</div>
`;
}

Expand Down Expand Up @@ -446,11 +541,15 @@ export class SpotifyCardEditor extends LitElement implements LovelaceCardEditor
}
.side-by-side {
display: flex;
align-items: center;
}
.side-by-side > * {
flex: 1;
padding-right: 4px;
}
.side-by-side > ha-icon-button {
flex: 0;
}
.hidden {
display: none;
}
Expand Down
6 changes: 6 additions & 0 deletions src/localize/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
"spotify_entity": "Spotify media player entity",
"default_device": "Default device name",
"filter_devices": "Hide devices (see documentation under 'Advanced usage')",
"known_connect_devices": "Known Spotify Connect devices (see documentation under 'Advanced usage')",
"known_connect_device_id": "Device id",
"known_connect_device_name": "Device name",
"known_connect_device_entity_id": "Media player entity",
"known_connect_device_add": "Add currently playing device",
"known_connect_device_remove": "Remove",
"include_playlists": "Filter playlists to show (see documentation under 'Advanced usage')",
"hide_connect_devices": "Hide all Spotify Connect Devices",
"hide_chromecast_devices": "Hide all Chromecast Devices",
Expand Down
7 changes: 6 additions & 1 deletion src/spotcast-connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,14 @@ export class SpotcastConnector implements ISpotcastConnector {

private startPlaybackOnDevice(device_name: string, uri: string): void {
const connect_device = this.parent.devices.filter((device) => device.name == device_name);
const known_device = this.parent.config.known_connect_devices?.filter((device) => device.name == device_name);
if (connect_device.length > 0) {
return this.playUriOnConnectDevice(connect_device[0].id, uri);
} else {
}
else if (known_device && known_device.length > 0) {
return this.playUriOnConnectDevice(known_device[0].id, uri);
}
else {
const cast_device = this.parent.chromecast_devices.filter((cast) => cast.friendly_name == device_name);
if (cast_device.length > 0) {
return this.playUriOnCastDevice(cast_device[0].friendly_name, uri);
Expand Down
Loading

0 comments on commit f2ad5ab

Please sign in to comment.