From 8a8b4034fa6b7ed3ede7d32d64a4e44f20239e61 Mon Sep 17 00:00:00 2001 From: Paul Gillesberger Date: Sun, 5 Feb 2023 14:15:13 +0100 Subject: [PATCH] Add option to display an artwork in the background of the media player card --- .hass_dev/views/media-player-view.yaml | 14 + docs/cards/media-player.md | 37 +- .../media-player-card-config.ts | 70 +-- .../media-player-card-editor.ts | 212 +++++---- .../media-player-card/media-player-card.ts | 440 ++++++++++-------- src/translations/en.json | 330 ++++++------- 6 files changed, 589 insertions(+), 514 deletions(-) diff --git a/.hass_dev/views/media-player-view.yaml b/.hass_dev/views/media-player-view.yaml index 572f183b0..c093ad7cb 100644 --- a/.hass_dev/views/media-player-view.yaml +++ b/.hass_dev/views/media-player-view.yaml @@ -37,6 +37,7 @@ cards: - type: custom:mushroom-media-player-card entity: media_player.living_room icon_type: entity-picture + artwork: cover columns: 2 square: false - type: grid @@ -91,6 +92,7 @@ cards: - volume_mute - volume_set - volume_buttons + artwork: cover - type: custom:mushroom-media-player-card entity: media_player.kitchen name: Collapsible controls @@ -102,6 +104,18 @@ cards: - repeat - on_off collapsible_controls: true + artwork: cover + - type: custom:mushroom-media-player-card + entity: media_player.living_room + media_controls: + - shuffle + - previous + - play_pause_stop + - next + - repeat + - on_off + collapsible_controls: true + artwork: full_cover - type: vertical-stack title: Layout cards: diff --git a/docs/cards/media-player.md b/docs/cards/media-player.md index 54b8bcf80..9c91ff62e 100644 --- a/docs/cards/media-player.md +++ b/docs/cards/media-player.md @@ -11,21 +11,22 @@ A media player card allows you to control a media player entity. All the options are available in the lovelace editor but you can use `yaml` if you want. -| Name | Type | Default | Description | -| :--------------------- | :-------------------------------------------------- | :---------- | :------------------------------------------------------------------------------------- | -| `entity` | string | Required | Media Player entity | -| `icon` | string | Optional | Custom icon | -| `name` | string | Optional | Custom name | -| `layout` | string | Optional | Layout of the card. Vertical, horizontal and default layout are supported | -| `fill_container` | boolean | `false` | Fill container or not. Useful when card is in a grid, vertical or horizontal layout | -| `primary_info` | `name` `state` `last-changed` `last-updated` `none` | `name` | Info to show as primary info | -| `secondary_info` | `name` `state` `last-changed` `last-updated` `none` | `state` | Info to show as secondary info | -| `icon_type` | `icon` `entity-picture` `none` | `icon` | Type of icon to display | -| `use_media_info` | boolean | `false` | Use media info instead of name, state and icon when a media is playing | -| `show_volume_level` | boolean | `false` | Show volume level next to media state when media is playing | -| `media_controls` | list | `[]` | List of controls to display (on_off, shuffle, previous, play_pause_stop, next, repeat) | -| `volume_controls` | list | `[]` | List of controls to display (volume_mute, volume_set, volume_buttons) | -| `collapsible_controls` | boolean | `false` | Collapse controls when off | -| `tap_action` | action | `more-info` | Home assistant action to perform on tap | -| `hold_action` | action | `more-info` | Home assistant action to perform on hold | -| `double_tap_action` | action | `more-info` | Home assistant action to perform on double_tap | +| Name | Type | Default | Description | +|:-----------------------|:----------------------------------------------------|:-------------|:---------------------------------------------------------------------------------------| +| `entity` | string | Required | Media Player entity | +| `icon` | string | Optional | Custom icon | +| `name` | string | Optional | Custom name | +| `layout` | string | Optional | Layout of the card. Vertical, horizontal and default layout are supported | +| `fill_container` | boolean | `false` | Fill container or not. Useful when card is in a grid, vertical or horizontal layout | +| `primary_info` | `name` `state` `last-changed` `last-updated` `none` | `name` | Info to show as primary info | +| `secondary_info` | `name` `state` `last-changed` `last-updated` `none` | `state` | Info to show as secondary info | +| `icon_type` | `icon` `entity-picture` `none` | `icon` | Type of icon to display | +| `use_media_info` | boolean | `false` | Use media info instead of name, state and icon when a media is playing | +| `show_volume_level` | boolean | `false` | Show volume level next to media state when media is playing | +| `media_controls` | list | `[]` | List of controls to display (on_off, shuffle, previous, play_pause_stop, next, repeat) | +| `volume_controls` | list | `[]` | List of controls to display (volume_mute, volume_set, volume_buttons) | +| `collapsible_controls` | boolean | `false` | Collapse controls when off | +| `tap_action` | action | `more-info` | Home assistant action to perform on tap | +| `hold_action` | action | `more-info` | Home assistant action to perform on hold | +| `double_tap_action` | action | `more-info` | Home assistant action to perform on double_tap | +| `artwork` | `none` `cover` `full_cover` | `more-info` | Background artwork | diff --git a/src/cards/media-player-card/media-player-card-config.ts b/src/cards/media-player-card/media-player-card-config.ts index 07bcaea7c..aeea4542e 100644 --- a/src/cards/media-player-card/media-player-card-config.ts +++ b/src/cards/media-player-card/media-player-card-config.ts @@ -2,50 +2,60 @@ import { array, assign, boolean, enums, object, optional } from "superstruct"; import { LovelaceCardConfig } from "../../ha"; import { ActionsSharedConfig, actionsSharedConfigStruct } from "../../shared/config/actions-config"; import { - AppearanceSharedConfig, - appearanceSharedConfigStruct, + AppearanceSharedConfig, + appearanceSharedConfigStruct, } from "../../shared/config/appearance-config"; import { EntitySharedConfig, entitySharedConfigStruct } from "../../shared/config/entity-config"; import { lovelaceCardConfigStruct } from "../../shared/config/lovelace-card-config"; export const MEDIA_LAYER_MEDIA_CONTROLS = [ - "on_off", - "shuffle", - "previous", - "play_pause_stop", - "next", - "repeat", + "on_off", + "shuffle", + "previous", + "play_pause_stop", + "next", + "repeat", ] as const; export type MediaPlayerMediaControl = (typeof MEDIA_LAYER_MEDIA_CONTROLS)[number]; export const MEDIA_PLAYER_VOLUME_CONTROLS = [ - "volume_mute", - "volume_set", - "volume_buttons", + "volume_mute", + "volume_set", + "volume_buttons", ] as const; export type MediaPlayerVolumeControl = (typeof MEDIA_PLAYER_VOLUME_CONTROLS)[number]; export type MediaPlayerCardConfig = LovelaceCardConfig & - EntitySharedConfig & - AppearanceSharedConfig & - ActionsSharedConfig & { - use_media_info?: boolean; - show_volume_level?: boolean; - volume_controls?: MediaPlayerVolumeControl[]; - media_controls?: MediaPlayerMediaControl[]; - collapsible_controls?: boolean; - }; + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig & { + use_media_info?: boolean; + show_volume_level?: boolean; + volume_controls?: MediaPlayerVolumeControl[]; + media_controls?: MediaPlayerMediaControl[]; + collapsible_controls?: boolean; + artwork?: MediaPlayerArtworkMode; +}; + +export const MEDIA_PLAYER_ARTWORK_MODES = [ + "none", + "cover", + "full_cover", +] as const; + +export type MediaPlayerArtworkMode = (typeof MEDIA_PLAYER_ARTWORK_MODES)[number]; export const mediaPlayerCardConfigStruct = assign( - lovelaceCardConfigStruct, - assign(entitySharedConfigStruct, appearanceSharedConfigStruct, actionsSharedConfigStruct), - object({ - use_media_info: optional(boolean()), - show_volume_level: optional(boolean()), - volume_controls: optional(array(enums(MEDIA_PLAYER_VOLUME_CONTROLS))), - media_controls: optional(array(enums(MEDIA_LAYER_MEDIA_CONTROLS))), - collapsible_controls: optional(boolean()), - }) -); + lovelaceCardConfigStruct, + assign(entitySharedConfigStruct, appearanceSharedConfigStruct, actionsSharedConfigStruct), + object({ + use_media_info: optional(boolean()), + show_volume_level: optional(boolean()), + volume_controls: optional(array(enums(MEDIA_PLAYER_VOLUME_CONTROLS))), + media_controls: optional(array(enums(MEDIA_LAYER_MEDIA_CONTROLS))), + collapsible_controls: optional(boolean()), + artwork: optional(enums(MEDIA_PLAYER_ARTWORK_MODES)), + }) +); \ No newline at end of file diff --git a/src/cards/media-player-card/media-player-card-editor.ts b/src/cards/media-player-card/media-player-card-editor.ts index c135b0b74..7bd92e02a 100644 --- a/src/cards/media-player-card/media-player-card-editor.ts +++ b/src/cards/media-player-card/media-player-card-editor.ts @@ -13,123 +13,139 @@ import { stateIcon } from "../../utils/icons/state-icon"; import { loadHaComponents } from "../../utils/loader"; import { MEDIA_PLAYER_CARD_EDITOR_NAME, MEDIA_PLAYER_ENTITY_DOMAINS } from "./const"; import { - MediaPlayerCardConfig, - mediaPlayerCardConfigStruct, - MEDIA_LAYER_MEDIA_CONTROLS, - MEDIA_PLAYER_VOLUME_CONTROLS, + MediaPlayerCardConfig, + mediaPlayerCardConfigStruct, + MEDIA_LAYER_MEDIA_CONTROLS, + MEDIA_PLAYER_VOLUME_CONTROLS, MEDIA_PLAYER_ARTWORK_MODES, } from "./media-player-card-config"; export const MEDIA_LABELS = [ - "use_media_info", - "use_media_artwork", - "show_volume_level", - "media_controls", - "volume_controls", + "use_media_info", + "use_media_artwork", + "show_volume_level", + "media_controls", + "volume_controls", + "artwork", ]; const computeSchema = memoizeOne((localize: LocalizeFunc, icon?: string): HaFormSchema[] => [ - { name: "entity", selector: { entity: { domain: MEDIA_PLAYER_ENTITY_DOMAINS } } }, - { name: "name", selector: { text: {} } }, - { name: "icon", selector: { icon: { placeholder: icon } } }, - ...APPEARANCE_FORM_SCHEMA, - { - type: "grid", - name: "", - schema: [ - { name: "use_media_info", selector: { boolean: {} } }, - { name: "show_volume_level", selector: { boolean: {} } }, - ], - }, - { - type: "grid", - name: "", - schema: [ - { - name: "volume_controls", - selector: { - select: { - options: MEDIA_PLAYER_VOLUME_CONTROLS.map((control) => ({ - value: control, - label: localize( - `editor.card.media-player.volume_controls_list.${control}` - ), - })), - mode: "list", - multiple: true, - }, - }, - }, - { - name: "media_controls", - selector: { - select: { - options: MEDIA_LAYER_MEDIA_CONTROLS.map((control) => ({ - value: control, - label: localize( - `editor.card.media-player.media_controls_list.${control}` - ), - })), - mode: "list", - multiple: true, - }, - }, - }, - { name: "collapsible_controls", selector: { boolean: {} } }, - ], - }, - ...computeActionsFormSchema(), + { name: "entity", selector: { entity: { domain: MEDIA_PLAYER_ENTITY_DOMAINS } } }, + { name: "name", selector: { text: {} } }, + { name: "icon", selector: { icon: { placeholder: icon } } }, + ...APPEARANCE_FORM_SCHEMA, + { + type: "grid", + name: "", + schema: [ + { name: "use_media_info", selector: { boolean: {} } }, + { name: "show_volume_level", selector: { boolean: {} } }, + ], + }, + { + type: "grid", + name: "", + schema: [ + { + name: "volume_controls", + selector: { + select: { + options: MEDIA_PLAYER_VOLUME_CONTROLS.map((control) => ({ + value: control, + label: localize( + `editor.card.media-player.volume_controls_list.${control}` + ), + })), + mode: "list", + multiple: true, + }, + }, + }, + { + name: "media_controls", + selector: { + select: { + options: MEDIA_LAYER_MEDIA_CONTROLS.map((control) => ({ + value: control, + label: localize( + `editor.card.media-player.media_controls_list.${control}` + ), + })), + mode: "list", + multiple: true, + }, + }, + }, + { name: "collapsible_controls", selector: { boolean: {} } }, + ], + }, + { + name: "artwork", + selector: { + select: { + mode: "dropdown", + multiple: false, + options: MEDIA_PLAYER_ARTWORK_MODES.map((mode) => ({ + value: mode, + label: localize( + `editor.card.media-player.artwork_list.${mode}` + ), + })), + }, + } + }, + ...computeActionsFormSchema(), ]); @customElement(MEDIA_PLAYER_CARD_EDITOR_NAME) export class MediaCardEditor extends MushroomBaseElement implements LovelaceCardEditor { - @state() private _config?: MediaPlayerCardConfig; + @state() private _config?: MediaPlayerCardConfig; - connectedCallback() { - super.connectedCallback(); - void loadHaComponents(); - } + connectedCallback() { + super.connectedCallback(); + void loadHaComponents(); + } - public setConfig(config: MediaPlayerCardConfig): void { - assert(config, mediaPlayerCardConfigStruct); - this._config = config; - } + public setConfig(config: MediaPlayerCardConfig): void { + assert(config, mediaPlayerCardConfigStruct); + this._config = config; + } - private _computeLabel = (schema: HaFormSchema) => { - const customLocalize = setupCustomlocalize(this.hass!); + private _computeLabel = (schema: HaFormSchema) => { + const customLocalize = setupCustomlocalize(this.hass!); - if (GENERIC_LABELS.includes(schema.name)) { - return customLocalize(`editor.card.generic.${schema.name}`); - } - if (MEDIA_LABELS.includes(schema.name)) { - return customLocalize(`editor.card.media-player.${schema.name}`); - } - return this.hass!.localize(`ui.panel.lovelace.editor.card.generic.${schema.name}`); - }; + if (GENERIC_LABELS.includes(schema.name)) { + return customLocalize(`editor.card.generic.${schema.name}`); + } + if (MEDIA_LABELS.includes(schema.name)) { + return customLocalize(`editor.card.media-player.${schema.name}`); + } + return this.hass!.localize(`ui.panel.lovelace.editor.card.generic.${schema.name}`); + }; - protected render(): TemplateResult { - if (!this.hass || !this._config) { - return html``; - } + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } - const entityState = this._config.entity ? this.hass.states[this._config.entity] : undefined; - const entityIcon = entityState ? stateIcon(entityState) : undefined; - const icon = this._config.icon || entityIcon; + const entityState = this._config.entity ? this.hass.states[this._config.entity] : undefined; + const entityIcon = entityState ? stateIcon(entityState) : undefined; + const icon = this._config.icon || entityIcon; - const customLocalize = setupCustomlocalize(this.hass!); - const schema = computeSchema(customLocalize, icon); + const customLocalize = setupCustomlocalize(this.hass!); + const schema = computeSchema(customLocalize, icon); - return html` + return html` - `; - } + `; + } - private _valueChanged(ev: CustomEvent): void { - fireEvent(this, "config-changed", { config: ev.detail.value }); - } + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } } diff --git a/src/cards/media-player-card/media-player-card.ts b/src/cards/media-player-card/media-player-card.ts index e3a3d78fc..e2e97e882 100644 --- a/src/cards/media-player-card/media-player-card.ts +++ b/src/cards/media-player-card/media-player-card.ts @@ -2,16 +2,16 @@ import { css, CSSResultGroup, html, PropertyValues, TemplateResult } from "lit"; import { customElement, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { - actionHandler, - ActionHandlerEvent, - computeRTL, - handleAction, - hasAction, - HomeAssistant, - isActive, - LovelaceCard, - LovelaceCardEditor, - MediaPlayerEntity, + actionHandler, + ActionHandlerEvent, + computeRTL, getEntityPicture, + handleAction, + hasAction, + HomeAssistant, + isActive, + LovelaceCard, + LovelaceCardEditor, + MediaPlayerEntity, OFF, UNAVAILABLE, UNKNOWN, } from "../../ha"; import "../../shared/badge-icon"; import "../../shared/card"; @@ -24,9 +24,9 @@ import { registerCustomCard } from "../../utils/custom-cards"; import { computeEntityPicture } from "../../utils/info"; import { Layout } from "../../utils/layout"; import { - MEDIA_PLAYER_CARD_EDITOR_NAME, - MEDIA_PLAYER_CARD_NAME, - MEDIA_PLAYER_ENTITY_DOMAINS, + MEDIA_PLAYER_CARD_EDITOR_NAME, + MEDIA_PLAYER_CARD_NAME, + MEDIA_PLAYER_ENTITY_DOMAINS, } from "./const"; import "./controls/media-player-media-control"; import { isMediaControlVisible } from "./controls/media-player-media-control"; @@ -34,233 +34,261 @@ import "./controls/media-player-volume-control"; import { isVolumeControlVisible } from "./controls/media-player-volume-control"; import { MediaPlayerCardConfig } from "./media-player-card-config"; import { - computeMediaIcon, - computeMediaNameDisplay, - computeMediaStateDisplay, - getVolumeLevel, + computeMediaIcon, + computeMediaNameDisplay, + computeMediaStateDisplay, + getVolumeLevel, } from "./utils"; +import { styleMap } from "lit/directives/style-map.js"; type MediaPlayerCardControl = "media_control" | "volume_control"; const CONTROLS_ICONS: Record = { - media_control: "mdi:play-pause", - volume_control: "mdi:volume-high", + media_control: "mdi:play-pause", + volume_control: "mdi:volume-high", }; registerCustomCard({ - type: MEDIA_PLAYER_CARD_NAME, - name: "Mushroom Media Card", - description: "Card for media player entity", + type: MEDIA_PLAYER_CARD_NAME, + name: "Mushroom Media Card", + description: "Card for media player entity", }); @customElement(MEDIA_PLAYER_CARD_NAME) export class MediaPlayerCard extends MushroomBaseCard implements LovelaceCard { - public static async getConfigElement(): Promise { - await import("./media-player-card-editor"); - return document.createElement(MEDIA_PLAYER_CARD_EDITOR_NAME) as LovelaceCardEditor; - } - - public static async getStubConfig(hass: HomeAssistant): Promise { - const entities = Object.keys(hass.states); - const mediaPlayers = entities.filter((e) => - MEDIA_PLAYER_ENTITY_DOMAINS.includes(e.split(".")[0]) - ); - return { - type: `custom:${MEDIA_PLAYER_CARD_NAME}`, - entity: mediaPlayers[0], - }; - } - - @state() private _config?: MediaPlayerCardConfig; - - @state() private _activeControl?: MediaPlayerCardControl; - - @state() private _controls: MediaPlayerCardControl[] = []; - - _onControlTap(ctrl, e): void { - e.stopPropagation(); - this._activeControl = ctrl; - } - - getCardSize(): number | Promise { - return 1; - } - - setConfig(config: MediaPlayerCardConfig): void { - this._config = { - tap_action: { - action: "more-info", - }, - hold_action: { - action: "more-info", - }, - ...config, - }; - this.updateControls(); - this.updateVolume(); - } - - protected updated(changedProperties: PropertyValues) { - super.updated(changedProperties); - if (this.hass && changedProperties.has("hass")) { - this.updateControls(); - this.updateVolume(); - } - } - - @state() - private volume?: number; - - updateVolume() { - this.volume = undefined; - if (!this._config || !this.hass || !this._config.entity) return; - - const entity_id = this._config.entity; - const entity = this.hass.states[entity_id] as MediaPlayerEntity; - - if (!entity) return; - const volume = getVolumeLevel(entity); - this.volume = volume != null ? Math.round(volume) : volume; - } - - private onCurrentVolumeChange(e: CustomEvent<{ value?: number }>): void { - if (e.detail.value != null) { - this.volume = e.detail.value; - } - } - - updateControls() { - if (!this._config || !this.hass || !this._config.entity) return; - - const entity_id = this._config.entity; - const entity = this.hass.states[entity_id] as MediaPlayerEntity; - - if (!entity) return; - - const controls: MediaPlayerCardControl[] = []; - if (!this._config.collapsible_controls || isActive(entity)) { - if (isMediaControlVisible(entity, this._config?.media_controls)) { - controls.push("media_control"); - } - if (isVolumeControlVisible(entity, this._config.volume_controls)) { - controls.push("volume_control"); - } - } - - this._controls = controls; - const isActiveControlSupported = this._activeControl - ? controls.includes(this._activeControl) - : false; - this._activeControl = isActiveControlSupported ? this._activeControl : controls[0]; - } - - private _handleAction(ev: ActionHandlerEvent) { - handleAction(this, this.hass!, this._config!, ev.detail.action!); - } - - protected render(): TemplateResult { - if (!this._config || !this.hass || !this._config.entity) { - return html``; - } - - const entity_id = this._config.entity; - const entity = this.hass.states[entity_id] as MediaPlayerEntity; - - const icon = computeMediaIcon(this._config, entity); - const nameDisplay = computeMediaNameDisplay(this._config, entity); - const stateDisplay = computeMediaStateDisplay(this._config, entity, this.hass); - const appearance = computeAppearance(this._config); - const picture = computeEntityPicture(entity, appearance.icon_type); - - const stateValue = - this.volume != null && this._config.show_volume_level - ? `${stateDisplay} - ${this.volume}%` - : stateDisplay; - - const rtl = computeRTL(this.hass); - - return html` + public static async getConfigElement(): Promise { + await import("./media-player-card-editor"); + return document.createElement(MEDIA_PLAYER_CARD_EDITOR_NAME) as LovelaceCardEditor; + } + + public static async getStubConfig(hass: HomeAssistant): Promise { + const entities = Object.keys(hass.states); + const mediaPlayers = entities.filter((e) => + MEDIA_PLAYER_ENTITY_DOMAINS.includes(e.split(".")[0]) + ); + return { + type: `custom:${MEDIA_PLAYER_CARD_NAME}`, + entity: mediaPlayers[0], + }; + } + + @state() private _config?: MediaPlayerCardConfig; + + @state() private _activeControl?: MediaPlayerCardControl; + + @state() private _controls: MediaPlayerCardControl[] = []; + + _onControlTap(ctrl, e): void { + e.stopPropagation(); + this._activeControl = ctrl; + } + + getCardSize(): number | Promise { + return 1; + } + + setConfig(config: MediaPlayerCardConfig): void { + this._config = { + tap_action: { + action: "more-info", + }, + hold_action: { + action: "more-info", + }, + ...config, + }; + this.updateControls(); + this.updateVolume(); + } + + protected updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + if (this.hass && changedProperties.has("hass")) { + this.updateControls(); + this.updateVolume(); + } + } + + @state() + private volume?: number; + + updateVolume() { + this.volume = undefined; + if (!this._config || !this.hass || !this._config.entity) return; + + const entity_id = this._config.entity; + const entity = this.hass.states[entity_id] as MediaPlayerEntity; + + if (!entity) return; + const volume = getVolumeLevel(entity); + this.volume = volume != null ? Math.round(volume) : volume; + } + + private onCurrentVolumeChange(e: CustomEvent<{ value?: number }>): void { + if (e.detail.value != null) { + this.volume = e.detail.value; + } + } + + updateControls() { + if (!this._config || !this.hass || !this._config.entity) return; + + const entity_id = this._config.entity; + const entity = this.hass.states[entity_id] as MediaPlayerEntity; + + if (!entity) return; + + const controls: MediaPlayerCardControl[] = []; + if (!this._config.collapsible_controls || isActive(entity)) { + if (isMediaControlVisible(entity, this._config?.media_controls)) { + controls.push("media_control"); + } + if (isVolumeControlVisible(entity, this._config.volume_controls)) { + controls.push("volume_control"); + } + } + + this._controls = controls; + const isActiveControlSupported = this._activeControl + ? controls.includes(this._activeControl) + : false; + this._activeControl = isActiveControlSupported ? this._activeControl : controls[0]; + } + + private _handleAction(ev: ActionHandlerEvent) { + handleAction(this, this.hass!, this._config!, ev.detail.action!); + } + + protected render(): TemplateResult { + if (!this._config || !this.hass || !this._config.entity) { + return html``; + } + + const entity_id = this._config.entity; + const entity = this.hass.states[entity_id] as MediaPlayerEntity; + + const icon = computeMediaIcon(this._config, entity); + const nameDisplay = computeMediaNameDisplay(this._config, entity); + const stateDisplay = computeMediaStateDisplay(this._config, entity, this.hass); + const appearance = computeAppearance(this._config); + const picture = computeEntityPicture(entity, appearance.icon_type); + + const stateValue = + this.volume != null && this._config.show_volume_level + ? `${stateDisplay} - ${this.volume}%` + : stateDisplay; + + const rtl = computeRTL(this.hass); + + const artwork = this.renderArtwork(entity); + const cardStyleMap = {}; + if (!!artwork && this._config?.artwork == "full_cover") { + cardStyleMap["padding-top"] = "calc(100% - 8 * var(--spacing))"; + } + + return html` - + ${artwork} + ${picture ? this.renderPicture(picture) : this.renderIcon(entity, icon)} ${this.renderBadge(entity)} ${this.renderStateInfo(entity, appearance, nameDisplay, stateValue)}; ${this._controls.length > 0 - ? html` -
- ${this.renderActiveControl(entity, appearance.layout)} - ${this.renderOtherControls()} -
- ` - : null} + ? html` +
+ ${this.renderActiveControl(entity, appearance.layout)} + ${this.renderOtherControls()} +
+ ` + : null}
- `; - } + `; + } - private renderOtherControls(): TemplateResult | null { - const otherControls = this._controls.filter((control) => control != this._activeControl); + private renderArtwork(entity: MediaPlayerEntity): TemplateResult | null { + if (!this._config?.artwork || this._config.artwork === "none" || [UNAVAILABLE, UNKNOWN, OFF].includes(entity.state)) { + return null; + } - return html` + return html``; + } + + private renderOtherControls(): TemplateResult | null { + const otherControls = this._controls.filter((control) => control != this._activeControl); + + return html` ${otherControls.map( - (ctrl) => html` - this._onControlTap(ctrl, e)} - /> - ` + (ctrl) => html` + this._onControlTap(ctrl, e)} + /> + ` )} - `; - } + `; + } - private renderActiveControl(entity: MediaPlayerEntity, layout: Layout): TemplateResult | null { - const media_controls = this._config?.media_controls ?? []; - const volume_controls = this._config?.volume_controls ?? []; + private renderActiveControl(entity: MediaPlayerEntity, layout: Layout): TemplateResult | null { + const media_controls = this._config?.media_controls ?? []; + const volume_controls = this._config?.volume_controls ?? []; - switch (this._activeControl) { - case "media_control": - return html` + switch (this._activeControl) { + case "media_control": + return html` - `; - case "volume_control": - return html` + `; + case "volume_control": + return html` - `; - default: - return null; - } - } - - static get styles(): CSSResultGroup { - return [ - super.styles, - cardStyle, - css` + `; + default: + return null; + } + } + + static get styles(): CSSResultGroup { + return [ + super.styles, + cardStyle, + css` + .artwork { + border-radius: var(--ha-card-border-radius, 12px); + height: 100%; + width: 100%; + position: absolute; + object-fit: cover; + margin: calc(-1 * var(--spacing)); + filter: var(--media-player-artwork-filter, brightness(30%)); + } mushroom-state-item { cursor: pointer; } + mushroom-card { + z-index: 1; + } mushroom-shape-icon { --icon-color: rgb(var(--rgb-state-media-player)); --shape-color: rgba(var(--rgb-state-media-player), 0.2); @@ -270,6 +298,6 @@ export class MediaPlayerCard extends MushroomBaseCard implements LovelaceCard { flex: 1; } `, - ]; - } + ]; + } } diff --git a/src/translations/en.json b/src/translations/en.json index ff50e278e..eedc59ddc 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1,168 +1,174 @@ { - "editor": { - "form": { - "color_picker": { - "values": { - "default": "Default color" - } - }, - "info_picker": { - "values": { - "default": "Default information", - "name": "Name", - "state": "State", - "last-changed": "Last Changed", - "last-updated": "Last Updated", - "none": "None" - } - }, - "icon_type_picker": { - "values": { - "default": "Default type", - "icon": "Icon", - "entity-picture": "Entity picture", - "none": "None" - } - }, - "layout_picker": { - "values": { - "default": "Default layout", - "vertical": "Vertical layout", - "horizontal": "Horizontal layout" - } - }, - "alignment_picker": { - "values": { - "default": "Default alignment", - "start": "Start", - "end": "End", - "center": "Center", - "justify": "Justify" - } - } + "editor": { + "form": { + "color_picker": { + "values": { + "default": "Default color" + } + }, + "info_picker": { + "values": { + "default": "Default information", + "name": "Name", + "state": "State", + "last-changed": "Last Changed", + "last-updated": "Last Updated", + "none": "None" + } + }, + "icon_type_picker": { + "values": { + "default": "Default type", + "icon": "Icon", + "entity-picture": "Entity picture", + "none": "None" + } + }, + "layout_picker": { + "values": { + "default": "Default layout", + "vertical": "Vertical layout", + "horizontal": "Horizontal layout" + } + }, + "alignment_picker": { + "values": { + "default": "Default alignment", + "start": "Start", + "end": "End", + "center": "Center", + "justify": "Justify" + } + } + }, + "card": { + "generic": { + "icon_color": "Icon color", + "layout": "Layout", + "fill_container": "Fill container", + "primary_info": "Primary information", + "secondary_info": "Secondary information", + "icon_type": "Icon type", + "content_info": "Content", + "use_entity_picture": "Use entity picture?", + "collapsible_controls": "Collapse controls when off", + "icon_animation": "Animate icon when active?" + }, + "light": { + "show_brightness_control": "Brightness control?", + "use_light_color": "Use light color", + "show_color_temp_control": "Temperature color control?", + "show_color_control": "Color control?", + "incompatible_controls": "Some controls may not be displayed if your light does not support the feature." + }, + "fan": { + "show_percentage_control": "Percentage control?", + "show_oscillate_control": "Oscillate control?" + }, + "cover": { + "show_buttons_control": "Control buttons?", + "show_position_control": "Position control?", + "show_tilt_position_control": "Tilt control?" + }, + "alarm_control_panel": { + "show_keypad": "Show keypad" + }, + "template": { + "primary": "Primary information", + "secondary": "Secondary information", + "multiline_secondary": "Multiline secondary?", + "entity_extra": "Used in templates and actions", + "content": "Content", + "badge_icon": "Badge icon", + "badge_color": "Badge color", + "picture": "Picture (will replace the icon)" + }, + "title": { + "title": "Title", + "subtitle": "Subtitle", + "title_tap_action": "Title tap action", + "subtitle_tap_action": "Subtitle tap action" + }, + "chips": { + "alignment": "Alignment" + }, + "weather": { + "show_conditions": "Conditions?", + "show_temperature": "Temperature?" + }, + "update": { + "show_buttons_control": "Control buttons?" + }, + "vacuum": { + "commands": "Commands", + "commands_list": { + "on_off": "Turn on/off" + } + }, + "media-player": { + "artwork": "Artwork", + "artwork_list": { + "cover": "Cover", + "full_cover": "Full cover", + "none": "None" }, - "card": { - "generic": { - "icon_color": "Icon color", - "layout": "Layout", - "fill_container": "Fill container", - "primary_info": "Primary information", - "secondary_info": "Secondary information", - "icon_type": "Icon type", - "content_info": "Content", - "use_entity_picture": "Use entity picture?", - "collapsible_controls": "Collapse controls when off", - "icon_animation": "Animate icon when active?" - }, - "light": { - "show_brightness_control": "Brightness control?", - "use_light_color": "Use light color", - "show_color_temp_control": "Temperature color control?", - "show_color_control": "Color control?", - "incompatible_controls": "Some controls may not be displayed if your light does not support the feature." - }, - "fan": { - "show_percentage_control": "Percentage control?", - "show_oscillate_control": "Oscillate control?" - }, - "cover": { - "show_buttons_control": "Control buttons?", - "show_position_control": "Position control?", - "show_tilt_position_control": "Tilt control?" - }, - "alarm_control_panel": { - "show_keypad": "Show keypad" - }, - "template": { - "primary": "Primary information", - "secondary": "Secondary information", - "multiline_secondary": "Multiline secondary?", - "entity_extra": "Used in templates and actions", - "content": "Content", - "badge_icon": "Badge icon", - "badge_color": "Badge color", - "picture": "Picture (will replace the icon)" - }, - "title": { - "title": "Title", - "subtitle": "Subtitle", - "title_tap_action": "Title tap action", - "subtitle_tap_action": "Subtitle tap action" - }, - "chips": { - "alignment": "Alignment" - }, - "weather": { - "show_conditions": "Conditions?", - "show_temperature": "Temperature?" - }, - "update": { - "show_buttons_control": "Control buttons?" - }, - "vacuum": { - "commands": "Commands", - "commands_list": { - "on_off": "Turn on/off" - } - }, - "media-player": { - "use_media_info": "Use media info", - "use_media_artwork": "Use media artwork", - "show_volume_level": "Show volume level", - "media_controls": "Media controls", - "media_controls_list": { - "on_off": "Turn on/off", - "shuffle": "Shuffle", - "previous": "Previous track", - "play_pause_stop": "Play/pause/stop", - "next": "Next track", - "repeat": "Repeat mode" - }, - "volume_controls": "Volume controls", - "volume_controls_list": { - "volume_buttons": "Volume buttons", - "volume_set": "Volume level", - "volume_mute": "Mute" - } - }, - "lock": { - "lock": "Lock", - "unlock": "Unlock", - "open": "Open" - }, - "humidifier": { - "show_target_humidity_control": "Humidity control?" - }, - "climate": { - "show_temperature_control": "Temperature control?", - "hvac_modes": "HVAC Modes" - } + "use_media_info": "Use media info", + "use_media_artwork": "Use media artwork", + "show_volume_level": "Show volume level", + "media_controls": "Media controls", + "media_controls_list": { + "on_off": "Turn on/off", + "shuffle": "Shuffle", + "previous": "Previous track", + "play_pause_stop": "Play/pause/stop", + "next": "Next track", + "repeat": "Repeat mode" }, - "chip": { - "sub_element_editor": { - "title": "Chip editor" - }, - "conditional": { - "chip": "Chip" - }, - "chip-picker": { - "chips": "Chips", - "add": "Add chip", - "edit": "Edit", - "clear": "Clear", - "select": "Select chip", - "types": { - "action": "Action", - "alarm-control-panel": "Alarm", - "back": "Back", - "conditional": "Conditional", - "entity": "Entity", - "light": "Light", - "menu": "Menu", - "template": "Template", - "weather": "Weather" - } - } + "volume_controls": "Volume controls", + "volume_controls_list": { + "volume_buttons": "Volume buttons", + "volume_set": "Volume level", + "volume_mute": "Mute" + } + }, + "lock": { + "lock": "Lock", + "unlock": "Unlock", + "open": "Open" + }, + "humidifier": { + "show_target_humidity_control": "Humidity control?" + }, + "climate": { + "show_temperature_control": "Temperature control?", + "hvac_modes": "HVAC Modes" + } + }, + "chip": { + "sub_element_editor": { + "title": "Chip editor" + }, + "conditional": { + "chip": "Chip" + }, + "chip-picker": { + "chips": "Chips", + "add": "Add chip", + "edit": "Edit", + "clear": "Clear", + "select": "Select chip", + "types": { + "action": "Action", + "alarm-control-panel": "Alarm", + "back": "Back", + "conditional": "Conditional", + "entity": "Entity", + "light": "Light", + "menu": "Menu", + "template": "Template", + "weather": "Weather" } + } } + } }