From d9315de97217b9e4baefd0c50941f085c2f44e97 Mon Sep 17 00:00:00 2001 From: Simon Zumbrunnen Date: Sat, 7 Dec 2024 14:13:46 +0000 Subject: [PATCH 1/4] Added "Media player volume" card feature. --- gallery/src/pages/lovelace/tile-card.ts | 12 +++ .../hui-media-player-volume-card-feature.ts | 90 +++++++++++++++++++ src/panels/lovelace/card-features/types.ts | 5 ++ .../create-card-feature-element.ts | 2 + .../hui-card-features-editor.ts | 3 + src/translations/en.json | 3 + 6 files changed, 115 insertions(+) create mode 100644 src/panels/lovelace/card-features/hui-media-player-volume-card-feature.ts diff --git a/gallery/src/pages/lovelace/tile-card.ts b/gallery/src/pages/lovelace/tile-card.ts index c5289165ea33..dc0743a81318 100644 --- a/gallery/src/pages/lovelace/tile-card.ts +++ b/gallery/src/pages/lovelace/tile-card.ts @@ -28,6 +28,9 @@ const ENTITIES = [ device_class: "lock", supported_features: LockEntityFeature.OPEN, }), + getEntity("media_player", "living_room", "playing", { + friendly_name: "Living room speaker", + }), getEntity("climate", "thermostat", "heat", { current_temperature: 73, min_temp: 45, @@ -197,6 +200,15 @@ const CONFIGS = [ - type: "lock-open-door" `, }, + { + heading: "Media player volume feature", + config: ` +- type: tile + entity: media_player.living_room + features: + - type: "media-player-volume" + `, + }, { heading: "Vacuum commands feature", config: ` diff --git a/src/panels/lovelace/card-features/hui-media-player-volume-card-feature.ts b/src/panels/lovelace/card-features/hui-media-player-volume-card-feature.ts new file mode 100644 index 000000000000..6f68e72cc0fd --- /dev/null +++ b/src/panels/lovelace/card-features/hui-media-player-volume-card-feature.ts @@ -0,0 +1,90 @@ +import type { HassEntity } from "home-assistant-js-websocket"; +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import { stateActive } from "../../../common/entity/state_active"; +import "../../../components/ha-control-slider"; +import { isUnavailableState } from "../../../data/entity"; +import type { HomeAssistant } from "../../../types"; +import type { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; +import type { MediaPlayerCardFeatureConfig } from "./types"; + +export const supportsMediaPlayerVolumeCardFeature = (stateObj: HassEntity) => { + const domain = computeDomain(stateObj.entity_id); + return domain === "media_player"; +}; + +@customElement("hui-media-player-volume-card-feature") +class HuiMediaPlayerVolumeCardFeature + extends LitElement + implements LovelaceCardFeature +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public stateObj?: HassEntity; + + @state() private _config?: MediaPlayerCardFeatureConfig; + + static getStubConfig(): MediaPlayerCardFeatureConfig { + return { + type: "media-player-volume", + }; + } + + public setConfig(config: MediaPlayerCardFeatureConfig): void { + if (!config) { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + protected render() { + if ( + !this._config || + !this.hass || + !this.stateObj || + !supportsMediaPlayerVolumeCardFeature(this.stateObj) + ) { + return nothing; + } + + const position = + this.stateObj.attributes.volume_level != null + ? Math.round(this.stateObj.attributes.volume_level * 100) + : undefined; + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent) { + ev.stopPropagation(); + const value = ev.detail.value; + + this.hass!.callService("media_player", "volume_set", { + entity_id: this.stateObj!.entity_id, + volume_level: value / 100, + }); + } + + static get styles() { + return cardFeatureStyles; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-media-player-volume-card-feature": HuiMediaPlayerVolumeCardFeature; + } +} diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts index ec60914086f8..17251bc22c79 100644 --- a/src/panels/lovelace/card-features/types.ts +++ b/src/panels/lovelace/card-features/types.ts @@ -34,6 +34,10 @@ export interface LockOpenDoorCardFeatureConfig { type: "lock-open-door"; } +export interface MediaPlayerCardFeatureConfig { + type: "media-player-volume"; +} + export interface FanPresetModesCardFeatureConfig { type: "fan-preset-modes"; style?: "dropdown" | "icons"; @@ -161,6 +165,7 @@ export type LovelaceCardFeatureConfig = | LightColorTempCardFeatureConfig | LockCommandsCardFeatureConfig | LockOpenDoorCardFeatureConfig + | MediaPlayerCardFeatureConfig | NumericInputCardFeatureConfig | SelectOptionsCardFeatureConfig | TargetHumidityCardFeatureConfig 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 872ceb0aba06..f8e3e49abdfa 100644 --- a/src/panels/lovelace/create-element/create-card-feature-element.ts +++ b/src/panels/lovelace/create-element/create-card-feature-element.ts @@ -17,6 +17,7 @@ import "../card-features/hui-light-brightness-card-feature"; import "../card-features/hui-light-color-temp-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-volume-card-feature"; import "../card-features/hui-numeric-input-card-feature"; import "../card-features/hui-select-options-card-feature"; import "../card-features/hui-target-temperature-card-feature"; @@ -51,6 +52,7 @@ const TYPES: Set = new Set([ "light-color-temp", "lock-commands", "lock-open-door", + "media-player-volume", "numeric-input", "select-options", "target-humidity", 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 41564ebe5e57..01980ab7eb5e 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 @@ -38,6 +38,7 @@ import { supportsLightBrightnessCardFeature } from "../../card-features/hui-ligh import { supportsLightColorTempCardFeature } from "../../card-features/hui-light-color-temp-card-feature"; import { supportsLockCommandsCardFeature } from "../../card-features/hui-lock-commands-card-feature"; import { supportsLockOpenDoorCardFeature } from "../../card-features/hui-lock-open-door-card-feature"; +import { supportsMediaPlayerVolumeCardFeature } from "../../card-features/hui-media-player-volume-card-feature"; import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric-input-card-feature"; import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature"; import { supportsTargetHumidityCardFeature } from "../../card-features/hui-target-humidity-card-feature"; @@ -71,6 +72,7 @@ const UI_FEATURE_TYPES = [ "light-color-temp", "lock-commands", "lock-open-door", + "media-player-volume", "numeric-input", "select-options", "target-humidity", @@ -123,6 +125,7 @@ const SUPPORTS_FEATURE_TYPES: Record< "light-color-temp": supportsLightColorTempCardFeature, "lock-commands": supportsLockCommandsCardFeature, "lock-open-door": supportsLockOpenDoorCardFeature, + "media-player-volume": supportsMediaPlayerVolumeCardFeature, "numeric-input": supportsNumericInputCardFeature, "select-options": supportsSelectOptionsCardFeature, "target-humidity": supportsTargetHumidityCardFeature, diff --git a/src/translations/en.json b/src/translations/en.json index e8bfe80a397a..416e294c1849 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6597,6 +6597,9 @@ "lock-open-door": { "label": "Lock open door" }, + "media-player-volume": { + "label": "Media player volume" + }, "vacuum-commands": { "label": "Vacuum commands", "commands": "Commands", From 8d6ba2330bbb814dfc43e0b01ad6197f24ac5d15 Mon Sep 17 00:00:00 2001 From: Simon Zumbrunnen <7323997+simon-zumbrunnen@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:32:40 +0100 Subject: [PATCH 2/4] Make sure the feature is not displayed on unsupported players Co-authored-by: Petar Petrov --- .../card-features/hui-media-player-volume-card-feature.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/card-features/hui-media-player-volume-card-feature.ts b/src/panels/lovelace/card-features/hui-media-player-volume-card-feature.ts index 6f68e72cc0fd..62f31dbb5e72 100644 --- a/src/panels/lovelace/card-features/hui-media-player-volume-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-media-player-volume-card-feature.ts @@ -9,10 +9,15 @@ import type { HomeAssistant } from "../../../types"; import type { LovelaceCardFeature } from "../types"; import { cardFeatureStyles } from "./common/card-feature-styles"; import type { MediaPlayerCardFeatureConfig } from "./types"; +import { MediaPlayerEntityFeature } from "../../../data/media-player"; +import { supportsFeature } from "../../../common/entity/supports-feature"; export const supportsMediaPlayerVolumeCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); - return domain === "media_player"; + return ( + domain === "media_player" && + supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_SET) + ); }; @customElement("hui-media-player-volume-card-feature") From 4376a04f57097b0bc84ebe55f32e9f4952a070ec Mon Sep 17 00:00:00 2001 From: Simon Zumbrunnen Date: Tue, 10 Dec 2024 11:04:38 +0000 Subject: [PATCH 3/4] Renamed to Media player volume *slider* --- gallery/src/pages/lovelace/tile-card.ts | 6 +++-- ...edia-player-volume-slider-card-feature.ts} | 22 ++++++++++--------- src/panels/lovelace/card-features/types.ts | 4 ++-- .../create-card-feature-element.ts | 4 ++-- .../hui-card-features-editor.ts | 6 ++--- src/translations/en.json | 4 ++-- 6 files changed, 25 insertions(+), 21 deletions(-) rename src/panels/lovelace/card-features/{hui-media-player-volume-card-feature.ts => hui-media-player-volume-slider-card-feature.ts} (76%) diff --git a/gallery/src/pages/lovelace/tile-card.ts b/gallery/src/pages/lovelace/tile-card.ts index dc0743a81318..ecfa9ca0b3cc 100644 --- a/gallery/src/pages/lovelace/tile-card.ts +++ b/gallery/src/pages/lovelace/tile-card.ts @@ -4,6 +4,7 @@ import { customElement, query } from "lit/decorators"; import { CoverEntityFeature } from "../../../../src/data/cover"; import { LightColorMode } from "../../../../src/data/light"; import { LockEntityFeature } from "../../../../src/data/lock"; +import { MediaPlayerEntityFeature } from "../../../../src/data/media-player"; import { VacuumEntityFeature } from "../../../../src/data/vacuum"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; @@ -30,6 +31,7 @@ const ENTITIES = [ }), getEntity("media_player", "living_room", "playing", { friendly_name: "Living room speaker", + supported_features: MediaPlayerEntityFeature.VOLUME_SET, }), getEntity("climate", "thermostat", "heat", { current_temperature: 73, @@ -201,12 +203,12 @@ const CONFIGS = [ `, }, { - heading: "Media player volume feature", + heading: "Media player volume slider feature", config: ` - type: tile entity: media_player.living_room features: - - type: "media-player-volume" + - type: "media-player-volume-slider" `, }, { diff --git a/src/panels/lovelace/card-features/hui-media-player-volume-card-feature.ts b/src/panels/lovelace/card-features/hui-media-player-volume-slider-card-feature.ts similarity index 76% rename from src/panels/lovelace/card-features/hui-media-player-volume-card-feature.ts rename to src/panels/lovelace/card-features/hui-media-player-volume-slider-card-feature.ts index 62f31dbb5e72..3aeffe2b19d2 100644 --- a/src/panels/lovelace/card-features/hui-media-player-volume-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-media-player-volume-slider-card-feature.ts @@ -8,11 +8,13 @@ import { isUnavailableState } from "../../../data/entity"; import type { HomeAssistant } from "../../../types"; import type { LovelaceCardFeature } from "../types"; import { cardFeatureStyles } from "./common/card-feature-styles"; -import type { MediaPlayerCardFeatureConfig } from "./types"; +import type { MediaPlayerVolumeSliderCardFeatureConfig } from "./types"; import { MediaPlayerEntityFeature } from "../../../data/media-player"; import { supportsFeature } from "../../../common/entity/supports-feature"; -export const supportsMediaPlayerVolumeCardFeature = (stateObj: HassEntity) => { +export const supportsMediaPlayerVolumeSliderCardFeature = ( + stateObj: HassEntity +) => { const domain = computeDomain(stateObj.entity_id); return ( domain === "media_player" && @@ -20,8 +22,8 @@ export const supportsMediaPlayerVolumeCardFeature = (stateObj: HassEntity) => { ); }; -@customElement("hui-media-player-volume-card-feature") -class HuiMediaPlayerVolumeCardFeature +@customElement("hui-media-player-volume-slider-card-feature") +class HuiMediaPlayerVolumeSliderCardFeature extends LitElement implements LovelaceCardFeature { @@ -29,15 +31,15 @@ class HuiMediaPlayerVolumeCardFeature @property({ attribute: false }) public stateObj?: HassEntity; - @state() private _config?: MediaPlayerCardFeatureConfig; + @state() private _config?: MediaPlayerVolumeSliderCardFeatureConfig; - static getStubConfig(): MediaPlayerCardFeatureConfig { + static getStubConfig(): MediaPlayerVolumeSliderCardFeatureConfig { return { - type: "media-player-volume", + type: "media-player-volume-slider", }; } - public setConfig(config: MediaPlayerCardFeatureConfig): void { + public setConfig(config: MediaPlayerVolumeSliderCardFeatureConfig): void { if (!config) { throw new Error("Invalid configuration"); } @@ -49,7 +51,7 @@ class HuiMediaPlayerVolumeCardFeature !this._config || !this.hass || !this.stateObj || - !supportsMediaPlayerVolumeCardFeature(this.stateObj) + !supportsMediaPlayerVolumeSliderCardFeature(this.stateObj) ) { return nothing; } @@ -90,6 +92,6 @@ class HuiMediaPlayerVolumeCardFeature declare global { interface HTMLElementTagNameMap { - "hui-media-player-volume-card-feature": HuiMediaPlayerVolumeCardFeature; + "hui-media-player-volume-slider-card-feature": HuiMediaPlayerVolumeSliderCardFeature; } } diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts index 17251bc22c79..52ce71ed504c 100644 --- a/src/panels/lovelace/card-features/types.ts +++ b/src/panels/lovelace/card-features/types.ts @@ -34,8 +34,8 @@ export interface LockOpenDoorCardFeatureConfig { type: "lock-open-door"; } -export interface MediaPlayerCardFeatureConfig { - type: "media-player-volume"; +export interface MediaPlayerVolumeSliderCardFeatureConfig { + type: "media-player-volume-slider"; } export interface FanPresetModesCardFeatureConfig { 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 f8e3e49abdfa..6c9799d18b13 100644 --- a/src/panels/lovelace/create-element/create-card-feature-element.ts +++ b/src/panels/lovelace/create-element/create-card-feature-element.ts @@ -17,7 +17,7 @@ import "../card-features/hui-light-brightness-card-feature"; import "../card-features/hui-light-color-temp-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-volume-card-feature"; +import "../card-features/hui-media-player-volume-slider-card-feature"; import "../card-features/hui-numeric-input-card-feature"; import "../card-features/hui-select-options-card-feature"; import "../card-features/hui-target-temperature-card-feature"; @@ -52,7 +52,7 @@ const TYPES: Set = new Set([ "light-color-temp", "lock-commands", "lock-open-door", - "media-player-volume", + "media-player-volume-slider", "numeric-input", "select-options", "target-humidity", 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 01980ab7eb5e..8018de2344d8 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 @@ -38,7 +38,7 @@ import { supportsLightBrightnessCardFeature } from "../../card-features/hui-ligh import { supportsLightColorTempCardFeature } from "../../card-features/hui-light-color-temp-card-feature"; import { supportsLockCommandsCardFeature } from "../../card-features/hui-lock-commands-card-feature"; import { supportsLockOpenDoorCardFeature } from "../../card-features/hui-lock-open-door-card-feature"; -import { supportsMediaPlayerVolumeCardFeature } from "../../card-features/hui-media-player-volume-card-feature"; +import { supportsMediaPlayerVolumeSliderCardFeature } from "../../card-features/hui-media-player-volume-slider-card-feature"; import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric-input-card-feature"; import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature"; import { supportsTargetHumidityCardFeature } from "../../card-features/hui-target-humidity-card-feature"; @@ -72,7 +72,7 @@ const UI_FEATURE_TYPES = [ "light-color-temp", "lock-commands", "lock-open-door", - "media-player-volume", + "media-player-volume-slider", "numeric-input", "select-options", "target-humidity", @@ -125,7 +125,7 @@ const SUPPORTS_FEATURE_TYPES: Record< "light-color-temp": supportsLightColorTempCardFeature, "lock-commands": supportsLockCommandsCardFeature, "lock-open-door": supportsLockOpenDoorCardFeature, - "media-player-volume": supportsMediaPlayerVolumeCardFeature, + "media-player-volume-slider": supportsMediaPlayerVolumeSliderCardFeature, "numeric-input": supportsNumericInputCardFeature, "select-options": supportsSelectOptionsCardFeature, "target-humidity": supportsTargetHumidityCardFeature, diff --git a/src/translations/en.json b/src/translations/en.json index 416e294c1849..5a51e26f8ba4 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6597,8 +6597,8 @@ "lock-open-door": { "label": "Lock open door" }, - "media-player-volume": { - "label": "Media player volume" + "media-player-volume-slider": { + "label": "Media player volume slider" }, "vacuum-commands": { "label": "Vacuum commands", From a4b31a834950f14df67eb7309f5d3832feeb6807 Mon Sep 17 00:00:00 2001 From: Simon Zumbrunnen <7323997+simon-zumbrunnen@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:07:50 +0100 Subject: [PATCH 4/4] Missed one rename. --- src/panels/lovelace/card-features/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts index 52ce71ed504c..2a031af808a4 100644 --- a/src/panels/lovelace/card-features/types.ts +++ b/src/panels/lovelace/card-features/types.ts @@ -165,7 +165,7 @@ export type LovelaceCardFeatureConfig = | LightColorTempCardFeatureConfig | LockCommandsCardFeatureConfig | LockOpenDoorCardFeatureConfig - | MediaPlayerCardFeatureConfig + | MediaPlayerVolumeSliderCardFeatureConfig | NumericInputCardFeatureConfig | SelectOptionsCardFeatureConfig | TargetHumidityCardFeatureConfig