Add webrtc support.

This commit is contained in:
Dermot Duffy
2021-08-12 20:26:50 -07:00
parent ee4ec4d23c
commit cc67c3ebf9
4 changed files with 81 additions and 41 deletions
+6
View File
@@ -56,6 +56,7 @@
.frigate-card-navbar {
position: absolute;
top: 0px;
width: 50px;
z-index: 1;
}
@@ -65,6 +66,11 @@
right: 3px;
}
.frigate-card-statusbar-webrtc {
@extend .frigate-card-navbar;
left: 53px;
}
ha-icon-button.button {
position: relative;
z-index: 2;
+72 -41
View File
@@ -1,5 +1,8 @@
// TODO Make editor work.
// TODO Explore webrtc support.
// TODO Add title to clips / snapshots playing/viewing
// TODO Add tooltips for gallary clips with details of event.
// TODO Sometimes webrtc component shows up as not found in browser (maybe after fresh build?)
// TODO Add gallery item to take to media browser
// TODO Add documentation & screenshots.
/* eslint-disable @typescript-eslint/no-explicit-any */
@@ -65,6 +68,8 @@ export class FrigateCard extends LitElement {
this._viewMode = FrigateCardView.LIVE;
this._viewEvent = null;
this._interactionTimerID = null;
this._webrtcElement = null;
this._hass = null;
}
// Get the configuration element.
@@ -76,9 +81,15 @@ export class FrigateCard extends LitElement {
public static getStubConfig(): Record<string, string> {
return {};
}
set hass(hass: HomeAssistant) {
if (this._webrtcElement) {
this._webrtcElement.hass = hass;
}
this._hass = hass;
}
@property({ attribute: false })
public hass!: HomeAssistant;
protected _hass: HomeAssistant | null;
@state()
public config!: FrigateCardConfig;
@@ -90,6 +101,7 @@ export class FrigateCard extends LitElement {
protected _viewEvent: FrigateEvent | null;
protected _interactionTimerID: number | null;
protected _webrtcElement: any | null;
// Set the object configuration.
public setConfig(inputConfig: FrigateCardConfig): void {
@@ -118,10 +130,23 @@ export class FrigateCard extends LitElement {
}
}
if (config.live_provider == "webrtc") {
// Create a WebRTC element (https://github.com/AlexxIT/WebRTC)
const webrtcElement = customElements.get('webrtc-camera');
if (webrtcElement) {
const webrtc = new webrtcElement();
webrtc.setConfig(config.webrtc || {});
webrtc.hass = this._hass;
this._webrtcElement = webrtc;
} else {
throw new Error(localize('common.missing_webrtc'));
}
}
this.config = config;
this._setViewModeToDefault();
}
// Set the view mode to the configured default.
protected _setViewModeToDefault(): void {
if (this.config.view_default == "live") {
@@ -139,19 +164,9 @@ export class FrigateCard extends LitElement {
}
}
// == RTC experimentation ==
// const div = document.createElement("div");
// const webrtcElement = customElements.get('webrtc-camera');
// const webrtc = new webrtcElement();
// webrtc.setConfig({ "entity": "camera.landing_rtsp" });
// webrtc.hass = this.hass;
// div.appendChild(webrtc);
// this.renderRoot.appendChild(div);
// ==
// Determine whether the card should be updated.
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (!this.config || !this.hass) {
if (!this.config || !this._hass) {
return false;
}
@@ -166,16 +181,16 @@ export class FrigateCard extends LitElement {
return true;
}
const oldHass = changedProps.get('hass') as HomeAssistant | undefined;
const oldHass = changedProps.get('_hass') as HomeAssistant | undefined;
if (oldHass) {
if (cameraEntity in this.hass
&& oldHass.states[cameraEntity] !== this.hass.states[cameraEntity]) {
if (cameraEntity in this._hass
&& oldHass.states[cameraEntity] !== this._hass.states[cameraEntity]) {
return true;
}
if (motionEntity
&& motionEntity in this.hass
&& oldHass.states[motionEntity] !== this.hass.states[motionEntity]) {
&& motionEntity in this._hass
&& oldHass.states[motionEntity] !== this._hass.states[motionEntity]) {
return true;
}
return false;
@@ -297,8 +312,9 @@ export class FrigateCard extends LitElement {
if (stop) {
video.pause();
video.currentTime = 0;
} else {
if (is_live) {
} else if (is_live) {
// Duration on webrtc is infinity so cannot fast-forward.
if (!this._webrtcElement) {
// If it's a live view, 'fast-forward' to most recent content.
const duration = video.duration;
video.currentTime = duration;
@@ -314,21 +330,28 @@ export class FrigateCard extends LitElement {
controlVideo(
stop,
false,
this.shadowRoot?.querySelector(
`#frigate-card-clip-player`) as HTMLVideoElement);
this.shadowRoot?.
querySelector('video.frigate-card-viewer') as HTMLVideoElement);
}
if (control_live) {
// Don't have direct access to the live video player as it is buried in
// multiple components/shadow-roots, so need to navigate the path to get to <video>.
const ha_camera_stream = this.shadowRoot?.querySelector(`#frigate-card-live-player`);
if (ha_camera_stream && ha_camera_stream?.shadowRoot) {
const ha_hls_player = ha_camera_stream.shadowRoot.querySelector(`ha-hls-player`);
if (ha_hls_player && ha_hls_player?.shadowRoot) {
controlVideo(
stop,
true,
ha_hls_player.shadowRoot.querySelector(`video`) as HTMLVideoElement);
}
if (this._webrtcElement) {
controlVideo(
stop,
true,
this.shadowRoot?.
querySelector('webrtc-camera video') as HTMLVideoElement
)
} else {
controlVideo(
stop,
true,
this.shadowRoot?.
querySelector('ha-camera-stream')?.shadowRoot?.
querySelector('ha-hls-player')?.shadowRoot?.
querySelector('video') as HTMLVideoElement
)
}
}
}
@@ -390,7 +413,7 @@ export class FrigateCard extends LitElement {
const url = `${this.config.frigate_url}/clips/` +
`${event.camera}-${event.id}.mp4`;
return html`
<video id="frigate-card-clip-player" class="frigate-card-viewer" autoplay controls>
<video class="frigate-card-viewer" autoplay controls>
<source src="${url}" type="video/mp4">
</video>`
}
@@ -421,14 +444,15 @@ export class FrigateCard extends LitElement {
// Render the status bar (motion icon).
protected _renderStatusBar(): TemplateResult {
const motionEntity = this.config.motion_entity
if (!motionEntity || !(motionEntity in this.hass.states)) {
if (!motionEntity || !this._hass || !(motionEntity in this._hass.states)) {
return html``;
}
const icon = this.hass.states[motionEntity].state == "on" ?
const icon = this._hass.states[motionEntity].state == "on" ?
"mdi:motion-sensor" : "mdi:walk";
return html`
<div class="frigate-card-statusbar ${
this._viewMode == FrigateCardView.LIVE ? 'visible' : 'invisible'}
<div class="
${this._webrtcElement ? 'frigate-card-statusbar-webrtc' : 'frigate-card-statusbar'}
${this._viewMode == FrigateCardView.LIVE ? 'visible' : 'invisible'}
">
<ha-icon-button
data-toggle="tooltip" title="View motion sensor"
@@ -445,14 +469,21 @@ export class FrigateCard extends LitElement {
// Note: The live viewer is the main element used to size the overall card. It
// is always rendered (but sometimes hidden).
protected _renderLiveViewer(): TemplateResult {
if (!(this.config.camera_entity in this.hass.states)) {
if (!this._hass || !(this.config.camera_entity in this._hass.states)) {
return this._renderError("mdi:camera-off")
}
if (this._webrtcElement) {
return html`
<div
class=${this._viewMode == FrigateCardView.LIVE ? 'visible' : 'invisible'}
>
${this._webrtcElement}
</div>`;
}
return html`
<ha-camera-stream
id="frigate-card-live-player"
.hass=${this.hass}
.stateObj=${this.hass.states[this.config.camera_entity]}
.hass=${this._hass}
.stateObj=${this._hass.states[this.config.camera_entity]}
.controls=${true}
.muted=${true}
class=${this._viewMode == FrigateCardView.LIVE ? 'visible' : 'invisible'}
+1
View File
@@ -2,6 +2,7 @@
"common": {
"version": "Version",
"invalid_configuration": "Invalid configuration",
"missing_webrtc": "WebRTC component not found",
"show_warning": "Show Warning",
"show_error": "Show Error"
}
+2
View File
@@ -19,6 +19,8 @@ export const frigateCardConfigSchema = z.object({
frigate_camera_name: z.string().optional(),
view_default: z.enum(["live", "clips", "clip", "snapshots", "snapshot"]).optional().default("live"),
view_timeout: z.number().optional(),
live_provider: z.enum(["frigate", "webrtc"]).default("frigate"),
webrtc: z.object({}).passthrough().optional(),
label: z.string().optional(),
// Stock lovelace card config.