diff --git a/src/components/media-browser-header.ts b/src/components/media-browser-header.ts
index 5c5027c4..3c81e6d9 100644
--- a/src/components/media-browser-header.ts
+++ b/src/components/media-browser-header.ts
@@ -1,59 +1,36 @@
import { css, html, LitElement } from 'lit';
-import { property, state } from 'lit/decorators.js';
-import { CardConfig } from '../types';
-import { mdiArrowUpLeftBold, mdiPlay, mdiPlayBoxMultiple, mdiStarOutline } from '@mdi/js';
-import { BROWSE_CLICKED, BROWSE_STATE, PLAY_DIR } from '../constants';
-import { iconButton } from './icon-button';
+import { property } from 'lit/decorators.js';
+import { MediaPlayerEntityFeature } from '../types';
+import Store from '../model/store';
+import { styleMap } from 'lit-html/directives/style-map.js';
class MediaBrowserHeader extends LitElement {
- @property() config!: CardConfig;
- @state() browseCanPlay!: boolean;
- @state() browseMedia = true;
- @state() mediaBrowserDir!: string;
- @state() title!: string;
+ @property() store!: Store;
render() {
- const browseIcon = this.browseMedia
- ? mdiPlayBoxMultiple
- : this.mediaBrowserDir
- ? mdiArrowUpLeftBold
- : mdiStarOutline;
+ const state = this.store.hass.states[this.store.activePlayer.id];
+ const playerState = {
+ ...state,
+ attributes: { ...state.attributes, supported_features: MediaPlayerEntityFeature.BROWSE_MEDIA },
+ };
return html`
-
- ${this.browseCanPlay
- ? iconButton(mdiPlay, () => window.dispatchEvent(new CustomEvent(PLAY_DIR)), {
- additionalStyle: { padding: '0.5rem' },
- })
- : ''}
-
- ${this.title}
- ${iconButton(browseIcon, () => window.dispatchEvent(new CustomEvent(BROWSE_CLICKED)), {
- additionalStyle: { padding: '0.5rem', flex: '1', textAlign: 'right' },
- })}
+ All Favorites
+
`;
}
- connectedCallback() {
- super.connectedCallback();
- window.addEventListener(BROWSE_STATE, (event: Event) => {
- const detail = (event as CustomEvent).detail;
- this.browseCanPlay = detail.canPlay;
- this.browseMedia = !detail.browse;
- this.mediaBrowserDir = detail.currentDir;
- this.title = detail.title;
- });
- }
static get styles() {
return css`
:host {
display: flex;
justify-content: space-between;
}
- .play {
- flex: 1;
- }
.title {
- flex: 6;
+ flex: 1;
text-align: center;
font-size: 1.2rem;
font-weight: bold;
diff --git a/src/components/media-browser-icons.ts b/src/components/media-browser-icons.ts
index 75604e53..d37eb08c 100755
--- a/src/components/media-browser-icons.ts
+++ b/src/components/media-browser-icons.ts
@@ -26,13 +26,12 @@ export class MediaBrowserIcons extends LitElement {
${itemsWithFallbacks(this.items, this.config).map(
- (item, index) =>
- html`
- ${mediaItemBackgroundImageStyle(item.thumbnail, index)}
- dispatchMediaItemSelected(item)}">
- ${renderMediaBrowserItem(item, !item.thumbnail || !!this.config.mediaBrowserShowTitleForThumbnailIcons)}
-
- `,
+ (item, index) => html`
+ ${mediaItemBackgroundImageStyle(item.thumbnail, index)}
+ dispatchMediaItemSelected(item)}">
+ ${renderMediaBrowserItem(item, !item.thumbnail || !!this.config.mediaBrowserShowTitleForThumbnailIcons)}
+
+ `,
)}
`;
@@ -63,11 +62,6 @@ export class MediaBrowserIcons extends LitElement {
background-position: center;
}
- .folder {
- margin: 5% 15% 25% 15%;
- --mdc-icon-size: 100%;
- }
-
.title {
font-size: 0.8rem;
position: absolute;
diff --git a/src/components/media-browser-list.ts b/src/components/media-browser-list.ts
index 69a816f8..bb78cbb5 100755
--- a/src/components/media-browser-list.ts
+++ b/src/components/media-browser-list.ts
@@ -52,11 +52,6 @@ export class MediaBrowserList extends LitElement {
background-position: left;
}
- .folder {
- width: var(--icon-width);
- --mdc-icon-size: 100%;
- }
-
.title {
font-size: 1.1rem;
align-self: center;
diff --git a/src/constants.ts b/src/constants.ts
index fb163cbd..6fc34878 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -6,9 +6,6 @@ export const REQUEST_PLAYER_EVENT = dispatchPrefix + 'request-player';
export const SHOW_SECTION = dispatchPrefix + 'show-section';
export const CALL_MEDIA_STARTED = dispatchPrefix + 'call-media-started';
export const CALL_MEDIA_DONE = dispatchPrefix + 'call-media-done';
-export const PLAY_DIR = dispatchPrefix + 'play-dir';
-export const BROWSE_CLICKED = dispatchPrefix + 'browse-up';
-export const BROWSE_STATE = dispatchPrefix + 'browse-state';
export const MEDIA_ITEM_SELECTED = dispatchPrefix + 'media-item-selected';
export const TV_BASE64_IMAGE =
diff --git a/src/model/media-player.ts b/src/model/media-player.ts
index c0f26354..008569ce 100644
--- a/src/model/media-player.ts
+++ b/src/model/media-player.ts
@@ -7,9 +7,7 @@ export class MediaPlayer {
name: string;
state: string;
members: MediaPlayer[];
- attributes: {
- [key: string]: any;
- };
+ attributes: HassEntity['attributes'];
private readonly config: CardConfig;
constructor(hassEntity: HassEntity, config: CardConfig, mediaPlayerHassEntities?: HassEntity[]) {
@@ -21,9 +19,6 @@ export class MediaPlayer {
this.members = mediaPlayerHassEntities ? this.createGroupMembers(hassEntity, mediaPlayerHassEntities) : [];
}
- hasPlayer(playerId: string) {
- return this.getPlayer(playerId) !== undefined;
- }
getPlayer(playerId: string) {
return this.id === playerId ? this : this.getMember(playerId);
}
diff --git a/src/sections/media-browser.ts b/src/sections/media-browser.ts
index b430a95b..307c63bd 100755
--- a/src/sections/media-browser.ts
+++ b/src/sections/media-browser.ts
@@ -1,50 +1,32 @@
import { html, LitElement } from 'lit';
-import { until } from 'lit-html/directives/until.js';
-import { property, state } from 'lit/decorators.js';
+import { property } from 'lit/decorators.js';
import '../components/media-browser-list';
import '../components/media-browser-icons';
import '../components/media-browser-header';
-import MediaBrowseService from '../services/media-browse-service';
import MediaControlService from '../services/media-control-service';
import Store from '../model/store';
import { CardConfig, MediaPlayerItem, Section } from '../types';
import { dispatchShowSection } from '../utils/utils';
-import { BROWSE_CLICKED, BROWSE_STATE, MEDIA_ITEM_SELECTED, PLAY_DIR } from '../constants';
+import { MEDIA_ITEM_SELECTED } from '../constants';
import { MediaPlayer } from '../model/media-player';
+import { until } from 'lit-html/directives/until.js';
+import MediaBrowseService from '../services/media-browse-service';
import { indexOfWithoutSpecialChars } from '../utils/media-browser-utils';
-const LOCAL_STORAGE_CURRENT_DIR = 'custom-sonos-card_currentDir';
-const LOCAL_STORAGE_BROWSE = 'custom-sonos-card_browse';
-
export class MediaBrowser extends LitElement {
@property() store!: Store;
private config!: CardConfig;
private activePlayer!: MediaPlayer;
- @state() private browse!: boolean;
- @state() private currentDir?: MediaPlayerItem;
private mediaPlayers!: MediaPlayer[];
- private parentDirs: MediaPlayerItem[] = [];
private mediaControlService!: MediaControlService;
private mediaBrowseService!: MediaBrowseService;
- private readonly playDirListener = async () => {
- await this.playItem(this.currentDir);
- };
-
- private readonly browseClickedListener = async () => {
- this.browseClicked();
- };
-
connectedCallback() {
super.connectedCallback();
- window.addEventListener(PLAY_DIR, this.playDirListener);
- window.addEventListener(BROWSE_CLICKED, this.browseClickedListener);
window.addEventListener(MEDIA_ITEM_SELECTED, this.onMediaItemSelected);
}
disconnectedCallback() {
- window.removeEventListener(PLAY_DIR, this.playDirListener);
- window.removeEventListener(BROWSE_CLICKED, this.browseClickedListener);
window.removeEventListener(MEDIA_ITEM_SELECTED, this.onMediaItemSelected);
super.disconnectedCallback();
}
@@ -56,23 +38,13 @@ export class MediaBrowser extends LitElement {
this.mediaPlayers = this.store.allMediaPlayers;
this.mediaControlService = this.store.mediaControlService;
- const currentDirJson = localStorage.getItem(LOCAL_STORAGE_CURRENT_DIR);
- if (currentDirJson) {
- const currentDir = JSON.parse(currentDirJson);
- if (currentDir !== this.currentDir) {
- this.currentDir = currentDir;
- this.browse = true;
- this.dispatchBrowseState();
- }
- } else {
- this.browse = !!localStorage.getItem(LOCAL_STORAGE_BROWSE);
- }
return html`
-
+
+
${this.activePlayer &&
until(
- (this.browse ? this.loadMediaDir(this.currentDir) : this.getAllFavorites()).then((items) => {
- return this.config.mediaBrowserItemsPerRow > 1 && this.currentDir?.children_media_class !== 'track'
+ this.getAllFavorites().then((items) => {
+ return this.config.mediaBrowserItemsPerRow > 1
? html``
: html` `;
}),
@@ -80,60 +52,10 @@ export class MediaBrowser extends LitElement {
`;
}
- firstUpdated() {
- this.dispatchBrowseState();
- }
-
- private dispatchBrowseState() {
- const title = !this.browse ? 'All Favorites' : this.currentDir ? this.currentDir.title : 'Media Browser';
- window.dispatchEvent(
- new CustomEvent(BROWSE_STATE, {
- detail: {
- canPlay: this.currentDir?.can_play,
- browse: this.browse,
- currentDir: this.currentDir,
- title,
- },
- }),
- );
- }
-
- private browseClicked() {
- if (this.parentDirs.length) {
- this.setCurrentDir(this.parentDirs.pop());
- } else if (this.currentDir) {
- this.setCurrentDir(undefined);
- } else {
- this.browse = !this.browse;
-
- if (this.browse) {
- localStorage.setItem(LOCAL_STORAGE_BROWSE, 'true');
- } else {
- localStorage.removeItem(LOCAL_STORAGE_BROWSE);
- }
- this.dispatchBrowseState();
- }
- }
-
- private setCurrentDir(mediaItem?: MediaPlayerItem) {
- this.currentDir = mediaItem;
- if (mediaItem) {
- localStorage.setItem(LOCAL_STORAGE_CURRENT_DIR, JSON.stringify(mediaItem));
- } else {
- localStorage.removeItem(LOCAL_STORAGE_CURRENT_DIR);
- }
- this.dispatchBrowseState();
- }
-
private onMediaItemSelected = (event: Event) => {
const mediaItem = (event as CustomEvent).detail;
- if (mediaItem.can_expand) {
- this.currentDir && this.parentDirs.push(this.currentDir);
- this.setCurrentDir(mediaItem);
- } else if (mediaItem.can_play) {
- this.playItem(mediaItem);
- setTimeout(() => dispatchShowSection(Section.PLAYER), 1000);
- }
+ this.playItem(mediaItem);
+ setTimeout(() => dispatchShowSection(Section.PLAYER), 1000);
};
private async playItem(mediaItem: MediaPlayerItem) {
@@ -179,10 +101,4 @@ export class MediaBrowser extends LitElement {
private static createSource(source: MediaPlayerItem) {
return { ...source, can_play: true };
}
-
- private async loadMediaDir(mediaItem?: MediaPlayerItem) {
- return await (mediaItem
- ? this.mediaBrowseService.getDir(this.activePlayer, mediaItem, this.config.mediaBrowserTitlesToIgnore)
- : this.mediaBrowseService.getRoot(this.activePlayer, this.config.mediaBrowserTitlesToIgnore));
- }
}
diff --git a/src/services/media-browse-service.ts b/src/services/media-browse-service.ts
index 9c926e92..3f3981e4 100644
--- a/src/services/media-browse-service.ts
+++ b/src/services/media-browse-service.ts
@@ -3,14 +3,6 @@ import HassService from './hass-service';
import { MediaPlayer } from '../model/media-player';
import { indexOfWithoutSpecialChars } from '../utils/media-browser-utils';
-function mediaBrowserFilter(ignoredTitles: string[] = [], items?: MediaPlayerItem[]) {
- return items?.filter(
- (item) =>
- !['media-source://tts', 'media-source://camera'].includes(item.media_content_id || '') &&
- indexOfWithoutSpecialChars(ignoredTitles, item.title) === -1,
- );
-}
-
export default class MediaBrowseService {
private hassService: HassService;
@@ -18,31 +10,15 @@ export default class MediaBrowseService {
this.hassService = hassService;
}
- async getRoot(mediaPlayer: MediaPlayer, ignoredTitles?: string[]): Promise {
- const root = await this.hassService.browseMedia(mediaPlayer);
- return mediaBrowserFilter(ignoredTitles, root.children) || [];
- }
-
- async getDir(mediaPlayer: MediaPlayer, dir: MediaPlayerItem, ignoredTitles?: string[]): Promise {
- try {
- const dirItem = await this.hassService.browseMedia(mediaPlayer, dir.media_content_type, dir.media_content_id);
- return mediaBrowserFilter(ignoredTitles, dirItem.children) || [];
- } catch (e) {
- console.error(e);
- return [];
- }
- }
-
async getAllFavorites(mediaPlayers: MediaPlayer[], ignoredTitles?: string[]): Promise {
if (!mediaPlayers.length) {
return [];
}
- const favoritesForAllPlayers = await Promise.all(
- mediaPlayers.map((player) => this.getFavoritesForPlayer(player, ignoredTitles)),
- );
+ const favoritesForAllPlayers = await Promise.all(mediaPlayers.map((player) => this.getFavoritesForPlayer(player)));
let favorites = favoritesForAllPlayers.flatMap((f) => f);
favorites = this.removeDuplicates(favorites);
- return favorites.length ? favorites : this.getFavoritesFromStates(mediaPlayers);
+ favorites = favorites.length ? favorites : this.getFavoritesFromStates(mediaPlayers);
+ return favorites.filter((item) => indexOfWithoutSpecialChars(ignoredTitles ?? [], item.title) === -1);
}
private removeDuplicates(items: MediaPlayerItem[]) {
@@ -51,13 +27,13 @@ export default class MediaBrowseService {
});
}
- private async getFavoritesForPlayer(player: MediaPlayer, ignoredTitles?: string[]) {
+ private async getFavoritesForPlayer(player: MediaPlayer) {
const favoritesRoot = await this.hassService.browseMedia(player, 'favorites', '');
const favoriteTypesPromise = favoritesRoot.children?.map((favoriteItem) =>
this.hassService.browseMedia(player, favoriteItem.media_content_type, favoriteItem.media_content_id),
);
const favoriteTypes = favoriteTypesPromise ? await Promise.all(favoriteTypesPromise) : [];
- return favoriteTypes.flatMap((item) => mediaBrowserFilter(ignoredTitles, item.children) || []);
+ return favoriteTypes.flatMap((item) => item.children ?? []);
}
private getFavoritesFromStates(mediaPlayers: MediaPlayer[]) {
diff --git a/src/types.ts b/src/types.ts
index 54fefd17..625f18dc 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -36,8 +36,8 @@ export interface CardConfig extends LovelaceCardConfig {
dynamicVolumeSlider: boolean;
mediaArtworkOverrides?: MediaArtworkOverride[];
customSources?: CustomSources;
- customThumbnail?: CustomThumbnail;
- customThumbnailIfMissing?: CustomThumbnail;
+ customThumbnail?: CustomThumbnails;
+ customThumbnailIfMissing?: CustomThumbnails;
mediaBrowserTitlesToIgnore?: string[];
mediaBrowserItemsPerRow: number;
mediaBrowserShowTitleForThumbnailIcons?: boolean;
@@ -62,7 +62,7 @@ export interface CustomSource {
thumbnail?: string;
}
-export interface CustomThumbnail {
+export interface CustomThumbnails {
[title: string]: string;
}
@@ -72,11 +72,8 @@ export interface MediaPlayerItem {
children?: MediaPlayerItem[];
children_media_class?: string;
media_class?: string;
- can_expand?: boolean;
- can_play?: boolean;
media_content_type?: string;
media_content_id?: string;
- showFolderIcon?: boolean;
}
export interface PredefinedGroup {
@@ -95,3 +92,26 @@ export interface PredefinedGroupPlayer {
export interface TemplateResult {
result: string[];
}
+
+export const enum MediaPlayerEntityFeature {
+ PAUSE = 1,
+ SEEK = 2,
+ VOLUME_SET = 4,
+ VOLUME_MUTE = 8,
+ PREVIOUS_TRACK = 16,
+ NEXT_TRACK = 32,
+
+ TURN_ON = 128,
+ TURN_OFF = 256,
+ PLAY_MEDIA = 512,
+ VOLUME_BUTTONS = 1024,
+ SELECT_SOURCE = 2048,
+ STOP = 4096,
+ CLEAR_PLAYLIST = 8192,
+ PLAY = 16384,
+ SHUFFLE_SET = 32768,
+ SELECT_SOUND_MODE = 65536,
+ BROWSE_MEDIA = 131072,
+ REPEAT_SET = 262144,
+ GROUPING = 524288,
+}
diff --git a/src/utils/media-browser-utils.ts b/src/utils/media-browser-utils.ts
index 8f087ec3..60e78ef7 100644
--- a/src/utils/media-browser-utils.ts
+++ b/src/utils/media-browser-utils.ts
@@ -1,4 +1,4 @@
-import { CardConfig, MediaPlayerItem } from '../types';
+import { CardConfig, CustomThumbnails, MediaPlayerItem } from '../types';
import { html } from 'lit';
const DEFAULT_MEDIA_THUMBNAIL =
@@ -8,10 +8,10 @@ function hasItemsWithImage(items: MediaPlayerItem[]) {
return items.some((item) => item.thumbnail);
}
-function getValueFromKeyIgnoreSpecialChars(array: { [title: string]: string } | undefined, str: string) {
- for (const key in array) {
- if (removeSpecialChars(key) === removeSpecialChars(str)) {
- return array[key];
+function getValueFromKeyIgnoreSpecialChars(customThumbnails: CustomThumbnails | undefined, currentTitle: string) {
+ for (const title in customThumbnails) {
+ if (removeSpecialChars(title) === removeSpecialChars(currentTitle)) {
+ return customThumbnails[title];
}
}
return undefined;
@@ -51,7 +51,6 @@ export function itemsWithFallbacks(mediaPlayerItems: MediaPlayerItem[], config:
return {
...item,
thumbnail,
- showFolderIcon: item.can_expand && !thumbnail,
};
});
}
@@ -69,7 +68,6 @@ export function mediaItemBackgroundImageStyle(thumbnail: string, index: number)
export function renderMediaBrowserItem(item: MediaPlayerItem, showTitle = true) {
return html`
-
${item.title}
`;
}