diff --git a/src/panels/lovelace/card-features/hui-media-player-sound-mode-card-feature.ts b/src/panels/lovelace/card-features/hui-media-player-sound-mode-card-feature.ts
new file mode 100644
index 0000000000..1020658796
--- /dev/null
+++ b/src/panels/lovelace/card-features/hui-media-player-sound-mode-card-feature.ts
@@ -0,0 +1,168 @@
+import type { PropertyValues } from "lit";
+import { html, LitElement, nothing } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { computeDomain } from "../../../common/entity/compute_domain";
+import { supportsFeature } from "../../../common/entity/supports-feature";
+import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
+import "../../../components/ha-control-select-menu";
+import "../../../components/ha-list-item";
+import { UNAVAILABLE } from "../../../data/entity/entity";
+import {
+ MediaPlayerEntityFeature,
+ type MediaPlayerEntity,
+} from "../../../data/media-player";
+import type { HomeAssistant } from "../../../types";
+import { hasConfigChanged } from "../common/has-changed";
+import type { LovelaceCardFeature } from "../types";
+import { cardFeatureStyles } from "./common/card-feature-styles";
+import type {
+ LovelaceCardFeatureContext,
+ MediaPlayerSoundModeCardFeatureConfig,
+} from "./types";
+
+export const supportsMediaPlayerSoundModeCardFeature = (
+ hass: HomeAssistant,
+ context: LovelaceCardFeatureContext
+) => {
+ const stateObj = context.entity_id
+ ? hass.states[context.entity_id]
+ : undefined;
+ if (!stateObj) return false;
+ const domain = computeDomain(stateObj.entity_id);
+ return (
+ domain === "media_player" &&
+ supportsFeature(stateObj, MediaPlayerEntityFeature.SELECT_SOUND_MODE) &&
+ !!stateObj.attributes.sound_mode_list?.length
+ );
+};
+
+@customElement("hui-media-player-sound-mode-card-feature")
+class HuiMediaPlayerSoundModeCardFeature
+ extends LitElement
+ implements LovelaceCardFeature
+{
+ @property({ attribute: false }) public hass?: HomeAssistant;
+
+ @property({ attribute: false }) public context?: LovelaceCardFeatureContext;
+
+ @state() private _config?: MediaPlayerSoundModeCardFeatureConfig;
+
+ @state() private _currentSoundMode?: string;
+
+ private get _stateObj() {
+ if (!this.hass || !this.context || !this.context.entity_id) {
+ return undefined;
+ }
+ return this.hass.states[this.context.entity_id!] as
+ | MediaPlayerEntity
+ | undefined;
+ }
+
+ static getStubConfig(): MediaPlayerSoundModeCardFeatureConfig {
+ return {
+ type: "media-player-sound-mode",
+ };
+ }
+
+ public setConfig(config: MediaPlayerSoundModeCardFeatureConfig): void {
+ if (!config) {
+ throw new Error("Invalid configuration");
+ }
+ this._config = config;
+ }
+
+ protected willUpdate(changedProps: PropertyValues): void {
+ super.willUpdate(changedProps);
+ if (
+ (changedProps.has("hass") || changedProps.has("context")) &&
+ this._stateObj
+ ) {
+ const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
+ const oldStateObj = oldHass?.states[this.context!.entity_id!];
+ if (oldStateObj !== this._stateObj) {
+ this._currentSoundMode = this._stateObj.attributes.sound_mode;
+ }
+ }
+ }
+
+ protected shouldUpdate(changedProps: PropertyValues): boolean {
+ const entityId = this.context?.entity_id;
+ const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
+
+ return (
+ changedProps.has("_currentSoundMode") ||
+ changedProps.has("context") ||
+ hasConfigChanged(this, changedProps) ||
+ (changedProps.has("hass") &&
+ (!oldHass ||
+ !entityId ||
+ oldHass.states[entityId] !== this.hass?.states[entityId]))
+ );
+ }
+
+ private async _valueChanged(ev: HaDropdownSelectEvent) {
+ const soundMode = ev.detail.item?.value;
+ const oldSoundMode = this._stateObj!.attributes.sound_mode;
+
+ if (soundMode === oldSoundMode || !soundMode) {
+ return;
+ }
+
+ this._currentSoundMode = soundMode;
+
+ try {
+ await this.hass!.callService("media_player", "select_sound_mode", {
+ entity_id: this._stateObj!.entity_id,
+ sound_mode: soundMode,
+ });
+ } catch (_err) {
+ this._currentSoundMode = oldSoundMode;
+ }
+ }
+
+ protected render() {
+ if (
+ !this._config ||
+ !this.hass ||
+ !this.context ||
+ !this._stateObj ||
+ !supportsMediaPlayerSoundModeCardFeature(this.hass, this.context)
+ ) {
+ return nothing;
+ }
+
+ const options = this._stateObj.attributes.sound_mode_list!.map(
+ (soundMode) => ({
+ value: soundMode,
+ label: this.hass!.formatEntityAttributeValue(
+ this._stateObj!,
+ "sound_mode",
+ soundMode
+ ),
+ })
+ );
+
+ return html`
+
+
+ `;
+ }
+
+ static get styles() {
+ return cardFeatureStyles;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-media-player-sound-mode-card-feature": HuiMediaPlayerSoundModeCardFeature;
+ }
+}
diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts
index 2502a6d968..6f08206fa2 100644
--- a/src/panels/lovelace/card-features/types.ts
+++ b/src/panels/lovelace/card-features/types.ts
@@ -67,6 +67,10 @@ export interface MediaPlayerVolumeButtonsCardFeatureConfig {
step?: number;
}
+export interface MediaPlayerSoundModeCardFeatureConfig {
+ type: "media-player-sound-mode";
+}
+
export interface FanDirectionCardFeatureConfig {
type: "fan-direction";
}
@@ -281,6 +285,7 @@ export type LovelaceCardFeatureConfig =
| LockCommandsCardFeatureConfig
| LockOpenDoorCardFeatureConfig
| MediaPlayerPlaybackCardFeatureConfig
+ | MediaPlayerSoundModeCardFeatureConfig
| MediaPlayerVolumeButtonsCardFeatureConfig
| MediaPlayerVolumeSliderCardFeatureConfig
| NumericInputCardFeatureConfig
diff --git a/src/panels/lovelace/create-element/create-card-feature-element.ts b/src/panels/lovelace/create-element/create-card-feature-element.ts
index 27aa37284d..613c07a4bd 100644
--- a/src/panels/lovelace/create-element/create-card-feature-element.ts
+++ b/src/panels/lovelace/create-element/create-card-feature-element.ts
@@ -26,6 +26,7 @@ import "../card-features/hui-light-color-favorites-card-feature";
import "../card-features/hui-lock-commands-card-feature";
import "../card-features/hui-lock-open-door-card-feature";
import "../card-features/hui-media-player-playback-card-feature";
+import "../card-features/hui-media-player-sound-mode-card-feature";
import "../card-features/hui-media-player-volume-buttons-card-feature";
import "../card-features/hui-media-player-volume-slider-card-feature";
import "../card-features/hui-numeric-input-card-feature";
@@ -80,6 +81,7 @@ const TYPES = new Set([
"lock-commands",
"lock-open-door",
"media-player-playback",
+ "media-player-sound-mode",
"media-player-volume-buttons",
"media-player-volume-slider",
"numeric-input",
diff --git a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts
index 991044e014..996a33808f 100644
--- a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts
@@ -53,6 +53,7 @@ import { supportsLightColorTempCardFeature } from "../../card-features/hui-light
import { supportsLockCommandsCardFeature } from "../../card-features/hui-lock-commands-card-feature";
import { supportsLockOpenDoorCardFeature } from "../../card-features/hui-lock-open-door-card-feature";
import { supportsMediaPlayerPlaybackCardFeature } from "../../card-features/hui-media-player-playback-card-feature";
+import { supportsMediaPlayerSoundModeCardFeature } from "../../card-features/hui-media-player-sound-mode-card-feature";
import { supportsMediaPlayerVolumeButtonsCardFeature } from "../../card-features/hui-media-player-volume-buttons-card-feature";
import { supportsMediaPlayerVolumeSliderCardFeature } from "../../card-features/hui-media-player-volume-slider-card-feature";
import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric-input-card-feature";
@@ -112,6 +113,7 @@ const UI_FEATURE_TYPES = [
"lock-commands",
"lock-open-door",
"media-player-playback",
+ "media-player-sound-mode",
"media-player-volume-buttons",
"media-player-volume-slider",
"numeric-input",
@@ -191,6 +193,7 @@ const SUPPORTS_FEATURE_TYPES: Record<
"lock-commands": supportsLockCommandsCardFeature,
"lock-open-door": supportsLockOpenDoorCardFeature,
"media-player-playback": supportsMediaPlayerPlaybackCardFeature,
+ "media-player-sound-mode": supportsMediaPlayerSoundModeCardFeature,
"media-player-volume-buttons": supportsMediaPlayerVolumeButtonsCardFeature,
"media-player-volume-slider": supportsMediaPlayerVolumeSliderCardFeature,
"numeric-input": supportsNumericInputCardFeature,
diff --git a/src/translations/en.json b/src/translations/en.json
index 6041e26507..a1e50b694a 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -9713,6 +9713,9 @@
"media-player-playback": {
"label": "Media player playback controls"
},
+ "media-player-sound-mode": {
+ "label": "Media player sound mode"
+ },
"media-player-volume-buttons": {
"label": "Media player volume buttons",
"step": "Step size"