2019-04-16 04:55:13 +02:00
|
|
|
import {
|
2020-04-14 18:05:45 +02:00
|
|
|
css,
|
2021-05-07 22:16:14 +02:00
|
|
|
CSSResultGroup,
|
2020-04-14 18:05:45 +02:00
|
|
|
html,
|
|
|
|
LitElement,
|
2019-04-16 04:55:13 +02:00
|
|
|
PropertyValues,
|
|
|
|
TemplateResult,
|
2021-05-18 16:37:53 +02:00
|
|
|
} from "lit";
|
|
|
|
import { customElement, property, state } from "lit/decorators";
|
|
|
|
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
2020-04-14 18:05:45 +02:00
|
|
|
import { computeStateName } from "../common/entity/compute_state_name";
|
|
|
|
import { supportsFeature } from "../common/entity/supports-feature";
|
2019-04-16 04:55:13 +02:00
|
|
|
import {
|
2020-11-25 12:31:51 +01:00
|
|
|
CameraEntity,
|
2019-04-16 04:55:13 +02:00
|
|
|
CAMERA_SUPPORT_STREAM,
|
|
|
|
computeMJPEGStreamUrl,
|
2020-04-14 18:05:45 +02:00
|
|
|
fetchStreamUrl,
|
2021-10-13 10:14:33 +02:00
|
|
|
STREAM_TYPE_HLS,
|
|
|
|
STREAM_TYPE_WEB_RTC,
|
2019-04-16 04:55:13 +02:00
|
|
|
} from "../data/camera";
|
2020-11-25 12:31:51 +01:00
|
|
|
import { HomeAssistant } from "../types";
|
2020-09-04 23:01:20 +02:00
|
|
|
import "./ha-hls-player";
|
2021-10-13 10:14:33 +02:00
|
|
|
import "./ha-web-rtc-player";
|
2019-04-16 04:55:13 +02:00
|
|
|
|
|
|
|
@customElement("ha-camera-stream")
|
|
|
|
class HaCameraStream extends LitElement {
|
2020-07-15 06:38:36 +02:00
|
|
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
2020-04-14 18:05:45 +02:00
|
|
|
|
2020-09-04 23:01:20 +02:00
|
|
|
@property({ attribute: false }) public stateObj?: CameraEntity;
|
2020-04-14 18:05:45 +02:00
|
|
|
|
2020-09-12 20:07:22 +02:00
|
|
|
@property({ type: Boolean, attribute: "controls" })
|
|
|
|
public controls = false;
|
|
|
|
|
|
|
|
@property({ type: Boolean, attribute: "muted" })
|
|
|
|
public muted = false;
|
2020-04-14 18:05:45 +02:00
|
|
|
|
2020-09-15 00:26:26 +02:00
|
|
|
@property({ type: Boolean, attribute: "allow-exoplayer" })
|
|
|
|
public allowExoPlayer = false;
|
|
|
|
|
2021-10-13 10:14:33 +02:00
|
|
|
// We keep track if we should force MJPEG if there was a failure
|
|
|
|
// to get the HLS stream url. This is reset if we change entities.
|
2021-05-07 22:16:14 +02:00
|
|
|
@state() private _forceMJPEG?: string;
|
2019-04-16 04:55:13 +02:00
|
|
|
|
2021-05-07 22:16:14 +02:00
|
|
|
@state() private _url?: string;
|
2019-04-16 04:55:13 +02:00
|
|
|
|
2021-08-23 07:40:37 +02:00
|
|
|
@state() private _connected = false;
|
|
|
|
|
|
|
|
public willUpdate(changedProps: PropertyValues): void {
|
|
|
|
if (
|
|
|
|
changedProps.has("stateObj") &&
|
|
|
|
!this._shouldRenderMJPEG &&
|
|
|
|
this.stateObj &&
|
|
|
|
(changedProps.get("stateObj") as CameraEntity | undefined)?.entity_id !==
|
2021-10-13 10:14:33 +02:00
|
|
|
this.stateObj.entity_id &&
|
2021-10-18 12:42:34 +02:00
|
|
|
this.stateObj!.attributes.frontend_stream_type === STREAM_TYPE_HLS
|
2021-08-23 07:40:37 +02:00
|
|
|
) {
|
|
|
|
this._forceMJPEG = undefined;
|
|
|
|
this._url = undefined;
|
|
|
|
this._getStreamUrl();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public connectedCallback() {
|
|
|
|
super.connectedCallback();
|
|
|
|
this._connected = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public disconnectedCallback() {
|
|
|
|
super.disconnectedCallback();
|
|
|
|
this._connected = false;
|
|
|
|
}
|
|
|
|
|
2020-01-27 17:34:22 +01:00
|
|
|
protected render(): TemplateResult {
|
2020-09-09 22:09:56 +02:00
|
|
|
if (!this.stateObj) {
|
2019-04-16 04:55:13 +02:00
|
|
|
return html``;
|
|
|
|
}
|
2021-10-13 10:14:33 +02:00
|
|
|
if (__DEMO__ || this._shouldRenderMJPEG) {
|
|
|
|
return html` <img
|
|
|
|
.src=${__DEMO__
|
|
|
|
? this.stateObj.attributes.entity_picture!
|
|
|
|
: this._connected
|
|
|
|
? computeMJPEGStreamUrl(this.stateObj)
|
|
|
|
: ""}
|
|
|
|
.alt=${`Preview of the ${computeStateName(this.stateObj)} camera.`}
|
|
|
|
/>`;
|
|
|
|
}
|
2021-10-18 12:42:34 +02:00
|
|
|
if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_HLS) {
|
2021-10-13 10:14:33 +02:00
|
|
|
return this._url
|
2021-10-28 20:02:06 +02:00
|
|
|
? html`<ha-hls-player
|
2021-10-13 10:14:33 +02:00
|
|
|
autoplay
|
|
|
|
playsinline
|
|
|
|
.allowExoPlayer=${this.allowExoPlayer}
|
|
|
|
.muted=${this.muted}
|
|
|
|
.controls=${this.controls}
|
|
|
|
.hass=${this.hass}
|
|
|
|
.url=${this._url}
|
|
|
|
></ha-hls-player>`
|
|
|
|
: html``;
|
|
|
|
}
|
2021-10-18 12:42:34 +02:00
|
|
|
if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_WEB_RTC) {
|
2021-10-28 20:02:06 +02:00
|
|
|
return html`<ha-web-rtc-player
|
2021-10-13 10:14:33 +02:00
|
|
|
autoplay
|
|
|
|
playsinline
|
|
|
|
.muted=${this.muted}
|
|
|
|
.controls=${this.controls}
|
|
|
|
.hass=${this.hass}
|
|
|
|
.entityid=${this.stateObj.entity_id}
|
|
|
|
></ha-web-rtc-player>`;
|
|
|
|
}
|
|
|
|
return html``;
|
2019-04-16 04:55:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private get _shouldRenderMJPEG() {
|
2021-10-13 10:14:33 +02:00
|
|
|
if (this._forceMJPEG === this.stateObj!.entity_id) {
|
|
|
|
// Fallback when unable to fetch stream url
|
|
|
|
return true;
|
|
|
|
}
|
2021-10-28 13:47:15 +02:00
|
|
|
if (!supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM)) {
|
2021-10-13 10:14:33 +02:00
|
|
|
// Steaming is not supported by the camera so fallback to MJPEG stream
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (
|
2021-10-28 13:47:15 +02:00
|
|
|
this.stateObj!.attributes.frontend_stream_type === STREAM_TYPE_WEB_RTC
|
2021-10-13 10:14:33 +02:00
|
|
|
) {
|
2021-10-28 13:47:15 +02:00
|
|
|
// Browser support required for WebRTC
|
|
|
|
return typeof RTCPeerConnection === "undefined";
|
2021-10-13 10:14:33 +02:00
|
|
|
}
|
2021-10-28 13:47:15 +02:00
|
|
|
// Server side stream component required for HLS
|
|
|
|
return !isComponentLoaded(this.hass!, "stream");
|
2019-04-16 04:55:13 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 23:01:20 +02:00
|
|
|
private async _getStreamUrl(): Promise<void> {
|
2019-04-16 04:55:13 +02:00
|
|
|
try {
|
|
|
|
const { url } = await fetchStreamUrl(
|
|
|
|
this.hass!,
|
|
|
|
this.stateObj!.entity_id
|
|
|
|
);
|
|
|
|
|
2020-09-04 23:01:20 +02:00
|
|
|
this._url = url;
|
2021-09-30 12:39:03 +02:00
|
|
|
} catch (err: any) {
|
2019-04-16 04:55:13 +02:00
|
|
|
// Fails if we were unable to get a stream
|
2020-04-14 18:05:45 +02:00
|
|
|
// eslint-disable-next-line
|
2019-04-16 04:55:13 +02:00
|
|
|
console.error(err);
|
2020-09-04 23:01:20 +02:00
|
|
|
|
2019-04-16 04:55:13 +02:00
|
|
|
this._forceMJPEG = this.stateObj!.entity_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-07 22:16:14 +02:00
|
|
|
static get styles(): CSSResultGroup {
|
2019-04-16 04:55:13 +02:00
|
|
|
return css`
|
|
|
|
:host,
|
2020-09-04 23:01:20 +02:00
|
|
|
img {
|
2019-04-16 04:55:13 +02:00
|
|
|
display: block;
|
|
|
|
}
|
|
|
|
|
2020-09-04 23:01:20 +02:00
|
|
|
img {
|
2019-04-16 04:55:13 +02:00
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
interface HTMLElementTagNameMap {
|
|
|
|
"ha-camera-stream": HaCameraStream;
|
|
|
|
}
|
|
|
|
}
|