mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 07:16:39 +00:00
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:
parent
f4319d9b13
commit
56e3514e40
@ -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,
|
||||||
|
});
|
||||||
|
@ -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);
|
||||||
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user