Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 6 additions & 48 deletions src/panels/config/cloud/account/cloud-tts-pref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,19 @@ import {
updateCloudPref,
} from "../../../../data/cloud";
import type { HomeAssistant } from "../../../../types";
import { convertTextToSpeech } from "../../../../data/tts";
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
import { translationMetadata } from "../../../../resources/translations-metadata";
import { caseInsensitiveCompare } from "../../../../common/string/compare";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import { showTryTtsDialog } from "./show-dialog-cloud-tts-try";

@customElement("cloud-tts-pref")
export class CloudTTSPref extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

@property() public cloudStatus?: CloudStatusLoggedIn;

@internalProperty() private loadingExample = false;

@internalProperty() private savingPreferences = false;

@internalProperty() private ttsInfo?: CloudTTSInfo;
Expand All @@ -62,12 +60,9 @@ export class CloudTTSPref extends LitElement {
header=${this.hass.localize("ui.panel.config.cloud.account.tts.title")}
>
<div class="example">
<mwc-button
@click=${this._playExample}
.disabled=${this.loadingExample}
>
<mwc-button @click=${this._openTryDialog}>
<ha-svg-icon .path=${mdiPlayCircleOutline}></ha-svg-icon>
&nbsp;Example
&nbsp;${this.hass.localize("ui.panel.config.cloud.account.tts.try")}
</mwc-button>
</div>
<div class="card-content">
Expand Down Expand Up @@ -191,46 +186,9 @@ export class CloudTTSPref extends LitElement {
}
);

async _playExample() {
this.loadingExample = true;
const defaultVoice = this.cloudStatus!.prefs.tts_default_voice;
// Our example sentence is English. If user uses English voice, use that
// for example.
let language;
let gender;
if (defaultVoice[0].split("-")[0] === "en") {
language = defaultVoice[0];
gender = defaultVoice[1];
} else {
language = "en-US";
gender = "female";
}

let url;
try {
const result = await convertTextToSpeech(this.hass, {
platform: "cloud",
message: `Hello ${
this.hass.user!.name
}, you can play any text on any supported media player!`,
language,
options: { gender },
});
url = result.url;
} catch (err) {
this.loadingExample = false;
// eslint-disable-next-line no-console
console.error(err);
showAlertDialog(this, {
text: `Unable to load example. ${err}`,
warning: true,
});
return;
}
const audio = new Audio(url);
audio.play();
audio.addEventListener("playing", () => {
this.loadingExample = false;
private _openTryDialog() {
showTryTtsDialog(this, {
defaultVoice: this.cloudStatus!.prefs.tts_default_voice,
});
}

Expand Down
201 changes: 201 additions & 0 deletions src/panels/config/cloud/account/dialog-cloud-tts-try.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import "@material/mwc-button";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
query,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../../types";
import { TryTtsDialogParams } from "./show-dialog-cloud-tts-try";
import { haStyleDialog } from "../../../../resources/styles";
import { fireEvent } from "../../../../common/dom/fire_event";
import { convertTextToSpeech } from "../../../../data/tts";
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
import "@polymer/paper-input/paper-textarea";
import "../../../../components/ha-paper-dropdown-menu";
import { computeStateDomain } from "../../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { supportsFeature } from "../../../../common/entity/supports-feature";
import { SUPPORT_PLAY_MEDIA } from "../../../../data/media-player";
import { createCloseHeading } from "../../../../components/ha-dialog";
import { mdiPlayCircleOutline } from "@mdi/js";
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
import type { PaperTextareaElement } from "@polymer/paper-input/paper-textarea";
import { LocalStorage } from "../../../../common/decorators/local-storage";

@customElement("dialog-cloud-try-tts")
export class DialogTryTts extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

@internalProperty() private _loadingExample = false;

@internalProperty() private _params?: TryTtsDialogParams;

@query("#target") private _targetInput?: PaperListboxElement;

@query("#message") private _messageInput?: PaperTextareaElement;

@LocalStorage("cloudTtsTryMessage") private _message?: string;

@LocalStorage("cloudTtsTryTarget") private _target?: string;

public showDialog(params: TryTtsDialogParams) {
this._params = params;
}

public closeDialog() {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}

protected render(): TemplateResult {
if (!this._params) {
return html``;
}
return html`
<ha-dialog
open
@closed=${this.closeDialog}
scrimClickAction
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.panel.config.cloud.account.tts.dialog.header")
)}
>
<div>
<paper-textarea
id="message"
label="Message"
.value=${this._message ||
this.hass.localize(
"ui.panel.config.cloud.account.tts.dialog.example_message",
"name",
this.hass.user!.name
)}
>
</paper-textarea>

<ha-paper-dropdown-menu
.label=${this.hass.localize(
"ui.panel.config.cloud.account.tts.dialog.target"
)}
>
<paper-listbox
id="target"
slot="dropdown-content"
attr-for-selected="item-value"
.selected=${this._target || "browser"}
>
<paper-item item-value="browser">
${this.hass.localize(
"ui.panel.config.cloud.account.tts.dialog.target_browser"
)}
</paper-item>
${Object.values(this.hass.states)
.filter(
(entity) =>
computeStateDomain(entity) === "media_player" &&
supportsFeature(entity, SUPPORT_PLAY_MEDIA)
)
.map(
(entity) => html`
<paper-item .itemValue=${entity.entity_id}>
${computeStateName(entity)}
</paper-item>
`
)}
</paper-listbox>
</ha-paper-dropdown-menu>
</div>
<mwc-button
slot="primaryAction"
@click=${this._playExample}
.disabled=${this._loadingExample}
>
<ha-svg-icon .path=${mdiPlayCircleOutline}></ha-svg-icon>
&nbsp;${this.hass.localize(
"ui.panel.config.cloud.account.tts.dialog.play"
)}
</mwc-button>
</ha-dialog>
`;
}

private async _playExample() {
const target = String(this._targetInput?.selected);
const message = this._messageInput?.value;

if (!message || !target) {
return;
}

this._message = message;
this._target = target;

if (target === "browser") {
this._playBrowser(message);
} else {
this.hass.callService("tts", "cloud_say", {
entity_id: target,
message,
});
}
}

private async _playBrowser(message: string) {
this._loadingExample = true;

const language = this._params!.defaultVoice[0];
const gender = this._params!.defaultVoice[1];

let url;
try {
const result = await convertTextToSpeech(this.hass, {
platform: "cloud",
message,
language,
options: { gender },
});
url = result.url;
} catch (err) {
this._loadingExample = false;
showAlertDialog(this, {
text: `Unable to load example. ${err.error || err.body || err}`,
warning: true,
});
return;
}
const audio = new Audio(url);
audio.addEventListener("canplaythrough", () => {
audio.play();
});
audio.addEventListener("playing", () => {
this._loadingExample = false;
});
}

static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
ha-dialog {
--mdc-dialog-max-width: 500px;
}
`,
];
}
}

declare global {
interface HTMLElementTagNameMap {
"dialog-cloud-try-tts": DialogTryTts;
}
}
18 changes: 18 additions & 0 deletions src/panels/config/cloud/account/show-dialog-cloud-tts-try.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { fireEvent } from "../../../../common/dom/fire_event";

export interface TryTtsDialogParams {
defaultVoice: [string, string];
}

export const loadTryTtsDialog = () => import("./dialog-cloud-tts-try");

export const showTryTtsDialog = (
element: HTMLElement,
dialogParams: TryTtsDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-cloud-try-tts",
dialogImport: loadTryTtsDialog,
dialogParams,
});
};
12 changes: 10 additions & 2 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1663,7 +1663,15 @@
"info": "Bring personality to your home by having it speak to you by using our Text-to-Speech services. You can use this in automations and scripts by using the {service} service.",
"default_language": "Default language to use",
"male": "Male",
"female": "Female"
"female": "Female",
"try": "Try",
"dialog": {
"header": "Try Text to Speech",
"example_message": "Hello {name}, you can play any text on any supported media player!",
"target": "Target",
"target_browser": "Browser",
"play": "Play"
}
},
"remote": {
"title": "Remote Control",
Expand Down Expand Up @@ -3318,4 +3326,4 @@
}
}
}
}
}