mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-14 21:06:34 +00:00
parent
eeaaecd5b7
commit
19804a713d
@ -73,6 +73,7 @@
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"es6-object-assign": "^1.1.0",
|
||||
"fecha": "^3.0.0",
|
||||
"hls.js": "^0.12.3",
|
||||
"home-assistant-js-websocket": "^3.3.0",
|
||||
"intl-messageformat": "^2.2.0",
|
||||
"jquery": "^3.3.1",
|
||||
@ -107,6 +108,7 @@
|
||||
"@gfx/zopfli": "^1.0.9",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/codemirror": "^0.0.71",
|
||||
"@types/hls.js": "^0.12.2",
|
||||
"@types/leaflet": "^1.4.3",
|
||||
"@types/memoize-one": "^4.1.0",
|
||||
"@types/mocha": "^5.2.5",
|
||||
|
@ -1,12 +1,37 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
import { HomeAssistant, CameraEntity } from "../types";
|
||||
|
||||
export interface CameraThumbnail {
|
||||
content_type: 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) =>
|
||||
hass.callWS<CameraThumbnail>({
|
||||
type: "camera_thumbnail",
|
||||
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> {
|
||||
component_name: string;
|
||||
icon?: string;
|
||||
|
20
yarn.lock
20
yarn.lock
@ -1690,6 +1690,11 @@
|
||||
"@types/node" "*"
|
||||
"@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":
|
||||
version "3.5.2"
|
||||
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"
|
||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||
|
||||
eventemitter3@^3.0.0:
|
||||
eventemitter3@3.1.0, eventemitter3@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
|
||||
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"
|
||||
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:
|
||||
version "1.0.1"
|
||||
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"
|
||||
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:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||
|
Loading…
x
Reference in New Issue
Block a user