Skip to content

Commit 73045e6

Browse files
committed
[ 1.0.27 ] * Updated Devices section to remove device entries that wish to be hidden as specified by SpotifyPlus integration configuration "hide devices" option.
* Added ability to play recently played tracks starting from selected track, which will now automatically add the following tracks (up to 50) to the player queue. Prior to this, only the selected track would play and then play would stop. Note that the 50 track limitation is a Spotify Web API limit. * Added ability to play player queue tracks starting from selected track, which will now automatically add the following tracks (up to 50) to the player queue. Prior to this, only the selected track would play and then play would stop. Note that the 50 track limitation is a Spotify Web API limit.
1 parent 522c04d commit 73045e6

File tree

10 files changed

+197
-60
lines changed

10 files changed

+197
-60
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@ Change are listed in reverse chronological order (newest to oldest).
66

77
<span class="changelog">
88

9+
###### [ 1.0.27 ] - 2024/12/28
10+
11+
* Updated Devices section to remove device entries that wish to be hidden as specified by SpotifyPlus integration configuration "hide devices" option.
12+
* Added ability to play recently played tracks starting from selected track, which will now automatically add the following tracks (up to 50) to the player queue. Prior to this, only the selected track would play and then play would stop. Note that the 50 track limitation is a Spotify Web API limit.
13+
* Added ability to play player queue tracks starting from selected track, which will now automatically add the following tracks (up to 50) to the player queue. Prior to this, only the selected track would play and then play would stop. Note that the 50 track limitation is a Spotify Web API limit.
14+
915
###### [ 1.0.26 ] - 2024/12/24
1016

1117
* Added `playerBackgroundImageSize` config option that specifies the size of the player background image. Defaults to "100% 100%"; More info can be found on the [wiki docs](https://github.com/thlucas1/spotifyplus_card/wiki/Configuration-Options#playerbackgroundimagesize).
1218

1319
###### [ 1.0.25 ] - 2024/12/23
1420

15-
* Ability to play track favorites starting from selected favorite track, which will now automatically add the following tracks (up to 50) to the player queue. Prior to this, only the selected track would play and then play would stop. Note that the 50 track limitation is a Spotify Web API limit.
21+
* Added ability to play track favorites starting from selected favorite track, which will now automatically add the following tracks (up to 50) to the player queue. Prior to this, only the selected track would play and then play would stop. Note that the 50 track limitation is a Spotify Web API limit.
1622

1723
###### [ 1.0.24 ] - 2024/12/22
1824

src/components/player-body-queue.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// debug logging.
2+
import Debug from 'debug/src/browser.js';
3+
import { DEBUG_APP_NAME } from '../constants';
4+
const debuglog = Debug(DEBUG_APP_NAME + ":player-body-queue");
5+
16
// lovelace card imports.
27
import { css, html, TemplateResult } from 'lit';
38
import { state } from 'lit/decorators.js';
@@ -9,14 +14,11 @@ import {
914
import { sharedStylesGrid } from '../styles/shared-styles-grid.js';
1015
import { sharedStylesMediaInfo } from '../styles/shared-styles-media-info.js';
1116
import { sharedStylesFavActions } from '../styles/shared-styles-fav-actions.js';
17+
import { getMediaListTrackUrisRemaining } from '../utils/media-browser-utils.js';
1218
import { PlayerBodyBase } from './player-body-base';
1319
import { MediaPlayer } from '../model/media-player';
14-
import { IPlayerQueueInfo } from '../types/spotifyplus/player-queue-info.js';
15-
16-
// debug logging.
17-
import Debug from 'debug/src/browser.js';
18-
import { DEBUG_APP_NAME } from '../constants';
19-
const debuglog = Debug(DEBUG_APP_NAME + ":player-body-queue");
20+
import { IPlayerQueueInfo } from '../types/spotifyplus/player-queue-info';
21+
import { ITrack } from '../types/spotifyplus/track';
2022

2123
/**
2224
* Track actions.
@@ -214,11 +216,19 @@ export class PlayerBodyQueue extends PlayerBodyBase {
214216

215217
} else if (action == Actions.TrackPlay) {
216218

217-
await this.spotifyPlusService.Card_PlayMediaBrowserItem(this.player, item);
219+
// build track uri list from media list.
220+
const { uris } = getMediaListTrackUrisRemaining(this.queueInfo?.queue as ITrack[], item);
221+
222+
// play media item.
223+
this.spotifyPlusService.PlayerMediaPlayTracks(this.player.id, uris.join(","));
218224
this.progressHide();
219225

220-
}
226+
} else {
221227

228+
// no action selected - hide progress indicator.
229+
this.progressHide();
230+
231+
}
222232
return true;
223233

224234
}

src/components/player-controls.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import Debug from 'debug/src/browser.js';
3434
import { DEBUG_APP_NAME } from '../constants';
3535
const debuglog = Debug(DEBUG_APP_NAME + ":player-controls");
3636

37-
const { NEXT_TRACK, PAUSE, PLAY, PREVIOUS_TRACK, REPEAT_SET, SHUFFLE_SET, TURN_ON } = MediaPlayerEntityFeature;
37+
const { NEXT_TRACK, PAUSE, PLAY, PREVIOUS_TRACK, REPEAT_SET, SHUFFLE_SET, TURN_ON, TURN_OFF } = MediaPlayerEntityFeature;
3838
const ACTION_FAVES = 900000000000;
3939
const PLAY_QUEUE = 990000000000;
4040

@@ -102,6 +102,7 @@ class PlayerControls extends LitElement {
102102
<spc-player-volume hide=${stopped} .store=${this.store} .player=${this.player} class="player-volume-container"></spc-player-volume>
103103
<div class="iconsPower">
104104
<ha-icon-button @click=${() => this.onClickAction(TURN_ON)} hide=${this.hideFeature(TURN_ON)} .path=${mdiPower} label="Turn On" style=${this.styleIcon(colorPower)}></ha-icon-button>
105+
<ha-icon-button @click=${() => this.onClickAction(TURN_OFF)} hide=${this.hideFeature(TURN_OFF)} .path=${mdiPower} label="Turn Off"></ha-icon-button>
105106
</div>
106107
</div>
107108
`;
@@ -458,9 +459,9 @@ class PlayerControls extends LitElement {
458459

459460
await this.mediaControlService.shuffle_set(this.player, !this.player.attributes.shuffle);
460461

461-
//} else if (action == TURN_OFF) {
462+
} else if (action == TURN_OFF) {
462463

463-
// this.mediaControlService.turn_off(this.player);
464+
await this.mediaControlService.turn_off(this.player);
464465

465466
} else if (action == TURN_ON) {
466467

@@ -554,14 +555,15 @@ class PlayerControls extends LitElement {
554555
return true; // hide icon
555556
}
556557

557-
//} else if (feature == TURN_OFF) {
558+
} else if (feature == TURN_OFF) {
558559

559-
// if (this.player.supportsFeature(TURN_OFF)) {
560-
// if (![MediaPlayerState.OFF, MediaPlayerState.UNKNOWN, MediaPlayerState.STANDBY].includes(this.player.state)) {
561-
// return nothing; // show icon
562-
// }
563-
// return true; // hide icon
564-
// }
560+
// this should only be allowed if the player state is IDLE.
561+
if (this.player.supportsFeature(TURN_OFF)) {
562+
if ([MediaPlayerState.IDLE].includes(this.player.state)) {
563+
return (this.config.playerVolumeControlsHidePower) ? true : nothing;
564+
}
565+
return true; // hide icon
566+
}
565567

566568
}
567569

src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { css } from 'lit';
22

33
/** current version of the card. */
4-
export const CARD_VERSION = '1.0.26';
4+
export const CARD_VERSION = '1.0.27';
55

66
/** SpotifyPlus integration domain identifier. */
77
export const DOMAIN_SPOTIFYPLUS = 'spotifyplus';

src/sections/device-browser.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,11 @@ export class DeviceBrowser extends FavBrowserBase {
204204
const refresh = this.refreshDeviceList || false; // refresh device list (defaults to cached list).
205205
const sortResult = true; // true to sort returned items; otherwise, false
206206

207+
// get source items to omit.
208+
const sourceListHide = player.attributes.sp_source_list_hide || [];
209+
207210
// call the service to retrieve the media list.
208-
this.spotifyPlusService.GetSpotifyConnectDevices(player.id, refresh, sortResult)
211+
this.spotifyPlusService.GetSpotifyConnectDevices(player.id, refresh, sortResult, sourceListHide)
209212
.then(result => {
210213

211214
// load media list results.

src/sections/recent-browser.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// debug logging.
2+
import Debug from 'debug/src/browser.js';
3+
import { DEBUG_APP_NAME } from '../constants';
4+
const debuglog = Debug(DEBUG_APP_NAME + ":recent-browser");
5+
16
// lovelace card imports.
27
import { html, TemplateResult } from 'lit';
38
import { customElement } from 'lit/decorators.js';
@@ -9,7 +14,7 @@ import '../components/track-actions';
914
import { FavBrowserBase } from './fav-browser-base';
1015
import { Section } from '../types/section';
1116
import { MediaPlayer } from '../model/media-player';
12-
import { formatTitleInfo } from '../utils/media-browser-utils';
17+
import { formatTitleInfo, getMediaListTrackUrisRemaining } from '../utils/media-browser-utils';
1318
import { getUtcNowTimestamp } from '../utils/utils';
1419
import { GetTracks } from '../types/spotifyplus/track-page-saved';
1520
import { ITrack } from '../types/spotifyplus/track';
@@ -96,6 +101,55 @@ export class RecentBrowser extends FavBrowserBase {
96101
}
97102

98103

104+
105+
106+
/**
107+
* Handles the `item-selected` event fired when a media browser item is clicked.
108+
*
109+
* @param evArgs Event arguments that contain the media item that was clicked on.
110+
*/
111+
protected override onItemSelected(evArgs: CustomEvent) {
112+
113+
if (debuglog.enabled) {
114+
debuglog("onItemSelected - media item selected:\n%s",
115+
JSON.stringify(evArgs.detail, null, 2),
116+
);
117+
}
118+
119+
try {
120+
121+
// show progress indicator.
122+
this.progressShow();
123+
124+
// set media item reference.
125+
const mediaItem = evArgs.detail as ITrack;
126+
127+
// build track uri list from media list.
128+
const { uris } = getMediaListTrackUrisRemaining(this.mediaList || [], mediaItem);
129+
130+
// play media item.
131+
this.spotifyPlusService.PlayerMediaPlayTracks(this.player.id, uris.join(","));
132+
133+
// show player section.
134+
this.store.card.SetSection(Section.PLAYER);
135+
136+
}
137+
catch (error) {
138+
139+
// set error message and reset scroll position to zero so the message is displayed.
140+
this.alertErrorSet("Could not play media item. " + (error as Error).message);
141+
this.mediaBrowserContentElement.scrollTop = 0;
142+
143+
}
144+
finally {
145+
146+
// hide progress indicator.
147+
this.progressHide();
148+
149+
}
150+
}
151+
152+
99153
/**
100154
* Updates the mediaList display.
101155
*/

src/sections/track-fav-browser.ts

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import '../components/track-actions';
1414
import { FavBrowserBase } from './fav-browser-base';
1515
import { Section } from '../types/section';
1616
import { MediaPlayer } from '../model/media-player';
17-
import { formatTitleInfo } from '../utils/media-browser-utils';
17+
import { formatTitleInfo, getMediaListTrackUrisRemaining } from '../utils/media-browser-utils';
1818
import { getUtcNowTimestamp } from '../utils/utils';
1919
import { GetTracks } from '../types/spotifyplus/track-page-saved';
2020
import { ITrack } from '../types/spotifyplus/track';
@@ -116,43 +116,14 @@ export class TrackFavBrowser extends FavBrowserBase {
116116

117117
try {
118118

119+
// show progress indicator.
120+
this.progressShow();
121+
119122
// set media item reference.
120123
const mediaItem = evArgs.detail as ITrack;
121124

122-
// build track uri list from favorites list.
123-
// note that Spotify web api can only play 50 tracks max.
124-
const maxItems = 50;
125-
const uris = new Array<string>();
126-
const names = new Array<string>();
127-
let count = 0;
128-
let startFound = false;
129-
130-
for (const item of (this.mediaList || [])) {
131-
132-
if (item.uri == mediaItem.uri) {
133-
startFound = true;
134-
}
135-
136-
if (startFound) {
137-
uris.push(item.uri);
138-
names.push(item.name);
139-
count += 1;
140-
if (count >= maxItems) {
141-
break;
142-
}
143-
}
144-
145-
}
146-
147-
// trace.
148-
if (debuglog.enabled) {
149-
debuglog("onItemSelected - tracks to play:\n%s",
150-
JSON.stringify(names, null, 2),
151-
);
152-
}
153-
154-
// show progress indicator.
155-
this.progressShow();
125+
// build track uri list from media list.
126+
const { uris } = getMediaListTrackUrisRemaining(this.mediaList || [], mediaItem);
156127

157128
// play media item.
158129
this.spotifyPlusService.PlayerMediaPlayTracks(this.player.id, uris.join(","));
@@ -174,8 +145,6 @@ export class TrackFavBrowser extends FavBrowserBase {
174145
this.progressHide();
175146

176147
}
177-
178-
179148
}
180149

181150

src/services/spotifyplus-service.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2261,12 +2261,14 @@ export class SpotifyPlusService {
22612261
*
22622262
* @param refresh True to return real-time information from the spotify zeroconf api and update the cache; otherwise, False to just return the cached value.
22632263
* @param sort_result True to sort the items by name; otherwise, False to leave the items in the same order they were returned in by the Spotify Zeroconf API. Default is true.
2264+
* @param source_list_hide List of device names to hide from the source list (colon delimited).
22642265
* @returns A SpotifyConnectDevices object.
22652266
*/
22662267
public async GetSpotifyConnectDevices(
22672268
entity_id: string,
22682269
refresh: boolean | null = null,
22692270
sort_result: boolean | null = null,
2271+
source_list_hide: Array<string> | null = null,
22702272
): Promise<ISpotifyConnectDevices> {
22712273

22722274
try {
@@ -2302,8 +2304,18 @@ export class SpotifyPlusService {
23022304
// get the "result" portion of the response, and convert it to a type.
23032305
const responseObj = response["result"] as ISpotifyConnectDevices;
23042306

2305-
// set image_url property based on device type.
2307+
// process all items returned.
23062308
if ((responseObj != null) && (responseObj.Items != null)) {
2309+
2310+
// remove source items that are hidden (based on SpotifyPlus config options);
2311+
// we have to do this in reverse order, due to iteration of the array.
2312+
for (let i = responseObj.Items.length - 1; i >= 0; i--) {
2313+
if (source_list_hide?.includes(responseObj.Items[i].Name.toLowerCase())) {
2314+
responseObj.Items.splice(i, 1);
2315+
}
2316+
}
2317+
2318+
// set image_url property based on device type.
23072319
responseObj.Items.forEach(item => {
23082320
// set image_url path using mdi icons for common sources.
23092321
const sourceCompare = (item.Name || "").toLocaleLowerCase();

src/types/spotifyplus-hass-entity-attributes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ export declare type SpotifyPlusHassEntityAttributes = HassEntityAttributesMediaP
5050
*/
5151
sp_playlist_uri?: string;
5252

53+
/**
54+
* List of device names (in lower-case) to hide from the source list.
55+
*/
56+
sp_source_list_hide?: Array<string>;
57+
5358
/**
5459
* True if the track / episode has explicit content; otherwise, false.
5560
*/

0 commit comments

Comments
 (0)