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 { .frigate-card-navbar {
position: absolute; position: absolute;
top: 0px;
width: 50px; width: 50px;
z-index: 1; z-index: 1;
} }
@@ -65,6 +66,11 @@
right: 3px; right: 3px;
} }
.frigate-card-statusbar-webrtc {
@extend .frigate-card-navbar;
left: 53px;
}
ha-icon-button.button { ha-icon-button.button {
position: relative; position: relative;
z-index: 2; z-index: 2;
+71 -40
View File
@@ -1,5 +1,8 @@
// TODO Make editor work. // 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. // TODO Add documentation & screenshots.
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -65,6 +68,8 @@ export class FrigateCard extends LitElement {
this._viewMode = FrigateCardView.LIVE; this._viewMode = FrigateCardView.LIVE;
this._viewEvent = null; this._viewEvent = null;
this._interactionTimerID = null; this._interactionTimerID = null;
this._webrtcElement = null;
this._hass = null;
} }
// Get the configuration element. // Get the configuration element.
@@ -76,9 +81,15 @@ export class FrigateCard extends LitElement {
public static getStubConfig(): Record<string, string> { public static getStubConfig(): Record<string, string> {
return {}; return {};
} }
set hass(hass: HomeAssistant) {
if (this._webrtcElement) {
this._webrtcElement.hass = hass;
}
this._hass = hass;
}
@property({ attribute: false }) @property({ attribute: false })
public hass!: HomeAssistant; protected _hass: HomeAssistant | null;
@state() @state()
public config!: FrigateCardConfig; public config!: FrigateCardConfig;
@@ -90,6 +101,7 @@ export class FrigateCard extends LitElement {
protected _viewEvent: FrigateEvent | null; protected _viewEvent: FrigateEvent | null;
protected _interactionTimerID: number | null; protected _interactionTimerID: number | null;
protected _webrtcElement: any | null;
// Set the object configuration. // Set the object configuration.
public setConfig(inputConfig: FrigateCardConfig): void { public setConfig(inputConfig: FrigateCardConfig): void {
@@ -118,6 +130,19 @@ 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.config = config;
this._setViewModeToDefault(); this._setViewModeToDefault();
} }
@@ -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. // Determine whether the card should be updated.
protected shouldUpdate(changedProps: PropertyValues): boolean { protected shouldUpdate(changedProps: PropertyValues): boolean {
if (!this.config || !this.hass) { if (!this.config || !this._hass) {
return false; return false;
} }
@@ -166,16 +181,16 @@ export class FrigateCard extends LitElement {
return true; return true;
} }
const oldHass = changedProps.get('hass') as HomeAssistant | undefined; const oldHass = changedProps.get('_hass') as HomeAssistant | undefined;
if (oldHass) { if (oldHass) {
if (cameraEntity in this.hass if (cameraEntity in this._hass
&& oldHass.states[cameraEntity] !== this.hass.states[cameraEntity]) { && oldHass.states[cameraEntity] !== this._hass.states[cameraEntity]) {
return true; return true;
} }
if (motionEntity if (motionEntity
&& motionEntity in this.hass && motionEntity in this._hass
&& oldHass.states[motionEntity] !== this.hass.states[motionEntity]) { && oldHass.states[motionEntity] !== this._hass.states[motionEntity]) {
return true; return true;
} }
return false; return false;
@@ -297,8 +312,9 @@ export class FrigateCard extends LitElement {
if (stop) { if (stop) {
video.pause(); video.pause();
video.currentTime = 0; video.currentTime = 0;
} else { } else if (is_live) {
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. // If it's a live view, 'fast-forward' to most recent content.
const duration = video.duration; const duration = video.duration;
video.currentTime = duration; video.currentTime = duration;
@@ -314,21 +330,28 @@ export class FrigateCard extends LitElement {
controlVideo( controlVideo(
stop, stop,
false, false,
this.shadowRoot?.querySelector( this.shadowRoot?.
`#frigate-card-clip-player`) as HTMLVideoElement); querySelector('video.frigate-card-viewer') as HTMLVideoElement);
} }
if (control_live) { if (control_live) {
// Don't have direct access to the live video player as it is buried in // 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>. // 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 (this._webrtcElement) {
if (ha_camera_stream && ha_camera_stream?.shadowRoot) { controlVideo(
const ha_hls_player = ha_camera_stream.shadowRoot.querySelector(`ha-hls-player`); stop,
if (ha_hls_player && ha_hls_player?.shadowRoot) { true,
controlVideo( this.shadowRoot?.
stop, querySelector('webrtc-camera video') as HTMLVideoElement
true, )
ha_hls_player.shadowRoot.querySelector(`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/` + const url = `${this.config.frigate_url}/clips/` +
`${event.camera}-${event.id}.mp4`; `${event.camera}-${event.id}.mp4`;
return html` 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"> <source src="${url}" type="video/mp4">
</video>` </video>`
} }
@@ -421,14 +444,15 @@ export class FrigateCard extends LitElement {
// Render the status bar (motion icon). // Render the status bar (motion icon).
protected _renderStatusBar(): TemplateResult { protected _renderStatusBar(): TemplateResult {
const motionEntity = this.config.motion_entity const motionEntity = this.config.motion_entity
if (!motionEntity || !(motionEntity in this.hass.states)) { if (!motionEntity || !this._hass || !(motionEntity in this._hass.states)) {
return html``; return html``;
} }
const icon = this.hass.states[motionEntity].state == "on" ? const icon = this._hass.states[motionEntity].state == "on" ?
"mdi:motion-sensor" : "mdi:walk"; "mdi:motion-sensor" : "mdi:walk";
return html` return html`
<div class="frigate-card-statusbar ${ <div class="
this._viewMode == FrigateCardView.LIVE ? 'visible' : 'invisible'} ${this._webrtcElement ? 'frigate-card-statusbar-webrtc' : 'frigate-card-statusbar'}
${this._viewMode == FrigateCardView.LIVE ? 'visible' : 'invisible'}
"> ">
<ha-icon-button <ha-icon-button
data-toggle="tooltip" title="View motion sensor" 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 // Note: The live viewer is the main element used to size the overall card. It
// is always rendered (but sometimes hidden). // is always rendered (but sometimes hidden).
protected _renderLiveViewer(): TemplateResult { 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") return this._renderError("mdi:camera-off")
} }
if (this._webrtcElement) {
return html`
<div
class=${this._viewMode == FrigateCardView.LIVE ? 'visible' : 'invisible'}
>
${this._webrtcElement}
</div>`;
}
return html` return html`
<ha-camera-stream <ha-camera-stream
id="frigate-card-live-player" .hass=${this._hass}
.hass=${this.hass} .stateObj=${this._hass.states[this.config.camera_entity]}
.stateObj=${this.hass.states[this.config.camera_entity]}
.controls=${true} .controls=${true}
.muted=${true} .muted=${true}
class=${this._viewMode == FrigateCardView.LIVE ? 'visible' : 'invisible'} class=${this._viewMode == FrigateCardView.LIVE ? 'visible' : 'invisible'}
+1
View File
@@ -2,6 +2,7 @@
"common": { "common": {
"version": "Version", "version": "Version",
"invalid_configuration": "Invalid configuration", "invalid_configuration": "Invalid configuration",
"missing_webrtc": "WebRTC component not found",
"show_warning": "Show Warning", "show_warning": "Show Warning",
"show_error": "Show Error" "show_error": "Show Error"
} }
+2
View File
@@ -19,6 +19,8 @@ export const frigateCardConfigSchema = z.object({
frigate_camera_name: z.string().optional(), frigate_camera_name: z.string().optional(),
view_default: z.enum(["live", "clips", "clip", "snapshots", "snapshot"]).optional().default("live"), view_default: z.enum(["live", "clips", "clip", "snapshots", "snapshot"]).optional().default("live"),
view_timeout: z.number().optional(), view_timeout: z.number().optional(),
live_provider: z.enum(["frigate", "webrtc"]).default("frigate"),
webrtc: z.object({}).passthrough().optional(),
label: z.string().optional(), label: z.string().optional(),
// Stock lovelace card config. // Stock lovelace card config.