Media Browser: Use Media Class (#6904)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Zack Barett 2020-09-12 11:59:19 -05:00 committed by GitHub
parent 7e70ba6ab2
commit 8b490c5047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 313 additions and 165 deletions

View File

@ -2,7 +2,7 @@ import "@material/mwc-button/mwc-button";
import "@material/mwc-fab/mwc-fab";
import "@material/mwc-list/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiArrowLeft, mdiClose, mdiFolder, mdiPlay, mdiPlus } from "@mdi/js";
import { mdiArrowLeft, mdiClose, mdiPlay, mdiPlus } from "@mdi/js";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
@ -19,7 +19,6 @@ import {
import { classMap } from "lit-html/directives/class-map";
import { ifDefined } from "lit-html/directives/if-defined";
import { styleMap } from "lit-html/directives/style-map";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import { computeRTLDirection } from "../../common/util/compute_rtl";
import { debounce } from "../../common/util/debounce";
@ -27,6 +26,7 @@ import {
browseLocalMediaPlayer,
browseMediaPlayer,
BROWSER_SOURCE,
MediaClassBrowserSettings,
MediaPickedEvent,
MediaPlayerBrowseAction,
} from "../../data/media-player";
@ -93,34 +93,6 @@ export class HaMediaPlayerBrowse extends LitElement {
this._navigate(item);
}
private _renderError(err: { message: string; code: string }) {
if (err.message === "Media directory does not exist.") {
return html`
<h2>No local media found.</h2>
<p>
It looks like you have not yet created a media directory.
<br />Create a directory with the name <b>"media"</b> in the
configuration directory of Home Assistant
(${this.hass.config.config_dir}). <br />Place your video, audio and
image files in this directory to be able to browse and play them in
the browser or on supported media players.
</p>
<p>
Check the
<a
href="https://www.home-assistant.io/integrations/media_source/#local-media"
target="_blank"
rel="noreferrer"
>documentation</a
>
for more info
</p>
`;
}
return err.message;
}
protected render(): TemplateResult {
if (this._loading) {
return html`<ha-circular-progress active></ha-circular-progress>`;
@ -136,7 +108,7 @@ export class HaMediaPlayerBrowse extends LitElement {
text: this._renderError(this._error),
});
} else {
return html`<div class="container error">
return html`<div class="container">
${this._renderError(this._error)}
</div>`;
}
@ -155,17 +127,12 @@ export class HaMediaPlayerBrowse extends LitElement {
? this._mediaPlayerItems[this._mediaPlayerItems.length - 2]
: undefined;
const hasExpandableChildren:
| MediaPlayerItem
| undefined = this._hasExpandableChildren(currentItem.children);
const showImages: boolean | undefined = currentItem.children?.some(
(child) => child.thumbnail && child.thumbnail !== currentItem.thumbnail
);
const mediaType = this.hass.localize(
`ui.components.media-browser.content-type.${currentItem.media_content_type}`
const subtitle = this.hass.localize(
`ui.components.media-browser.class.${currentItem.media_class}`
);
const mediaClass = MediaClassBrowserSettings[currentItem.media_class];
const childrenMediaClass =
MediaClassBrowserSettings[currentItem.children_media_class];
return html`
<div
@ -174,6 +141,7 @@ export class HaMediaPlayerBrowse extends LitElement {
"no-dialog": !this.dialog,
})}"
>
<div class="header-wrapper">
<div class="header-content">
${currentItem.thumbnail
? html`
@ -197,7 +165,9 @@ export class HaMediaPlayerBrowse extends LitElement {
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
)}
.path=${this.action === "play" ? mdiPlay : mdiPlus}
.path=${this.action === "play"
? mdiPlay
: mdiPlus}
></ha-svg-icon>
${this.hass.localize(
`ui.components.media-browser.${this.action}`
@ -219,15 +189,16 @@ export class HaMediaPlayerBrowse extends LitElement {
`
: ""}
<h1 class="title">${currentItem.title}</h1>
${mediaType
${subtitle
? html`
<h2 class="subtitle">
${mediaType}
${subtitle}
</h2>
`
: ""}
</div>
${currentItem.can_play && (!currentItem.thumbnail || !this._narrow)
${currentItem.can_play &&
(!currentItem.thumbnail || !this._narrow)
? html`
<mwc-button
raised
@ -262,14 +233,21 @@ export class HaMediaPlayerBrowse extends LitElement {
`
: ""}
</div>
</div>
${this._error
? html`<div class="container error">
${this._renderError(this._error)}
</div>`
: currentItem.children?.length
? hasExpandableChildren
? html`
<div class="children">
<div class="container error">
${this._renderError(this._error)}
</div>
`
: currentItem.children?.length
? childrenMediaClass.layout === "grid"
? html`
<div
class="children ${classMap({
portrait: childrenMediaClass.thumbnail_ratio === "portrait",
})}"
>
${currentItem.children.map(
(child) => html`
<div
@ -286,11 +264,16 @@ export class HaMediaPlayerBrowse extends LitElement {
: "none",
})}
>
${child.can_expand && !child.thumbnail
${!child.thumbnail
? html`
<ha-svg-icon
class="folder"
.path=${mdiFolder}
.path=${MediaClassBrowserSettings[
child.media_class === "directory"
? child.children_media_class ||
child.media_class
: child.media_class
].icon}
></ha-svg-icon>
`
: ""}
@ -298,7 +281,9 @@ export class HaMediaPlayerBrowse extends LitElement {
${child.can_play
? html`
<mwc-icon-button
class="play"
class="play ${classMap({
can_expand: child.can_expand,
})}"
.item=${child}
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
@ -330,7 +315,7 @@ export class HaMediaPlayerBrowse extends LitElement {
${currentItem.children.map(
(child) => html`
<mwc-list-item
@click=${this._actionClicked}
@click=${this._childClicked}
.item=${child}
graphic="avatar"
hasMeta
@ -339,7 +324,7 @@ export class HaMediaPlayerBrowse extends LitElement {
<div
class="graphic"
style=${ifDefined(
showImages && child.thumbnail
mediaClass.show_list_images && child.thumbnail
? `background-image: url(${child.thumbnail})`
: undefined
)}
@ -347,7 +332,8 @@ export class HaMediaPlayerBrowse extends LitElement {
>
<mwc-icon-button
class="play ${classMap({
show: !showImages || !child.thumbnail,
show:
!mediaClass.show_list_images || !child.thumbnail,
})}"
.item=${child}
.label=${this.hass.localize(
@ -367,9 +353,11 @@ export class HaMediaPlayerBrowse extends LitElement {
)}
</mwc-list>
`
: html`<div class="container">
: html`
<div class="container">
${this.hass.localize("ui.components.media-browser.no_items")}
</div>`}
</div>
`}
`;
}
@ -504,14 +492,38 @@ export class HaMediaPlayerBrowse extends LitElement {
this._resizeObserver.observe(this);
}
private _hasExpandableChildren = memoizeOne((children?: MediaPlayerItem[]) =>
children?.find((item: MediaPlayerItem) => item.can_expand)
);
private _closeDialogAction(): void {
fireEvent(this, "close-dialog");
}
private _renderError(err: { message: string; code: string }) {
if (err.message === "Media directory does not exist.") {
return html`
<h2>No local media found.</h2>
<p>
It looks like you have not yet created a media directory.
<br />Create a directory with the name <b>"media"</b> in the
configuration directory of Home Assistant
(${this.hass.config.config_dir}). <br />Place your video, audio and
image files in this directory to be able to browse and play them in
the browser or on supported media players.
</p>
<p>
Check the
<a
href="https://www.home-assistant.io/integrations/media_source/#local-media"
target="_blank"
rel="noreferrer"
>documentation</a
>
for more info
</p>
`;
}
return html`<span class="error">err.message</span>`;
}
static get styles(): CSSResultArray {
return [
haStyle,
@ -529,12 +541,9 @@ export class HaMediaPlayerBrowse extends LitElement {
}
.header {
display: flex;
display: block;
justify-content: space-between;
border-bottom: 1px solid var(--divider-color);
}
.header {
background-color: var(--card-background-color);
position: sticky;
position: -webkit-sticky;
@ -543,6 +552,10 @@ export class HaMediaPlayerBrowse extends LitElement {
padding: 20px 24px 10px;
}
.header-wrapper {
display: flex;
}
.header-content {
display: flex;
flex-wrap: wrap;
@ -570,6 +583,7 @@ export class HaMediaPlayerBrowse extends LitElement {
.header-info mwc-button {
display: block;
--mdc-theme-primary: var(--primary-color);
}
.breadcrumb {
@ -655,7 +669,7 @@ export class HaMediaPlayerBrowse extends LitElement {
width: 100%;
}
ha-card {
.children ha-card {
width: 100%;
padding-bottom: 100%;
position: relative;
@ -663,6 +677,11 @@ export class HaMediaPlayerBrowse extends LitElement {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
transition: padding-bottom 0.1s ease-out;
}
.portrait.children ha-card {
padding-bottom: 150%;
}
.child .folder,
@ -678,18 +697,36 @@ export class HaMediaPlayerBrowse extends LitElement {
}
.child .play {
transition: color 0.5s;
border-radius: 50%;
bottom: calc(50% - 35px);
right: calc(50% - 35px);
opacity: 0;
transition: opacity 0.1s ease-out;
}
.child .play:not(.can_expand) {
--mdc-icon-button-size: 70px;
--mdc-icon-size: 48px;
}
.ha-card-parent:hover .play:not(.can_expand) {
opacity: 1;
color: var(--primary-color);
}
.child .play.can_expand {
opacity: 1;
background-color: rgba(var(--rgb-card-background-color), 0.5);
bottom: 4px;
right: 4px;
transition: all 0.5s;
background-color: rgba(var(--rgb-card-background-color), 0.5);
border-radius: 50%;
}
.child .play:hover {
color: var(--primary-color);
}
ha-card:hover {
.ha-card-parent:hover ha-card {
opacity: 0.5;
}
@ -706,6 +743,7 @@ export class HaMediaPlayerBrowse extends LitElement {
.child .type {
font-size: 12px;
color: var(--secondary-text-color);
padding-left: 2px;
}
mwc-list-item .graphic {

View File

@ -1,5 +1,23 @@
import type { HassEntity } from "home-assistant-js-websocket";
import type { HomeAssistant } from "../types";
import {
mdiFolder,
mdiPlaylistMusic,
mdiFileMusic,
mdiAlbum,
mdiMusic,
mdiTelevisionClassic,
mdiMovie,
mdiVideo,
mdiImage,
mdiWeb,
mdiGamepadVariant,
mdiAccountMusic,
mdiPodcast,
mdiApplication,
mdiAccountMusicOutline,
mdiDramaMasks,
} from "@mdi/js";
export const SUPPORT_PAUSE = 1;
export const SUPPORT_SEEK = 2;
@ -22,6 +40,66 @@ export type MediaPlayerBrowseAction = "pick" | "play";
export const BROWSER_SOURCE = "browser";
export type MediaClassBrowserSetting = {
icon: string;
thumbnail_ratio?: string;
layout?: string;
show_list_images?: boolean;
};
export const MediaClassBrowserSettings: {
[type: string]: MediaClassBrowserSetting;
} = {
album: { icon: mdiAlbum, layout: "grid" },
app: { icon: mdiApplication, layout: "grid" },
artist: { icon: mdiAccountMusic, layout: "grid", show_list_images: true },
channel: {
icon: mdiTelevisionClassic,
thumbnail_ratio: "portrait",
layout: "grid",
},
composer: {
icon: mdiAccountMusicOutline,
layout: "grid",
show_list_images: true,
},
contributing_artist: {
icon: mdiAccountMusic,
layout: "grid",
show_list_images: true,
},
directory: { icon: mdiFolder, layout: "grid", show_list_images: true },
episode: {
icon: mdiTelevisionClassic,
layout: "grid",
thumbnail_ratio: "portrait",
},
game: {
icon: mdiGamepadVariant,
layout: "grid",
thumbnail_ratio: "portrait",
},
genre: { icon: mdiDramaMasks, layout: "grid", show_list_images: true },
image: { icon: mdiImage, layout: "grid" },
movie: { icon: mdiMovie, thumbnail_ratio: "portrait", layout: "grid" },
music: { icon: mdiMusic },
playlist: { icon: mdiPlaylistMusic, layout: "grid", show_list_images: true },
podcast: { icon: mdiPodcast, layout: "grid" },
season: {
icon: mdiTelevisionClassic,
layout: "grid",
thumbnail_ratio: "portrait",
},
track: { icon: mdiFileMusic },
tv_show: {
icon: mdiTelevisionClassic,
layout: "grid",
thumbnail_ratio: "portrait",
},
url: { icon: mdiWeb },
video: { icon: mdiVideo, layout: "grid" },
};
export interface MediaPickedEvent {
item: MediaPlayerItem;
}
@ -40,6 +118,8 @@ export interface MediaPlayerItem {
title: string;
media_content_type: string;
media_content_id: string;
media_class: string;
children_media_class: string;
can_play: boolean;
can_expand: boolean;
thumbnail?: string;

View File

@ -1,5 +1,4 @@
import "@material/mwc-icon-button";
import { mdiPlayNetwork } from "@mdi/js";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import {
@ -46,9 +45,9 @@ class PanelMediaBrowser extends LitElement {
const title =
this._entityId === BROWSER_SOURCE
? `${this.hass.localize("ui.components.media-browser.web-browser")} - `
? `${this.hass.localize("ui.components.media-browser.web-browser")}`
: stateObj?.attributes.friendly_name
? `${stateObj?.attributes.friendly_name} - `
? `${stateObj?.attributes.friendly_name}`
: undefined;
return html`
@ -59,16 +58,16 @@ class PanelMediaBrowser extends LitElement {
.hass=${this.hass}
.narrow=${this.narrow}
></ha-menu-button>
<div main-title>
${title || ""}${this.hass.localize(
<div main-title class="heading">
<div>
${this.hass.localize(
"ui.components.media-browser.media-player-browser"
)}
</div>
<div class="secondary">${title || ""}</div>
</div>
<mwc-button @click=${this._showSelectMediaPlayerDialog}>
<ha-svg-icon .path=${mdiPlayNetwork}></ha-svg-icon>
${this.hass.localize(
"ui.components.media-browser.choose_player"
)}
${this.hass.localize("ui.components.media-browser.choose_player")}
</mwc-button>
</app-toolbar>
</app-header>
@ -134,9 +133,25 @@ class PanelMediaBrowser extends LitElement {
return [
haStyle,
css`
:host {
--mdc-theme-primary: var(--app-header-text-color);
}
ha-media-player-browse {
height: calc(100vh - 84px);
}
:host([narrow]) app-toolbar mwc-button {
width: 65px;
}
.heading {
overflow: hidden;
white-space: nowrap;
}
.heading .secondary {
color: var(--secondary-text-color);
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
}
`,
];
}

View File

@ -373,12 +373,27 @@
"video_not_supported": "Your browser does not support the video element.",
"media_not_supported": "The Browser Media Player does not support this type of media",
"media_browsing_error": "Media Browsing Error",
"content-type": {
"server": "Server",
"library": "Library",
"artist": "Artist",
"class": {
"album": "Album",
"playlist": "Playlist"
"app": "App",
"artist": "Artist",
"channel": "Channel",
"composer": "Composer",
"contributing_artist": "Contributing Artist",
"directory": "Library",
"episode": "Episode",
"game": "Game",
"genre": "Genre",
"image": "Image",
"movie": "Movie",
"music": "Music",
"playlist": "Playlist",
"podcast": "Podcast",
"season": "Season",
"track": "Track",
"tv_show": "TV Show",
"url": "Url",
"video": "Video"
}
}
},