Skip to content

Commit

Permalink
feature: configurable volume step size
Browse files Browse the repository at this point in the history
  • Loading branch information
punxaphil committed Feb 4, 2024
1 parent 77e9d90 commit 639f58a
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 38 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ entities: # Entities are automatically discovered if you don't supply this setti
- media_player.sonos_livingroom
excludeItemsInEntitiesList: true # Will invert the selection in the `entities` list, so that all players that are not in the list will be used.
showNonSonosPlayers: true # default is false, which means only Sonos players will be shown.
volumeStepSize: 1 # Use this to change the step size when using volume up/down.. Default is to use the step size of Home Assistant's media player integration.

# groups specific
groupsTitle: ''
Expand Down
5 changes: 5 additions & 0 deletions src/editor/advanced-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ export const ADVANCED_SCHEMA = [
name: 'replaceHttpWithHttpsForThumbnails',
selector: { boolean: {} },
},
{
type: 'integer',
name: 'volumeStepSize',
valueMin: 1,
},
];

class AdvancedEditor extends BaseEditor {
Expand Down
6 changes: 3 additions & 3 deletions src/model/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ export default class Store {
)
.sort((a, b) => a.name.localeCompare(b.name));
this.activePlayer = this.determineActivePlayer(activePlayerId);
this.hassService = new HassService(this.hass, currentSection, card);
this.mediaControlService = new MediaControlService(this.hassService);
this.mediaBrowseService = new MediaBrowseService(this.hassService);
this.hassService = new HassService(this.hass, currentSection, card, config);
this.mediaControlService = new MediaControlService(this.hassService, config);
this.mediaBrowseService = new MediaBrowseService(this.hassService, config);
this.predefinedGroups = this.createPredefinedGroups();
}

Expand Down
13 changes: 5 additions & 8 deletions src/services/hass-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ export default class HassService {
private readonly hass: HomeAssistant;
private readonly currentSection: Section;
private readonly card: Element;
private readonly config: CardConfig;

constructor(hass: HomeAssistant, section: Section, card: Element) {
constructor(hass: HomeAssistant, section: Section, card: Element, config: CardConfig) {
this.hass = hass;
this.currentSection = section;
this.card = card;
this.config = config;
}

async callMediaService(service: string, inOptions: ServiceCallRequest['serviceData']) {
Expand All @@ -26,19 +28,14 @@ export default class HassService {
}
}

async browseMedia(
mediaPlayer: MediaPlayer,
config: CardConfig,
media_content_type?: string,
media_content_id?: string,
) {
async browseMedia(mediaPlayer: MediaPlayer, media_content_type?: string, media_content_id?: string) {
const mediaPlayerItem = await this.hass.callWS<MediaPlayerItem>({
type: 'media_player/browse_media',
entity_id: mediaPlayer.id,
media_content_id,
media_content_type,
});
if (config.replaceHttpWithHttpsForThumbnails) {
if (this.config.replaceHttpWithHttpsForThumbnails) {
mediaPlayerItem.children = mediaPlayerItem.children?.map((child) => ({
...child,
thumbnail: child.thumbnail?.replace('http://', 'https://'),
Expand Down
16 changes: 9 additions & 7 deletions src/services/media-browse-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,23 @@ import { indexOfWithoutSpecialChars } from '../utils/media-browser-utils';

export default class MediaBrowseService {
private hassService: HassService;
private config: CardConfig;

constructor(hassService: HassService) {
constructor(hassService: HassService, config: CardConfig) {
this.hassService = hassService;
this.config = config;
}

async getFavorites(player: MediaPlayer, config: CardConfig): Promise<MediaPlayerItem[]> {
async getFavorites(player: MediaPlayer): Promise<MediaPlayerItem[]> {
if (!player) {
return [];
}
let favorites = await this.getFavoritesForPlayer(player, config);
let favorites = await this.getFavoritesForPlayer(player);
favorites = favorites.flatMap((f) => f);
favorites = this.removeDuplicates(favorites);
favorites = favorites.length ? favorites : this.getFavoritesFromStates(player);
return favorites.filter(
(item) => indexOfWithoutSpecialChars(config.mediaBrowserTitlesToIgnore ?? [], item.title) === -1,
(item) => indexOfWithoutSpecialChars(this.config.mediaBrowserTitlesToIgnore ?? [], item.title) === -1,
);
}

Expand All @@ -29,11 +31,11 @@ export default class MediaBrowseService {
});
}

private async getFavoritesForPlayer(player: MediaPlayer, config: CardConfig) {
private async getFavoritesForPlayer(player: MediaPlayer) {
try {
const favoritesRoot = await this.hassService.browseMedia(player, config, 'favorites', '');
const favoritesRoot = await this.hassService.browseMedia(player, 'favorites', '');
const favoriteTypesPromise = favoritesRoot.children?.map((favoriteItem) =>
this.hassService.browseMedia(player, config, favoriteItem.media_content_type, favoriteItem.media_content_id),
this.hassService.browseMedia(player, favoriteItem.media_content_type, favoriteItem.media_content_id),
);
const favoriteTypes = favoriteTypesPromise ? await Promise.all(favoriteTypesPromise) : [];
return favoriteTypes.flatMap((item) => item.children ?? []);
Expand Down
50 changes: 30 additions & 20 deletions src/services/media-control-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { MediaPlayer } from '../model/media-player';

export default class MediaControlService {
private hassService: HassService;
private readonly config: CardConfig;

constructor(hassService: HassService) {
constructor(hassService: HassService, config: CardConfig) {
this.hassService = hassService;
this.config = config;
}

async join(main: string, memberIds: string[]) {
Expand All @@ -28,38 +30,33 @@ export default class MediaControlService {
});
}

async createGroup(
predefinedGroup: PredefinedGroup,
currentGroups: MediaPlayer[],
config: CardConfig,
element: Element,
) {
async createGroup(predefinedGroup: PredefinedGroup, currentGroups: MediaPlayer[], element: Element) {
let candidateGroup!: MediaPlayer;
for (const group of currentGroups) {
if (predefinedGroup.entities.some((item) => item.player.id === group.id)) {
if (group.isPlaying()) {
await this.modifyExistingGroup(group, predefinedGroup, config, element);
await this.modifyExistingGroup(group, predefinedGroup, element);
return;
}
candidateGroup = candidateGroup || group;
}
}
if (candidateGroup) {
await this.modifyExistingGroup(candidateGroup, predefinedGroup, config, element);
await this.modifyExistingGroup(candidateGroup, predefinedGroup, element);
} else {
const { player } = predefinedGroup.entities[0];
dispatchActivePlayerId(player.id, config, element);
dispatchActivePlayerId(player.id, this.config, element);
await this.joinPredefinedGroup(player, predefinedGroup);
}
}

private async modifyExistingGroup(group: MediaPlayer, pg: PredefinedGroup, config: CardConfig, element: Element) {
private async modifyExistingGroup(group: MediaPlayer, pg: PredefinedGroup, element: Element) {
const members = group.members;
const membersNotToBeGrouped = members.filter((member) => !pg.entities.some((item) => item.player.id === member.id));
if (membersNotToBeGrouped?.length) {
await this.unJoin(membersNotToBeGrouped.map((member) => member.id));
}
dispatchActivePlayerId(group.id, config, element);
dispatchActivePlayerId(group.id, this.config, element);
await this.joinPredefinedGroup(group, pg);
for (const pgp of pg.entities) {
const volume = pgp.volume ?? pg.volume;
Expand All @@ -76,19 +73,32 @@ export default class MediaControlService {
}

async volumeDown(mediaPlayer: MediaPlayer, updateMembers = true) {
await this.hassService.callMediaService('volume_down', { entity_id: mediaPlayer.id });
if (updateMembers) {
for (const member of mediaPlayer.members) {
await this.hassService.callMediaService('volume_down', { entity_id: member.id });
if (this.config.volumeStepSize) {
const volume = mediaPlayer.attributes.volume_level * 100;
const newVolume = Math.max(0, volume - this.config.volumeStepSize);
await this.volumeSet(mediaPlayer, newVolume, updateMembers);
return;
} else {
await this.hassService.callMediaService('volume_down', { entity_id: mediaPlayer.id });
if (updateMembers) {
for (const member of mediaPlayer.members) {
await this.hassService.callMediaService('volume_down', { entity_id: member.id });
}
}
}
}

async volumeUp(mediaPlayer: MediaPlayer, updateMembers = true) {
await this.hassService.callMediaService('volume_up', { entity_id: mediaPlayer.id });
if (updateMembers) {
for (const member of mediaPlayer.members) {
await this.hassService.callMediaService('volume_up', { entity_id: member.id });
if (this.config.volumeStepSize) {
const volume = mediaPlayer.attributes.volume_level * 100;
const newVolume = Math.min(100, volume + this.config.volumeStepSize);
await this.volumeSet(mediaPlayer, newVolume, updateMembers);
} else {
await this.hassService.callMediaService('volume_up', { entity_id: mediaPlayer.id });
if (updateMembers) {
for (const member of mediaPlayer.members) {
await this.hassService.callMediaService('volume_up', { entity_id: member.id });
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface CardConfig extends LovelaceCardConfig {
fallbackArtwork?: string;
entitiesToIgnoreVolumeLevelFor?: string[];
replaceHttpWithHttpsForThumbnails?: boolean;
volumeStepSize?: number;
}

export interface MediaArtworkOverride {
Expand Down

0 comments on commit 639f58a

Please sign in to comment.