Move seeking support into the engine.

This commit is contained in:
Dermot Duffy
2023-01-24 19:36:54 -08:00
parent 13816c3025
commit 6b2036676c
12 changed files with 142 additions and 176 deletions
+2 -1
View File
@@ -160,6 +160,7 @@ export class FrigateCardThumbnailCarousel extends LitElement {
'slide-selected': this.selected === index,
};
const seekTarget = this.view?.context?.mediaViewer?.seek;
return html` <frigate-card-thumbnail
class="${classMap(classes)}"
.cameraManager=${this.cameraManager}
@@ -167,7 +168,7 @@ export class FrigateCardThumbnailCarousel extends LitElement {
.media=${media}
.cameraConfig=${cameraConfig}
.view=${this.view}
.mediaSeek=${this.view?.context?.mediaViewer?.seek.get(index)}
.seek=${seekTarget && media.includesTime(seekTarget) ? seekTarget : undefined}
?details=${!!this.config?.show_details}
?show_favorite_control=${this.config?.show_favorite_control}
?show_timeline_control=${this.config?.show_timeline_control}
+9 -10
View File
@@ -14,7 +14,6 @@ import { getCameraTitle } from '../utils/camera.js';
import { renderTask } from '../utils/task.js';
import { createFetchThumbnailTask } from '../utils/thumbnail.js';
import { View } from '../view/view.js';
import type { MediaSeek } from './viewer.js';
import { TaskStatus } from '@lit-labs/task';
import type { CameraConfig, ExtendedHomeAssistant } from '../types.js';
@@ -131,7 +130,7 @@ export class FrigateCardThumbnailDetailsEvent extends LitElement {
public media?: EventViewMedia;
@property({ attribute: false })
public mediaSeek?: MediaSeek;
public seek?: Date;
protected render(): TemplateResult | void {
if (!this.media) {
@@ -158,10 +157,10 @@ export class FrigateCardThumbnailDetailsEvent extends LitElement {
>
</div>`
: ``}
${this.mediaSeek
${this.seek
? html` <div>
<span class="heading">${localize('event.seek')}</span>
<span>${format(fromUnixTime(this.mediaSeek.seekTime), 'HH:mm:ss')}</span>
<span>${format(this.seek, 'HH:mm:ss')}</span>
</div>`
: html``}
</div>
@@ -183,7 +182,7 @@ export class FrigateCardThumbnailDetailsRecording extends LitElement {
public media?: RecordingViewMedia;
@property({ attribute: false })
public mediaSeek?: MediaSeek;
public seek?: Date;
@property({ attribute: false })
public cameraTitle?: string;
@@ -195,10 +194,10 @@ export class FrigateCardThumbnailDetailsRecording extends LitElement {
const eventCount = this.media.getEventCount();
return html`<div class="left">
<div class="larger">${this.cameraTitle ?? ''}</div>
${this.mediaSeek
${this.seek
? html` <div>
<span class="heading">${localize('recording.seek')}</span>
<span>${format(fromUnixTime(this.mediaSeek.seekTime), 'HH:mm:ss')}</span>
<span>${format(this.seek, 'HH:mm:ss')}</span>
</div>`
: html``}
</div>
@@ -242,7 +241,7 @@ export class FrigateCardThumbnail extends LitElement {
public show_timeline_control = false;
@property({ attribute: false })
public mediaSeek?: MediaSeek;
public seek?: Date;
@property({ attribute: false })
public view?: Readonly<View>;
@@ -314,13 +313,13 @@ export class FrigateCardThumbnail extends LitElement {
${this.details && ViewMediaClassifier.isEvent(this.media)
? html`<frigate-card-thumbnail-details-event
.media=${this.media ?? undefined}
.mediaSeek=${this.mediaSeek}
.seek=${this.seek}
></frigate-card-thumbnail-details-event>`
: this.details && ViewMediaClassifier.isRecording(this.media)
? html`<frigate-card-thumbnail-details-recording
.media=${this.media ?? undefined}
.cameraTitle=${getCameraTitle(this.hass, this.cameraConfig)}
.mediaSeek=${this.mediaSeek}
.seek=${this.seek}
></frigate-card-thumbnail-details-recording>`
: html``}
${shouldShowTimelineControl
+4 -20
View File
@@ -48,7 +48,6 @@ import {
createViewForEvents,
createViewForRecordings,
findClosestMediaIndex,
generateMediaViewerContext,
} from '../utils/media-to-view';
import { CameraManager } from '../camera/manager';
import { EventMediaQueries, MediaQueries } from '../view/media-queries';
@@ -407,16 +406,6 @@ export class FrigateCardTimelineCore extends LitElement {
}
const canSeek = !!this.view?.isViewerView();
const context = canSeek
? await generateMediaViewerContext(
this.hass,
this.cameraManager,
media,
targetTime,
)
: null;
const newResults = this._locked
? null
: results
@@ -444,7 +433,7 @@ export class FrigateCardTimelineCore extends LitElement {
}) // Whether or not to set the timeline window.
.mergeInContext({
...this._generateTimelineContext({ noSetWindow: true }),
...context,
...(canSeek && { mediaViewer: { seek: targetTime }})
})
.dispatchChangeEvent(this);
}
@@ -533,12 +522,7 @@ export class FrigateCardTimelineCore extends LitElement {
// view change.
if (eventView && results && results.length) {
eventView.mergeInContext(
await generateMediaViewerContext(
this.hass,
this.cameraManager,
results,
properties.time,
),
{mediaViewer: {seek: properties.time}}
);
view = eventView;
}
@@ -608,7 +592,7 @@ export class FrigateCardTimelineCore extends LitElement {
}
const prefetchedWindow = this._getPrefetchWindow(properties);
await this._timelineSource?.refresh(this.hass, this.cameras, prefetchedWindow);
await this._timelineSource?.refresh(this.hass, prefetchedWindow);
// Don't show event thumbnails if the user is looking at recordings,
// as the recording "hours" are the media, not the event
@@ -918,7 +902,7 @@ export class FrigateCardTimelineCore extends LitElement {
// (via fetchIfNecessary) may update the timeline contents which causes
// the visjs timeline to stop dragging/panning operations which is very
// disruptive to the user.
await this._timelineSource?.refresh(this.hass, this.cameras, prefetchedWindow);
await this._timelineSource?.refresh(this.hass, prefetchedWindow);
}
const mediaID = media?.getID();
+26 -22
View File
@@ -58,17 +58,8 @@ import { guard } from 'lit/directives/guard.js';
import { localize } from '../localize/localize.js';
import { MediaQueriesResults } from '../view/media-queries-results.js';
export interface MediaSeek {
// Specifies the point at which this recording should be played, the
// seek_time is the date of the desired play point (for display purposes
// usually), and seek_seconds is the number of seconds to seek into the video
// stream to reach that point.
seekTime: number;
seekSeconds: number;
}
export interface MediaViewerViewContext {
seek: Map<number, MediaSeek>;
seek?: Date;
}
declare module 'view' {
@@ -260,6 +251,8 @@ export class FrigateCardViewerCarousel extends LitElement {
* @param changedProperties The properties that were changed in this render.
*/
updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (changedProperties.has('view')) {
const oldView = changedProperties.get('view') as View | undefined;
// Seek into the video if the seek time has changed (this is also called
@@ -269,7 +262,6 @@ export class FrigateCardViewerCarousel extends LitElement {
this._seekHandler();
}
}
super.updated(changedProperties);
}
/**
@@ -418,17 +410,26 @@ export class FrigateCardViewerCarousel extends LitElement {
* Handle the user selecting a new slide in the carousel.
*/
protected _setViewHandler(ev: CustomEvent<CarouselSelect>): void {
// The slide may already be selected on load, so don't dispatch a new view
// unless necessary.
if (ev.detail.index !== this.view?.queryResults?.getSelectedIndex()) {
this._setViewSelectedIndex(ev.detail.index);
}
}
protected _setViewSelectedIndex(index: number): void {
// The slide may already be selected on load, so don't dispatch a new view
// unless necessary.
const newResults = this.view?.queryResults?.clone().selectResult(index);
if (!newResults) {
return;
}
const cameraID = newResults.getSelectedResult()?.getCameraID();
this.view
?.evolve({
queryResults: this.view.queryResults?.clone().selectResult(index),
queryResults: newResults,
// Always change the camera to the owner of the selected media.
...(cameraID && { camera: cameraID }),
})
// Ensure the timeline is able to update its position.
.mergeInContext({ timeline: { noSetWindow: false } })
@@ -626,15 +627,18 @@ export class FrigateCardViewerCarousel extends LitElement {
/**
* Fire a media show event when a slide is selected.
*/
protected _seekHandler(): void {
const selectedIndex = this.view?.queryResults?.getSelectedIndex() ?? null;
const seek =
selectedIndex !== null
? this.view?.context?.mediaViewer?.seek.get(selectedIndex)
: null;
protected async _seekHandler(): Promise<void> {
const seek = this.view?.context?.mediaViewer?.seek;
const media = this.view?.queryResults?.getSelectedResult();
if (!this.hass || !media || !seek) {
return;
}
const seekTime =
(await this.cameraManager?.getMediaSeekTime(this.hass, media, seek)) ?? null;
const player = this._getPlayer();
if (player && seek) {
player.seek(seek.seekSeconds);
if (player && seekTime !== null) {
player.seek(seekTime);
}
}