Refactor to avoid use of "all-static" classes.
This commit is contained in:
+2
-2
@@ -84,7 +84,7 @@ import {
|
|||||||
shouldUpdateBasedOnHass,
|
shouldUpdateBasedOnHass,
|
||||||
sideLoadHomeAssistantElements
|
sideLoadHomeAssistantElements
|
||||||
} from './utils/ha';
|
} from './utils/ha';
|
||||||
import { BrowseMediaUtil } from './utils/ha/browse-media.js';
|
import { getEventID } from './utils/ha/browse-media.js';
|
||||||
import { supportsFeature } from './utils/ha/update.js';
|
import { supportsFeature } from './utils/ha/update.js';
|
||||||
import { isValidMediaShowInfo } from './utils/media-info.js';
|
import { isValidMediaShowInfo } from './utils/media-info.js';
|
||||||
import { ResolvedMediaCache } from './utils/resolved-media.js';
|
import { ResolvedMediaCache } from './utils/resolved-media.js';
|
||||||
@@ -915,7 +915,7 @@ export class FrigateCard extends LitElement {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const event_id = BrowseMediaUtil.getEventID(this._view.media);
|
const event_id = getEventID(this._view.media);
|
||||||
if (!event_id) {
|
if (!event_id) {
|
||||||
this._setMessageAndUpdate({
|
this._setMessageAndUpdate({
|
||||||
message: localize('error.download_no_event_id'),
|
message: localize('error.download_no_event_id'),
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ import { customElement, property } from 'lit/decorators.js';
|
|||||||
import galleryStyle from '../scss/gallery.scss';
|
import galleryStyle from '../scss/gallery.scss';
|
||||||
import { CameraConfig, frigateCardConfigDefaults, GalleryConfig } from '../types.js';
|
import { CameraConfig, frigateCardConfigDefaults, GalleryConfig } from '../types.js';
|
||||||
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
||||||
import { BrowseMediaUtil } from '../utils/ha/browse-media.js';
|
import {
|
||||||
|
fetchChildMediaAndDispatchViewChange,
|
||||||
|
fetchLatestMediaAndDispatchViewChange,
|
||||||
|
getFullDependentBrowseMediaQueryParametersOrDispatchError
|
||||||
|
} from '../utils/ha/browse-media';
|
||||||
import { View } from '../view.js';
|
import { View } from '../view.js';
|
||||||
import { renderProgressIndicator } from './message.js';
|
import { renderProgressIndicator } from './message.js';
|
||||||
import './thumbnail.js';
|
import './thumbnail.js';
|
||||||
@@ -51,7 +55,7 @@ export class FrigateCardGallery extends LitElement {
|
|||||||
|
|
||||||
if (!this.view.target) {
|
if (!this.view.target) {
|
||||||
const browseMediaQueryParameters =
|
const browseMediaQueryParameters =
|
||||||
BrowseMediaUtil.getFullDependentBrowseMediaQueryParametersOrDispatchError(
|
getFullDependentBrowseMediaQueryParametersOrDispatchError(
|
||||||
this,
|
this,
|
||||||
this.hass,
|
this.hass,
|
||||||
this.cameras,
|
this.cameras,
|
||||||
@@ -63,7 +67,7 @@ export class FrigateCardGallery extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BrowseMediaUtil.fetchLatestMediaAndDispatchViewChange(
|
fetchLatestMediaAndDispatchViewChange(
|
||||||
this,
|
this,
|
||||||
this.hass,
|
this.hass,
|
||||||
this.view,
|
this.view,
|
||||||
@@ -216,7 +220,7 @@ export class FrigateCardGalleryCore extends LitElement {
|
|||||||
<ha-card
|
<ha-card
|
||||||
@click=${(ev) => {
|
@click=${(ev) => {
|
||||||
if (this.hass && this.view) {
|
if (this.hass && this.view) {
|
||||||
BrowseMediaUtil.fetchChildMediaAndDispatchViewChange(
|
fetchChildMediaAndDispatchViewChange(
|
||||||
this,
|
this,
|
||||||
this.hass,
|
this.hass,
|
||||||
this.view,
|
this.view,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
|||||||
import { contentsChanged } from '../utils/basic.js';
|
import { contentsChanged } from '../utils/basic.js';
|
||||||
import { getCameraIcon, getCameraTitle } from '../utils/camera.js';
|
import { getCameraIcon, getCameraTitle } from '../utils/camera.js';
|
||||||
import { homeAssistantSignPath } from '../utils/ha';
|
import { homeAssistantSignPath } from '../utils/ha';
|
||||||
import { BrowseMediaUtil } from '../utils/ha/browse-media.js';
|
import { getFullDependentBrowseMediaQueryParameters } from '../utils/ha/browse-media.js';
|
||||||
import {
|
import {
|
||||||
dispatchExistingMediaShowInfoAsEvent,
|
dispatchExistingMediaShowInfoAsEvent,
|
||||||
dispatchMediaShowEvent
|
dispatchMediaShowEvent
|
||||||
@@ -125,7 +125,7 @@ export class FrigateCardLive extends LitElement {
|
|||||||
// Does not use getFullDependentBrowseMediaQueryParametersOrDispatchError to
|
// Does not use getFullDependentBrowseMediaQueryParametersOrDispatchError to
|
||||||
// ensure that non-Frigate cameras will work in live view (they will not
|
// ensure that non-Frigate cameras will work in live view (they will not
|
||||||
// have a Frigate camera name).
|
// have a Frigate camera name).
|
||||||
const browseMediaParams = BrowseMediaUtil.getFullDependentBrowseMediaQueryParameters(
|
const browseMediaParams = getFullDependentBrowseMediaQueryParameters(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.cameras,
|
this.cameras,
|
||||||
this.view.camera,
|
this.view.camera,
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import {
|
|||||||
ThumbnailsControlConfig
|
ThumbnailsControlConfig
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { dispatchFrigateCardEvent } from '../utils/basic.js';
|
import { dispatchFrigateCardEvent } from '../utils/basic.js';
|
||||||
import { BrowseMediaUtil } from '../utils/ha/browse-media.js';
|
import {
|
||||||
|
getFirstTrueMediaChildIndex,
|
||||||
|
multipleBrowseMediaQueryMerged
|
||||||
|
} from '../utils/ha/browse-media';
|
||||||
import { View } from '../view.js';
|
import { View } from '../view.js';
|
||||||
import { dispatchErrorMessageEvent } from './message.js';
|
import { dispatchErrorMessageEvent } from './message.js';
|
||||||
import './surround.js';
|
import './surround.js';
|
||||||
@@ -68,14 +71,11 @@ export class FrigateCardSurround extends LitElement {
|
|||||||
}
|
}
|
||||||
let parent: FrigateBrowseMediaSource | null;
|
let parent: FrigateBrowseMediaSource | null;
|
||||||
try {
|
try {
|
||||||
parent = await BrowseMediaUtil.multipleBrowseMediaQueryMerged(
|
parent = await multipleBrowseMediaQueryMerged(this.hass, browseMediaParams);
|
||||||
this.hass,
|
|
||||||
browseMediaParams,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return dispatchErrorMessageEvent(this, (e as Error).message);
|
return dispatchErrorMessageEvent(this, (e as Error).message);
|
||||||
}
|
}
|
||||||
if (BrowseMediaUtil.getFirstTrueMediaChildIndex(parent) !== null) {
|
if (getFirstTrueMediaChildIndex(parent) !== null) {
|
||||||
this.view
|
this.view
|
||||||
?.evolve({
|
?.evolve({
|
||||||
...(this.targetView && { view: this.targetView }),
|
...(this.targetView && { view: this.targetView }),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss';
|
|||||||
import type { FrigateBrowseMediaSource, ThumbnailsControlConfig } from '../types.js';
|
import type { FrigateBrowseMediaSource, ThumbnailsControlConfig } from '../types.js';
|
||||||
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
||||||
import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic.js';
|
import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic.js';
|
||||||
import { BrowseMediaUtil } from '../utils/ha/browse-media.js';
|
import { isTrueMedia } from '../utils/ha/browse-media';
|
||||||
import { View } from '../view.js';
|
import { View } from '../view.js';
|
||||||
import { FrigateCardCarousel } from './carousel.js';
|
import { FrigateCardCarousel } from './carousel.js';
|
||||||
import './thumbnail.js';
|
import './thumbnail.js';
|
||||||
@@ -154,7 +154,7 @@ export class FrigateCardThumbnailCarousel extends FrigateCardCarousel {
|
|||||||
if (
|
if (
|
||||||
!parent.children ||
|
!parent.children ||
|
||||||
!parent.children.length ||
|
!parent.children.length ||
|
||||||
!BrowseMediaUtil.isTrueMedia(parent.children[childIndex])
|
!isTrueMedia(parent.children[childIndex])
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-21
@@ -37,7 +37,12 @@ import {
|
|||||||
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
||||||
import { dispatchFrigateCardEvent } from '../utils/basic.js';
|
import { dispatchFrigateCardEvent } from '../utils/basic.js';
|
||||||
import { getCameraTitle } from '../utils/camera.js';
|
import { getCameraTitle } from '../utils/camera.js';
|
||||||
import { BrowseMediaUtil } from '../utils/ha/browse-media.js';
|
import {
|
||||||
|
createEventParentForChildren,
|
||||||
|
getBrowseMediaQueryParameters,
|
||||||
|
isTrueMedia,
|
||||||
|
multipleBrowseMediaQuery
|
||||||
|
} from '../utils/ha/browse-media';
|
||||||
import { View, ViewContext } from '../view';
|
import { View, ViewContext } from '../view';
|
||||||
import { dispatchErrorMessageEvent, dispatchMessageEvent } from './message.js';
|
import { dispatchErrorMessageEvent, dispatchMessageEvent } from './message.js';
|
||||||
import './surround-thumbnails.js';
|
import './surround-thumbnails.js';
|
||||||
@@ -137,7 +142,7 @@ class TimelineEventManager {
|
|||||||
const event = child.frigate?.event;
|
const event = child.frigate?.event;
|
||||||
if (
|
if (
|
||||||
event &&
|
event &&
|
||||||
BrowseMediaUtil.isTrueMedia(child) &&
|
isTrueMedia(child) &&
|
||||||
['video', 'image'].includes(child.media_content_type)
|
['video', 'image'].includes(child.media_content_type)
|
||||||
) {
|
) {
|
||||||
let item = this._dataset.get(event.id);
|
let item = this._dataset.get(event.id);
|
||||||
@@ -282,20 +287,15 @@ class TimelineEventManager {
|
|||||||
this._dateStart &&
|
this._dateStart &&
|
||||||
cameraConfig.camera_name !== CAMERA_BIRDSEYE
|
cameraConfig.camera_name !== CAMERA_BIRDSEYE
|
||||||
) {
|
) {
|
||||||
const param = BrowseMediaUtil.getBrowseMediaQueryParameters(
|
const param = getBrowseMediaQueryParameters(hass, cameraID, cameraConfig, {
|
||||||
hass,
|
// Events are always fetched for the maximum extent of the managed
|
||||||
cameraID,
|
// range. This is because events may change at any point in time
|
||||||
cameraConfig,
|
// (e.g. a long-running event that ends).
|
||||||
{
|
before: this._dateEnd.getTime() / 1000,
|
||||||
// Events are always fetched for the maximum extent of the managed
|
after: this._dateStart.getTime() / 1000,
|
||||||
// range. This is because events may change at any point in time
|
unlimited: true,
|
||||||
// (e.g. a long-running event that ends).
|
mediaType: mediaType as 'clips' | 'snapshots',
|
||||||
before: this._dateEnd.getTime() / 1000,
|
});
|
||||||
after: this._dateStart.getTime() / 1000,
|
|
||||||
unlimited: true,
|
|
||||||
mediaType: mediaType as 'clips' | 'snapshots',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (param) {
|
if (param) {
|
||||||
params.push(param);
|
params.push(param);
|
||||||
}
|
}
|
||||||
@@ -309,7 +309,7 @@ class TimelineEventManager {
|
|||||||
|
|
||||||
let results: Map<BrowseMediaQueryParameters, FrigateBrowseMediaSource>;
|
let results: Map<BrowseMediaQueryParameters, FrigateBrowseMediaSource>;
|
||||||
try {
|
try {
|
||||||
results = await BrowseMediaUtil.multipleBrowseMediaQuery(hass, params);
|
results = await multipleBrowseMediaQuery(hass, params);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return dispatchErrorMessageEvent(element, (e as Error).message);
|
return dispatchErrorMessageEvent(element, (e as Error).message);
|
||||||
}
|
}
|
||||||
@@ -572,10 +572,7 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = BrowseMediaUtil.createEventParentForChildren(
|
const target = createEventParentForChildren('Timeline events', children);
|
||||||
'Timeline events',
|
|
||||||
children,
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
target: target,
|
target: target,
|
||||||
childIndex: childIndex < 0 ? null : childIndex,
|
childIndex: childIndex < 0 ? null : childIndex,
|
||||||
|
|||||||
+28
-22
@@ -28,9 +28,16 @@ import type {
|
|||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
||||||
import { contentsChanged } from '../utils/basic.js';
|
import { contentsChanged } from '../utils/basic.js';
|
||||||
import { BrowseMediaUtil } from '../utils/ha/browse-media.js';
|
import {
|
||||||
|
fetchLatestMediaAndDispatchViewChange,
|
||||||
|
getEventStartTime,
|
||||||
|
getFullDependentBrowseMediaQueryParametersOrDispatchError,
|
||||||
|
isTrueMedia,
|
||||||
|
multipleBrowseMediaQueryMerged,
|
||||||
|
overrideMultiBrowseMediaQueryParameters
|
||||||
|
} from '../utils/ha/browse-media.js';
|
||||||
import { createMediaShowInfo } from '../utils/media-info.js';
|
import { createMediaShowInfo } from '../utils/media-info.js';
|
||||||
import { ResolvedMediaCache, ResolvedMediaUtil } from '../utils/resolved-media.js';
|
import { ResolvedMediaCache, resolveMedia } from '../utils/resolved-media.js';
|
||||||
import { View } from '../view.js';
|
import { View } from '../view.js';
|
||||||
import { AutoMediaPlugin } from './embla-plugins/automedia.js';
|
import { AutoMediaPlugin } from './embla-plugins/automedia.js';
|
||||||
import { Lazyload, LazyloadType } from './embla-plugins/lazyload.js';
|
import { Lazyload, LazyloadType } from './embla-plugins/lazyload.js';
|
||||||
@@ -66,7 +73,7 @@ export class FrigateCardViewer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const browseMediaQueryParameters =
|
const browseMediaQueryParameters =
|
||||||
BrowseMediaUtil.getFullDependentBrowseMediaQueryParametersOrDispatchError(
|
getFullDependentBrowseMediaQueryParametersOrDispatchError(
|
||||||
this,
|
this,
|
||||||
this.hass,
|
this.hass,
|
||||||
this.cameras,
|
this.cameras,
|
||||||
@@ -83,14 +90,13 @@ export class FrigateCardViewer extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BrowseMediaUtil.fetchLatestMediaAndDispatchViewChange(
|
fetchLatestMediaAndDispatchViewChange(
|
||||||
this,
|
this,
|
||||||
this.hass,
|
this.hass,
|
||||||
this.view,
|
this.view,
|
||||||
BrowseMediaUtil.overrideMultiBrowseMediaQueryParameters(
|
overrideMultiBrowseMediaQueryParameters(browseMediaQueryParameters, {
|
||||||
browseMediaQueryParameters,
|
mediaType: mediaType,
|
||||||
{ mediaType: mediaType },
|
}),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return renderProgressIndicator();
|
return renderProgressIndicator();
|
||||||
}
|
}
|
||||||
@@ -161,8 +167,8 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
|||||||
i < (target.children || []).length;
|
i < (target.children || []).length;
|
||||||
++i
|
++i
|
||||||
) {
|
) {
|
||||||
if (BrowseMediaUtil.isTrueMedia(target.children[i])) {
|
if (isTrueMedia(target.children[i])) {
|
||||||
await ResolvedMediaUtil.resolveMedia(
|
await resolveMedia(
|
||||||
this.hass,
|
this.hass,
|
||||||
target.children[i],
|
target.children[i],
|
||||||
this.resolvedMediaCache,
|
this.resolvedMediaCache,
|
||||||
@@ -321,7 +327,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
|||||||
let prevIndex: number | null = null;
|
let prevIndex: number | null = null;
|
||||||
for (let i = this.view.childIndex - 1; i >= 0; i--) {
|
for (let i = this.view.childIndex - 1; i >= 0; i--) {
|
||||||
const media = this.view.target.children[i];
|
const media = this.view.target.children[i];
|
||||||
if (media && BrowseMediaUtil.isTrueMedia(media)) {
|
if (media && isTrueMedia(media)) {
|
||||||
prevIndex = i;
|
prevIndex = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -331,7 +337,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
|||||||
let nextIndex: number | null = null;
|
let nextIndex: number | null = null;
|
||||||
for (let i = this.view.childIndex + 1; i < this.view.target.children.length; i++) {
|
for (let i = this.view.childIndex + 1; i < this.view.target.children.length; i++) {
|
||||||
const media = this.view.target.children[i];
|
const media = this.view.target.children[i];
|
||||||
if (media && BrowseMediaUtil.isTrueMedia(media)) {
|
if (media && isTrueMedia(media)) {
|
||||||
nextIndex = i;
|
nextIndex = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -365,7 +371,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const snapshotStartTime = BrowseMediaUtil.getEventStartTime(snapshot);
|
const snapshotStartTime = getEventStartTime(snapshot);
|
||||||
if (!snapshotStartTime) {
|
if (!snapshotStartTime) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -383,10 +389,10 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
|||||||
let latest: number | null = null;
|
let latest: number | null = null;
|
||||||
for (let i = 0; i < this.view.target.children.length; i++) {
|
for (let i = 0; i < this.view.target.children.length; i++) {
|
||||||
const child = this.view.target.children[i];
|
const child = this.view.target.children[i];
|
||||||
if (!BrowseMediaUtil.isTrueMedia(child)) {
|
if (!isTrueMedia(child)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const startTime = BrowseMediaUtil.getEventStartTime(child);
|
const startTime = getEventStartTime(child);
|
||||||
|
|
||||||
if (startTime && (earliest === null || startTime < earliest)) {
|
if (startTime && (earliest === null || startTime < earliest)) {
|
||||||
earliest = startTime;
|
earliest = startTime;
|
||||||
@@ -401,7 +407,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
|||||||
|
|
||||||
let clips: FrigateBrowseMediaSource | null;
|
let clips: FrigateBrowseMediaSource | null;
|
||||||
|
|
||||||
const params = BrowseMediaUtil.overrideMultiBrowseMediaQueryParameters(
|
const params = overrideMultiBrowseMediaQueryParameters(
|
||||||
this.browseMediaQueryParameters,
|
this.browseMediaQueryParameters,
|
||||||
{
|
{
|
||||||
mediaType: 'clips',
|
mediaType: 'clips',
|
||||||
@@ -411,7 +417,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
clips = await BrowseMediaUtil.multipleBrowseMediaQueryMerged(this.hass, params);
|
clips = await multipleBrowseMediaQueryMerged(this.hass, params);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// This is best effort.
|
// This is best effort.
|
||||||
return null;
|
return null;
|
||||||
@@ -423,10 +429,10 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
|||||||
|
|
||||||
for (let i = 0; i < clips.children.length; i++) {
|
for (let i = 0; i < clips.children.length; i++) {
|
||||||
const child = clips.children[i];
|
const child = clips.children[i];
|
||||||
if (!BrowseMediaUtil.isTrueMedia(child)) {
|
if (!isTrueMedia(child)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const clipStartTime = BrowseMediaUtil.getEventStartTime(child);
|
const clipStartTime = getEventStartTime(child);
|
||||||
if (clipStartTime && clipStartTime === snapshotStartTime) {
|
if (clipStartTime && clipStartTime === snapshotStartTime) {
|
||||||
return this.view.evolve({
|
return this.view.evolve({
|
||||||
view: 'clip',
|
view: 'clip',
|
||||||
@@ -487,12 +493,12 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
|||||||
!this.view ||
|
!this.view ||
|
||||||
!this.view.target ||
|
!this.view.target ||
|
||||||
!this.view.target.children ||
|
!this.view.target.children ||
|
||||||
!BrowseMediaUtil.isTrueMedia(this.view.target.children[childIndex])
|
!isTrueMedia(this.view.target.children[childIndex])
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResolvedMediaUtil.resolveMedia(
|
resolveMedia(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.view.target.children[childIndex],
|
this.view.target.children[childIndex],
|
||||||
this.resolvedMediaCache,
|
this.resolvedMediaCache,
|
||||||
@@ -664,7 +670,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
|||||||
!this.hass ||
|
!this.hass ||
|
||||||
!this.view ||
|
!this.view ||
|
||||||
!this.viewerConfig ||
|
!this.viewerConfig ||
|
||||||
!BrowseMediaUtil.isTrueMedia(mediaToRender) ||
|
!isTrueMedia(mediaToRender) ||
|
||||||
!['video', 'image'].includes(mediaToRender.media_content_type)
|
!['video', 'image'].includes(mediaToRender.media_content_type)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
+364
-373
@@ -1,414 +1,405 @@
|
|||||||
import { HomeAssistant } from 'custom-card-helpers';
|
import { HomeAssistant } from 'custom-card-helpers';
|
||||||
import { homeAssistantWSRequest } from '.';
|
import { homeAssistantWSRequest } from '.';
|
||||||
import {
|
import {
|
||||||
dispatchErrorMessageEvent,
|
dispatchErrorMessageEvent,
|
||||||
dispatchMessageEvent
|
dispatchMessageEvent
|
||||||
} from '../../components/message.js';
|
} from '../../components/message.js';
|
||||||
import { localize } from '../../localize/localize.js';
|
import { localize } from '../../localize/localize.js';
|
||||||
import {
|
import {
|
||||||
BrowseMediaQueryParameters,
|
BrowseMediaQueryParameters,
|
||||||
CameraConfig,
|
CameraConfig,
|
||||||
FrigateBrowseMediaSource,
|
FrigateBrowseMediaSource,
|
||||||
frigateBrowseMediaSourceSchema,
|
frigateBrowseMediaSourceSchema,
|
||||||
MEDIA_CLASS_PLAYLIST,
|
MEDIA_CLASS_PLAYLIST,
|
||||||
MEDIA_TYPE_PLAYLIST
|
MEDIA_TYPE_PLAYLIST
|
||||||
} from '../../types.js';
|
} from '../../types.js';
|
||||||
import { View } from '../../view.js';
|
import { View } from '../../view.js';
|
||||||
import { getCameraTitle } from '../camera.js';
|
import { getCameraTitle } from '../camera.js';
|
||||||
|
|
||||||
export class BrowseMediaUtil {
|
/**
|
||||||
/**
|
* Return the Frigate event_id given a FrigateBrowseMediaSource object.
|
||||||
* Return the Frigate event_id given a FrigateBrowseMediaSource object.
|
* @param media The event to get the id from.
|
||||||
* @param media The event to get the id from.
|
* @returns The `event_id` or `null` if not successfully parsed.
|
||||||
* @returns The `event_id` or `null` if not successfully parsed.
|
*/
|
||||||
*/
|
export const getEventID = (media: FrigateBrowseMediaSource): string | null => {
|
||||||
static getEventID(media: FrigateBrowseMediaSource): string | null {
|
return media.frigate?.event.id ?? null;
|
||||||
return media.frigate?.event.id ?? null;
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the event start time given a FrigateBrowseMediaSource object.
|
||||||
|
* @param browseMedia The media object to get the start time from.
|
||||||
|
* @returns The start time in unix/epoch time, or null if it cannot be determined.
|
||||||
|
*/
|
||||||
|
export const getEventStartTime = (media: FrigateBrowseMediaSource): number | null => {
|
||||||
|
return media.frigate?.event.start_time ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a FrigateBrowseMediaSource object is truly a media item (vs a folder).
|
||||||
|
* @param media The media object.
|
||||||
|
* @returns `true` if it's truly a media item, `false` otherwise.
|
||||||
|
*/
|
||||||
|
export const isTrueMedia = (media?: FrigateBrowseMediaSource): boolean => {
|
||||||
|
return !!media && !media.can_expand;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From a FrigateBrowseMediaSource item extract the first true media item from the
|
||||||
|
* children (i.e. a clip/snapshot, not a folder).
|
||||||
|
* @param media The media object with children.
|
||||||
|
* @returns The first true media item found.
|
||||||
|
*/
|
||||||
|
export const getFirstTrueMediaChildIndex = (
|
||||||
|
media: FrigateBrowseMediaSource | null,
|
||||||
|
): number | null => {
|
||||||
|
if (!media || !media.children) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
const index = media.children.findIndex((child) => isTrueMedia(child));
|
||||||
|
return index >= 0 ? index : null;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the event start time given a FrigateBrowseMediaSource object.
|
* Browse Frigate media with a media content id. May throw.
|
||||||
* @param browseMedia The media object to get the start time from.
|
* @param hass The HomeAssistant object.
|
||||||
* @returns The start time in unix/epoch time, or null if it cannot be determined.
|
* @param media_content_id The media content id to browse.
|
||||||
*/
|
* @returns A FrigateBrowseMediaSource object or null on malformed.
|
||||||
static getEventStartTime(media: FrigateBrowseMediaSource): number | null {
|
*/
|
||||||
return media.frigate?.event.start_time ?? null;
|
export const browseMedia = async (
|
||||||
}
|
hass: HomeAssistant,
|
||||||
|
media_content_id: string,
|
||||||
|
): Promise<FrigateBrowseMediaSource> => {
|
||||||
|
const request = {
|
||||||
|
type: 'media_source/browse_media',
|
||||||
|
media_content_id: media_content_id,
|
||||||
|
};
|
||||||
|
return await homeAssistantWSRequest(hass, frigateBrowseMediaSourceSchema, request);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a FrigateBrowseMediaSource object is truly a media item (vs a folder).
|
* Browse Frigate media with a media query. May throw.
|
||||||
* @param media The media object.
|
* @param hass The HomeAssistant object.
|
||||||
* @returns `true` if it's truly a media item, `false` otherwise.
|
* @param params The search parameters to use to search for media.
|
||||||
*/
|
* @returns A FrigateBrowseMediaSource object or null on malformed.
|
||||||
static isTrueMedia(media?: FrigateBrowseMediaSource): boolean {
|
*/
|
||||||
return !!media && !media.can_expand;
|
export const browseMediaQuery = async (
|
||||||
}
|
hass: HomeAssistant,
|
||||||
|
params: BrowseMediaQueryParameters,
|
||||||
|
): Promise<FrigateBrowseMediaSource> => {
|
||||||
|
return browseMedia(
|
||||||
|
hass,
|
||||||
|
// Defined in:
|
||||||
|
// https://github.com/blakeblackshear/frigate-hass-integration/blob/master/custom_components/frigate/media_source.py
|
||||||
|
[
|
||||||
|
'media-source://frigate',
|
||||||
|
params.clientId,
|
||||||
|
'event-search',
|
||||||
|
params.mediaType,
|
||||||
|
|
||||||
/**
|
// If the name field ends in '.all' the integration will return up to 10K events.
|
||||||
* From a FrigateBrowseMediaSource item extract the first true media item from the
|
params.unlimited ? '.all' : '',
|
||||||
* children (i.e. a clip/snapshot, not a folder).
|
params.after ? String(Math.floor(params.after)) : '',
|
||||||
* @param media The media object with children.
|
params.before ? String(Math.ceil(params.before)) : '',
|
||||||
* @returns The first true media item found.
|
params.cameraName,
|
||||||
*/
|
params.label,
|
||||||
static getFirstTrueMediaChildIndex(
|
params.zone,
|
||||||
media: FrigateBrowseMediaSource | null,
|
].join('/'),
|
||||||
): number | null {
|
);
|
||||||
if (!media || !media.children) {
|
};
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const index = media.children.findIndex((child) => this.isTrueMedia(child));
|
|
||||||
return index >= 0 ? index : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Browse Frigate media with a media content id. May throw.
|
* Browse multiple Frigate media queries. May throw.
|
||||||
* @param hass The HomeAssistant object.
|
* @param hass The HomeAssistant object.
|
||||||
* @param media_content_id The media content id to browse.
|
* @param params An array of search parameters to use to search for media.
|
||||||
* @returns A FrigateBrowseMediaSource object or null on malformed.
|
* @returns A map of FrigateBrowseMediaSource object or null on malformed.
|
||||||
*/
|
*/
|
||||||
static async browseMedia(
|
export const multipleBrowseMediaQuery = async (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
media_content_id: string,
|
params: BrowseMediaQueryParameters | BrowseMediaQueryParameters[],
|
||||||
): Promise<FrigateBrowseMediaSource> {
|
): Promise<Map<BrowseMediaQueryParameters, FrigateBrowseMediaSource>> => {
|
||||||
const request = {
|
params = Array.isArray(params) ? params : [params];
|
||||||
type: 'media_source/browse_media',
|
const output: Map<BrowseMediaQueryParameters, FrigateBrowseMediaSource> = new Map();
|
||||||
media_content_id: media_content_id,
|
await Promise.all(
|
||||||
};
|
params.map(async (param: BrowseMediaQueryParameters): Promise<void> => {
|
||||||
return await homeAssistantWSRequest(hass, frigateBrowseMediaSourceSchema, request);
|
output.set(param, await browseMediaQuery(hass, param));
|
||||||
}
|
}),
|
||||||
|
);
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Browse Frigate media with a media query. May throw.
|
* Browse multiple Frigate media queries, then merged them. May throw.
|
||||||
* @param hass The HomeAssistant object.
|
* @param hass The HomeAssistant object.
|
||||||
* @param params The search parameters to use to search for media.
|
* @param params An array of search parameters to use to search for media.
|
||||||
* @returns A FrigateBrowseMediaSource object or null on malformed.
|
* @returns A single FrigateBrowseMediaSource object or null on malformed.
|
||||||
*/
|
*/
|
||||||
static async browseMediaQuery(
|
export const multipleBrowseMediaQueryMerged = async (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
params: BrowseMediaQueryParameters,
|
params: BrowseMediaQueryParameters | BrowseMediaQueryParameters[],
|
||||||
): Promise<FrigateBrowseMediaSource> {
|
): Promise<FrigateBrowseMediaSource> => {
|
||||||
return this.browseMedia(
|
return mergeFrigateBrowseMediaSources(await multipleBrowseMediaQuery(hass, params));
|
||||||
hass,
|
};
|
||||||
// Defined in:
|
|
||||||
// https://github.com/blakeblackshear/frigate-hass-integration/blob/master/custom_components/frigate/media_source.py
|
|
||||||
[
|
|
||||||
'media-source://frigate',
|
|
||||||
params.clientId,
|
|
||||||
'event-search',
|
|
||||||
params.mediaType,
|
|
||||||
|
|
||||||
// If the name field ends in '.all' the integration will return up to 10K events.
|
/**
|
||||||
params.unlimited ? '.all' : '',
|
* Merge multiple FrigateBrowseMediaSource into a single. Note that this may
|
||||||
params.after ? String(Math.floor(params.after)) : '',
|
* use information from the query to differentiate results that may otherwise
|
||||||
params.before ? String(Math.ceil(params.before)) : '',
|
* be identical.
|
||||||
params.cameraName,
|
* @param input A map of query -> result.
|
||||||
params.label,
|
* @returns A single FrigateBrowseMediaSource object.
|
||||||
params.zone,
|
*/
|
||||||
].join('/'),
|
export const mergeFrigateBrowseMediaSources = async (
|
||||||
);
|
input: Map<BrowseMediaQueryParameters, FrigateBrowseMediaSource>,
|
||||||
}
|
): Promise<FrigateBrowseMediaSource> => {
|
||||||
|
const children: FrigateBrowseMediaSource[] = [];
|
||||||
|
|
||||||
/**
|
for (const [query, result] of input.entries()) {
|
||||||
* Browse multiple Frigate media queries. May throw.
|
for (const child of result.children || []) {
|
||||||
* @param hass The HomeAssistant object.
|
if (isTrueMedia(child)) {
|
||||||
* @param params An array of search parameters to use to search for media.
|
children.push(child);
|
||||||
* @returns A map of FrigateBrowseMediaSource object or null on malformed.
|
} else {
|
||||||
*/
|
// If there are multiple inputs, separate the folder names with the
|
||||||
static async multipleBrowseMediaQuery(
|
// query title (if available).
|
||||||
hass: HomeAssistant,
|
if (query.title && input.size > 1) {
|
||||||
params: BrowseMediaQueryParameters | BrowseMediaQueryParameters[],
|
children.push({ ...child, title: `[${query.title}] ${child.title}` });
|
||||||
): Promise<Map<BrowseMediaQueryParameters, FrigateBrowseMediaSource>> {
|
|
||||||
params = Array.isArray(params) ? params : [params];
|
|
||||||
const output: Map<BrowseMediaQueryParameters, FrigateBrowseMediaSource> = new Map();
|
|
||||||
await Promise.all(
|
|
||||||
params.map(async (param: BrowseMediaQueryParameters): Promise<void> => {
|
|
||||||
output.set(param, await this.browseMediaQuery(hass, param));
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Browse multiple Frigate media queries, then merged them. May throw.
|
|
||||||
* @param hass The HomeAssistant object.
|
|
||||||
* @param params An array of search parameters to use to search for media.
|
|
||||||
* @returns A single FrigateBrowseMediaSource object or null on malformed.
|
|
||||||
*/
|
|
||||||
static async multipleBrowseMediaQueryMerged(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
params: BrowseMediaQueryParameters | BrowseMediaQueryParameters[],
|
|
||||||
): Promise<FrigateBrowseMediaSource> {
|
|
||||||
return this.mergeFrigateBrowseMediaSources(
|
|
||||||
await this.multipleBrowseMediaQuery(hass, params),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge multiple FrigateBrowseMediaSource into a single. Note that this may
|
|
||||||
* use information from the query to differentiate results that may otherwise
|
|
||||||
* be identical.
|
|
||||||
* @param input A map of query -> result.
|
|
||||||
* @returns A single FrigateBrowseMediaSource object.
|
|
||||||
*/
|
|
||||||
static mergeFrigateBrowseMediaSources(
|
|
||||||
input: Map<BrowseMediaQueryParameters, FrigateBrowseMediaSource>,
|
|
||||||
): FrigateBrowseMediaSource {
|
|
||||||
const children: FrigateBrowseMediaSource[] = [];
|
|
||||||
|
|
||||||
for (const [query, result] of input.entries()) {
|
|
||||||
for (const child of result.children || []) {
|
|
||||||
if (this.isTrueMedia(child)) {
|
|
||||||
children.push(child);
|
|
||||||
} else {
|
} else {
|
||||||
// If there are multiple inputs, separate the folder names with the
|
children.push(child);
|
||||||
// query title (if available).
|
|
||||||
if (query.title && input.size > 1) {
|
|
||||||
children.push({ ...child, title: `[${query.title}] ${child.title}` });
|
|
||||||
} else {
|
|
||||||
children.push(child);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventSort = (
|
|
||||||
a: FrigateBrowseMediaSource,
|
|
||||||
b: FrigateBrowseMediaSource,
|
|
||||||
): number => {
|
|
||||||
if (
|
|
||||||
!a.frigate?.event ||
|
|
||||||
(b.frigate?.event && b.frigate.event.start_time > a.frigate.event.start_time)
|
|
||||||
) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!b.frigate?.event ||
|
|
||||||
(a.frigate?.event && b.frigate.event.start_time < a.frigate.event.start_time)
|
|
||||||
) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.createEventParentForChildren('Merged events', children.sort(eventSort));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const eventSort = (
|
||||||
* Get the parameters to search for media.
|
a: FrigateBrowseMediaSource,
|
||||||
* @returns A BrowseMediaQueryParameters object.
|
b: FrigateBrowseMediaSource,
|
||||||
*/
|
): number => {
|
||||||
static getBrowseMediaQueryParameters(
|
if (
|
||||||
hass: HomeAssistant,
|
!a.frigate?.event ||
|
||||||
cameraID: string,
|
(b.frigate?.event && b.frigate.event.start_time > a.frigate.event.start_time)
|
||||||
cameraConfig?: CameraConfig,
|
) {
|
||||||
overrides?: Partial<BrowseMediaQueryParameters>,
|
return 1;
|
||||||
): BrowseMediaQueryParameters | null {
|
|
||||||
if (!cameraConfig || !cameraConfig.camera_name) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
clientId: cameraConfig.client_id,
|
|
||||||
cameraName: cameraConfig.camera_name,
|
|
||||||
label: cameraConfig.label,
|
|
||||||
zone: cameraConfig.zone,
|
|
||||||
title: getCameraTitle(hass, cameraConfig),
|
|
||||||
cameraID: cameraID,
|
|
||||||
...overrides,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (
|
||||||
* Apply overrides to multiple query parameters.
|
!b.frigate?.event ||
|
||||||
* @param parameters An array of query parameters.
|
(a.frigate?.event && b.frigate.event.start_time < a.frigate.event.start_time)
|
||||||
* @param overrides The overrides to apply.
|
) {
|
||||||
* @returns The override query parameters.
|
return -1;
|
||||||
*/
|
}
|
||||||
static overrideMultiBrowseMediaQueryParameters(
|
return 0;
|
||||||
parameters: BrowseMediaQueryParameters[],
|
};
|
||||||
overrides: Partial<BrowseMediaQueryParameters>,
|
|
||||||
): BrowseMediaQueryParameters[] {
|
|
||||||
const output: BrowseMediaQueryParameters[] = [];
|
|
||||||
parameters.forEach((param) => {
|
|
||||||
output.push({ ...param, ...overrides });
|
|
||||||
});
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return createEventParentForChildren('Merged events', children.sort(eventSort));
|
||||||
* Get BrowseMediaQueryParameters for a camera (including its dependencies).
|
};
|
||||||
* @param hass Home Assistant object.
|
|
||||||
* @param cameras Cameras map.
|
/**
|
||||||
* @param camera Name of the current camera.
|
* Get the parameters to search for media.
|
||||||
* @param mediaType Optional media type to include in the parameters.
|
* @returns A BrowseMediaQueryParameters object.
|
||||||
* @returns An array of query parameters.
|
*/
|
||||||
*/
|
export const getBrowseMediaQueryParameters = (
|
||||||
static getFullDependentBrowseMediaQueryParameters(
|
hass: HomeAssistant,
|
||||||
hass: HomeAssistant,
|
cameraID: string,
|
||||||
cameras: Map<string, CameraConfig>,
|
cameraConfig?: CameraConfig,
|
||||||
camera: string,
|
overrides?: Partial<BrowseMediaQueryParameters>,
|
||||||
mediaType?: 'clips' | 'snapshots',
|
): BrowseMediaQueryParameters | null => {
|
||||||
): BrowseMediaQueryParameters[] | null {
|
if (!cameraConfig || !cameraConfig.camera_name) {
|
||||||
const cameraIDs: Set<string> = new Set();
|
return null;
|
||||||
const getDependentCameras = (camera: string): void => {
|
}
|
||||||
const cameraConfig = cameras.get(camera);
|
return {
|
||||||
if (cameraConfig) {
|
clientId: cameraConfig.client_id,
|
||||||
cameraIDs.add(camera);
|
cameraName: cameraConfig.camera_name,
|
||||||
for (const eventCameraID of cameraConfig.dependent_cameras || []) {
|
label: cameraConfig.label,
|
||||||
if (!cameraIDs.has(eventCameraID)) {
|
zone: cameraConfig.zone,
|
||||||
getDependentCameras(eventCameraID);
|
title: getCameraTitle(hass, cameraConfig),
|
||||||
}
|
cameraID: cameraID,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply overrides to multiple query parameters.
|
||||||
|
* @param parameters An array of query parameters.
|
||||||
|
* @param overrides The overrides to apply.
|
||||||
|
* @returns The override query parameters.
|
||||||
|
*/
|
||||||
|
export const overrideMultiBrowseMediaQueryParameters = (
|
||||||
|
parameters: BrowseMediaQueryParameters[],
|
||||||
|
overrides: Partial<BrowseMediaQueryParameters>,
|
||||||
|
): BrowseMediaQueryParameters[] => {
|
||||||
|
const output: BrowseMediaQueryParameters[] = [];
|
||||||
|
parameters.forEach((param) => {
|
||||||
|
output.push({ ...param, ...overrides });
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get BrowseMediaQueryParameters for a camera (including its dependencies).
|
||||||
|
* @param hass Home Assistant object.
|
||||||
|
* @param cameras Cameras map.
|
||||||
|
* @param camera Name of the current camera.
|
||||||
|
* @param mediaType Optional media type to include in the parameters.
|
||||||
|
* @returns An array of query parameters.
|
||||||
|
*/
|
||||||
|
export const getFullDependentBrowseMediaQueryParameters = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
cameras: Map<string, CameraConfig>,
|
||||||
|
camera: string,
|
||||||
|
mediaType?: 'clips' | 'snapshots',
|
||||||
|
): BrowseMediaQueryParameters[] | null => {
|
||||||
|
const cameraIDs: Set<string> = new Set();
|
||||||
|
const getDependentCameras = (camera: string): void => {
|
||||||
|
const cameraConfig = cameras.get(camera);
|
||||||
|
if (cameraConfig) {
|
||||||
|
cameraIDs.add(camera);
|
||||||
|
for (const eventCameraID of cameraConfig.dependent_cameras || []) {
|
||||||
|
if (!cameraIDs.has(eventCameraID)) {
|
||||||
|
getDependentCameras(eventCameraID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
getDependentCameras(camera);
|
|
||||||
|
|
||||||
const params: BrowseMediaQueryParameters[] = [];
|
|
||||||
for (const cameraID of cameraIDs) {
|
|
||||||
const param = BrowseMediaUtil.getBrowseMediaQueryParameters(
|
|
||||||
hass,
|
|
||||||
cameraID,
|
|
||||||
cameras.get(cameraID),
|
|
||||||
mediaType ? { mediaType: mediaType } : {},
|
|
||||||
);
|
|
||||||
// Fail on a single bad camera, as it's almost certainly a user error that
|
|
||||||
// should be fixed rather than hidden.
|
|
||||||
if (!param) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
params.push(param);
|
|
||||||
}
|
}
|
||||||
return params.length ? params : null;
|
};
|
||||||
}
|
getDependentCameras(camera);
|
||||||
|
|
||||||
/**
|
const params: BrowseMediaQueryParameters[] = [];
|
||||||
* Get BrowseMediaQueryParameters for a camera (including its dependencies) or dispatch an error.
|
for (const cameraID of cameraIDs) {
|
||||||
* @param element The element from which to dispatch the error.
|
const param = getBrowseMediaQueryParameters(
|
||||||
* @param hass Home Assistant object.
|
|
||||||
* @param cameras Cameras map.
|
|
||||||
* @param camera Name of the current camera.
|
|
||||||
* @param mediaType Optional media type to include in the parameters.
|
|
||||||
* @returns An array of query parameters.
|
|
||||||
*/
|
|
||||||
static getFullDependentBrowseMediaQueryParametersOrDispatchError(
|
|
||||||
element: HTMLElement,
|
|
||||||
hass: HomeAssistant,
|
|
||||||
cameras: Map<string, CameraConfig>,
|
|
||||||
camera: string,
|
|
||||||
mediaType?: 'clips' | 'snapshots',
|
|
||||||
): BrowseMediaQueryParameters[] | null {
|
|
||||||
const params = this.getFullDependentBrowseMediaQueryParameters(
|
|
||||||
hass,
|
hass,
|
||||||
cameras,
|
cameraID,
|
||||||
camera,
|
cameras.get(cameraID),
|
||||||
mediaType,
|
mediaType ? { mediaType: mediaType } : {},
|
||||||
);
|
);
|
||||||
if (!params) {
|
// Fail on a single bad camera, as it's almost certainly a user error that
|
||||||
dispatchErrorMessageEvent(
|
// should be fixed rather than hidden.
|
||||||
element,
|
if (!param) {
|
||||||
localize('error.no_camera_name'),
|
|
||||||
cameras.get(camera),
|
|
||||||
);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return params;
|
params.push(param);
|
||||||
|
}
|
||||||
|
return params.length ? params : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get BrowseMediaQueryParameters for a camera (including its dependencies) or dispatch an error.
|
||||||
|
* @param element The element from which to dispatch the error.
|
||||||
|
* @param hass Home Assistant object.
|
||||||
|
* @param cameras Cameras map.
|
||||||
|
* @param camera Name of the current camera.
|
||||||
|
* @param mediaType Optional media type to include in the parameters.
|
||||||
|
* @returns An array of query parameters.
|
||||||
|
*/
|
||||||
|
export const getFullDependentBrowseMediaQueryParametersOrDispatchError = (
|
||||||
|
element: HTMLElement,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
cameras: Map<string, CameraConfig>,
|
||||||
|
camera: string,
|
||||||
|
mediaType?: 'clips' | 'snapshots',
|
||||||
|
): BrowseMediaQueryParameters[] | null => {
|
||||||
|
const params = getFullDependentBrowseMediaQueryParameters(
|
||||||
|
hass,
|
||||||
|
cameras,
|
||||||
|
camera,
|
||||||
|
mediaType,
|
||||||
|
);
|
||||||
|
if (!params) {
|
||||||
|
dispatchErrorMessageEvent(
|
||||||
|
element,
|
||||||
|
localize('error.no_camera_name'),
|
||||||
|
cameras.get(camera),
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the latest media and dispatch a change view event to reflect the
|
||||||
|
* results. If no media is found a suitable message event will be triggered
|
||||||
|
* instead.
|
||||||
|
* @param element The HTMLElement to dispatch events from.
|
||||||
|
* @param hass The Home Assistant object.
|
||||||
|
* @param view The current view to evolve.
|
||||||
|
* @param browseMediaQueryParameters The media parameters to query with.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const fetchLatestMediaAndDispatchViewChange = async (
|
||||||
|
element: HTMLElement,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
view: Readonly<View>,
|
||||||
|
browseMediaQueryParameters: BrowseMediaQueryParameters | BrowseMediaQueryParameters[],
|
||||||
|
): Promise<void> => {
|
||||||
|
let parent: FrigateBrowseMediaSource | null;
|
||||||
|
try {
|
||||||
|
parent = await multipleBrowseMediaQueryMerged(hass, browseMediaQueryParameters);
|
||||||
|
} catch (e) {
|
||||||
|
return dispatchErrorMessageEvent(element, (e as Error).message);
|
||||||
|
}
|
||||||
|
const childIndex = getFirstTrueMediaChildIndex(parent);
|
||||||
|
if (!parent || !parent.children || childIndex == null) {
|
||||||
|
return dispatchMessageEvent(
|
||||||
|
element,
|
||||||
|
view.isClipRelatedView()
|
||||||
|
? localize('common.no_clip')
|
||||||
|
: localize('common.no_snapshot'),
|
||||||
|
view.isClipRelatedView() ? 'mdi:filmstrip-off' : 'mdi:camera-off',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
view
|
||||||
* Fetch the latest media and dispatch a change view event to reflect the
|
.evolve({
|
||||||
* results. If no media is found a suitable message event will be triggered
|
target: parent,
|
||||||
* instead.
|
childIndex: childIndex,
|
||||||
* @param element The HTMLElement to dispatch events from.
|
})
|
||||||
* @param hass The Home Assistant object.
|
.dispatchChangeEvent(element);
|
||||||
* @param view The current view to evolve.
|
};
|
||||||
* @param browseMediaQueryParameters The media parameters to query with.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
static async fetchLatestMediaAndDispatchViewChange(
|
|
||||||
element: HTMLElement,
|
|
||||||
hass: HomeAssistant,
|
|
||||||
view: Readonly<View>,
|
|
||||||
browseMediaQueryParameters:
|
|
||||||
| BrowseMediaQueryParameters
|
|
||||||
| BrowseMediaQueryParameters[],
|
|
||||||
): Promise<void> {
|
|
||||||
let parent: FrigateBrowseMediaSource | null;
|
|
||||||
try {
|
|
||||||
parent = await BrowseMediaUtil.multipleBrowseMediaQueryMerged(
|
|
||||||
hass,
|
|
||||||
browseMediaQueryParameters,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
return dispatchErrorMessageEvent(element, (e as Error).message);
|
|
||||||
}
|
|
||||||
const childIndex = BrowseMediaUtil.getFirstTrueMediaChildIndex(parent);
|
|
||||||
if (!parent || !parent.children || childIndex == null) {
|
|
||||||
return dispatchMessageEvent(
|
|
||||||
element,
|
|
||||||
view.isClipRelatedView()
|
|
||||||
? localize('common.no_clip')
|
|
||||||
: localize('common.no_snapshot'),
|
|
||||||
view.isClipRelatedView() ? 'mdi:filmstrip-off' : 'mdi:camera-off',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
view
|
/**
|
||||||
.evolve({
|
* Fetch the media of a child FrigateBrowseMediaSource object and dispatch a change
|
||||||
target: parent,
|
* view event to reflect the results.
|
||||||
childIndex: childIndex,
|
* @param node The HTMLElement to dispatch events from.
|
||||||
})
|
* @param hass The Home Assistant object.
|
||||||
.dispatchChangeEvent(element);
|
* @param view The current view to evolve.
|
||||||
|
* @param child The FrigateBrowseMediaSource child to query for.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const fetchChildMediaAndDispatchViewChange = async (
|
||||||
|
element: HTMLElement,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
view: Readonly<View>,
|
||||||
|
child: Readonly<FrigateBrowseMediaSource>,
|
||||||
|
): Promise<void> => {
|
||||||
|
let parent: FrigateBrowseMediaSource;
|
||||||
|
try {
|
||||||
|
parent = await browseMedia(hass, child.media_content_id);
|
||||||
|
} catch (e) {
|
||||||
|
return dispatchErrorMessageEvent(element, (e as Error).message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
view
|
||||||
* Fetch the media of a child FrigateBrowseMediaSource object and dispatch a change
|
.evolve({
|
||||||
* view event to reflect the results.
|
target: parent,
|
||||||
* @param node The HTMLElement to dispatch events from.
|
})
|
||||||
* @param hass The Home Assistant object.
|
.dispatchChangeEvent(element);
|
||||||
* @param view The current view to evolve.
|
};
|
||||||
* @param child The FrigateBrowseMediaSource child to query for.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
static async fetchChildMediaAndDispatchViewChange(
|
|
||||||
element: HTMLElement,
|
|
||||||
hass: HomeAssistant,
|
|
||||||
view: Readonly<View>,
|
|
||||||
child: Readonly<FrigateBrowseMediaSource>,
|
|
||||||
): Promise<void> {
|
|
||||||
let parent: FrigateBrowseMediaSource;
|
|
||||||
try {
|
|
||||||
parent = await BrowseMediaUtil.browseMedia(hass, child.media_content_id);
|
|
||||||
} catch (e) {
|
|
||||||
return dispatchErrorMessageEvent(element, (e as Error).message);
|
|
||||||
}
|
|
||||||
|
|
||||||
view
|
/**
|
||||||
.evolve({
|
* Given an array of media children, create a parent for them.
|
||||||
target: parent,
|
* @param title The title to use for the parent.
|
||||||
})
|
* @param children The children media items.
|
||||||
.dispatchChangeEvent(element);
|
* @returns A single parent containing the children.
|
||||||
}
|
*/
|
||||||
|
export const createEventParentForChildren = (
|
||||||
/**
|
title: string,
|
||||||
* Given an array of media children, create a parent for them.
|
children: FrigateBrowseMediaSource[],
|
||||||
* @param title The title to use for the parent.
|
): FrigateBrowseMediaSource => {
|
||||||
* @param children The children media items.
|
return {
|
||||||
* @returns A single parent containing the children.
|
title: title,
|
||||||
*/
|
media_class: MEDIA_CLASS_PLAYLIST,
|
||||||
static createEventParentForChildren(
|
media_content_type: MEDIA_TYPE_PLAYLIST,
|
||||||
title: string,
|
media_content_id: '',
|
||||||
children: FrigateBrowseMediaSource[],
|
can_play: false,
|
||||||
): FrigateBrowseMediaSource {
|
can_expand: true,
|
||||||
return {
|
children_media_class: MEDIA_CLASS_PLAYLIST,
|
||||||
title: title,
|
thumbnail: null,
|
||||||
media_class: MEDIA_CLASS_PLAYLIST,
|
children: children,
|
||||||
media_content_type: MEDIA_TYPE_PLAYLIST,
|
};
|
||||||
media_content_id: '',
|
};
|
||||||
can_play: false,
|
|
||||||
can_expand: true,
|
|
||||||
children_media_class: MEDIA_CLASS_PLAYLIST,
|
|
||||||
thumbnail: null,
|
|
||||||
children: children,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+24
-30
@@ -1,9 +1,9 @@
|
|||||||
import { HomeAssistant } from 'custom-card-helpers';
|
import { HomeAssistant } from 'custom-card-helpers';
|
||||||
import QuickLRU from 'quick-lru';
|
import QuickLRU from 'quick-lru';
|
||||||
import {
|
import {
|
||||||
FrigateBrowseMediaSource,
|
FrigateBrowseMediaSource,
|
||||||
ResolvedMedia,
|
ResolvedMedia,
|
||||||
resolvedMediaSchema
|
resolvedMediaSchema
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { homeAssistantWSRequest } from './ha';
|
import { homeAssistantWSRequest } from './ha';
|
||||||
|
|
||||||
@@ -34,31 +34,25 @@ export class ResolvedMediaCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ResolvedMediaUtil {
|
export const resolveMedia = async (
|
||||||
static async resolveMedia(
|
hass: HomeAssistant,
|
||||||
hass: HomeAssistant,
|
mediaSource?: FrigateBrowseMediaSource,
|
||||||
mediaSource?: FrigateBrowseMediaSource,
|
cache?: ResolvedMediaCache,
|
||||||
cache?: ResolvedMediaCache,
|
): Promise<ResolvedMedia | null> => {
|
||||||
): Promise<ResolvedMedia | null> {
|
if (!mediaSource) {
|
||||||
if (!mediaSource) {
|
return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const cachedValue = cache ? cache.get(mediaSource.media_content_id) : undefined;
|
|
||||||
if (cachedValue) {
|
|
||||||
return cachedValue;
|
|
||||||
}
|
|
||||||
const request = {
|
|
||||||
type: 'media_source/resolve_media',
|
|
||||||
media_content_id: mediaSource.media_content_id,
|
|
||||||
};
|
|
||||||
const resolvedMedia = await homeAssistantWSRequest(
|
|
||||||
hass,
|
|
||||||
resolvedMediaSchema,
|
|
||||||
request,
|
|
||||||
);
|
|
||||||
if (cache && resolvedMedia) {
|
|
||||||
cache.set(mediaSource.media_content_id, resolvedMedia);
|
|
||||||
}
|
|
||||||
return resolvedMedia;
|
|
||||||
}
|
}
|
||||||
}
|
const cachedValue = cache ? cache.get(mediaSource.media_content_id) : undefined;
|
||||||
|
if (cachedValue) {
|
||||||
|
return cachedValue;
|
||||||
|
}
|
||||||
|
const request = {
|
||||||
|
type: 'media_source/resolve_media',
|
||||||
|
media_content_id: mediaSource.media_content_id,
|
||||||
|
};
|
||||||
|
const resolvedMedia = await homeAssistantWSRequest(hass, resolvedMediaSchema, request);
|
||||||
|
if (cache && resolvedMedia) {
|
||||||
|
cache.set(mediaSource.media_content_id, resolvedMedia);
|
||||||
|
}
|
||||||
|
return resolvedMedia;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user