Allow changing camera prefs (#3035)

* Check camera supported_features before streaming

* Allow mutating camera prefs

* Move when we fetch prefs
This commit is contained in:
Paulus Schoutsen 2019-03-29 16:43:32 -07:00 committed by GitHub
parent f4319d9b13
commit 56e3514e40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 120 additions and 10 deletions

View File

@ -1,6 +1,13 @@
import { HomeAssistant, CameraEntity } from "../types"; import { HomeAssistant, CameraEntity } from "../types";
import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise"; import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise";
export const CAMERA_SUPPORT_ON_OFF = 1;
export const CAMERA_SUPPORT_STREAM = 2;
export interface CameraPreferences {
preload_stream: boolean;
}
export interface CameraThumbnail { export interface CameraThumbnail {
content_type: string; content_type: string;
content: string; content: string;
@ -41,3 +48,22 @@ export const fetchStreamUrl = (
} }
return hass.callWS<Stream>(data); return hass.callWS<Stream>(data);
}; };
export const fetchCameraPrefs = (hass: HomeAssistant, entityId: string) =>
hass.callWS<CameraPreferences>({
type: "camera/get_prefs",
entity_id: entityId,
});
export const updateCameraPrefs = (
hass: HomeAssistant,
entityId: string,
prefs: {
preload_stream?: boolean;
}
) =>
hass.callWS<CameraPreferences>({
type: "camera/update_prefs",
entity_id: entityId,
...prefs,
});

View File

@ -1,15 +1,36 @@
import { property, UpdatingElement, PropertyValues } from "lit-element"; import {
property,
PropertyValues,
LitElement,
TemplateResult,
html,
CSSResult,
css,
} from "lit-element";
import computeStateName from "../../../common/entity/compute_state_name"; import computeStateName from "../../../common/entity/compute_state_name";
import { HomeAssistant, CameraEntity } from "../../../types"; import { HomeAssistant, CameraEntity } from "../../../types";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { fetchStreamUrl, computeMJPEGStreamUrl } from "../../../data/camera"; import {
fetchStreamUrl,
computeMJPEGStreamUrl,
CAMERA_SUPPORT_STREAM,
CameraPreferences,
fetchCameraPrefs,
updateCameraPrefs,
} from "../../../data/camera";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "@polymer/paper-checkbox/paper-checkbox";
// Not duplicate import, it's for typing
// tslint:disable-next-line
import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
type HLSModule = typeof import("hls.js"); type HLSModule = typeof import("hls.js");
class MoreInfoCamera extends UpdatingElement { class MoreInfoCamera extends LitElement {
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
@property() public stateObj?: CameraEntity; @property() public stateObj?: CameraEntity;
@property() private _cameraPrefs?: CameraPreferences;
private _hlsPolyfillInstance?: Hls; private _hlsPolyfillInstance?: Hls;
public disconnectedCallback() { public disconnectedCallback() {
@ -17,6 +38,22 @@ class MoreInfoCamera extends UpdatingElement {
this._teardownPlayback(); this._teardownPlayback();
} }
protected render(): TemplateResult | void {
return html`
<div id="root"></div>
${this._cameraPrefs
? html`
<paper-checkbox
.checked=${this._cameraPrefs.preload_stream}
@change=${this._handleCheckboxChanged}
>
Preload stream
</paper-checkbox>
`
: undefined}
`;
}
protected updated(changedProps: PropertyValues) { protected updated(changedProps: PropertyValues) {
if (!changedProps.has("stateObj")) { if (!changedProps.has("stateObj")) {
return; return;
@ -46,7 +83,10 @@ class MoreInfoCamera extends UpdatingElement {
return; return;
} }
if (!this.hass!.config.components.includes("stream")) { if (
!this.hass!.config.components.includes("stream") ||
!supportsFeature(this.stateObj, CAMERA_SUPPORT_STREAM)
) {
this._renderMJPEG(); this._renderMJPEG();
return; return;
} }
@ -73,6 +113,8 @@ class MoreInfoCamera extends UpdatingElement {
this.hass!, this.hass!,
this.stateObj.entity_id this.stateObj.entity_id
); );
// Fetch in background while we set up the video.
this._fetchCameraPrefs();
if (Hls.isSupported()) { if (Hls.isSupported()) {
this._renderHLSPolyfill(videoEl, Hls, url); this._renderHLSPolyfill(videoEl, Hls, url);
@ -81,16 +123,20 @@ class MoreInfoCamera extends UpdatingElement {
} }
return; return;
} catch (err) { } catch (err) {
// Fails if entity doesn't support it. In that case we go // When an error happens, we will do nothing so we render mjpeg.
// for mjpeg.
} }
} }
this._renderMJPEG(); this._renderMJPEG();
} }
private get _videoRoot(): HTMLDivElement {
return this.shadowRoot!.getElementById("root")! as HTMLDivElement;
}
private async _renderHLSNative(videoEl: HTMLVideoElement, url: string) { private async _renderHLSNative(videoEl: HTMLVideoElement, url: string) {
videoEl.src = url; videoEl.src = url;
this._videoRoot.appendChild(videoEl);
await new Promise((resolve) => await new Promise((resolve) =>
videoEl.addEventListener("loadedmetadata", resolve) videoEl.addEventListener("loadedmetadata", resolve)
); );
@ -110,7 +156,7 @@ class MoreInfoCamera extends UpdatingElement {
hls.attachMedia(videoEl); hls.attachMedia(videoEl);
}); });
hls.loadSource(url); hls.loadSource(url);
this.appendChild(videoEl); this._videoRoot.appendChild(videoEl);
videoEl.addEventListener("loadeddata", () => videoEl.addEventListener("loadeddata", () =>
fireEvent(this, "iron-resize") fireEvent(this, "iron-resize")
); );
@ -124,7 +170,7 @@ class MoreInfoCamera extends UpdatingElement {
? "/demo/webcamp.jpg" ? "/demo/webcamp.jpg"
: computeMJPEGStreamUrl(this.stateObj!); : computeMJPEGStreamUrl(this.stateObj!);
img.alt = computeStateName(this.stateObj!); img.alt = computeStateName(this.stateObj!);
this.appendChild(img); this._videoRoot.appendChild(img);
} }
private _teardownPlayback(): any { private _teardownPlayback(): any {
@ -132,11 +178,48 @@ class MoreInfoCamera extends UpdatingElement {
this._hlsPolyfillInstance.destroy(); this._hlsPolyfillInstance.destroy();
this._hlsPolyfillInstance = undefined; this._hlsPolyfillInstance = undefined;
} }
while (this.lastChild) { const root = this._videoRoot;
this.removeChild(this.lastChild); while (root.lastChild) {
root.removeChild(root.lastChild);
} }
this.stateObj = undefined; this.stateObj = undefined;
} }
private async _fetchCameraPrefs() {
this._cameraPrefs = await fetchCameraPrefs(
this.hass!,
this.stateObj!.entity_id
);
}
private async _handleCheckboxChanged(ev) {
const checkbox = ev.currentTarget as PaperCheckboxElement;
try {
this._cameraPrefs = await updateCameraPrefs(
this.hass!,
this.stateObj!.entity_id,
{
preload_stream: checkbox.checked!,
}
);
} catch (err) {
alert(err.message);
checkbox.checked = !checkbox.checked;
}
}
static get styles(): CSSResult {
return css`
paper-checkbox {
position: absolute;
top: 0;
right: 0;
background-color: var(--secondary-background-color);
padding: 5px;
border-bottom-left-radius: 6px;
}
`;
}
} }
customElements.define("more-info-camera", MoreInfoCamera); customElements.define("more-info-camera", MoreInfoCamera);

View File

@ -43,6 +43,7 @@ class MoreInfoContent extends UpdatingElement {
} }
protected firstUpdated(): void { protected firstUpdated(): void {
this.style.position = "relative";
this.style.display = "block"; this.style.display = "block";
} }