148 lines
3.6 KiB
TypeScript
148 lines
3.6 KiB
TypeScript
import {
|
|
css,
|
|
CSSResultGroup,
|
|
html,
|
|
LitElement,
|
|
PropertyValues,
|
|
TemplateResult,
|
|
} from "lit";
|
|
import { customElement, property, state } from "lit/decorators";
|
|
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
|
import { computeStateName } from "../common/entity/compute_state_name";
|
|
import { supportsFeature } from "../common/entity/supports-feature";
|
|
import {
|
|
CameraEntity,
|
|
CAMERA_SUPPORT_STREAM,
|
|
computeMJPEGStreamUrl,
|
|
fetchStreamUrl,
|
|
} from "../data/camera";
|
|
import { HomeAssistant } from "../types";
|
|
import "./ha-hls-player";
|
|
|
|
@customElement("ha-camera-stream")
|
|
class HaCameraStream extends LitElement {
|
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
|
|
|
@property({ attribute: false }) public stateObj?: CameraEntity;
|
|
|
|
@property({ type: Boolean, attribute: "controls" })
|
|
public controls = false;
|
|
|
|
@property({ type: Boolean, attribute: "muted" })
|
|
public muted = false;
|
|
|
|
@property({ type: Boolean, attribute: "allow-exoplayer" })
|
|
public allowExoPlayer = false;
|
|
|
|
// We keep track if we should force MJPEG with a string
|
|
// that way it automatically resets if we change entity.
|
|
@state() private _forceMJPEG?: string;
|
|
|
|
@state() private _url?: string;
|
|
|
|
@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 !==
|
|
this.stateObj.entity_id
|
|
) {
|
|
this._forceMJPEG = undefined;
|
|
this._url = undefined;
|
|
this._getStreamUrl();
|
|
}
|
|
}
|
|
|
|
public connectedCallback() {
|
|
super.connectedCallback();
|
|
this._connected = true;
|
|
}
|
|
|
|
public disconnectedCallback() {
|
|
super.disconnectedCallback();
|
|
this._connected = false;
|
|
}
|
|
|
|
protected render(): TemplateResult {
|
|
if (!this.stateObj) {
|
|
return html``;
|
|
}
|
|
|
|
return html`
|
|
${__DEMO__ || this._shouldRenderMJPEG
|
|
? html`
|
|
<img
|
|
.src=${__DEMO__
|
|
? this.stateObj!.attributes.entity_picture!
|
|
: this._connected
|
|
? computeMJPEGStreamUrl(this.stateObj)
|
|
: ""}
|
|
.alt=${`Preview of the ${computeStateName(
|
|
this.stateObj
|
|
)} camera.`}
|
|
/>
|
|
`
|
|
: this._url
|
|
? html`
|
|
<ha-hls-player
|
|
autoplay
|
|
playsinline
|
|
.allowExoPlayer=${this.allowExoPlayer}
|
|
.muted=${this.muted}
|
|
.controls=${this.controls}
|
|
.hass=${this.hass}
|
|
.url=${this._url}
|
|
></ha-hls-player>
|
|
`
|
|
: ""}
|
|
`;
|
|
}
|
|
|
|
private get _shouldRenderMJPEG() {
|
|
return (
|
|
this._forceMJPEG === this.stateObj!.entity_id ||
|
|
!isComponentLoaded(this.hass!, "stream") ||
|
|
!supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM)
|
|
);
|
|
}
|
|
|
|
private async _getStreamUrl(): Promise<void> {
|
|
try {
|
|
const { url } = await fetchStreamUrl(
|
|
this.hass!,
|
|
this.stateObj!.entity_id
|
|
);
|
|
|
|
this._url = url;
|
|
} catch (err: any) {
|
|
// Fails if we were unable to get a stream
|
|
// eslint-disable-next-line
|
|
console.error(err);
|
|
|
|
this._forceMJPEG = this.stateObj!.entity_id;
|
|
}
|
|
}
|
|
|
|
static get styles(): CSSResultGroup {
|
|
return css`
|
|
:host,
|
|
img {
|
|
display: block;
|
|
}
|
|
|
|
img {
|
|
width: 100%;
|
|
}
|
|
`;
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"ha-camera-stream": HaCameraStream;
|
|
}
|
|
}
|