Skip to content

Commit 11cad8c

Browse files
committed
fix: updates due to breaking changes in home assistant 2025.10
- Browse media replaced with more info dialog (since they removed the browse media possibility in Home assistant core home-assistant/frontend#26904) - Instead of reusing components from Home assistant, they had to be implemented again. Prev, play, next, pause, stop, power, shuffle, repeat, source.
1 parent 9baf221 commit 11cad8c

19 files changed

+241
-90
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ card_mod:
697697
.icons {
698698
margin-bottom: 1rem;
699699
}
700-
sonos-ha-player,ha-icon-button {
700+
.icons * {
701701
--mdc-icon-size: 3rem !important;
702702
--mdc-icon-button-size: 4rem !important;
703703
}

src/card.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import Store from './model/store';
66
import { CardConfig, Section } from './types';
77
import './components/footer';
88
import './editor/editor';
9+
import './components/shuffle';
10+
import './components/repeat';
11+
import './components/source';
912
import { ACTIVE_PLAYER_EVENT, CALL_MEDIA_DONE, CALL_MEDIA_STARTED } from './constants';
1013
import { when } from 'lit/directives/when.js';
1114
import { styleMap } from 'lit-html/directives/style-map.js';

src/components/ha-player.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/components/media-browser-header.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import { css, html, LitElement, nothing } from 'lit';
22
import { property } from 'lit/decorators.js';
3-
import { MediaPlayerEntityFeature } from '../types';
43
import Store from '../model/store';
4+
import { mdiPlayBoxMultiple } from '@mdi/js';
55

66
class MediaBrowserHeader extends LitElement {
77
@property({ attribute: false }) store!: Store;
88

99
render() {
1010
return html`
1111
<div class="title">${this.store.config.mediaBrowserTitle ?? 'All Favorites'}</div>
12-
<sonos-ha-player
12+
<ha-icon-button
1313
hide=${this.store.config.hideBrowseMediaButton || nothing}
14-
.store=${this.store}
15-
.features=${[MediaPlayerEntityFeature.BROWSE_MEDIA]}
16-
></sonos-ha-player>
14+
@click=${() => this.store.mediaBrowseService.showBrowseMedia(this.store.activePlayer, this)}
15+
.path=${mdiPlayBoxMultiple}
16+
></ha-icon-button>
1717
`;
1818
}
1919

src/components/player-controls.ts

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,88 @@ import { css, html, LitElement, nothing } from 'lit';
22
import { property } from 'lit/decorators.js';
33
import MediaControlService from '../services/media-control-service';
44
import Store from '../model/store';
5-
import { CardConfig, MediaPlayerEntityFeature } from '../types';
6-
import { mdiFastForward, mdiRewind, mdiVolumeMinus, mdiVolumePlus } from '@mdi/js';
5+
import { CardConfig } from '../types';
6+
import {
7+
mdiFastForward,
8+
mdiPauseCircle,
9+
mdiPlayBoxMultiple,
10+
mdiPlayCircle,
11+
mdiRewind,
12+
mdiSkipNext,
13+
mdiSkipPrevious,
14+
mdiStopCircle,
15+
mdiVolumeMinus,
16+
mdiVolumePlus,
17+
} from '@mdi/js';
718
import { MediaPlayer } from '../model/media-player';
819
import { until } from 'lit-html/directives/until.js';
920
import { findPlayer } from '../utils/utils';
10-
11-
const { SHUFFLE_SET, REPEAT_SET, PLAY, PAUSE, NEXT_TRACK, PREVIOUS_TRACK, BROWSE_MEDIA, STOP } =
12-
MediaPlayerEntityFeature;
21+
import MediaBrowseService from '../services/media-browse-service';
1322

1423
class PlayerControls extends LitElement {
1524
@property({ attribute: false }) store!: Store;
1625
private config!: CardConfig;
1726
private activePlayer!: MediaPlayer;
1827
private mediaControlService!: MediaControlService;
28+
private mediaBrowseService!: MediaBrowseService;
1929
private volumePlayer!: MediaPlayer;
2030
private updateMemberVolumes!: boolean;
2131

2232
render() {
2333
this.config = this.store.config;
2434
this.activePlayer = this.store.activePlayer;
2535
this.mediaControlService = this.store.mediaControlService;
36+
this.mediaBrowseService = this.store.mediaBrowseService;
2637
const noUpDown = !!this.config.showVolumeUpAndDownButtons && nothing;
2738
const noFastForwardAndRewind = !!this.config.showFastForwardAndRewindButtons && nothing;
39+
const noShuffle = !this.config.hidePlayerControlShuffleButton && nothing;
40+
const noPrev = !this.config.hidePlayerControlPrevTrackButton && nothing;
41+
const noNext = !this.config.hidePlayerControlNextTrackButton && nothing;
42+
const noRepeat = !this.config.hidePlayerControlRepeatButton && nothing;
43+
const noBrowse = !!this.config.showBrowseMediaInPlayerSection && nothing;
44+
2845
this.volumePlayer = this.getVolumePlayer();
2946
this.updateMemberVolumes = !this.config.playerVolumeEntityId;
30-
const pauseOrStop = this.config.stopInsteadOfPause ? STOP : PAUSE;
47+
const pauseOrStop = this.config.stopInsteadOfPause ? mdiStopCircle : mdiPauseCircle;
48+
const playing = this.activePlayer.isPlaying();
3149
return html`
3250
<div class="main" id="mediaControls">
3351
<div class="icons">
3452
<div class="flex-1"></div>
3553
<ha-icon-button hide=${noUpDown} @click=${this.volDown} .path=${mdiVolumeMinus}></ha-icon-button>
36-
<sonos-ha-player .store=${this.store} .features=${this.showShuffle()}></sonos-ha-player>
37-
<sonos-ha-player .store=${this.store} .features=${this.showPrev()}></sonos-ha-player>
54+
<sonos-shuffle hide=${noShuffle} .store=${this.store}></sonos-shuffle>
55+
<ha-icon-button hide=${noPrev} @click=${this.prev} .path=${mdiSkipPrevious}></ha-icon-button>
3856
<ha-icon-button hide=${noFastForwardAndRewind} @click=${this.rewind} .path=${mdiRewind}></ha-icon-button>
39-
<sonos-ha-player .store=${this.store} .features=${[PLAY, pauseOrStop]} class="big-icon"></sonos-ha-player>
57+
<ha-icon-button @click=${playing ? this.pauseOrStop : this.play} .path=${playing ? pauseOrStop : mdiPlayCircle} class="big-icon"></ha-icon-button>
4058
<ha-icon-button hide=${noFastForwardAndRewind} @click=${this.fastForward} .path=${mdiFastForward}></ha-icon-button>
41-
<sonos-ha-player .store=${this.store} .features=${this.showNext()}></sonos-ha-player>
42-
<sonos-ha-player .store=${this.store} .features=${this.showRepeat()}></sonos-ha-player>
59+
<ha-icon-button hide=${noNext} @click=${this.next} .path=${mdiSkipNext}></ha-icon-button>
60+
<sonos-repeat hide=${noRepeat} .store=${this.store} ></sonos-repeat>
61+
4362
<ha-icon-button hide=${noUpDown} @click=${this.volUp} .path=${mdiVolumePlus}></ha-icon-button>
4463
<div class="audio-input-format">
4564
${this.config.showAudioInputFormat && until(this.getAudioInputFormat())}
4665
</div>
47-
<sonos-ha-player .store=${this.store} .features=${this.showBrowseMedia()}></sonos-ha-player>
66+
<ha-icon-button hide=${noBrowse} @click=${this.browseMedia} .path=${mdiPlayBoxMultiple}></ha-icon-button>
4867
</div>
4968
<sonos-volume .store=${this.store} .player=${this.volumePlayer}
5069
.updateMembers=${this.updateMemberVolumes}></sonos-volume>
5170
<div class="icons">
52-
<sonos-ha-player .store=${this.store} .features=${this.store.showPower(true)}></sonos-ha-player>
71+
<ha-icon-button hide=${this.store.hidePower(true)} @click=${this.togglePower}></ha-icon-button>
5372
</div">
5473
</div>
5574
`;
5675
}
76+
private prev = async () => await this.mediaControlService.prev(this.activePlayer);
77+
private play = async () => await this.mediaControlService.play(this.activePlayer);
78+
private pauseOrStop = async () => {
79+
return this.config.stopInsteadOfPause
80+
? await this.mediaControlService.stop(this.activePlayer)
81+
: await this.mediaControlService.pause(this.activePlayer);
82+
};
83+
private next = async () => await this.mediaControlService.next(this.activePlayer);
84+
85+
private browseMedia = async () => this.mediaBrowseService.showBrowseMedia(this.activePlayer, this);
86+
private togglePower = async () => await this.mediaControlService.togglePower(this.activePlayer);
5787

5888
private getVolumePlayer() {
5989
let result;
@@ -88,26 +118,6 @@ class PlayerControls extends LitElement {
88118
: '';
89119
}
90120

91-
private showShuffle() {
92-
return this.config.hidePlayerControlShuffleButton ? [] : [SHUFFLE_SET];
93-
}
94-
95-
private showPrev() {
96-
return this.config.hidePlayerControlPrevTrackButton ? [] : [PREVIOUS_TRACK];
97-
}
98-
99-
private showNext() {
100-
return this.config.hidePlayerControlNextTrackButton ? [] : [NEXT_TRACK];
101-
}
102-
103-
private showRepeat() {
104-
return this.config.hidePlayerControlRepeatButton ? [] : [REPEAT_SET];
105-
}
106-
107-
private showBrowseMedia() {
108-
return this.config.showBrowseMediaInPlayerSection ? [BROWSE_MEDIA] : [];
109-
}
110-
111121
static get styles() {
112122
return css`
113123
.main {

src/components/repeat.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { html, LitElement } from 'lit';
2+
import { property } from 'lit/decorators.js';
3+
import MediaControlService from '../services/media-control-service';
4+
import Store from '../model/store';
5+
import { MediaPlayer } from '../model/media-player';
6+
import { mdiRepeat, mdiRepeatOff, mdiRepeatOnce } from '@mdi/js';
7+
8+
class Repeat extends LitElement {
9+
@property({ attribute: false }) store!: Store;
10+
private activePlayer!: MediaPlayer;
11+
private mediaControlService!: MediaControlService;
12+
13+
render() {
14+
this.activePlayer = this.store.activePlayer;
15+
this.mediaControlService = this.store.mediaControlService;
16+
17+
return html`<ha-icon-button @click=${this.repeat} .path=${this.repeatIcon()}></ha-icon-button> `;
18+
}
19+
20+
private repeat = async () => await this.mediaControlService.repeat(this.activePlayer);
21+
22+
private repeatIcon() {
23+
const repeatState = this.activePlayer?.attributes.repeat;
24+
return repeatState === 'all' ? mdiRepeat : repeatState === 'one' ? mdiRepeatOnce : mdiRepeatOff;
25+
}
26+
}
27+
28+
customElements.define('sonos-repeat', Repeat);

src/components/shuffle.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { html, LitElement } from 'lit';
2+
import { property } from 'lit/decorators.js';
3+
import MediaControlService from '../services/media-control-service';
4+
import Store from '../model/store';
5+
import { mdiShuffle, mdiShuffleDisabled } from '@mdi/js';
6+
import { MediaPlayer } from '../model/media-player';
7+
8+
class Shuffle extends LitElement {
9+
@property({ attribute: false }) store!: Store;
10+
private activePlayer!: MediaPlayer;
11+
private mediaControlService!: MediaControlService;
12+
13+
render() {
14+
this.activePlayer = this.store.activePlayer;
15+
this.mediaControlService = this.store.mediaControlService;
16+
17+
return html`<ha-icon-button @click=${this.shuffle} .path=${this.shuffleIcon()}></ha-icon-button> `;
18+
}
19+
20+
private shuffle = async () => await this.mediaControlService.shuffle(this.activePlayer);
21+
22+
private shuffleIcon() {
23+
return this.activePlayer?.attributes.shuffle ? mdiShuffle : mdiShuffleDisabled;
24+
}
25+
}
26+
27+
customElements.define('sonos-shuffle', Shuffle);

src/components/source.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { css, html, LitElement } from 'lit';
2+
import { property } from 'lit/decorators.js';
3+
import MediaControlService from '../services/media-control-service';
4+
import Store from '../model/store';
5+
import { MediaPlayer } from '../model/media-player';
6+
7+
class Source extends LitElement {
8+
@property({ attribute: false }) store!: Store;
9+
private activePlayer!: MediaPlayer;
10+
private mediaControlService!: MediaControlService;
11+
12+
render() {
13+
this.activePlayer = this.store.activePlayer;
14+
this.mediaControlService = this.store.mediaControlService;
15+
16+
return html`
17+
<div>
18+
<span> Source </span>
19+
<ha-select
20+
.label="Source"
21+
.value=${this.activePlayer.attributes.source}
22+
@selected=${this.setSource}
23+
naturalMenuWidth
24+
>
25+
${this.activePlayer.attributes.source_list.map((source: string) => {
26+
return html` <ha-list-item .value=${source}> ${source} </ha-list-item> `;
27+
})}
28+
</ha-select>
29+
</div>
30+
`;
31+
}
32+
33+
private setSource = async (event: CustomEvent) =>
34+
await this.mediaControlService.setSource(
35+
this.activePlayer,
36+
this.activePlayer.attributes.source_list[event.detail.index],
37+
);
38+
static get styles() {
39+
return css`
40+
div {
41+
display: flex;
42+
color: var(--primary-text-color);
43+
justify-content: center;
44+
gap: 10px;
45+
}
46+
span {
47+
align-content: center;
48+
}
49+
`;
50+
}
51+
}
52+
53+
customElements.define('sonos-source', Source);

src/components/volume.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { property } from 'lit/decorators.js';
33
import MediaControlService from '../services/media-control-service';
44
import Store from '../model/store';
55
import { CardConfig } from '../types';
6-
import { mdiVolumeHigh, mdiVolumeMute } from '@mdi/js';
6+
import { mdiPower, mdiVolumeHigh, mdiVolumeMute } from '@mdi/js';
77
import { MediaPlayer } from '../model/media-player';
88

99
class Volume extends LitElement {
@@ -14,6 +14,7 @@ class Volume extends LitElement {
1414
@property({ type: Boolean }) updateMembers = true;
1515
@property() volumeClicked?: () => void;
1616
@property() slim: boolean = false;
17+
private togglePower = async () => await this.mediaControlService.togglePower(this.player);
1718

1819
render() {
1920
this.config = this.store.config;
@@ -44,7 +45,7 @@ class Volume extends LitElement {
4445
</div>
4546
</div>
4647
<div class="percentage-slim" hide=${this.slim && nothing}>${volume}%</div>
47-
<sonos-ha-player .store=${this.store} .features=${this.store.showPower()}></sonos-ha-player>
48+
<ha-icon-button hide=${this.store.hidePower()} @click=${this.togglePower} .path=${mdiPower}></ha-icon-button>
4849
</div>
4950
`;
5051
}

src/constants.ts

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)