mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-18 21:54:09 +00:00
refactor: migrate custom fields to transactions
refactor: extract functions to api domain refactor: strict custom field parsing refactor: remove rundown cache utilities refactor: directory restructure
This commit is contained in:
@@ -23,23 +23,22 @@ import { eventStore } from '../../stores/EventStore.js';
|
||||
import { triggerReportEntry } from '../../api-data/report/report.service.js';
|
||||
import { timerConfig } from '../../setup/config.js';
|
||||
import { triggerAutomations } from '../../api-data/automation/automation.service.js';
|
||||
import { getCurrentRundown } from '../../api-data/rundown/rundown.dao.js';
|
||||
import { getCurrentRundown, getEntryWithId, getRundownMetadata } from '../../api-data/rundown/rundown.dao.js';
|
||||
|
||||
import { EventTimer } from '../EventTimer.js';
|
||||
import { RestorePoint, restoreService } from '../RestoreService.js';
|
||||
import {
|
||||
findNext,
|
||||
findPrevious,
|
||||
getEventAtIndex,
|
||||
getNextEventWithCue,
|
||||
getEntryWithId,
|
||||
getTimedEvents,
|
||||
getRundownData,
|
||||
} from '../rundown-service/rundownUtils.js';
|
||||
import { skippedOutOfEvent } from '../timerUtils.js';
|
||||
import { getEventOrder } from '../rundown-service/rundownCache.js';
|
||||
|
||||
import { getForceUpdate, getShouldClockUpdate, getShouldTimerUpdate } from './rundownService.utils.js';
|
||||
import {
|
||||
filterTimedEvents,
|
||||
findNextPlayableId,
|
||||
findNextPlayableWithCue,
|
||||
findPreviousPlayableId,
|
||||
getEventAtIndex,
|
||||
getForceUpdate,
|
||||
getShouldClockUpdate,
|
||||
getShouldTimerUpdate,
|
||||
} from './rundownService.utils.js';
|
||||
|
||||
type RuntimeStateEventKeys = keyof Pick<RuntimeState, 'eventNext' | 'eventNow' | 'publicEventNow' | 'publicEventNext'>;
|
||||
|
||||
@@ -198,7 +197,10 @@ class RuntimeService {
|
||||
}
|
||||
|
||||
private isNewNext() {
|
||||
const timedEvents = getTimedEvents();
|
||||
const rundown = getCurrentRundown();
|
||||
const { timedEventOrder } = getRundownMetadata();
|
||||
const timedEvents = filterTimedEvents(rundown, timedEventOrder);
|
||||
|
||||
const state = runtimeState.getState();
|
||||
const now = state.eventNow?.id;
|
||||
const next = state.eventNext?.id;
|
||||
@@ -271,8 +273,8 @@ class RuntimeService {
|
||||
runtimeState.updateLoaded(eventNow);
|
||||
} else {
|
||||
const rundown = getCurrentRundown();
|
||||
const { timedEventsOrder } = getEventOrder();
|
||||
runtimeState.updateAll(rundown, timedEventsOrder);
|
||||
const { timedEventOrder } = getRundownMetadata();
|
||||
runtimeState.updateAll(rundown, timedEventOrder);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -281,7 +283,9 @@ class RuntimeService {
|
||||
// Maybe the event will become the next
|
||||
isNext = this.isNewNext();
|
||||
if (isNext) {
|
||||
const timedEvents = getTimedEvents();
|
||||
const rundown = getCurrentRundown();
|
||||
const { timedEventOrder } = getRundownMetadata();
|
||||
const timedEvents = filterTimedEvents(rundown, timedEventOrder);
|
||||
runtimeState.loadNext(timedEvents);
|
||||
}
|
||||
}
|
||||
@@ -299,8 +303,10 @@ class RuntimeService {
|
||||
}
|
||||
const previousState = runtimeState.getState();
|
||||
|
||||
const { rundown, rundownOrder } = getRundownData();
|
||||
const success = runtimeState.load(event, rundown, rundownOrder.timedEventsOrder, initialData);
|
||||
// we can ignore events which are not playable
|
||||
const rundown = getCurrentRundown();
|
||||
const rundownMetadata = getRundownMetadata();
|
||||
const success = runtimeState.load(event, rundown, rundownMetadata.playableEventOrder, initialData);
|
||||
|
||||
if (success) {
|
||||
logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`);
|
||||
@@ -342,14 +348,20 @@ class RuntimeService {
|
||||
*/
|
||||
@broadcastResult
|
||||
public startByIndex(eventIndex: number): boolean {
|
||||
const event = getEventAtIndex(eventIndex);
|
||||
const rundown = getCurrentRundown();
|
||||
const { timedEventOrder } = getRundownMetadata();
|
||||
|
||||
const event = getEventAtIndex(rundown, timedEventOrder, eventIndex);
|
||||
|
||||
if (!event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const loaded = this.loadEvent(event);
|
||||
if (!loaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.handleStart();
|
||||
}
|
||||
|
||||
@@ -360,10 +372,21 @@ class RuntimeService {
|
||||
*/
|
||||
@broadcastResult
|
||||
public startByCue(cue: string): boolean {
|
||||
const event = getNextEventWithCue(cue); //TODO: add index
|
||||
const state = runtimeState.getState();
|
||||
const rundown = getCurrentRundown();
|
||||
const { playableEventOrder } = getRundownMetadata();
|
||||
|
||||
const event = findNextPlayableWithCue(
|
||||
rundown,
|
||||
playableEventOrder,
|
||||
cue,
|
||||
state.runtime.selectedEventIndex ?? undefined,
|
||||
);
|
||||
|
||||
if (!event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const loaded = this.loadEvent(event);
|
||||
if (!loaded) {
|
||||
return false;
|
||||
@@ -392,7 +415,11 @@ class RuntimeService {
|
||||
*/
|
||||
@broadcastResult
|
||||
public loadByIndex(eventIndex: number): boolean {
|
||||
const event = getEventAtIndex(eventIndex);
|
||||
const rundown = getCurrentRundown();
|
||||
const { timedEventOrder } = getRundownMetadata();
|
||||
|
||||
const event = getEventAtIndex(rundown, timedEventOrder, eventIndex);
|
||||
|
||||
if (!event) {
|
||||
return false;
|
||||
}
|
||||
@@ -406,10 +433,21 @@ class RuntimeService {
|
||||
*/
|
||||
@broadcastResult
|
||||
public loadByCue(cue: string): boolean {
|
||||
const event = getNextEventWithCue(cue); //TODO: add index
|
||||
const state = runtimeState.getState();
|
||||
const rundown = getCurrentRundown();
|
||||
const { playableEventOrder } = getRundownMetadata();
|
||||
|
||||
const event = findNextPlayableWithCue(
|
||||
rundown,
|
||||
playableEventOrder,
|
||||
cue,
|
||||
state.runtime.selectedEventIndex ?? undefined,
|
||||
);
|
||||
|
||||
if (!event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.loadEvent(event);
|
||||
}
|
||||
|
||||
@@ -421,10 +459,16 @@ class RuntimeService {
|
||||
*/
|
||||
private handleLoadPrevious(): boolean {
|
||||
const state = runtimeState.getState();
|
||||
const previousEvent = findPrevious(state.eventNow?.id);
|
||||
if (previousEvent) {
|
||||
return this.loadEvent(previousEvent);
|
||||
const { playableEventOrder } = getRundownMetadata();
|
||||
|
||||
const previousId = findPreviousPlayableId(playableEventOrder, state.eventNow?.id);
|
||||
if (previousId) {
|
||||
const previousEvent = getEntryWithId(previousId);
|
||||
if (previousEvent && isOntimeEvent(previousEvent)) {
|
||||
return this.loadEvent(previousEvent);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -446,8 +490,15 @@ class RuntimeService {
|
||||
*/
|
||||
private handleLoadNext(): boolean {
|
||||
const state = runtimeState.getState();
|
||||
const nextEvent = findNext(state.eventNow?.id);
|
||||
if (nextEvent) {
|
||||
const { playableEventOrder } = getRundownMetadata();
|
||||
|
||||
const nextId = findNextPlayableId(playableEventOrder, state.eventNow?.id);
|
||||
if (nextId) {
|
||||
const nextEvent = getEntryWithId(nextId);
|
||||
if (!nextEvent || !isOntimeEvent(nextEvent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state.timer.playback === Playback.Roll) {
|
||||
return this.loadEvent(nextEvent, { firstStart: state.runtime.actualStart });
|
||||
}
|
||||
@@ -457,7 +508,6 @@ class RuntimeService {
|
||||
logger.info(LogOrigin.Playback, 'No next event found! Continuing playback');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads event after currently selected
|
||||
* @return {boolean} success
|
||||
@@ -585,10 +635,10 @@ class RuntimeService {
|
||||
*/
|
||||
private rollLoaded(offset?: number) {
|
||||
const rundown = getCurrentRundown();
|
||||
const { timedEventsOrder } = getEventOrder();
|
||||
const { timedEventOrder } = getRundownMetadata();
|
||||
|
||||
try {
|
||||
runtimeState.roll(rundown, timedEventsOrder, offset);
|
||||
runtimeState.roll(rundown, timedEventOrder, offset);
|
||||
} catch (error) {
|
||||
logger.error(LogOrigin.Server, `Roll: ${error}`);
|
||||
}
|
||||
@@ -608,8 +658,10 @@ class RuntimeService {
|
||||
}
|
||||
|
||||
try {
|
||||
const { rundown, rundownOrder } = getRundownData();
|
||||
const result = runtimeState.roll(rundown, rundownOrder.timedEventsOrder);
|
||||
const rundown = getCurrentRundown();
|
||||
const rundownMetadata = getRundownMetadata();
|
||||
const result = runtimeState.roll(rundown, rundownMetadata.playableEventOrder);
|
||||
|
||||
const newState = runtimeState.getState();
|
||||
if (result.eventId !== previousState.eventNow?.id) {
|
||||
logger.info(LogOrigin.Playback, `Loaded event with ID ${result.eventId}`);
|
||||
@@ -660,8 +712,10 @@ class RuntimeService {
|
||||
return;
|
||||
}
|
||||
|
||||
const { rundown, rundownOrder } = getRundownData();
|
||||
runtimeState.resume(restorePoint, event, rundown, rundownOrder.timedEventsOrder);
|
||||
const rundown = getCurrentRundown();
|
||||
const rundownMetadata = getRundownMetadata();
|
||||
runtimeState.resume(restorePoint, event, rundown, rundownMetadata.playableEventOrder);
|
||||
|
||||
logger.info(LogOrigin.Playback, 'Resuming playback');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { millisToSeconds } from 'ontime-utils';
|
||||
import { MaybeNumber, TimerType } from 'ontime-types';
|
||||
import { EntryId, isOntimeEvent, isPlayableEvent, MaybeNumber, OntimeEvent, Rundown, TimerType } from 'ontime-types';
|
||||
|
||||
import { timerConfig } from '../../setup/config.js';
|
||||
|
||||
@@ -33,3 +33,97 @@ export function getForceUpdate(previousUpdate: number, now: number): boolean {
|
||||
const hasExceededRate = now - previousUpdate >= timerConfig.notificationRate;
|
||||
return isClockBehind || hasExceededRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* finds the previous playable event, if it exists
|
||||
*/
|
||||
export function findPreviousPlayableId(playableEventsOrder: EntryId[], currentEventId?: string): EntryId | undefined {
|
||||
if (!playableEventsOrder.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is no event running, go to first
|
||||
if (!currentEventId) {
|
||||
return getFirstPlayableId(playableEventsOrder);
|
||||
}
|
||||
|
||||
const currentIndex = playableEventsOrder.findIndex((eventId) => eventId === currentEventId);
|
||||
|
||||
if (currentIndex < 1) {
|
||||
return getFirstPlayableId(playableEventsOrder);
|
||||
}
|
||||
|
||||
return playableEventsOrder.at(currentIndex - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* finds the next event playable event, if it exists
|
||||
*/
|
||||
export function findNextPlayableId(playableEventsOrder: EntryId[], currentEventId?: string): EntryId | undefined {
|
||||
if (!playableEventsOrder.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is no event running, go to first
|
||||
if (!currentEventId) {
|
||||
return getFirstPlayableId(playableEventsOrder);
|
||||
}
|
||||
|
||||
const currentIndex = playableEventsOrder.findIndex((eventId) => eventId === currentEventId);
|
||||
if (currentIndex === -1 || currentIndex >= playableEventsOrder.length - 1) {
|
||||
return getFirstPlayableId(playableEventsOrder);
|
||||
}
|
||||
|
||||
return playableEventsOrder.at(currentIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns first event that matches a given cue
|
||||
*/
|
||||
export function findNextPlayableWithCue(
|
||||
rundown: Rundown,
|
||||
playableEventsOrder: EntryId[],
|
||||
targetCue: string,
|
||||
currentEventIndex = 0,
|
||||
): OntimeEvent | undefined {
|
||||
const lowerCaseCue = targetCue.toLowerCase();
|
||||
|
||||
for (let i = currentEventIndex; i < playableEventsOrder.length; i++) {
|
||||
const eventId = playableEventsOrder[i];
|
||||
const event = rundown.entries[eventId];
|
||||
if (isOntimeEvent(event) && isPlayableEvent(event) && event.cue.toLowerCase() === lowerCaseCue) {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility returns the first playable event in rundown, if it exists
|
||||
*/
|
||||
export function getFirstPlayableId(playableOrder: EntryId[]): EntryId | undefined {
|
||||
return playableOrder.at(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a utility function to return an event at a given index
|
||||
* It uses the timedEventOrder so that the index is the same as the one in the UI
|
||||
*/
|
||||
export function getEventAtIndex(
|
||||
rundown: Rundown,
|
||||
timedEventOrder: EntryId[],
|
||||
eventIndex: number,
|
||||
): OntimeEvent | undefined {
|
||||
const eventId = timedEventOrder[eventIndex];
|
||||
if (!eventId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return rundown.entries[eventId] as OntimeEvent | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO(v4): we dont need this function
|
||||
*/
|
||||
export function filterTimedEvents(rundown: Rundown, timedEventOrder: EntryId[]): OntimeEvent[] {
|
||||
return timedEventOrder.map((id) => rundown.entries[id] as OntimeEvent);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user