Skip to content

Commit

Permalink
fix: Multiple predefinedGroups with same speakers but different volum…
Browse files Browse the repository at this point in the history
…e - doesn't set volume

Also improved grouping by adding volume percentage, color indicator for dynamic threshold, and sort selected on top

fixes #369
  • Loading branch information
punxaphil committed May 9, 2024
1 parent 5020074 commit 1160ac8
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 54 deletions.
6 changes: 5 additions & 1 deletion src/components/grouping-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { property } from 'lit/decorators.js';
export class GroupingButton extends LitElement {
@property() icon!: string;
@property() name!: string;
@property() selected!: boolean;

render() {
const iconAndName = (!!this.icon && !!this.name) || nothing;
return html`
<ha-control-button>
<ha-control-button selected=${this.selected || nothing}>
${this.icon ? html` <ha-icon icon-and-name=${iconAndName} .icon=${this.icon}></ha-icon>` : ''}
${this.name ? html`<span>${this.name}</span>` : ''}
</ha-control-button>
Expand All @@ -22,6 +23,9 @@ export class GroupingButton extends LitElement {
--control-button-background-color: var(--accent-color);
--control-button-icon-color: var(--secondary-text-color);
}
ha-control-button[selected] {
--control-button-icon-color: var(--accent-color);
}
ha-icon {
padding-left: 1rem;
Expand Down
16 changes: 15 additions & 1 deletion src/components/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ class Volume extends LitElement {
max=${max}
@value-changed=${this.volumeChanged}
.disabled=${disabled}
class=${this.config.dynamicVolumeSlider && max === 100 ? 'over-threshold' : ''}
></ha-control-slider>
<div class="volume-level">
<div style="flex: ${volume}">0%</div>
<div class="percentage">${Math.round(volume)}%</div>
<div class="percentage">${volume}%</div>
<div style="flex: ${max - volume};text-align: right">${max}%</div>
</div>
</div>
<div class="percentage-slim" hide=${this.slim && nothing}>${volume}%</div>
<sonos-ha-player
hide=${showPowerButton}
.store=${this.store}
Expand Down Expand Up @@ -74,6 +76,11 @@ class Volume extends LitElement {
ha-control-slider {
--control-slider-color: var(--accent-color);
}
ha-control-slider.over-threshold {
--control-slider-color: var(--primary-text-color);
}
ha-control-slider[disabled] {
--control-slider-color: var(--disabled-text-color);
}
Expand Down Expand Up @@ -107,11 +114,18 @@ class Volume extends LitElement {
font-size: x-small;
display: flex;
}
.percentage {
flex: 2;
}
.percentage,
.percentage-slim {
font-weight: bold;
font-size: 12px;
align-self: center;
}
*[hide] {
display: none;
}
Expand Down
21 changes: 21 additions & 0 deletions src/model/grouping-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MediaPlayer } from './media-player';

export class GroupingItem {
isSelected: boolean;
icon!: string;
isDisabled = false;
isModified: boolean;
readonly name: string;
readonly isMain: boolean;
readonly player: MediaPlayer;

constructor(player: MediaPlayer, activePlayer: MediaPlayer, isModified: boolean) {
this.isMain = player.id === activePlayer.id;
this.isModified = isModified;
const currentlyJoined = this.isMain || activePlayer.hasMember(player.id);
this.isSelected = isModified ? !currentlyJoined : currentlyJoined;
this.player = player;
this.name = player.name;
this.icon = this.isSelected ? 'check-circle' : 'checkbox-blank-circle-outline';
}
}
6 changes: 4 additions & 2 deletions src/model/media-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,13 @@ export class MediaPlayer {
}

getVolume() {
let volume: number;
if (this.members.length > 1 && this.config.adjustVolumeRelativeToMainPlayer) {
return this.getAverageVolume();
volume = this.getAverageVolume();
} else {
return 100 * this.volumePlayer.attributes.volume_level;
volume = 100 * this.volumePlayer.attributes.volume_level;
}
return Math.round(volume);
}

private getAverageVolume() {
Expand Down
83 changes: 33 additions & 50 deletions src/sections/grouping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { property, state } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js';
import MediaControlService from '../services/media-control-service';
import Store from '../model/store';
import { dispatchActivePlayerId } from '../utils/utils';
import { dispatchActivePlayerId, getGroupingChanges } from '../utils/utils';
import { listStyle } from '../constants';
import { MediaPlayer } from '../model/media-player';
import '../components/grouping-button';
import { PredefinedGroup, PredefinedGroupPlayer } from '../types';
import { GroupingItem } from '../model/grouping-item';

export class Grouping extends LitElement {
@property({ attribute: false }) store!: Store;
Expand All @@ -18,6 +19,7 @@ export class Grouping extends LitElement {
private notJoinedPlayers!: string[];
private joinedPlayers!: string[];
@state() modifiedItems: string[] = [];
@state() selectedPredefinedGroup?: PredefinedGroup;

render() {
this.activePlayer = this.store.activePlayer;
Expand All @@ -27,7 +29,10 @@ export class Grouping extends LitElement {
this.notJoinedPlayers = this.getNotJoinedPlayers();
this.joinedPlayers = this.getJoinedPlayers();

if (this.store.config.skipApplyButtonWhenGrouping && this.modifiedItems.length > 0) {
if (
this.store.config.skipApplyButtonWhenGrouping &&
(this.modifiedItems.length > 0 || this.selectedPredefinedGroup)
) {
this.applyGrouping();
}

Expand Down Expand Up @@ -63,10 +68,12 @@ export class Grouping extends LitElement {
</div>
<ha-control-button-group
class="buttons"
hide=${this.modifiedItems.length === 0 || this.store.config.skipApplyButtonWhenGrouping || nothing}
hide=${(this.modifiedItems.length === 0 && !this.selectedPredefinedGroup) ||
this.store.config.skipApplyButtonWhenGrouping ||
nothing}
>
<ha-control-button class="apply" @click=${this.applyGrouping}> Apply </ha-control-button>
<ha-control-button @click=${this.cancelGrouping}> Cancel </ha-control-button>
<ha-control-button class="apply" @click=${this.applyGrouping}> Apply</ha-control-button>
<ha-control-button @click=${this.cancelGrouping}> Cancel</ha-control-button>
</ha-control-button-group>
</div>
`;
Expand Down Expand Up @@ -164,43 +171,31 @@ export class Grouping extends LitElement {
} else {
this.modifiedItems = [...this.modifiedItems, item.player.id];
}
this.selectedPredefinedGroup = undefined;
}

async applyGrouping() {
const isSelected = this.groupingItems.filter((item) => item.isSelected);
const unJoin = this.groupingItems
.filter((item) => !item.isSelected && this.joinedPlayers.includes(item.player.id))
.map((item) => item.player.id);
const join = this.groupingItems
.filter((item) => item.isSelected && !this.joinedPlayers.includes(item.player.id))
.map((item) => item.player.id);

let main = this.activePlayer.id;
const groupingItems = this.groupingItems;
const joinedPlayers = this.joinedPlayers;
const activePlayerId = this.activePlayer.id;
const { unJoin, join, newMainPlayer } = getGroupingChanges(groupingItems, joinedPlayers, activePlayerId);

if (join.length > 0) {
await this.mediaControlService.join(main, join);
await this.mediaControlService.join(newMainPlayer, join);
}
if (unJoin.length > 0) {
await this.mediaControlService.unJoin(unJoin);
}
await this.handlePredefinedGroupConfig(isSelected);
if (unJoin.includes(this.activePlayer.id)) {
main = !!this.store.config.dontSwitchPlayerWhenGrouping ? this.activePlayer.id : isSelected[0].player.id;
dispatchActivePlayerId(main, this.store.config, this);
if (this.selectedPredefinedGroup) {
console.log('Setting volume and media for predefined group', this.selectedPredefinedGroup.volume);
await this.mediaControlService.setVolumeAndMediaForPredefinedGroup(this.selectedPredefinedGroup);
}
this.modifiedItems = [];
}

private async handlePredefinedGroupConfig(isSelected: GroupingItem[]) {
const predefinedGroup = this.store.predefinedGroups.find((pg) => {
return (
pg.entities.length === isSelected.length &&
pg.entities.every((pgp) => isSelected.some((s) => s.player.id === pgp.player.id))
);
});
if (predefinedGroup) {
await this.mediaControlService.setVolumeAndMediaForPredefinedGroup(predefinedGroup);
if (newMainPlayer !== activePlayerId && !this.store.config.dontSwitchPlayerWhenGrouping) {
dispatchActivePlayerId(newMainPlayer, this.store.config, this);
}
this.modifiedItems = [];
this.selectedPredefinedGroup = undefined;
}

private cancelGrouping() {
Expand All @@ -215,6 +210,12 @@ export class Grouping extends LitElement {
if (selectedItems.length === 1) {
selectedItems[0].isDisabled = true;
}
groupingItems.sort((a, b) => {
if ((a.isMain && !b.isMain) || (a.isSelected && !b.isSelected)) {
return -1;
}
return a.name.localeCompare(b.name);
});

return groupingItems;
}
Expand Down Expand Up @@ -254,6 +255,7 @@ export class Grouping extends LitElement {
@click=${async () => this.selectPredefinedGroup(predefinedGroup)}
.icon=${'mdi:speaker-multiple'}
.name=${predefinedGroup.name}
.selected=${this.selectedPredefinedGroup?.name === predefinedGroup.name}
></sonos-grouping-button>
`;
});
Expand All @@ -266,6 +268,7 @@ export class Grouping extends LitElement {
this.toggleItemWithoutDisabledCheck(item);
}
});
this.selectedPredefinedGroup = predefinedGroup;
}

private selectAll() {
Expand All @@ -284,23 +287,3 @@ export class Grouping extends LitElement {
});
}
}

class GroupingItem {
isSelected: boolean;
icon!: string;
isDisabled = false;
isModified: boolean;
readonly name: string;
readonly isMain: boolean;
readonly player: MediaPlayer;

constructor(player: MediaPlayer, activePlayer: MediaPlayer, isModified: boolean) {
this.isMain = player.id === activePlayer.id;
this.isModified = isModified;
const currentlyJoined = this.isMain || activePlayer.hasMember(player.id);
this.isSelected = isModified ? !currentlyJoined : currentlyJoined;
this.player = player;
this.name = player.name;
this.icon = this.isSelected ? 'check-circle' : 'checkbox-blank-circle-outline';
}
}
18 changes: 18 additions & 0 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { HassEntity } from 'home-assistant-js-websocket';
import { CardConfig, PredefinedGroup, Section } from '../types';
import { ACTIVE_PLAYER_EVENT, ACTIVE_PLAYER_EVENT_INTERNAL } from '../constants';
import { MediaPlayer } from '../model/media-player';
import { GroupingItem } from '../model/grouping-item';

export function getSpeakerList(mainPlayer: MediaPlayer, predefinedGroups: PredefinedGroup[] = []) {
const playerIds = mainPlayer.members.map((member) => member.id).sort();
Expand Down Expand Up @@ -65,3 +66,20 @@ export function getWidth(config: CardConfig) {
export function getGroupPlayerIds(hassEntity: HassEntity): string[] {
return hassEntity.attributes.sonos_group || hassEntity.attributes.group_members || [hassEntity.entity_id];
}

export function getGroupingChanges(groupingItems: GroupingItem[], joinedPlayers: string[], activePlayerId: string) {
const isSelected = groupingItems.filter((item) => item.isSelected);
const unJoin = groupingItems
.filter((item) => !item.isSelected && joinedPlayers.includes(item.player.id))
.map((item) => item.player.id);
const join = groupingItems
.filter((item) => item.isSelected && !joinedPlayers.includes(item.player.id))
.map((item) => item.player.id);

let newMainPlayer = activePlayerId;

if (unJoin.includes(activePlayerId)) {
newMainPlayer = isSelected[0].player.id;
}
return { unJoin, join, newMainPlayer };
}

0 comments on commit 1160ac8

Please sign in to comment.