mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-16 13:56:35 +00:00
parent
eeaaecd5b7
commit
19804a713d
@ -73,6 +73,7 @@
|
|||||||
"deep-clone-simple": "^1.1.1",
|
"deep-clone-simple": "^1.1.1",
|
||||||
"es6-object-assign": "^1.1.0",
|
"es6-object-assign": "^1.1.0",
|
||||||
"fecha": "^3.0.0",
|
"fecha": "^3.0.0",
|
||||||
|
"hls.js": "^0.12.3",
|
||||||
"home-assistant-js-websocket": "^3.3.0",
|
"home-assistant-js-websocket": "^3.3.0",
|
||||||
"intl-messageformat": "^2.2.0",
|
"intl-messageformat": "^2.2.0",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
@ -107,6 +108,7 @@
|
|||||||
"@gfx/zopfli": "^1.0.9",
|
"@gfx/zopfli": "^1.0.9",
|
||||||
"@types/chai": "^4.1.7",
|
"@types/chai": "^4.1.7",
|
||||||
"@types/codemirror": "^0.0.71",
|
"@types/codemirror": "^0.0.71",
|
||||||
|
"@types/hls.js": "^0.12.2",
|
||||||
"@types/leaflet": "^1.4.3",
|
"@types/leaflet": "^1.4.3",
|
||||||
"@types/memoize-one": "^4.1.0",
|
"@types/memoize-one": "^4.1.0",
|
||||||
"@types/mocha": "^5.2.5",
|
"@types/mocha": "^5.2.5",
|
||||||
|
@ -1,12 +1,37 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant, CameraEntity } from "../types";
|
||||||
|
|
||||||
export interface CameraThumbnail {
|
export interface CameraThumbnail {
|
||||||
content_type: string;
|
content_type: string;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Stream {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const computeMJPEGStreamUrl = (entity: CameraEntity) =>
|
||||||
|
`/api/camera_proxy_stream/${entity.entity_id}?token=${
|
||||||
|
entity.attributes.access_token
|
||||||
|
}`;
|
||||||
|
|
||||||
export const fetchThumbnail = (hass: HomeAssistant, entityId: string) =>
|
export const fetchThumbnail = (hass: HomeAssistant, entityId: string) =>
|
||||||
hass.callWS<CameraThumbnail>({
|
hass.callWS<CameraThumbnail>({
|
||||||
type: "camera_thumbnail",
|
type: "camera_thumbnail",
|
||||||
entity_id: entityId,
|
entity_id: entityId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const fetchStreamUrl = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entityId: string,
|
||||||
|
format?: "hls"
|
||||||
|
) => {
|
||||||
|
const data = {
|
||||||
|
type: "camera/stream",
|
||||||
|
entity_id: entityId,
|
||||||
|
};
|
||||||
|
if (format) {
|
||||||
|
// @ts-ignore
|
||||||
|
data.format = format;
|
||||||
|
}
|
||||||
|
return hass.callWS<Stream>(data);
|
||||||
|
};
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import computeStateName from "../../../common/entity/compute_state_name";
|
|
||||||
import emptyImageBase64 from "../../../common/empty_image_base64";
|
|
||||||
import EventsMixin from "../../../mixins/events-mixin";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
*/
|
|
||||||
class MoreInfoCamera extends EventsMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
max-width: 640px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-image {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<img
|
|
||||||
class="camera-image"
|
|
||||||
src="[[computeCameraImageUrl(hass, stateObj, isVisible)]]"
|
|
||||||
on-load="imageLoaded"
|
|
||||||
alt="[[_computeStateName(stateObj)]]"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
stateObj: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
isVisible: {
|
|
||||||
type: Boolean,
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
this.isVisible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectedCallback() {
|
|
||||||
this.isVisible = false;
|
|
||||||
super.disconnectedCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
imageLoaded() {
|
|
||||||
this.fire("iron-resize");
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeStateName(stateObj) {
|
|
||||||
return computeStateName(stateObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeCameraImageUrl(hass, stateObj, isVisible) {
|
|
||||||
if (hass.demo) {
|
|
||||||
return "/demo/webcam.jpg";
|
|
||||||
}
|
|
||||||
if (stateObj && isVisible) {
|
|
||||||
return (
|
|
||||||
"/api/camera_proxy_stream/" +
|
|
||||||
stateObj.entity_id +
|
|
||||||
"?token=" +
|
|
||||||
stateObj.attributes.access_token
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Return an empty image if no stateObj (= dialog not open) or in cleanup mode.
|
|
||||||
return emptyImageBase64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("more-info-camera", MoreInfoCamera);
|
|
113
src/dialogs/more-info/controls/more-info-camera.ts
Normal file
113
src/dialogs/more-info/controls/more-info-camera.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { property, UpdatingElement, PropertyValues } from "lit-element";
|
||||||
|
|
||||||
|
import computeStateName from "../../../common/entity/compute_state_name";
|
||||||
|
import { HomeAssistant, CameraEntity } from "../../../types";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { fetchStreamUrl, computeMJPEGStreamUrl } from "../../../data/camera";
|
||||||
|
|
||||||
|
type HLSModule = typeof import("hls.js");
|
||||||
|
|
||||||
|
class MoreInfoCamera extends UpdatingElement {
|
||||||
|
@property() public hass?: HomeAssistant;
|
||||||
|
@property() public stateObj?: CameraEntity;
|
||||||
|
private _mode: "loading" | "hls" | "mjpeg" = "loading";
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this._teardownPlayback();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
if (!changedProps.has("stateObj")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldState = changedProps.get("stateObj") as this["stateObj"];
|
||||||
|
const oldEntityId = oldState ? oldState.entity_id : undefined;
|
||||||
|
const curEntityId = this.stateObj ? this.stateObj.entity_id : undefined;
|
||||||
|
|
||||||
|
// Same entity, ignore.
|
||||||
|
if (curEntityId === oldEntityId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tear down if we have something and we need to build it up
|
||||||
|
if (oldEntityId) {
|
||||||
|
this._teardownPlayback();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curEntityId) {
|
||||||
|
this._startPlayback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _startPlayback(): Promise<void> {
|
||||||
|
if (!this.stateObj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line
|
||||||
|
const Hls = ((await import("hls.js")) as any).default as HLSModule;
|
||||||
|
|
||||||
|
if (Hls.isSupported()) {
|
||||||
|
try {
|
||||||
|
const { url } = await fetchStreamUrl(
|
||||||
|
this.hass!,
|
||||||
|
this.stateObj.entity_id
|
||||||
|
);
|
||||||
|
this._renderHLS(Hls, url);
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
// Fails if entity doesn't support it. In that case we go
|
||||||
|
// for mjpeg.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._renderMJPEG();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _renderHLS(
|
||||||
|
// tslint:disable-next-line
|
||||||
|
Hls: HLSModule,
|
||||||
|
url: string
|
||||||
|
) {
|
||||||
|
const videoEl = document.createElement("video");
|
||||||
|
videoEl.style.width = "100%";
|
||||||
|
videoEl.autoplay = true;
|
||||||
|
videoEl.controls = true;
|
||||||
|
videoEl.muted = true;
|
||||||
|
const hls = new Hls();
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
hls.on(Hls.Events.MEDIA_ATTACHED, resolve);
|
||||||
|
hls.attachMedia(videoEl);
|
||||||
|
});
|
||||||
|
hls.loadSource(url);
|
||||||
|
this.appendChild(videoEl);
|
||||||
|
videoEl.addEventListener("loadeddata", () =>
|
||||||
|
fireEvent(this, "iron-resize")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderMJPEG() {
|
||||||
|
this._mode = "mjpeg";
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.style.width = "100%";
|
||||||
|
img.addEventListener("load", () => fireEvent(this, "iron-resize"));
|
||||||
|
img.src = __DEMO__
|
||||||
|
? "/demo/webcamp.jpg"
|
||||||
|
: computeMJPEGStreamUrl(this.stateObj!);
|
||||||
|
img.alt = computeStateName(this.stateObj!);
|
||||||
|
this.appendChild(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _teardownPlayback(): any {
|
||||||
|
if (this._mode === "hls") {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
this._mode = "loading";
|
||||||
|
while (this.lastChild) {
|
||||||
|
this.removeChild(this.lastChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("more-info-camera", MoreInfoCamera);
|
@ -200,6 +200,15 @@ export type GroupEntity = HassEntityBase & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CameraEntity = HassEntityBase & {
|
||||||
|
attributes: HassEntityAttributeBase & {
|
||||||
|
model_name: string;
|
||||||
|
access_token: string;
|
||||||
|
brand: string;
|
||||||
|
motion_detection: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export interface PanelInfo<T = unknown> {
|
export interface PanelInfo<T = unknown> {
|
||||||
component_name: string;
|
component_name: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
20
yarn.lock
20
yarn.lock
@ -1690,6 +1690,11 @@
|
|||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
"@types/vinyl" "*"
|
"@types/vinyl" "*"
|
||||||
|
|
||||||
|
"@types/hls.js@^0.12.2":
|
||||||
|
version "0.12.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/hls.js/-/hls.js-0.12.2.tgz#e16293a2b1cf4e975fad55a65621764a539f9657"
|
||||||
|
integrity sha512-VXLfVlZYlKWfsdsU2lo7rzwrKBTJj+DFdPIUyF84uTePbYXd30Gx0K6g62kzOPvojgYF/bMLkk1VDXHDb1X+VA==
|
||||||
|
|
||||||
"@types/html-minifier@^3.5.1":
|
"@types/html-minifier@^3.5.1":
|
||||||
version "3.5.2"
|
version "3.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/html-minifier/-/html-minifier-3.5.2.tgz#f897a13d847a774e9b6fd91497e9b0e0ead71c35"
|
resolved "https://registry.yarnpkg.com/@types/html-minifier/-/html-minifier-3.5.2.tgz#f897a13d847a774e9b6fd91497e9b0e0ead71c35"
|
||||||
@ -5706,7 +5711,7 @@ etag@~1.8.1:
|
|||||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||||
|
|
||||||
eventemitter3@^3.0.0:
|
eventemitter3@3.1.0, eventemitter3@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
|
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
|
||||||
integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==
|
integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==
|
||||||
@ -7203,6 +7208,14 @@ he@1.2.x:
|
|||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||||
|
|
||||||
|
hls.js@^0.12.3:
|
||||||
|
version "0.12.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.12.3.tgz#6743456fa443ed6050ab2888083e4b75c39b396f"
|
||||||
|
integrity sha512-tNvH/LIQzjLIXSI1AaAFYDLKxJKKKnE/rqCcFr76Ez6fVpMczWe65pI7qlYxFQC+urVhz9JokJYmeZod6d+5JA==
|
||||||
|
dependencies:
|
||||||
|
eventemitter3 "3.1.0"
|
||||||
|
url-toolkit "^2.1.6"
|
||||||
|
|
||||||
hmac-drbg@^1.0.0:
|
hmac-drbg@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||||
@ -13634,6 +13647,11 @@ url-to-options@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
|
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
|
||||||
integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=
|
integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=
|
||||||
|
|
||||||
|
url-toolkit@^2.1.6:
|
||||||
|
version "2.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/url-toolkit/-/url-toolkit-2.1.6.tgz#6d03246499e519aad224c44044a4ae20544154f2"
|
||||||
|
integrity sha512-UaZ2+50am4HwrV2crR/JAf63Q4VvPYphe63WGeoJxeu8gmOm0qxPt+KsukfakPNrX9aymGNEkkaoICwn+OuvBw==
|
||||||
|
|
||||||
url@^0.11.0:
|
url@^0.11.0:
|
||||||
version "0.11.0"
|
version "0.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user