Add play/pause/mute/unmute Frigate card commands.
This commit is contained in:
+16
-3
@@ -6,7 +6,7 @@ import {
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
unsafeCSS
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { live } from 'lit/directives/live.js';
|
||||
@@ -20,13 +20,15 @@ import {
|
||||
CameraConfig,
|
||||
FrigateCardMediaPlayer,
|
||||
ImageViewConfig,
|
||||
MediaLoadedInfo,
|
||||
MediaLoadedInfo
|
||||
} from '../types.js';
|
||||
import { contentsChanged } from '../utils/basic.js';
|
||||
import { isHassDifferent } from '../utils/ha';
|
||||
import {
|
||||
createMediaLoadedInfo,
|
||||
dispatchExistingMediaLoadedInfoAsEvent,
|
||||
dispatchMediaPauseEvent,
|
||||
dispatchMediaPlayEvent
|
||||
} from '../utils/media-info.js';
|
||||
import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js';
|
||||
import { View } from '../view/view.js';
|
||||
@@ -89,6 +91,10 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
public isPaused(): boolean {
|
||||
return !this._cachedValueController?.hasTimer() ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the camera entity for the current camera configuration.
|
||||
* @returns The entity or undefined if no camera entity is available.
|
||||
@@ -143,6 +149,8 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
|
||||
this,
|
||||
this.imageConfig.refresh_seconds,
|
||||
this._getImageSource.bind(this),
|
||||
() => dispatchMediaPlayEvent(this),
|
||||
() => dispatchMediaPauseEvent(this),
|
||||
);
|
||||
}
|
||||
updateElementStyleFromMediaLayoutConfig(this, this.imageConfig?.layout);
|
||||
@@ -285,7 +293,12 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
|
||||
${ref(this._refImage)}
|
||||
src=${live(src)}
|
||||
@load=${(ev: Event) => {
|
||||
const mediaLoadedInfo = createMediaLoadedInfo(ev, { player: this });
|
||||
const mediaLoadedInfo = createMediaLoadedInfo(ev, {
|
||||
player: this,
|
||||
capabilities: {
|
||||
supportsPause: !!this.imageConfig?.refresh_seconds,
|
||||
},
|
||||
});
|
||||
// Avoid the media being reported as repeatedly loading unless the
|
||||
// media info changes.
|
||||
if (mediaLoadedInfo && !isEqual(this._mediaLoadedInfo, mediaLoadedInfo)) {
|
||||
|
||||
@@ -4,27 +4,33 @@ import {
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
unsafeCSS
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { CameraEndpoints } from '../../camera-manager/types.js';
|
||||
import { VideoRTC } from '../../external/go2rtc/video-rtc';
|
||||
import { localize } from '../../localize/localize';
|
||||
import liveMSEStyle from '../../scss/live-go2rtc.scss';
|
||||
import {
|
||||
CameraConfig,
|
||||
ExtendedHomeAssistant,
|
||||
FrigateCardMediaPlayer,
|
||||
MicrophoneConfig,
|
||||
MicrophoneConfig
|
||||
} from '../../types.js';
|
||||
import '../image.js';
|
||||
import { mayHaveAudio } from '../../utils/audio';
|
||||
import { getEndpointAddressOrDispatchError } from '../../utils/endpoint';
|
||||
import {
|
||||
hideMediaControlsTemporarily,
|
||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS
|
||||
} from '../../utils/media';
|
||||
import { dispatchMediaLoadedEvent } from '../../utils/media-info';
|
||||
import { localize } from '../../localize/localize';
|
||||
import {
|
||||
dispatchMediaLoadedEvent,
|
||||
dispatchMediaPauseEvent,
|
||||
dispatchMediaPlayEvent,
|
||||
dispatchMediaVolumeChangeEvent
|
||||
} from '../../utils/media-info';
|
||||
import '../image.js';
|
||||
import { dispatchErrorMessageEvent } from '../message';
|
||||
import { VideoRTC } from '../../external/go2rtc/video-rtc';
|
||||
import { CameraEndpoints } from '../../camera-manager/types.js';
|
||||
import { getEndpointAddressOrDispatchError } from '../../utils/endpoint';
|
||||
|
||||
// Note (2023-02-18): Depending on the behavior of the player / browser is
|
||||
// possible this URL will need to be re-signed in order to avoid HA spamming
|
||||
@@ -75,11 +81,7 @@ class FrigateCardGo2RTCPlayer extends VideoRTC {
|
||||
super.oninit();
|
||||
|
||||
if (this.video) {
|
||||
const onloadeddata = this.video.onloadeddata;
|
||||
this.video.onloadeddata = (e) => {
|
||||
if (onloadeddata) {
|
||||
onloadeddata.call(this.video, e);
|
||||
}
|
||||
this.video.onloadeddata = () => {
|
||||
hideMediaControlsTemporarily(this.video, MEDIA_LOAD_CONTROLS_HIDE_SECONDS);
|
||||
dispatchMediaLoadedEvent(this, this.video, {
|
||||
player: this._containingPlayer,
|
||||
@@ -89,10 +91,15 @@ class FrigateCardGo2RTCPlayer extends VideoRTC {
|
||||
// that can be created after the fact -- this is purely saying that
|
||||
// were a microphone stream available it could be used usefully.
|
||||
supports2WayAudio: !!this.pc,
|
||||
supportsPause: true,
|
||||
hasAudio: mayHaveAudio(this.video),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
this.video.onvolumechange = () => dispatchMediaVolumeChangeEvent(this),
|
||||
this.video.onplay = () => dispatchMediaPlayEvent(this),
|
||||
this.video.onpause = () => dispatchMediaPauseEvent(this),
|
||||
|
||||
// Always started muted. Media may be unmuted in accordance with user
|
||||
// configuration.
|
||||
this.video.muted = true;
|
||||
@@ -233,6 +240,10 @@ export class FrigateCardGo2RTC extends LitElement implements FrigateCardMediaPla
|
||||
}
|
||||
}
|
||||
|
||||
public isPaused(): boolean {
|
||||
return this._player?.video.paused ?? true;
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
this._player = undefined;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,10 @@ export class FrigateCardLiveHA extends LitElement implements FrigateCardMediaPla
|
||||
this._playerRef.value?.setControls(controls);
|
||||
}
|
||||
|
||||
public isPaused(): boolean {
|
||||
return this._playerRef.value?.isPaused() ?? true;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
|
||||
@@ -45,6 +45,10 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia
|
||||
await this._refImage.value?.setControls(controls);
|
||||
}
|
||||
|
||||
public isPaused(): boolean {
|
||||
return this._refImage.value?.isPaused() ?? true;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.hass || !this.cameraConfig) {
|
||||
return;
|
||||
|
||||
@@ -2,6 +2,7 @@ import JSMpeg from '@cycjimmy/jsmpeg-player';
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { until } from 'lit/directives/until.js';
|
||||
import { CameraEndpoints } from '../../camera-manager/types.js';
|
||||
import { renderProgressIndicator } from '../../components/message.js';
|
||||
import { localize } from '../../localize/localize.js';
|
||||
import liveJSMPEGStyle from '../../scss/live-jsmpeg.scss';
|
||||
@@ -9,12 +10,15 @@ import {
|
||||
CameraConfig,
|
||||
CardWideConfig,
|
||||
ExtendedHomeAssistant,
|
||||
FrigateCardMediaPlayer,
|
||||
FrigateCardMediaPlayer
|
||||
} from '../../types.js';
|
||||
import { dispatchMediaLoadedEvent } from '../../utils/media-info.js';
|
||||
import { dispatchErrorMessageEvent } from '../message.js';
|
||||
import { CameraEndpoints } from '../../camera-manager/types.js';
|
||||
import { getEndpointAddressOrDispatchError } from '../../utils/endpoint.js';
|
||||
import {
|
||||
dispatchMediaLoadedEvent,
|
||||
dispatchMediaPauseEvent,
|
||||
dispatchMediaPlayEvent
|
||||
} from '../../utils/media-info.js';
|
||||
import { dispatchErrorMessageEvent } from '../message.js';
|
||||
|
||||
// Number of seconds a signed URL is valid for.
|
||||
const JSMPEG_URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60;
|
||||
@@ -75,6 +79,10 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
public isPaused(): boolean {
|
||||
return this._jsmpegVideoPlayer?.player?.paused ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JSMPEG player.
|
||||
* @param url The URL for the player to connect to.
|
||||
@@ -113,10 +121,15 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
|
||||
videoDecoded = true;
|
||||
dispatchMediaLoadedEvent(this, this._jsmpegCanvasElement, {
|
||||
player: this,
|
||||
capabilities: {
|
||||
supportsPause: true,
|
||||
},
|
||||
});
|
||||
resolve(player);
|
||||
}
|
||||
},
|
||||
onPlay: () => dispatchMediaPlayEvent(this),
|
||||
onPause: () => dispatchMediaPauseEvent(this),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -10,7 +10,12 @@ import {
|
||||
FrigateCardError,
|
||||
FrigateCardMediaPlayer,
|
||||
} from '../../types.js';
|
||||
import { dispatchMediaLoadedEvent } from '../../utils/media-info.js';
|
||||
import {
|
||||
dispatchMediaLoadedEvent,
|
||||
dispatchMediaPauseEvent,
|
||||
dispatchMediaPlayEvent,
|
||||
dispatchMediaVolumeChangeEvent,
|
||||
} from '../../utils/media-info.js';
|
||||
import { dispatchErrorMessageEvent, renderProgressIndicator } from '../message.js';
|
||||
import { renderTask } from '../../utils/task.js';
|
||||
import {
|
||||
@@ -18,6 +23,7 @@ import {
|
||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||
} from '../../utils/media.js';
|
||||
import { CameraEndpoints } from '../../camera-manager/types.js';
|
||||
import { mayHaveAudio } from '../../utils/audio.js';
|
||||
|
||||
// Create a wrapper for AlexxIT's WebRTC card
|
||||
// - https://github.com/AlexxIT/WebRTC
|
||||
@@ -80,6 +86,10 @@ export class FrigateCardLiveWebRTCCard
|
||||
}
|
||||
}
|
||||
|
||||
public isPaused(): boolean {
|
||||
return this._getPlayer()?.paused ?? true;
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
@@ -178,15 +188,19 @@ export class FrigateCardLiveWebRTCCard
|
||||
this.updateComplete.then(() => {
|
||||
const video = this._getPlayer();
|
||||
if (video) {
|
||||
const onloadeddata = video.onloadeddata;
|
||||
|
||||
video.onloadeddata = (e) => {
|
||||
if (onloadeddata) {
|
||||
onloadeddata.call(video, e);
|
||||
}
|
||||
video.onloadeddata = () => {
|
||||
hideMediaControlsTemporarily(video, MEDIA_LOAD_CONTROLS_HIDE_SECONDS);
|
||||
dispatchMediaLoadedEvent(this, video, { player: this });
|
||||
dispatchMediaLoadedEvent(this, video, {
|
||||
player: this,
|
||||
capabilities: {
|
||||
supportsPause: true,
|
||||
hasAudio: mayHaveAudio(video),
|
||||
},
|
||||
});
|
||||
};
|
||||
video.onplay = () => dispatchMediaPlayEvent(this);
|
||||
video.onpause = () => dispatchMediaPauseEvent(this);
|
||||
video.onvolumechange = () => dispatchMediaVolumeChangeEvent(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -787,6 +787,10 @@ export class FrigateCardLiveProvider
|
||||
this._refProvider.value?.setControls(controls);
|
||||
}
|
||||
|
||||
public isPaused(): boolean {
|
||||
return this._refProvider.value?.isPaused() ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully resolved live provider.
|
||||
* @returns A live provider (that is not 'auto').
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
unsafeCSS
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { guard } from 'lit/directives/guard.js';
|
||||
@@ -25,22 +25,23 @@ import {
|
||||
FrigateCardMediaPlayer,
|
||||
MediaLoadedInfo,
|
||||
TransitionEffect,
|
||||
ViewerConfig,
|
||||
ViewerConfig
|
||||
} from '../types.js';
|
||||
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
||||
import { mayHaveAudio } from '../utils/audio.js';
|
||||
import { contentsChanged, errorToConsole } from '../utils/basic.js';
|
||||
import { canonicalizeHAURL } from '../utils/ha/index.js';
|
||||
import { ResolvedMediaCache, resolveMedia } from '../utils/ha/resolved-media.js';
|
||||
import { dispatchMediaLoadedEvent } from '../utils/media-info.js';
|
||||
import { dispatchMediaLoadedEvent, dispatchMediaPauseEvent, dispatchMediaPlayEvent, dispatchMediaVolumeChangeEvent } from '../utils/media-info.js';
|
||||
import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js';
|
||||
import {
|
||||
changeViewToRecentEventsForCameraAndDependents,
|
||||
changeViewToRecentRecordingForCameraAndDependents,
|
||||
changeViewToRecentRecordingForCameraAndDependents
|
||||
} from '../utils/media-to-view.js';
|
||||
import {
|
||||
hideMediaControlsTemporarily,
|
||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||
playMediaMutingIfNecessary,
|
||||
playMediaMutingIfNecessary
|
||||
} from '../utils/media.js';
|
||||
import { ViewMediaClassifier } from '../view/media-classifier';
|
||||
import { MediaQueriesClassifier } from '../view/media-queries-classifier';
|
||||
@@ -52,7 +53,7 @@ import { AutoMediaPlugin } from './embla-plugins/automedia.js';
|
||||
import { Lazyload } from './embla-plugins/lazyload.js';
|
||||
import {
|
||||
FrigateCardMediaCarousel,
|
||||
wrapMediaLoadedEventForCarousel,
|
||||
wrapMediaLoadedEventForCarousel
|
||||
} from './media-carousel.js';
|
||||
import './next-prev-control.js';
|
||||
import './surround.js';
|
||||
@@ -610,6 +611,15 @@ export class FrigateCardViewerProvider
|
||||
}
|
||||
}
|
||||
|
||||
public isPaused(): boolean {
|
||||
if (this._refFrigateCardMediaPlayer.value) {
|
||||
return this._refFrigateCardMediaPlayer.value.isPaused();
|
||||
} else if (this._refVideoProvider.value) {
|
||||
return this._refVideoProvider.value.paused;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a clip view that matches the current (snapshot) query.
|
||||
*/
|
||||
@@ -750,8 +760,17 @@ export class FrigateCardViewerProvider
|
||||
}
|
||||
}}
|
||||
@loadeddata=${(ev: Event) => {
|
||||
dispatchMediaLoadedEvent(this, ev, { player: this });
|
||||
dispatchMediaLoadedEvent(this, ev, {
|
||||
player: this,
|
||||
capabilities: {
|
||||
supportsPause: true,
|
||||
hasAudio: mayHaveAudio(ev.target as HTMLVideoElement),
|
||||
},
|
||||
});
|
||||
}}
|
||||
@volumechange=${() => dispatchMediaVolumeChangeEvent(this)}
|
||||
@play=${() => dispatchMediaPlayEvent(this)}
|
||||
@pause=${() => dispatchMediaPauseEvent(this)}
|
||||
>
|
||||
<source
|
||||
src=${canonicalizeHAURL(this.hass, resolvedMedia?.url) ?? ''}
|
||||
|
||||
Reference in New Issue
Block a user