Skip to content

Commit 74ed515

Browse files
committed
feat: add shortcut configuration for media browser
1 parent 3f434f8 commit 74ed515

File tree

5 files changed

+176
-20
lines changed

5 files changed

+176
-20
lines changed

README.md

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ mediaBrowser:
290290
hideHeader: true # default is false. Hides the header of the media browser section (title and navigation buttons).
291291
itemsPerRow: 1 # default is 4. Use this to show items as list. Applies to both favorites and media browser.
292292
onlyFavorites: true # default is false. Hides the media browser button, showing only favorites.
293+
shortcut: # Optional shortcut button for quick access to a specific folder in the media browser. media_content_id, media_content_type, and name are required.
294+
media_content_id: 'media-source://spotify/library/made-for-you' # Required: The content ID of the folder
295+
media_content_type: 'spotify://library' # Required: The content type
296+
icon: 'mdi:spotify' # Optional: Icon for the shortcut button (default is bookmark icon)
297+
name: 'Made for You' # Required: Tooltip/name for the shortcut button
293298
favorites: # Settings specific to the favorites view within media browser
294299
title: My favorites # default is 'Favorites'. Use this to change the title for the favorites view.
295300
customFavorites: # Read more in 'Custom Favorites' section below
@@ -496,29 +501,62 @@ mediaBrowser:
496501

497502
### Finding media_content_id (advanced)
498503

499-
If you want to find the `media_content_id` for a specific radio station or playlist, sometimes the above method is not enough. If so, you can use the following method to find it:
500-
501-
1. Open Media tab
502-
2. Open Chrome Dev Tools
503-
3. Go to Network tab
504-
4. Filter on "WS"
505-
5. Reload page
506-
6. Now you will see a row `websocket`, click on `websocket`
507-
7. Select Messages tab
508-
8. Add filter `play_media`
509-
9. Now navigate to your playlist, and start playing it
510-
10. A line will appear, click on it
511-
11. Expand the JSON object, and look under `service_data`
512-
There you will see something like:
513-
504+
If you want to find the `media_content_id` for a specific radio station, playlist, or media browser folder, sometimes the above method is not enough. If so, you can use the following method to find it:
505+
506+
1. Open your browser's Developer Tools (F12 or right-click → Inspect)
507+
2. Go to the **Network** tab
508+
3. Filter by **WS** (WebSocket)
509+
4. Reload the page
510+
5. Click on the `websocket` connection that appears
511+
6. Go to the **Messages** tab
512+
7. Filter messages by:
513+
- `play_media` - for finding content to play (custom favorites)
514+
- `browse_media` - for finding folder paths (media browser shortcuts)
515+
8. Navigate to your playlist/folder and start playing it or click on it
516+
9. A message will appear - click on it and expand the JSON object
517+
10. Look for `media_content_id` and `media_content_type` in the data
518+
519+
Example for a playable item (custom favorite):
520+
```json
521+
{
522+
"service_data": {
523+
"entity_id": "media_player.living_room",
524+
"media_content_id": "spotify://playlist:1Oz4xMzRKtRiEs51243ZknqGJm",
525+
"media_content_type": "spotify://playlist"
526+
}
527+
}
514528
```
515-
entity_id: "media_player.kok"
516-
media_content_id: "spotify://8fb1de564ba7e4c8c4512361860574c83b9/spotify:playlist:1Oz4xMzRKtRiEs51243ZknqGJm"
517-
media_content_type: "spotify://playlist"
529+
530+
Example for a folder (media browser shortcut):
531+
```json
532+
{
533+
"type": "media_player/browse_media",
534+
"entity_id": "media_player.living_room",
535+
"media_content_id": "spotify://8fb1de564ba7e4c8c4561860574c83b9",
536+
"media_content_type": "spotify://library"
537+
}
518538
```
519539

520540
![media_content_id.png](https://github.com/punxaphil/custom-sonos-card/raw/main/img/media_content_id.png)
521541

542+
## Media Browser Shortcut
543+
544+
You can configure a shortcut button in the media browser header that takes you directly to a specific folder. This is especially useful for wall-mounted touch displays where you want quick access to frequently used folders like Spotify playlists.
545+
546+
### Example configuration
547+
548+
```yaml
549+
type: custom:sonos-card
550+
mediaBrowser:
551+
shortcut:
552+
media_content_id: 'spotify://8fb1de564ba7e4c8c4561860574c83b9'
553+
media_content_type: 'spotify://library'
554+
icon: 'mdi:spotify'
555+
name: 'My Spotify'
556+
```
557+
558+
The shortcut button will appear in the media browser header (both in Favorites and Browse Media views), to the left of the other navigation icons. Clicking it will navigate directly to the specified folder, reducing multiple taps to just one.
559+
522560
## Dynamic volume level slider
523561

524562
The volume level slider is dynamically adjusting its scale. If volume is below 20% it will show a scale up to 30%. Above

src/editor/schema/media-browser-schema.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,29 @@ export const MEDIA_BROWSER_SCHEMA = [
1414
},
1515
];
1616

17+
export const SHORTCUT_SUB_SCHEMA = [
18+
{
19+
name: 'media_content_id',
20+
type: 'string',
21+
help: 'The content ID of the folder (use browser DevTools to find this)',
22+
},
23+
{
24+
name: 'media_content_type',
25+
type: 'string',
26+
help: 'The content type (e.g., spotify://library)',
27+
},
28+
{
29+
name: 'icon',
30+
type: 'string',
31+
help: 'Icon for the button (e.g., mdi:spotify). Default is bookmark icon.',
32+
},
33+
{
34+
name: 'name',
35+
type: 'string',
36+
help: 'Tooltip/name for the shortcut button',
37+
},
38+
];
39+
1740
export const FAVORITES_SUB_SCHEMA = [
1841
{
1942
name: 'title',

src/editor/tabs/media-browser-tab.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { css, html, TemplateResult } from 'lit';
22
import { BaseEditor } from '../base-editor';
3-
import { FAVORITES_SUB_SCHEMA, MEDIA_BROWSER_SCHEMA } from '../schema/media-browser-schema';
3+
import { FAVORITES_SUB_SCHEMA, MEDIA_BROWSER_SCHEMA, SHORTCUT_SUB_SCHEMA } from '../schema/media-browser-schema';
4+
import { MediaBrowserShortcut } from '../../types';
45

56
class MediaBrowserTab extends BaseEditor {
67
protected render(): TemplateResult {
78
const mediaBrowserConfig = this.config.mediaBrowser ?? {};
89
const favoritesConfig = mediaBrowserConfig.favorites ?? {};
10+
const shortcutConfig = mediaBrowserConfig.shortcut ?? {};
911
const exclude = favoritesConfig.exclude ?? [];
1012
const topItems = favoritesConfig.topItems ?? [];
1113

1214
const mediaBrowserData = { ...mediaBrowserConfig };
1315
const favoritesData = { ...favoritesConfig, exclude: exclude.join(', '), topItems: topItems.join(', ') };
16+
const shortcutData = { ...shortcutConfig };
1417

1518
return html`
1619
<sonos-card-editor-form
@@ -21,6 +24,15 @@ class MediaBrowserTab extends BaseEditor {
2124
.changed=${this.mediaBrowserChanged}
2225
></sonos-card-editor-form>
2326
27+
<h3>Shortcut</h3>
28+
<sonos-card-editor-form
29+
.schema=${SHORTCUT_SUB_SCHEMA}
30+
.config=${this.config}
31+
.hass=${this.hass}
32+
.data=${shortcutData}
33+
.changed=${this.shortcutChanged}
34+
></sonos-card-editor-form>
35+
2436
<h3>Favorites</h3>
2537
<sonos-card-editor-form
2638
.schema=${FAVORITES_SUB_SCHEMA}
@@ -53,6 +65,26 @@ class MediaBrowserTab extends BaseEditor {
5365
this.configChanged();
5466
};
5567

68+
private shortcutChanged = (ev: CustomEvent) => {
69+
const changed = ev.detail.value;
70+
const mediaBrowser = this.config.mediaBrowser ?? {};
71+
// Remove empty values to clean up config
72+
const shortcut = Object.fromEntries(
73+
Object.entries({
74+
...(mediaBrowser.shortcut ?? {}),
75+
...changed,
76+
}).filter(([, v]) => v !== '' && v !== undefined),
77+
);
78+
this.config = {
79+
...this.config,
80+
mediaBrowser: {
81+
...mediaBrowser,
82+
shortcut: Object.keys(shortcut).length > 0 ? (shortcut as unknown as MediaBrowserShortcut) : undefined,
83+
},
84+
};
85+
this.configChanged();
86+
};
87+
5688
private favoritesChanged = (ev: CustomEvent) => {
5789
const changed = ev.detail.value;
5890
const mediaBrowser = this.config.mediaBrowser ?? {};

src/sections/media-browser.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { property, state } from 'lit/decorators.js';
33
import {
44
mdiAlphaABoxOutline,
55
mdiArrowLeft,
6+
mdiBookmark,
67
mdiDotsVertical,
78
mdiFolderStar,
89
mdiFolderStarOutline,
@@ -17,7 +18,7 @@ import '../components/favorites-list';
1718
import '../components/favorites-icons';
1819
import { MEDIA_ITEM_SELECTED } from '../constants';
1920
import { customEvent } from '../utils/utils';
20-
import { FavoritesConfig, MediaBrowserConfig, MediaPlayerItem } from '../types';
21+
import { FavoritesConfig, MediaBrowserConfig, MediaBrowserShortcut, MediaPlayerItem } from '../types';
2122
import { until } from 'lit-html/directives/until.js';
2223
import { indexOfWithoutSpecialChars } from '../utils/media-browse-utils';
2324

@@ -53,6 +54,25 @@ export class MediaBrowser extends LitElement {
5354
this.loadLayout();
5455
}
5556

57+
private onShortcutClick = () => {
58+
const shortcut = this.store.config.mediaBrowser?.shortcut;
59+
if (shortcut) {
60+
// Navigate to browser view with the shortcut path
61+
this.view = 'browser';
62+
this.navigateIds = [
63+
{ media_content_id: undefined, media_content_type: undefined },
64+
{
65+
media_content_id: shortcut.media_content_id,
66+
media_content_type: shortcut.media_content_type,
67+
title: shortcut.name,
68+
},
69+
];
70+
this.currentTitle = shortcut.name || '';
71+
this.saveCurrentState();
72+
this.updateIsCurrentPathStart();
73+
}
74+
};
75+
5676
private loadLayout() {
5777
const savedLayout = localStorage.getItem(LAYOUT_KEY) as LayoutType | null;
5878
if (savedLayout && ['auto', 'grid', 'list'].includes(savedLayout)) {
@@ -173,12 +193,15 @@ export class MediaBrowser extends LitElement {
173193
const hideHeader = mediaBrowserConfig.hideHeader ?? false;
174194
const onlyFavorites = mediaBrowserConfig.onlyFavorites ?? false;
175195

196+
const shortcut = mediaBrowserConfig.shortcut;
197+
176198
return html`
177199
${hideHeader
178200
? ''
179201
: html`<div class="header">
180202
<div class="spacer"></div>
181203
<span class="title">${title}</span>
204+
${onlyFavorites ? '' : this.renderShortcutButton(shortcut)}
182205
${onlyFavorites
183206
? ''
184207
: html`<ha-icon-button
@@ -198,6 +221,33 @@ export class MediaBrowser extends LitElement {
198221
`;
199222
}
200223

224+
private renderShortcutButton(shortcut: MediaBrowserShortcut | undefined) {
225+
// Only show if all required properties are provided
226+
if (!shortcut?.media_content_id || !shortcut?.media_content_type || !shortcut?.name) {
227+
return '';
228+
}
229+
const icon = shortcut.icon ?? mdiBookmark;
230+
const isActive = this.isInShortcutFolder(shortcut);
231+
return html`
232+
<ha-icon-button
233+
class=${isActive ? 'shortcut-active' : ''}
234+
@click=${this.onShortcutClick}
235+
title=${shortcut.name}
236+
.path=${icon.startsWith('mdi:') ? undefined : icon}
237+
>
238+
${icon.startsWith('mdi:') ? html`<ha-icon .icon=${icon}></ha-icon>` : ''}
239+
</ha-icon-button>
240+
`;
241+
}
242+
243+
private isInShortcutFolder(shortcut: MediaBrowserShortcut): boolean {
244+
if (this.view !== 'browser' || this.navigateIds.length < 2) {
245+
return false;
246+
}
247+
// Check if any item in the path matches the shortcut
248+
return this.navigateIds.some((nav) => nav.media_content_id === shortcut.media_content_id);
249+
}
250+
201251
private renderLayoutMenu() {
202252
return html`
203253
<ha-button-menu fixed corner="BOTTOM_END" @action=${this.handleMenuAction}>
@@ -312,6 +362,7 @@ export class MediaBrowser extends LitElement {
312362
const canGoBack = this.navigateIds.length > 1;
313363
const hideHeader = mediaBrowserConfig.hideHeader ?? false;
314364
const title = this.currentTitle || 'Media Browser';
365+
const shortcut = mediaBrowserConfig.shortcut;
315366

316367
return html`
317368
${hideHeader
@@ -321,6 +372,7 @@ export class MediaBrowser extends LitElement {
321372
? html`<ha-icon-button .path=${mdiArrowLeft} @click=${this.goBack}></ha-icon-button>`
322373
: html`<div class="spacer"></div>`}
323374
<span class="title">${title}</span>
375+
${this.renderShortcutButton(shortcut)}
324376
<ha-icon-button .path=${mdiStar} @click=${this.goToFavorites} title="Favorites"></ha-icon-button>
325377
<ha-icon-button
326378
class=${this.isCurrentPathStart ? 'startpath-active' : ''}
@@ -404,6 +456,9 @@ export class MediaBrowser extends LitElement {
404456
ha-icon-button.startpath-active {
405457
color: var(--accent-color);
406458
}
459+
ha-icon-button.shortcut-active {
460+
color: var(--accent-color);
461+
}
407462
sonos-ha-media-player-browse,
408463
sonos-favorites-icons,
409464
sonos-favorites-list {

src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,19 @@ export interface FavoritesConfig {
103103
typeMarginBottom?: string;
104104
}
105105

106+
export interface MediaBrowserShortcut {
107+
icon?: string;
108+
name: string;
109+
media_content_id: string;
110+
media_content_type: string;
111+
}
112+
106113
export interface MediaBrowserConfig {
107114
favorites?: FavoritesConfig;
108115
hideHeader?: boolean;
109116
itemsPerRow?: number;
110117
onlyFavorites?: boolean;
118+
shortcut?: MediaBrowserShortcut;
111119
}
112120

113121
export interface GroupsConfig {

0 commit comments

Comments
 (0)