From b5861869e39290fd2e15737e89571dfc543b3ad3 Mon Sep 17 00:00:00 2001 From: NachtaktiverHalbaffe <57433516+NachtaktiverHalbaffe@users.noreply.github.com> Date: Wed, 30 Mar 2022 19:16:27 +0200 Subject: [PATCH] Add shuffle and repeat-mode of media_player to UI (#12052) Co-authored-by: Paulus Schoutsen --- src/data/media-player.ts | 68 ++++++++++++++++++- .../controls/more-info-media_player.ts | 14 ++-- .../lovelace/cards/hui-media-control-card.ts | 12 ++-- .../media-browser/ha-bar-media-player.ts | 11 +-- src/translations/en.json | 2 + 5 files changed, 90 insertions(+), 17 deletions(-) diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 14b1a19c80..199cbbb386 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -16,6 +16,11 @@ import { mdiPlayPause, mdiPodcast, mdiPower, + mdiRepeat, + mdiRepeatOff, + mdiRepeatOnce, + mdiShuffle, + mdiShuffleDisabled, mdiSkipNext, mdiSkipPrevious, mdiStop, @@ -49,6 +54,8 @@ interface MediaPlayerEntityAttributes extends HassEntityAttributeBase { entity_picture_local?: string; is_volume_muted?: boolean; volume_level?: number; + repeat?: string; + shuffle?: boolean; source?: string; source_list?: string[]; sound_mode?: string; @@ -80,7 +87,9 @@ export const SUPPORT_VOLUME_BUTTONS = 1024; export const SUPPORT_SELECT_SOURCE = 2048; export const SUPPORT_STOP = 4096; export const SUPPORT_PLAY = 16384; +export const SUPPORT_REPEAT_SET = 262144; export const SUPPORT_SELECT_SOUND_MODE = 65536; +export const SUPPORT_SHUFFLE_SET = 32768; export const SUPPORT_BROWSE_MEDIA = 131072; export type MediaPlayerBrowseAction = "pick" | "play"; @@ -233,7 +242,8 @@ export const computeMediaDescription = ( }; export const computeMediaControls = ( - stateObj: MediaPlayerEntity + stateObj: MediaPlayerEntity, + useExtendedControls = false ): ControlButton[] | undefined => { if (!stateObj) { return undefined; @@ -266,6 +276,18 @@ export const computeMediaControls = ( } const assumedState = stateObj.attributes.assumed_state === true; + const stateAttr = stateObj.attributes; + + if ( + (state === "playing" || state === "paused" || assumedState) && + supportsFeature(stateObj, SUPPORT_SHUFFLE_SET) && + useExtendedControls + ) { + buttons.push({ + icon: stateAttr.shuffle === true ? mdiShuffle : mdiShuffleDisabled, + action: "shuffle_set", + }); + } if ( (state === "playing" || state === "paused" || assumedState) && @@ -337,6 +359,22 @@ export const computeMediaControls = ( }); } + if ( + (state === "playing" || state === "paused" || assumedState) && + supportsFeature(stateObj, SUPPORT_REPEAT_SET) && + useExtendedControls + ) { + buttons.push({ + icon: + stateAttr.repeat === "all" + ? mdiRepeat + : stateAttr.repeat === "one" + ? mdiRepeatOnce + : mdiRepeatOff, + action: "repeat_set", + }); + } + return buttons.length > 0 ? buttons : undefined; }; @@ -375,3 +413,31 @@ export const setMediaPlayerVolume = ( volume_level: number ) => hass.callService("media_player", "volume_set", { entity_id, volume_level }); + +export const handleMediaControlClick = ( + hass: HomeAssistant, + stateObj: MediaPlayerEntity, + action: string +) => + hass!.callService( + "media_player", + action, + action === "shuffle_set" + ? { + entity_id: stateObj!.entity_id, + shuffle: !stateObj!.attributes.shuffle, + } + : action === "repeat_set" + ? { + entity_id: stateObj!.entity_id, + repeat: + stateObj!.attributes.repeat === "all" + ? "one" + : stateObj!.attributes.repeat === "off" + ? "all" + : "off", + } + : { + entity_id: stateObj!.entity_id, + } + ); diff --git a/src/dialogs/more-info/controls/more-info-media_player.ts b/src/dialogs/more-info/controls/more-info-media_player.ts index 916257695a..6f4b88926e 100644 --- a/src/dialogs/more-info/controls/more-info-media_player.ts +++ b/src/dialogs/more-info/controls/more-info-media_player.ts @@ -23,6 +23,7 @@ import { showMediaBrowserDialog } from "../../../components/media-player/show-me import { UNAVAILABLE, UNKNOWN } from "../../../data/entity"; import { computeMediaControls, + handleMediaControlClick, MediaPickedEvent, MediaPlayerEntity, SUPPORT_BROWSE_MEDIA, @@ -47,7 +48,7 @@ class MoreInfoMediaPlayer extends LitElement { } const stateObj = this.stateObj; - const controls = computeMediaControls(stateObj); + const controls = computeMediaControls(stateObj, true); return html`
@@ -202,6 +203,7 @@ class MoreInfoMediaPlayer extends LitElement { } .basic-controls { + display: inline-flex; flex-grow: 1; } @@ -231,12 +233,10 @@ class MoreInfoMediaPlayer extends LitElement { } private _handleClick(e: MouseEvent): void { - this.hass!.callService( - "media_player", - (e.currentTarget! as HTMLElement).getAttribute("action")!, - { - entity_id: this.stateObj!.entity_id, - } + handleMediaControlClick( + this.hass!, + this.stateObj!, + (e.currentTarget as HTMLElement).getAttribute("action")! ); } diff --git a/src/panels/lovelace/cards/hui-media-control-card.ts b/src/panels/lovelace/cards/hui-media-control-card.ts index 50e26074f2..f1d2793ae0 100644 --- a/src/panels/lovelace/cards/hui-media-control-card.ts +++ b/src/panels/lovelace/cards/hui-media-control-card.ts @@ -28,6 +28,7 @@ import { computeMediaControls, computeMediaDescription, getCurrentProgress, + handleMediaControlClick, MediaPickedEvent, MediaPlayerEntity, SUPPORT_BROWSE_MEDIA, @@ -174,7 +175,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { UNAVAILABLE_STATES.includes(entityState) || (entityState === "off" && !supportsFeature(stateObj, SUPPORT_TURN_ON)); const hasNoImage = !this._image; - const controls = computeMediaControls(stateObj); + const controls = computeMediaControls(stateObj, false); const showControls = controls && (!this._veryNarrow || @@ -504,10 +505,11 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { } private _handleClick(e: MouseEvent): void { - const action = (e.currentTarget! as HTMLElement).getAttribute("action")!; - this.hass!.callService("media_player", action, { - entity_id: this._config!.entity, - }); + handleMediaControlClick( + this.hass!, + this._stateObj!, + (e.currentTarget as HTMLElement).getAttribute("action")! + ); } private _updateProgressBar(): void { diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index 3e913c6e43..bfc2e63656 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -39,6 +39,7 @@ import { computeMediaDescription, formatMediaTime, getCurrentProgress, + handleMediaControlClick, MediaPlayerEntity, MediaPlayerItem, setMediaPlayerVolume, @@ -173,7 +174,7 @@ export class BarMediaPlayer extends LitElement { } const controls = !this.narrow - ? computeMediaControls(stateObj) + ? computeMediaControls(stateObj, true) : (stateObj.state === "playing" && (supportsFeature(stateObj, SUPPORT_PAUSE) || supportsFeature(stateObj, SUPPORT_STOP))) || @@ -490,9 +491,11 @@ export class BarMediaPlayer extends LitElement { const action = (e.currentTarget! as HTMLElement).getAttribute("action")!; if (!this._browserPlayer) { - this.hass!.callService("media_player", action, { - entity_id: this.entityId, - }); + handleMediaControlClick( + this.hass!, + this._stateObj!, + (e.currentTarget as HTMLElement).getAttribute("action")! + ); return; } if (action === "media_pause") { diff --git a/src/translations/en.json b/src/translations/en.json index 2d05c998e8..051fa237c0 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -211,6 +211,8 @@ "media_volume_down": "Volume down", "media_volume_mute": "Volume mute", "media_volume_unmute": "Volume unmute", + "repeat_set": "Repeat mode", + "shuffle_set": "Shuffle", "text_to_speak": "Text to speak", "nothing_playing": "Nothing Playing" },