Skip to content

Commit

Permalink
feature: make it possible to inverse mute button state
Browse files Browse the repository at this point in the history
  • Loading branch information
punxaphil committed Dec 22, 2024
1 parent 6412041 commit cefe948
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 11 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ sectionButtonIcons: # customize icons for the section buttons
volumes: mdi:volume-high
mediaTitleRegexToReplace: '.wav?.*' # Regex pattern to replace parts of the media title
mediaTitleReplacement: ' radio' # Replacement for the media title regex pattern
inverseGroupMuteState: true # default is false, which means that only if all players are muted, mute icon shows as 'muted'. If set to true, mute icon will show as 'muted' if any player is muted.

# groups specific
groupsTitle: ''
Expand Down
3 changes: 2 additions & 1 deletion src/components/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class Volume extends LitElement {
const volume = this.player.getVolume();
const max = this.getMax(volume);

const muteIcon = this.player.isMuted(this.updateMembers) ? mdiVolumeMute : mdiVolumeHigh;
const isMuted = this.updateMembers ? this.player.isGroupMuted() : this.player.isMemberMuted();
const muteIcon = isMuted ? mdiVolumeMute : mdiVolumeHigh;
const disabled = this.player.ignoreVolume;

return html`
Expand Down
4 changes: 4 additions & 0 deletions src/editor/advanced-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ export const ADVANCED_SCHEMA = [
name: 'stopInsteadOfPause',
selector: { boolean: {} },
},
{
name: 'inverseGroupMuteState',
selector: { boolean: {} },
},
];

class AdvancedEditor extends BaseEditor {
Expand Down
11 changes: 9 additions & 2 deletions src/model/media-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@ export class MediaPlayer {
return this.state === 'playing';
}

isMuted(checkMembers: boolean): boolean {
return this.attributes.is_volume_muted && (!checkMembers || this.members.every((member) => member.isMuted(false)));
isMemberMuted() {
return this.attributes.is_volume_muted;
}

isGroupMuted(): boolean {
if (this.config.inverseGroupMuteState) {
return this.members.some((member) => member.isMemberMuted());
}
return this.members.every((member) => member.isMemberMuted());
}

getCurrentTrack() {
Expand Down
3 changes: 2 additions & 1 deletion src/services/media-control-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ export default class MediaControlService {
}

async toggleMute(mediaPlayer: MediaPlayer, updateMembers = true) {
const muteVolume = !mediaPlayer.isMuted(updateMembers);
const isMuted = updateMembers ? mediaPlayer.isGroupMuted() : mediaPlayer.isMemberMuted();
const muteVolume = !isMuted;
await this.setVolumeMute(mediaPlayer, muteVolume, updateMembers);
}

Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export interface CardConfig extends LovelaceCardConfig {
mediaTitleRegexToReplace?: string;
mediaTitleReplacement?: string;
stopInsteadOfPause?: boolean;
inverseGroupMuteState?: boolean;
}

export interface MediaArtworkOverride {
Expand Down
31 changes: 24 additions & 7 deletions test/model/media-player.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('MediaPlayer', () => {
let config: CardConfig;
let mediaPlayer: MediaPlayer;

beforeEach(() => {
function initMediaPlayer(groupSize: number, inverseGroupMuteState = false) {
hassEntity1 = {
entity_id: 'media_player.first',
state: 'playing',
Expand All @@ -21,24 +21,29 @@ describe('MediaPlayer', () => {
media_title: 'Title',
media_content_id: 'http://example.com',
volume_level: 0.5,
group_members: ['media_player.first'],
group_members: ['media_player.first', 'media_player.second'],
},
} as unknown as HassEntity;
hassEntity2 = {
entity_id: 'media_player.second',
attributes: {
friendly_name: 'Second Player',
},
} as HassEntity;
} as unknown as HassEntity;

config = newConfig({
entitiesToIgnoreVolumeLevelFor: ['media_player.ignore'],
mediaTitleRegexToReplace: 'Title',
mediaTitleReplacement: 'Replaced Title',
adjustVolumeRelativeToMainPlayer: true,
inverseGroupMuteState: inverseGroupMuteState,
});

hassEntity1.attributes.group_members.length = groupSize;
mediaPlayer = new MediaPlayer(hassEntity1, config, [hassEntity1, hassEntity2]);
}

beforeEach(() => {
initMediaPlayer(1);
});

it('should initialize correctly', () => {
Expand Down Expand Up @@ -66,10 +71,22 @@ describe('MediaPlayer', () => {
expect(mediaPlayer.isPlaying()).toBe(false);
});

it('should check if is muted correctly', () => {
expect(mediaPlayer.isMuted(false)).toBe(false);
it('should check if member is muted correctly', () => {
expect(mediaPlayer.isMemberMuted()).toBe(false);
mediaPlayer.attributes.is_volume_muted = true;
expect(mediaPlayer.isMuted(false)).toBe(true);
expect(mediaPlayer.isMemberMuted()).toBe(true);
});

it('should check if group is muted correctly', () => {
initMediaPlayer(2);
expect(mediaPlayer.isGroupMuted()).toBe(false);
mediaPlayer.members[1].attributes.is_volume_muted = true;
expect(mediaPlayer.isGroupMuted()).toBe(false);
mediaPlayer.members.forEach((member) => (member.attributes.is_volume_muted = true));
expect(mediaPlayer.isGroupMuted()).toBe(true);
initMediaPlayer(2, true);
mediaPlayer.members[1].attributes.is_volume_muted = true;
expect(mediaPlayer.isGroupMuted()).toBe(true);
});

it('should get current track correctly', () => {
Expand Down

0 comments on commit cefe948

Please sign in to comment.