mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-11 00:59:35 +00:00
refactor: use runtime metadate in runtimeState (#1653)
* change load to use metadata * update roll to use metadata * remove filterTimedEvents * add comment * atempth to refactor rollUtils * refactor util functions
This commit is contained in:
committed by
GitHub
parent
e090bde8bb
commit
14a99ce27b
@@ -23,6 +23,7 @@ import {
|
|||||||
|
|
||||||
import { event as eventDef, block as blockDef, delay as delayDef } from '../../models/eventsDefinition.js';
|
import { event as eventDef, block as blockDef, delay as delayDef } from '../../models/eventsDefinition.js';
|
||||||
import { makeString } from '../../utils/parserUtils.js';
|
import { makeString } from '../../utils/parserUtils.js';
|
||||||
|
import { RundownMetadata } from './rundown.types.js';
|
||||||
|
|
||||||
type CompleteEntry<T> =
|
type CompleteEntry<T> =
|
||||||
T extends Partial<OntimeEvent>
|
T extends Partial<OntimeEvent>
|
||||||
@@ -356,3 +357,23 @@ export function getInsertAfterId(rundown: Rundown, afterId?: EntryId, beforeId?:
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* converts an index from the timedEventOrder to an index in the playableEventOrder
|
||||||
|
* or returns null if it can not be found
|
||||||
|
*/
|
||||||
|
export function getPlayableIndexFromTimedIndex(metadata: RundownMetadata, index: number): number | null {
|
||||||
|
const timedId = metadata.timedEventOrder[index];
|
||||||
|
const playableIndex = metadata.playableEventOrder.findIndex((id) => id === timedId);
|
||||||
|
return playableIndex < 0 ? null : playableIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* converts an index from the playableEventOrder to an index in the timedEventOrder
|
||||||
|
* all indexes in playableEventOrder must also exist in timedEventOrder, otherwise the app is broken
|
||||||
|
*/
|
||||||
|
export function getTimedIndexFromPlayableIndex(metadata: RundownMetadata, index: number): number {
|
||||||
|
const playableId = metadata.playableEventOrder[index];
|
||||||
|
const timedIndex = metadata.timedEventOrder.findIndex((id) => id === playableId);
|
||||||
|
return timedIndex;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,185 +1,265 @@
|
|||||||
import { MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from 'ontime-utils';
|
import { dayInMs, MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||||
|
|
||||||
import { loadRoll } from '../rollUtils.js';
|
import { loadRoll } from '../rollUtils.js';
|
||||||
import { prepareTimedEvents, makeOntimeEvent } from '../../api-data/rundown/__mocks__/rundown.mocks.js';
|
import { makeRundown } from '../../api-data/rundown/__mocks__/rundown.mocks.js';
|
||||||
|
import { PlayableEvent } from 'ontime-types';
|
||||||
|
import { initRundown } from '../../api-data/rundown/rundown.service.js';
|
||||||
|
import { rundownCache } from '../../api-data/rundown/rundown.dao.js';
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
vi.mock('../../classes/data-provider/DataProvider.js', () => {
|
||||||
|
return {
|
||||||
|
getDataProvider: vi.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
setCustomFields: vi.fn().mockImplementation((newData) => newData),
|
||||||
|
setRundown: vi.fn().mockImplementation((newData) => newData),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockEvent = {
|
||||||
|
type: 'event',
|
||||||
|
id: 'mock',
|
||||||
|
cue: 'mock',
|
||||||
|
timeStart: 0,
|
||||||
|
timeEnd: 1000,
|
||||||
|
duration: 1000,
|
||||||
|
skip: false,
|
||||||
|
parent: null,
|
||||||
|
} as PlayableEvent;
|
||||||
|
|
||||||
describe('loadRoll()', () => {
|
describe('loadRoll()', () => {
|
||||||
const eventlist = [
|
beforeEach(async () => {
|
||||||
{
|
vi.useFakeTimers();
|
||||||
|
await initRundown(
|
||||||
|
makeRundown({
|
||||||
|
entries: {
|
||||||
|
'1': {
|
||||||
|
...mockEvent,
|
||||||
id: '1',
|
id: '1',
|
||||||
timeStart: 5,
|
timeStart: 5,
|
||||||
timeEnd: 10,
|
timeEnd: 10,
|
||||||
|
duration: 5,
|
||||||
},
|
},
|
||||||
{
|
'2': {
|
||||||
|
...mockEvent,
|
||||||
id: '2',
|
id: '2',
|
||||||
timeStart: 10,
|
timeStart: 10,
|
||||||
timeEnd: 20,
|
timeEnd: 20,
|
||||||
|
duration: 10,
|
||||||
},
|
},
|
||||||
{
|
'3': {
|
||||||
|
...mockEvent,
|
||||||
id: '3',
|
id: '3',
|
||||||
timeStart: 20,
|
timeStart: 20,
|
||||||
timeEnd: 30,
|
timeEnd: 30,
|
||||||
|
duration: 10,
|
||||||
},
|
},
|
||||||
{
|
'4': {
|
||||||
|
...mockEvent,
|
||||||
id: '4',
|
id: '4',
|
||||||
timeStart: 30,
|
timeStart: 30,
|
||||||
timeEnd: 40,
|
timeEnd: 40,
|
||||||
|
duration: 10,
|
||||||
},
|
},
|
||||||
{
|
'5': {
|
||||||
|
...mockEvent,
|
||||||
id: '5',
|
id: '5',
|
||||||
timeStart: 40,
|
timeStart: 40,
|
||||||
timeEnd: 50,
|
timeEnd: 50,
|
||||||
|
duration: 10,
|
||||||
},
|
},
|
||||||
{
|
'6': {
|
||||||
|
...mockEvent,
|
||||||
id: '6',
|
id: '6',
|
||||||
timeStart: 50,
|
timeStart: 50,
|
||||||
timeEnd: 60,
|
timeEnd: 60,
|
||||||
|
duration: 10,
|
||||||
},
|
},
|
||||||
{
|
'7': {
|
||||||
|
...mockEvent,
|
||||||
id: '7',
|
id: '7',
|
||||||
timeStart: 60,
|
timeStart: 60,
|
||||||
timeEnd: 70,
|
timeEnd: 70,
|
||||||
|
duration: 10,
|
||||||
},
|
},
|
||||||
{
|
'8': {
|
||||||
|
...mockEvent,
|
||||||
id: '8',
|
id: '8',
|
||||||
timeStart: 70,
|
timeStart: 70,
|
||||||
timeEnd: 80,
|
timeEnd: 80,
|
||||||
|
duration: 10,
|
||||||
},
|
},
|
||||||
];
|
},
|
||||||
const timedEvents = prepareTimedEvents(eventlist);
|
order: ['1', '2', '3', '4', '5', '6', '7', '8'],
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
it('should roll to the day after if timer is at 100', () => {
|
it('should roll to the day after if timer is at 100', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 100;
|
const now = 100;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[0],
|
event: rundown.entries['1'],
|
||||||
index: 0,
|
index: 0,
|
||||||
isPending: true,
|
isPending: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be waiting to start if timer is at 0', () => {
|
it('should be waiting to start if timer is at 0', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 0;
|
const now = 0;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[0],
|
event: rundown.entries['1'],
|
||||||
index: 0,
|
index: 0,
|
||||||
isPending: true,
|
isPending: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should start the first event if timer is at 5', () => {
|
it('should start the first event if timer is at 5', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 5;
|
const now = 5;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[0],
|
event: rundown.entries['1'],
|
||||||
index: 0,
|
index: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should start the second event if timer is at 15', () => {
|
it('should start the second event if timer is at 15', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 15;
|
const now = 15;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[1],
|
event: rundown.entries['2'],
|
||||||
index: 1,
|
index: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should start the third event if timer is at 10', () => {
|
it('should start the third event if timer is at 10', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 20;
|
const now = 20;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[2],
|
event: rundown.entries['3'],
|
||||||
index: 2,
|
index: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should start the fifth event if timer is at 49', () => {
|
it('should start the fifth event if timer is at 49', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 49;
|
const now = 49;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[4],
|
event: rundown.entries['5'],
|
||||||
index: 4,
|
index: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should start the seventh event if timer is at 63', () => {
|
it('should start the seventh event if timer is at 63', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 63;
|
const now = 63;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[6],
|
event: rundown.entries['7'],
|
||||||
index: 6,
|
index: 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should start the eight event if timer is at 75', () => {
|
it('should start the eight event if timer is at 75', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 75;
|
const now = 75;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[7],
|
event: rundown.entries['8'],
|
||||||
index: 7,
|
index: 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loadRoll() handle edge cases with midnight', () => {
|
describe('loadRoll() handle edge cases with midnight', () => {
|
||||||
it('should find an event that crosses midnight', () => {
|
it('should find an event that crosses midnight', async () => {
|
||||||
const now = 23 * MILLIS_PER_HOUR;
|
const now = 23 * MILLIS_PER_HOUR;
|
||||||
const eventlist = [
|
vi.useFakeTimers();
|
||||||
{
|
rundownCache.init(
|
||||||
|
makeRundown({
|
||||||
|
order: ['0', '1', '2', '3', '4'],
|
||||||
|
entries: {
|
||||||
|
'0': {
|
||||||
|
...mockEvent,
|
||||||
id: '0',
|
id: '0',
|
||||||
timeStart: 9 * MILLIS_PER_HOUR,
|
timeStart: 9 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 10 * MILLIS_PER_HOUR,
|
timeEnd: 10 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
{
|
'1': {
|
||||||
|
...mockEvent,
|
||||||
id: '1',
|
id: '1',
|
||||||
timeStart: 20 * MILLIS_PER_HOUR,
|
timeStart: 20 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 22 * MILLIS_PER_HOUR,
|
timeEnd: 22 * MILLIS_PER_HOUR,
|
||||||
|
duration: 2 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
{
|
'2': {
|
||||||
|
...mockEvent,
|
||||||
id: '2',
|
id: '2',
|
||||||
timeStart: 22 * MILLIS_PER_HOUR,
|
timeStart: 22 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 1 * MILLIS_PER_HOUR,
|
timeEnd: 1 * MILLIS_PER_HOUR,
|
||||||
|
duration: 3 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
{
|
'3': {
|
||||||
|
...mockEvent,
|
||||||
id: '3',
|
id: '3',
|
||||||
timeStart: 1 * MILLIS_PER_HOUR,
|
timeStart: 1 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 1 * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE,
|
timeEnd: 1 * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE,
|
||||||
|
duration: 10 * MILLIS_PER_MINUTE,
|
||||||
},
|
},
|
||||||
{
|
'4': {
|
||||||
|
...mockEvent,
|
||||||
id: '4',
|
id: '4',
|
||||||
timeStart: 1 * MILLIS_PER_HOUR,
|
timeStart: 1 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 2 * MILLIS_PER_HOUR,
|
timeEnd: 2 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
];
|
},
|
||||||
const timedEvents = prepareTimedEvents(eventlist);
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[2],
|
event: rundown.entries['2'],
|
||||||
index: 2,
|
index: 2,
|
||||||
};
|
};
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not skip to the second day', () => {
|
it('should not skip to the second day', async () => {
|
||||||
/**
|
/**
|
||||||
* NOTE: this is a potentially contentious decision
|
* NOTE: this is a potentially contentious decision
|
||||||
*
|
*
|
||||||
@@ -188,102 +268,160 @@ describe('loadRoll() handle edge cases with midnight', () => {
|
|||||||
*
|
*
|
||||||
* On our side, this simplifies logic and makes behaviour more predictable
|
* On our side, this simplifies logic and makes behaviour more predictable
|
||||||
*/
|
*/
|
||||||
const now = 8 * MILLIS_PER_HOUR;
|
|
||||||
const eventlist = [
|
vi.useFakeTimers();
|
||||||
{
|
await initRundown(
|
||||||
|
makeRundown({
|
||||||
|
order: ['0', '1', '2'],
|
||||||
|
entries: {
|
||||||
|
'0': {
|
||||||
|
...mockEvent,
|
||||||
id: '0',
|
id: '0',
|
||||||
timeStart: 21 * MILLIS_PER_HOUR,
|
timeStart: 21 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 22 * MILLIS_PER_HOUR,
|
timeEnd: 22 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
{
|
'1': {
|
||||||
|
...mockEvent,
|
||||||
id: '1',
|
id: '1',
|
||||||
timeStart: 22 * MILLIS_PER_HOUR,
|
timeStart: 22 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 3 * MILLIS_PER_HOUR,
|
timeEnd: 3 * MILLIS_PER_HOUR,
|
||||||
|
duration: 5 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
{
|
'2': {
|
||||||
|
...mockEvent,
|
||||||
id: '2',
|
id: '2',
|
||||||
timeStart: 3 * MILLIS_PER_HOUR,
|
timeStart: 3 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 10 * MILLIS_PER_HOUR,
|
timeEnd: 10 * MILLIS_PER_HOUR,
|
||||||
|
duration: 7 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
];
|
},
|
||||||
const timedEvents = prepareTimedEvents(eventlist);
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
const now = 8 * MILLIS_PER_HOUR;
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[0],
|
event: rundown.entries['0'],
|
||||||
index: 0,
|
index: 0,
|
||||||
isPending: true,
|
isPending: true,
|
||||||
};
|
};
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loadRoll() handle rundowns with several days', () => {
|
describe('loadRoll() handle rundowns with several days', () => {
|
||||||
it('should find the correct event, when we have many days', () => {
|
it('should find the correct event, when we have many days', async () => {
|
||||||
const now = 11 * MILLIS_PER_HOUR + 30 * MILLIS_PER_MINUTE;
|
vi.useFakeTimers();
|
||||||
const timedEvents = [
|
await initRundown(
|
||||||
{
|
makeRundown({
|
||||||
|
order: ['0', '2', '3', '4'],
|
||||||
|
entries: {
|
||||||
|
'0': {
|
||||||
|
...mockEvent,
|
||||||
id: '0',
|
id: '0',
|
||||||
timeStart: 10 * MILLIS_PER_HOUR,
|
timeStart: 10 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 11 * MILLIS_PER_HOUR,
|
timeEnd: 11 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
{
|
'2': {
|
||||||
|
...mockEvent,
|
||||||
id: '2',
|
id: '2',
|
||||||
timeStart: 11 * MILLIS_PER_HOUR,
|
timeStart: 11 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 12 * MILLIS_PER_HOUR,
|
timeEnd: 12 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
{
|
'3': {
|
||||||
|
...mockEvent,
|
||||||
id: '3',
|
id: '3',
|
||||||
timeStart: 12 * MILLIS_PER_HOUR,
|
timeStart: 12 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 13 * MILLIS_PER_HOUR,
|
timeEnd: 13 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
{
|
'4': {
|
||||||
|
...mockEvent,
|
||||||
id: '4',
|
id: '4',
|
||||||
timeStart: 11 * MILLIS_PER_HOUR,
|
timeStart: 11 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 12 * MILLIS_PER_HOUR,
|
timeEnd: 12 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
];
|
},
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
const state = loadRoll(prepareTimedEvents(timedEvents), now);
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
const now = 11 * MILLIS_PER_HOUR + 30 * MILLIS_PER_MINUTE;
|
||||||
|
|
||||||
|
const state = loadRoll(rundown, metadata, now);
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[1],
|
event: rundown.entries['2'],
|
||||||
index: 1,
|
index: 1,
|
||||||
};
|
};
|
||||||
expect(state).toMatchObject(expected);
|
expect(state).toMatchObject(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find the correct event, when we have events of zero duration', () => {
|
it('should find the correct event, when we have events of zero duration', async () => {
|
||||||
const now = 20 * MILLIS_PER_HOUR + 37 * MILLIS_PER_MINUTE;
|
vi.useFakeTimers();
|
||||||
const timedEvents = [
|
await initRundown(
|
||||||
{
|
makeRundown({
|
||||||
|
order: ['0', '1 no duration', '2', '3 no duration', '4'],
|
||||||
|
entries: {
|
||||||
|
'0': {
|
||||||
|
...mockEvent,
|
||||||
id: '0',
|
id: '0',
|
||||||
timeStart: 18 * MILLIS_PER_HOUR,
|
timeStart: 18 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 19 * MILLIS_PER_HOUR,
|
timeEnd: 19 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
{
|
'1 no duration': {
|
||||||
|
...mockEvent,
|
||||||
id: '1 no duration',
|
id: '1 no duration',
|
||||||
timeStart: 0,
|
timeStart: 0,
|
||||||
timeEnd: 0,
|
timeEnd: 0,
|
||||||
|
duration: 0,
|
||||||
},
|
},
|
||||||
{
|
'2': {
|
||||||
|
...mockEvent,
|
||||||
id: '2',
|
id: '2',
|
||||||
timeStart: 19 * MILLIS_PER_HOUR,
|
timeStart: 19 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 20 * MILLIS_PER_HOUR,
|
timeEnd: 20 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
{
|
['3 no duration']: {
|
||||||
|
...mockEvent,
|
||||||
id: '3 no duration',
|
id: '3 no duration',
|
||||||
timeStart: 0,
|
timeStart: 0,
|
||||||
timeEnd: 0,
|
timeEnd: 0,
|
||||||
|
duration: 0,
|
||||||
},
|
},
|
||||||
{
|
'4': {
|
||||||
|
...mockEvent,
|
||||||
id: '4',
|
id: '4',
|
||||||
timeStart: 20 * MILLIS_PER_HOUR,
|
timeStart: 20 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 21 * MILLIS_PER_HOUR,
|
timeEnd: 21 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
},
|
},
|
||||||
];
|
},
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
const state = loadRoll(prepareTimedEvents(timedEvents), now);
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
const now = 20 * MILLIS_PER_HOUR + 37 * MILLIS_PER_MINUTE;
|
||||||
|
|
||||||
|
const state = loadRoll(rundown, metadata, now);
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[4],
|
event: rundown.entries['4'],
|
||||||
index: 4,
|
index: 4,
|
||||||
};
|
};
|
||||||
expect(state).toMatchObject(expected);
|
expect(state).toMatchObject(expected);
|
||||||
@@ -291,194 +429,338 @@ describe('loadRoll() handle rundowns with several days', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('loadRoll() handle edge cases with before and after start', () => {
|
describe('loadRoll() handle edge cases with before and after start', () => {
|
||||||
it('should prepare first event, if we are not yet in the rundown start', () => {
|
it('should prepare first event, if we are not yet in the rundown start', async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
await initRundown(
|
||||||
|
makeRundown({
|
||||||
|
order: ['1'],
|
||||||
|
entries: {
|
||||||
|
'1': {
|
||||||
|
...mockEvent,
|
||||||
|
id: '1',
|
||||||
|
timeStart: 10 * MILLIS_PER_HOUR,
|
||||||
|
timeEnd: 11 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 7 * MILLIS_PER_HOUR;
|
const now = 7 * MILLIS_PER_HOUR;
|
||||||
const singleEventList = [
|
|
||||||
makeOntimeEvent({
|
|
||||||
id: '1',
|
|
||||||
timeStart: 10 * MILLIS_PER_HOUR,
|
|
||||||
timeEnd: 11 * MILLIS_PER_HOUR,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
event: singleEventList[0],
|
event: rundown.entries['1'],
|
||||||
index: 0,
|
index: 0,
|
||||||
isPending: true,
|
isPending: true,
|
||||||
};
|
};
|
||||||
const state = loadRoll(singleEventList, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should prepare first event, if we are over the rundown end', () => {
|
it('should prepare first event, if we are over the rundown end', async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
await initRundown(
|
||||||
|
makeRundown({
|
||||||
|
order: ['1'],
|
||||||
|
entries: {
|
||||||
|
'1': {
|
||||||
|
...mockEvent,
|
||||||
|
id: '1',
|
||||||
|
timeStart: 10 * MILLIS_PER_HOUR,
|
||||||
|
timeEnd: 11 * MILLIS_PER_HOUR,
|
||||||
|
duration: 1 * MILLIS_PER_HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 18 * MILLIS_PER_HOUR;
|
const now = 18 * MILLIS_PER_HOUR;
|
||||||
const singleEventList = [
|
|
||||||
makeOntimeEvent({
|
|
||||||
id: '1',
|
|
||||||
timeStart: 10 * MILLIS_PER_HOUR,
|
|
||||||
timeEnd: 11 * MILLIS_PER_HOUR,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
event: singleEventList[0],
|
event: rundown.entries['1'],
|
||||||
index: 0,
|
index: 0,
|
||||||
isPending: true,
|
isPending: true,
|
||||||
};
|
};
|
||||||
const state = loadRoll(singleEventList, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should account for a rundown that goes through midnight', () => {
|
it('should account for a rundown that goes through midnight', async () => {
|
||||||
const now = 1 * MILLIS_PER_HOUR;
|
vi.useFakeTimers();
|
||||||
const singleEventList = [
|
await initRundown(
|
||||||
makeOntimeEvent({
|
makeRundown({
|
||||||
|
order: ['1'],
|
||||||
|
entries: {
|
||||||
|
'1': {
|
||||||
|
...mockEvent,
|
||||||
id: '1',
|
id: '1',
|
||||||
timeStart: 10 * MILLIS_PER_HOUR,
|
timeStart: 10 * MILLIS_PER_HOUR,
|
||||||
timeEnd: 2 * MILLIS_PER_HOUR,
|
timeEnd: 2 * MILLIS_PER_HOUR,
|
||||||
|
duration: 16 * MILLIS_PER_HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
];
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
const now = 1 * MILLIS_PER_HOUR;
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
event: singleEventList[0],
|
event: rundown.entries['1'],
|
||||||
index: 0,
|
index: 0,
|
||||||
};
|
};
|
||||||
const state = loadRoll(singleEventList, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state.isPending).toBeUndefined(); // we are playing the event
|
expect(state.isPending).toBeUndefined(); // we are playing the event
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads upcoming event while waiting to roll', () => {
|
it('loads upcoming event while waiting to roll', async () => {
|
||||||
const now = 6000; // 00:01
|
vi.useFakeTimers();
|
||||||
const singleEventList = [
|
await initRundown(
|
||||||
makeOntimeEvent({
|
makeRundown({
|
||||||
|
order: ['1'],
|
||||||
|
entries: {
|
||||||
|
['1']: {
|
||||||
|
...mockEvent,
|
||||||
id: '1',
|
id: '1',
|
||||||
timeStart: 72000000, // 20:00
|
timeStart: 72000000, // 20:00
|
||||||
timeEnd: 72010000, // 20:10
|
timeEnd: 72010000, // 20:10
|
||||||
|
duration: 10 * MILLIS_PER_MINUTE,
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
];
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
const now = 6000; // 00:01
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
event: singleEventList[0],
|
event: rundown.entries['1'],
|
||||||
index: 0,
|
index: 0,
|
||||||
isPending: true,
|
isPending: true,
|
||||||
};
|
};
|
||||||
const state = loadRoll(singleEventList, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loadRoll() test that roll behaviour with overlapping times', () => {
|
describe('loadRoll() test that roll behaviour with overlapping times', () => {
|
||||||
const eventlist = [
|
beforeEach(async () => {
|
||||||
{
|
vi.useFakeTimers();
|
||||||
|
await initRundown(
|
||||||
|
makeRundown({
|
||||||
|
order: ['1', '2', '3'],
|
||||||
|
entries: {
|
||||||
|
['1']: {
|
||||||
|
...mockEvent,
|
||||||
id: '1',
|
id: '1',
|
||||||
timeStart: 10,
|
timeStart: 9,
|
||||||
timeEnd: 10,
|
timeEnd: 10,
|
||||||
|
duration: 1,
|
||||||
},
|
},
|
||||||
{
|
['2']: {
|
||||||
|
...mockEvent,
|
||||||
id: '2',
|
id: '2',
|
||||||
timeStart: 10,
|
timeStart: 10,
|
||||||
timeEnd: 20,
|
timeEnd: 20,
|
||||||
|
duration: 10,
|
||||||
},
|
},
|
||||||
{
|
['3']: {
|
||||||
|
...mockEvent,
|
||||||
id: '3',
|
id: '3',
|
||||||
timeStart: 10,
|
timeStart: 10,
|
||||||
timeEnd: 30,
|
timeEnd: 30,
|
||||||
|
duration: 10,
|
||||||
},
|
},
|
||||||
];
|
},
|
||||||
const timedEvents = prepareTimedEvents(eventlist);
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
it('if timer is at 0', () => {
|
it('if timer is at 0', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 0;
|
const now = 0;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[0],
|
event: rundown.entries['1'],
|
||||||
index: 0,
|
index: 0,
|
||||||
isPending: true,
|
isPending: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('if timer is at 10, it ignores events with 0 duration', () => {
|
it('if timer is at 10, it ignores events with 0 duration', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 10;
|
const now = 10;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[1],
|
event: rundown.entries['2'],
|
||||||
index: 1,
|
index: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('if timer is at 15', () => {
|
it('if timer is at 15', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 15;
|
const now = 15;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[1],
|
event: rundown.entries['2'],
|
||||||
index: 1,
|
index: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('if timer is at 20', () => {
|
it('if timer is at 20', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 20;
|
const now = 20;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[2],
|
event: rundown.entries['3'],
|
||||||
index: 2,
|
index: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('if timer is at 25', () => {
|
it('if timer is at 25', () => {
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const now = 25;
|
const now = 25;
|
||||||
const expected = {
|
const expected = {
|
||||||
event: timedEvents[2],
|
event: rundown.entries['3'],
|
||||||
index: 2,
|
index: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(timedEvents, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// issue #58
|
// issue #58
|
||||||
describe('loadRoll() test that roll behaviour multi day event edge cases', () => {
|
describe('loadRoll() test that roll behaviour multi day event edge cases', () => {
|
||||||
it('should recognise a playing event where its schedule spans over midnight', () => {
|
it('should recognise a playing event where its schedule spans over midnight', async () => {
|
||||||
const now = 66600000; // 19:30
|
vi.useFakeTimers();
|
||||||
const eventlist = [
|
await initRundown(
|
||||||
makeOntimeEvent({
|
makeRundown({
|
||||||
|
order: ['1'],
|
||||||
|
entries: {
|
||||||
|
'1': {
|
||||||
|
...mockEvent,
|
||||||
id: '1',
|
id: '1',
|
||||||
timeStart: 66000000, // 19:20
|
timeStart: 66000000, // 19:20
|
||||||
timeEnd: 54600000, // 16:10
|
timeEnd: 54600000, // 16:10
|
||||||
|
duration: 1, // value not important
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
];
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
const now = 66600000; // 19:30
|
||||||
const expected = {
|
const expected = {
|
||||||
event: eventlist[0],
|
event: rundown.entries['1'],
|
||||||
index: 0,
|
index: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(eventlist, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('if the start time is the day after end time, and both are later than now', () => {
|
it('if the start time is the day after end time, and both are later than now', async () => {
|
||||||
const now = 66840000; // 19:34
|
vi.useFakeTimers();
|
||||||
const eventlist = [
|
await initRundown(
|
||||||
makeOntimeEvent({
|
makeRundown({
|
||||||
|
order: ['1'],
|
||||||
|
entries: {
|
||||||
|
'1': {
|
||||||
|
...mockEvent,
|
||||||
id: '1',
|
id: '1',
|
||||||
timeStart: 67200000, // 19:40
|
timeStart: 67200000, // 19:40
|
||||||
timeEnd: 66900000, // 19:35
|
timeEnd: 66900000, // 19:35
|
||||||
|
duration: dayInMs - 5 * MILLIS_PER_MINUTE,
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
];
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
const now = 66840000; // 19:34
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
event: eventlist[0],
|
event: rundown.entries['1'],
|
||||||
index: 0,
|
index: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = loadRoll(eventlist, now);
|
const state = loadRoll(rundown, metadata, now);
|
||||||
expect(state.isPending).toBeUndefined(); // we are playing the event
|
expect(state.isPending).toBeUndefined(); // we are playing the event
|
||||||
expect(state).toStrictEqual(expected);
|
expect(state).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('loadRoll() should not roll skipped events', () => {
|
||||||
|
test('', async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
await initRundown(
|
||||||
|
makeRundown({
|
||||||
|
order: ['1', '2'],
|
||||||
|
entries: {
|
||||||
|
'1': {
|
||||||
|
...mockEvent,
|
||||||
|
id: '1',
|
||||||
|
timeStart: 10,
|
||||||
|
timeEnd: 20,
|
||||||
|
duration: 10,
|
||||||
|
skip: true,
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
...mockEvent,
|
||||||
|
id: '2',
|
||||||
|
timeStart: 20,
|
||||||
|
timeEnd: 30,
|
||||||
|
duration: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
vi.runAllTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
const now = 2;
|
||||||
|
const expected = {
|
||||||
|
event: rundown.entries['2'],
|
||||||
|
index: 1,
|
||||||
|
isPending: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = loadRoll(rundown, metadata, now);
|
||||||
|
expect(state).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
import { dayInMs, getFirstEvent } from 'ontime-utils';
|
import { dayInMs } from 'ontime-utils';
|
||||||
import { OntimeEvent, MaybeNumber, PlayableEvent, isPlayableEvent } from 'ontime-types';
|
import { MaybeNumber, PlayableEvent, Rundown } from 'ontime-types';
|
||||||
|
|
||||||
import { normaliseEndTime } from './timerUtils.js';
|
import { normaliseEndTime } from './timerUtils.js';
|
||||||
|
import { RundownMetadata } from '../api-data/rundown/rundown.types.js';
|
||||||
|
import { getTimedIndexFromPlayableIndex } from '../api-data/rundown/rundown.utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds current event in a rolling rundown
|
* Finds current event in a rolling rundown
|
||||||
*/
|
*/
|
||||||
export function loadRoll(
|
export function loadRoll(
|
||||||
timedEvents: OntimeEvent[],
|
rundown: Rundown,
|
||||||
|
metadata: RundownMetadata,
|
||||||
timeNow: number,
|
timeNow: number,
|
||||||
): {
|
): {
|
||||||
event: PlayableEvent | null;
|
event: PlayableEvent | null;
|
||||||
index: MaybeNumber;
|
index: MaybeNumber;
|
||||||
isPending?: boolean;
|
isPending?: boolean;
|
||||||
} {
|
} {
|
||||||
const { firstEvent } = getFirstEvent(timedEvents);
|
const firstEventId = metadata.playableEventOrder[0];
|
||||||
|
|
||||||
if (!firstEvent) {
|
if (!firstEventId) {
|
||||||
return { event: null, index: null };
|
return { event: null, index: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,13 +27,8 @@ export function loadRoll(
|
|||||||
// account for number of times we went over midnight
|
// account for number of times we went over midnight
|
||||||
let daySpan = 0;
|
let daySpan = 0;
|
||||||
|
|
||||||
for (let i = 0; i < timedEvents.length; i++) {
|
for (let i = 0; i < metadata.playableEventOrder.length; i++) {
|
||||||
const event = timedEvents[i];
|
const event = rundown.entries[metadata.playableEventOrder[i]] as PlayableEvent;
|
||||||
|
|
||||||
if (!isPlayableEvent(event)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.duration === 0) {
|
if (event.duration === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -62,16 +60,17 @@ export function loadRoll(
|
|||||||
const isFromDayBefore = normalEnd > dayInMs && timeNow < event.timeEnd;
|
const isFromDayBefore = normalEnd > dayInMs && timeNow < event.timeEnd;
|
||||||
const hasStarted = isFromDayBefore || timeNow >= event.timeStart;
|
const hasStarted = isFromDayBefore || timeNow >= event.timeStart;
|
||||||
if (hasStarted) {
|
if (hasStarted) {
|
||||||
return { event, index: i };
|
return { event, index: getTimedIndexFromPlayableIndex(metadata, i) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. event will run in the future
|
// 3. event will run in the future
|
||||||
// we set the isPending flag to indicate that the event is currently playing
|
// we set the isPending flag to indicate that the event is currently playing
|
||||||
return { event, index: i, isPending: true };
|
return { event, index: getTimedIndexFromPlayableIndex(metadata, i), isPending: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
// in case we were unable to find anything, we load the first event
|
// in case we were unable to find anything, we load the first event
|
||||||
return { event: firstEvent, index: 0, isPending: true };
|
console.log('returning first event');
|
||||||
|
return { event: rundown.entries[firstEventId] as PlayableEvent, index: 0, isPending: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import { RestorePoint, restoreService } from '../RestoreService.js';
|
|||||||
import { skippedOutOfEvent } from '../timerUtils.js';
|
import { skippedOutOfEvent } from '../timerUtils.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
filterTimedEvents,
|
|
||||||
findNextPlayableId,
|
findNextPlayableId,
|
||||||
findNextPlayableWithCue,
|
findNextPlayableWithCue,
|
||||||
findPreviousPlayableId,
|
findPreviousPlayableId,
|
||||||
@@ -190,17 +189,15 @@ class RuntimeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isNewNext() {
|
private isNewNext() {
|
||||||
const rundown = getCurrentRundown();
|
|
||||||
const { timedEventOrder } = getRundownMetadata();
|
const { timedEventOrder } = getRundownMetadata();
|
||||||
const timedEvents = filterTimedEvents(rundown, timedEventOrder);
|
|
||||||
|
|
||||||
const state = runtimeState.getState();
|
const state = runtimeState.getState();
|
||||||
const now = state.eventNow?.id;
|
const now = state.eventNow?.id;
|
||||||
const next = state.eventNext?.id;
|
const next = state.eventNext?.id;
|
||||||
|
|
||||||
// check whether the index of now and next are consecutive
|
// check whether the index of now and next are consecutive
|
||||||
const indexNow = timedEvents.findIndex((event) => event.id === now);
|
const indexNow = timedEventOrder.findIndex((id) => id === now);
|
||||||
const indexNext = timedEvents.findIndex((event) => event.id === next);
|
const indexNext = timedEventOrder.findIndex((id) => id === next);
|
||||||
|
|
||||||
return indexNext - indexNow !== 1;
|
return indexNext - indexNow !== 1;
|
||||||
}
|
}
|
||||||
@@ -241,8 +238,8 @@ class RuntimeService {
|
|||||||
runtimeState.updateLoaded(eventNow);
|
runtimeState.updateLoaded(eventNow);
|
||||||
} else {
|
} else {
|
||||||
const rundown = getCurrentRundown();
|
const rundown = getCurrentRundown();
|
||||||
const { timedEventOrder } = getRundownMetadata();
|
const metadata = getRundownMetadata();
|
||||||
runtimeState.updateAll(rundown, timedEventOrder);
|
runtimeState.updateAll(rundown, metadata);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -252,9 +249,8 @@ class RuntimeService {
|
|||||||
isNext = this.isNewNext();
|
isNext = this.isNewNext();
|
||||||
if (isNext) {
|
if (isNext) {
|
||||||
const rundown = getCurrentRundown();
|
const rundown = getCurrentRundown();
|
||||||
const { timedEventOrder } = getRundownMetadata();
|
const metadata = getRundownMetadata();
|
||||||
const timedEvents = filterTimedEvents(rundown, timedEventOrder);
|
runtimeState.loadNext(rundown, metadata);
|
||||||
runtimeState.loadNext(timedEvents);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,8 +269,8 @@ class RuntimeService {
|
|||||||
|
|
||||||
// we can ignore events which are not playable
|
// we can ignore events which are not playable
|
||||||
const rundown = getCurrentRundown();
|
const rundown = getCurrentRundown();
|
||||||
const rundownMetadata = getRundownMetadata();
|
const metadata = getRundownMetadata();
|
||||||
const success = runtimeState.load(event, rundown, rundownMetadata.playableEventOrder, initialData);
|
const success = runtimeState.load(event, rundown, metadata, initialData);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`);
|
logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`);
|
||||||
@@ -603,10 +599,10 @@ class RuntimeService {
|
|||||||
*/
|
*/
|
||||||
private rollLoaded(offset?: number) {
|
private rollLoaded(offset?: number) {
|
||||||
const rundown = getCurrentRundown();
|
const rundown = getCurrentRundown();
|
||||||
const { timedEventOrder } = getRundownMetadata();
|
const metadata = getRundownMetadata();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
runtimeState.roll(rundown, timedEventOrder, offset);
|
runtimeState.roll(rundown, metadata, offset);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(LogOrigin.Server, `Roll: ${error}`);
|
logger.error(LogOrigin.Server, `Roll: ${error}`);
|
||||||
}
|
}
|
||||||
@@ -627,8 +623,8 @@ class RuntimeService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const rundown = getCurrentRundown();
|
const rundown = getCurrentRundown();
|
||||||
const rundownMetadata = getRundownMetadata();
|
const metadata = getRundownMetadata();
|
||||||
const result = runtimeState.roll(rundown, rundownMetadata.playableEventOrder);
|
const result = runtimeState.roll(rundown, metadata);
|
||||||
|
|
||||||
const newState = runtimeState.getState();
|
const newState = runtimeState.getState();
|
||||||
if (result.eventId !== previousState.eventNow?.id) {
|
if (result.eventId !== previousState.eventNow?.id) {
|
||||||
@@ -681,8 +677,8 @@ class RuntimeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rundown = getCurrentRundown();
|
const rundown = getCurrentRundown();
|
||||||
const rundownMetadata = getRundownMetadata();
|
const metadata = getRundownMetadata();
|
||||||
runtimeState.resume(restorePoint, event, rundown, rundownMetadata.playableEventOrder);
|
runtimeState.resume(restorePoint, event, rundown, metadata);
|
||||||
|
|
||||||
logger.info(LogOrigin.Playback, 'Resuming playback');
|
logger.info(LogOrigin.Playback, 'Resuming playback');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,10 +120,3 @@ export function getEventAtIndex(
|
|||||||
|
|
||||||
return rundown.entries[eventId] as OntimeEvent | 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);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
start,
|
start,
|
||||||
stop,
|
stop,
|
||||||
} from '../runtimeState.js';
|
} from '../runtimeState.js';
|
||||||
|
import { rundownCache } from '../../api-data/rundown/rundown.dao.js';
|
||||||
|
|
||||||
const mockEvent = {
|
const mockEvent = {
|
||||||
type: 'event',
|
type: 'event',
|
||||||
@@ -93,7 +94,8 @@ describe('mutation on runtimeState', () => {
|
|||||||
vi.runAllTimers();
|
vi.runAllTimers();
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
|
|
||||||
load(mockEvent, mockRundown, mockRundown.order);
|
const { metadata, rundown } = rundownCache.get();
|
||||||
|
load(mockEvent, rundown, metadata);
|
||||||
let newState = getState();
|
let newState = getState();
|
||||||
expect(newState.eventNow?.id).toBe(mockEvent.id);
|
expect(newState.eventNow?.id).toBe(mockEvent.id);
|
||||||
expect(newState.timer.playback).toBe(Playback.Armed);
|
expect(newState.timer.playback).toBe(Playback.Armed);
|
||||||
@@ -162,16 +164,18 @@ describe('mutation on runtimeState', () => {
|
|||||||
event1: { ...mockEvent, id: 'event1', timeStart: 0, timeEnd: 1000, duration: 1000, parent: null },
|
event1: { ...mockEvent, id: 'event1', timeStart: 0, timeEnd: 1000, duration: 1000, parent: null },
|
||||||
event2: { ...mockEvent, id: 'event2', timeStart: 1000, timeEnd: 1500, duration: 500, parent: null },
|
event2: { ...mockEvent, id: 'event2', timeStart: 1000, timeEnd: 1500, duration: 500, parent: null },
|
||||||
};
|
};
|
||||||
const rundown = makeRundown({ entries, order: ['event1', 'event2'] });
|
const mockRundown = makeRundown({ entries, order: ['event1', 'event2'] });
|
||||||
|
|
||||||
// force update
|
// force update
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
await initRundown(rundown, {});
|
await initRundown(mockRundown, {});
|
||||||
vi.runAllTimers();
|
vi.runAllTimers();
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
|
|
||||||
|
const { metadata, rundown } = rundownCache.get();
|
||||||
|
|
||||||
// 1. Load event
|
// 1. Load event
|
||||||
load(entries.event1, rundown, rundown.order);
|
load(entries.event1, rundown, metadata);
|
||||||
let newState = getState();
|
let newState = getState();
|
||||||
expect(newState.runtime.actualStart).toBeNull();
|
expect(newState.runtime.actualStart).toBeNull();
|
||||||
expect(newState.runtime.plannedStart).toBe(0);
|
expect(newState.runtime.plannedStart).toBe(0);
|
||||||
@@ -192,7 +196,7 @@ describe('mutation on runtimeState', () => {
|
|||||||
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd - newState.runtime.offset);
|
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd - newState.runtime.offset);
|
||||||
|
|
||||||
// 3. Next event
|
// 3. Next event
|
||||||
load(entries.event2, rundown, rundown.order);
|
load(entries.event2, rundown, metadata);
|
||||||
start();
|
start();
|
||||||
|
|
||||||
newState = getState();
|
newState = getState();
|
||||||
@@ -237,12 +241,15 @@ describe('roll mode', () => {
|
|||||||
vi.setSystemTime('jan 1 00:00');
|
vi.setSystemTime('jan 1 00:00');
|
||||||
clearState();
|
clearState();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('normal roll', () => {
|
describe('normal roll', async () => {
|
||||||
const rundown = makeRundown({
|
beforeEach(async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const mockRundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
|
1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
|
||||||
2: { ...mockEvent, id: '2', timeStart: 2000, duration: 1000, timeEnd: 3000 },
|
2: { ...mockEvent, id: '2', timeStart: 2000, duration: 1000, timeEnd: 3000 },
|
||||||
@@ -250,9 +257,13 @@ describe('roll mode', () => {
|
|||||||
},
|
},
|
||||||
order: ['1', '2', '3'],
|
order: ['1', '2', '3'],
|
||||||
});
|
});
|
||||||
|
await initRundown(mockRundown, {});
|
||||||
|
vi.runAllTimers();
|
||||||
|
});
|
||||||
|
|
||||||
test('pending event', () => {
|
test('pending event', () => {
|
||||||
const { eventId, didStart } = roll(rundown, rundown.order);
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
const { eventId, didStart } = roll(rundown, metadata);
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
expect(eventId).toBe('1');
|
expect(eventId).toBe('1');
|
||||||
@@ -263,20 +274,22 @@ describe('roll mode', () => {
|
|||||||
|
|
||||||
test('roll events', () => {
|
test('roll events', () => {
|
||||||
vi.setSystemTime('jan 1 00:00:01');
|
vi.setSystemTime('jan 1 00:00:01');
|
||||||
let result = roll(rundown, rundown.order);
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
let result = roll(rundown, metadata);
|
||||||
expect(result).toStrictEqual({ eventId: '1', didStart: true });
|
expect(result).toStrictEqual({ eventId: '1', didStart: true });
|
||||||
|
|
||||||
vi.setSystemTime('jan 1 00:00:02');
|
vi.setSystemTime('jan 1 00:00:02');
|
||||||
result = roll(rundown, rundown.order);
|
result = roll(rundown, metadata);
|
||||||
expect(result).toStrictEqual({ eventId: '2', didStart: true });
|
expect(result).toStrictEqual({ eventId: '2', didStart: true });
|
||||||
|
|
||||||
vi.setSystemTime('jan 1 00:00:03:500');
|
vi.setSystemTime('jan 1 00:00:03:500');
|
||||||
result = roll(rundown, rundown.order);
|
result = roll(rundown, metadata);
|
||||||
expect(result).toStrictEqual({ eventId: '3', didStart: true });
|
expect(result).toStrictEqual({ eventId: '3', didStart: true });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('roll takeover', async () => {
|
describe('roll takeover', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
|
1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
|
||||||
@@ -290,11 +303,12 @@ describe('roll mode', () => {
|
|||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
await initRundown(rundown, {});
|
await initRundown(rundown, {});
|
||||||
vi.runAllTimers();
|
vi.runAllTimers();
|
||||||
vi.useRealTimers();
|
});
|
||||||
|
|
||||||
test('from load', () => {
|
test('from load', () => {
|
||||||
load(rundown.entries[3] as PlayableEvent, rundown, rundown.order);
|
const { rundown, metadata } = rundownCache.get();
|
||||||
const result = roll(rundown, rundown.order);
|
load(rundown.entries[3] as PlayableEvent, rundown, metadata);
|
||||||
|
const result = roll(rundown, metadata);
|
||||||
expect(result).toStrictEqual({ eventId: '3', didStart: false });
|
expect(result).toStrictEqual({ eventId: '3', didStart: false });
|
||||||
const state = getState();
|
const state = getState();
|
||||||
expect(state.timer.phase).toBe(TimerPhase.Pending);
|
expect(state.timer.phase).toBe(TimerPhase.Pending);
|
||||||
@@ -302,9 +316,10 @@ describe('roll mode', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('from play', () => {
|
test('from play', () => {
|
||||||
load(rundown.entries[1] as PlayableEvent, rundown, rundown.order);
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
load(rundown.entries[1] as PlayableEvent, rundown, metadata);
|
||||||
start();
|
start();
|
||||||
const result = roll(rundown, rundown.order);
|
const result = roll(rundown, metadata);
|
||||||
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
||||||
expect(getState().runtime.offset).toBe(1000);
|
expect(getState().runtime.offset).toBe(1000);
|
||||||
});
|
});
|
||||||
@@ -312,7 +327,7 @@ describe('roll mode', () => {
|
|||||||
|
|
||||||
describe('roll continue with offset', () => {
|
describe('roll continue with offset', () => {
|
||||||
test('no gaps', async () => {
|
test('no gaps', async () => {
|
||||||
const rundown = makeRundown({
|
const mockRundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
|
1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
|
||||||
2: { ...mockEvent, id: '2', timeStart: 2000, duration: 1000, timeEnd: 3000 },
|
2: { ...mockEvent, id: '2', timeStart: 2000, duration: 1000, timeEnd: 3000 },
|
||||||
@@ -323,25 +338,26 @@ describe('roll mode', () => {
|
|||||||
|
|
||||||
// force update
|
// force update
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
await initRundown(rundown, {});
|
await initRundown(mockRundown, {});
|
||||||
vi.runAllTimers();
|
vi.runAllTimers();
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
|
||||||
load(rundown.entries[1] as PlayableEvent, rundown, rundown.order);
|
load(rundown.entries[1] as PlayableEvent, rundown, metadata);
|
||||||
start();
|
start();
|
||||||
// the current offset after manual play
|
// the current offset after manual play
|
||||||
const currentOffset = getState().runtime.offset;
|
const currentOffset = getState().runtime.offset;
|
||||||
let result = roll(rundown, rundown.order, getState().runtime.offset);
|
let result = roll(rundown, metadata, getState().runtime.offset);
|
||||||
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
||||||
// the current offset should be maintain by roll mode whn taking over from play
|
// the current offset should be maintain by roll mode whn taking over from play
|
||||||
expect(getState().runtime.offset).toBe(currentOffset);
|
expect(getState().runtime.offset).toBe(currentOffset);
|
||||||
|
|
||||||
vi.setSystemTime('jan 1 00:00:01');
|
vi.setSystemTime('jan 1 00:00:01');
|
||||||
result = roll(rundown, rundown.order, getState().runtime.offset);
|
result = roll(rundown, metadata, getState().runtime.offset);
|
||||||
expect(result).toStrictEqual({ eventId: '2', didStart: true });
|
expect(result).toStrictEqual({ eventId: '2', didStart: true });
|
||||||
expect(getState().runtime.offset).toBe(1000);
|
expect(getState().runtime.offset).toBe(1000);
|
||||||
|
|
||||||
vi.setSystemTime('jan 1 00:00:02');
|
vi.setSystemTime('jan 1 00:00:02');
|
||||||
result = roll(rundown, rundown.order, getState().runtime.offset);
|
result = roll(rundown, metadata, getState().runtime.offset);
|
||||||
expect(result).toStrictEqual({ eventId: '3', didStart: true });
|
expect(result).toStrictEqual({ eventId: '3', didStart: true });
|
||||||
expect(getState().runtime.offset).toBe(1000);
|
expect(getState().runtime.offset).toBe(1000);
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
CurrentBlockState,
|
CurrentBlockState,
|
||||||
EntryId,
|
|
||||||
isPlayableEvent,
|
|
||||||
MaybeNumber,
|
MaybeNumber,
|
||||||
MaybeString,
|
MaybeString,
|
||||||
OffsetMode,
|
OffsetMode,
|
||||||
OntimeBlock,
|
OntimeBlock,
|
||||||
OntimeEvent,
|
|
||||||
PlayableEvent,
|
PlayableEvent,
|
||||||
Playback,
|
Playback,
|
||||||
Rundown,
|
Rundown,
|
||||||
@@ -28,7 +25,8 @@ import {
|
|||||||
} from '../services/timerUtils.js';
|
} from '../services/timerUtils.js';
|
||||||
import { loadRoll, normaliseRollStart } from '../services/rollUtils.js';
|
import { loadRoll, normaliseRollStart } from '../services/rollUtils.js';
|
||||||
import { timerConfig } from '../setup/config.js';
|
import { timerConfig } from '../setup/config.js';
|
||||||
import { filterTimedEvents } from '../services/runtime-service/rundownService.utils.js';
|
import { RundownMetadata } from '../api-data/rundown/rundown.types.js';
|
||||||
|
import { getPlayableIndexFromTimedIndex } from '../api-data/rundown/rundown.utils.js';
|
||||||
|
|
||||||
export type RuntimeState = {
|
export type RuntimeState = {
|
||||||
clock: number; // realtime clock
|
clock: number; // realtime clock
|
||||||
@@ -171,12 +169,13 @@ export function updateRundownData(rundownData: RundownData) {
|
|||||||
export function load(
|
export function load(
|
||||||
event: PlayableEvent,
|
event: PlayableEvent,
|
||||||
rundown: Rundown,
|
rundown: Rundown,
|
||||||
timedEventOrder: EntryId[],
|
metadata: RundownMetadata,
|
||||||
initialData?: Partial<TimerState & RestorePoint>,
|
initialData?: Partial<TimerState & RestorePoint>,
|
||||||
): boolean {
|
): boolean {
|
||||||
clearEventData();
|
clearEventData();
|
||||||
|
|
||||||
if (timedEventOrder.length === 0 || !isPlayableEvent(event)) {
|
const { timedEventOrder } = metadata;
|
||||||
|
if (timedEventOrder.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,18 +185,16 @@ export function load(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(remove public): it is wasteful to recreate the object
|
|
||||||
const timedEvents = filterTimedEvents(rundown, timedEventOrder);
|
|
||||||
// load events in memory along with their data
|
// load events in memory along with their data
|
||||||
loadNow(timedEvents, eventIndex);
|
loadNow(rundown, metadata, eventIndex);
|
||||||
loadNext(timedEvents, eventIndex);
|
loadNext(rundown, metadata, eventIndex);
|
||||||
loadBlock(rundown);
|
loadBlock(rundown);
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
runtimeState.timer.playback = Playback.Armed;
|
runtimeState.timer.playback = Playback.Armed;
|
||||||
runtimeState.timer.duration = calculateDuration(event.timeStart, event.timeEnd);
|
runtimeState.timer.duration = calculateDuration(event.timeStart, event.timeEnd);
|
||||||
runtimeState.timer.current = getCurrent(runtimeState);
|
runtimeState.timer.current = getCurrent(runtimeState);
|
||||||
runtimeState.runtime.numEvents = timedEvents.length;
|
runtimeState.runtime.numEvents = metadata.timedEventOrder.length;
|
||||||
|
|
||||||
// patch with potential provided data
|
// patch with potential provided data
|
||||||
if (initialData) {
|
if (initialData) {
|
||||||
@@ -220,7 +217,11 @@ export function load(
|
|||||||
/**
|
/**
|
||||||
* Loads current event and its public counterpart
|
* Loads current event and its public counterpart
|
||||||
*/
|
*/
|
||||||
export function loadNow(timedEvents: OntimeEvent[], eventIndex: MaybeNumber = runtimeState.runtime.selectedEventIndex) {
|
export function loadNow(
|
||||||
|
rundown: Rundown,
|
||||||
|
metadata: RundownMetadata,
|
||||||
|
eventIndex: MaybeNumber = runtimeState.runtime.selectedEventIndex,
|
||||||
|
) {
|
||||||
if (eventIndex === null) {
|
if (eventIndex === null) {
|
||||||
// reset the state to indicate there is no selection
|
// reset the state to indicate there is no selection
|
||||||
runtimeState.runtime.selectedEventIndex = null;
|
runtimeState.runtime.selectedEventIndex = null;
|
||||||
@@ -228,7 +229,7 @@ export function loadNow(timedEvents: OntimeEvent[], eventIndex: MaybeNumber = ru
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = timedEvents[eventIndex] as PlayableEvent;
|
const event = rundown.entries[metadata.timedEventOrder[eventIndex]] as PlayableEvent;
|
||||||
runtimeState.runtime.selectedEventIndex = eventIndex;
|
runtimeState.runtime.selectedEventIndex = eventIndex;
|
||||||
runtimeState.eventNow = event;
|
runtimeState.eventNow = event;
|
||||||
}
|
}
|
||||||
@@ -237,7 +238,8 @@ export function loadNow(timedEvents: OntimeEvent[], eventIndex: MaybeNumber = ru
|
|||||||
* Loads the next event and its public counterpart
|
* Loads the next event and its public counterpart
|
||||||
*/
|
*/
|
||||||
export function loadNext(
|
export function loadNext(
|
||||||
timedEvents: OntimeEvent[],
|
rundown: Rundown,
|
||||||
|
metadata: RundownMetadata,
|
||||||
eventIndex: MaybeNumber = runtimeState.runtime.selectedEventIndex,
|
eventIndex: MaybeNumber = runtimeState.runtime.selectedEventIndex,
|
||||||
) {
|
) {
|
||||||
if (eventIndex === null) {
|
if (eventIndex === null) {
|
||||||
@@ -245,27 +247,22 @@ export function loadNext(
|
|||||||
runtimeState.eventNext = null;
|
runtimeState.eventNext = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const nowPlayableIndex = getPlayableIndexFromTimedIndex(metadata, eventIndex);
|
||||||
|
|
||||||
// temporarily reset this value to simplify loop logic
|
if (!nowPlayableIndex || nowPlayableIndex > metadata.playableEventOrder.length - 2) {
|
||||||
|
// we cound not find the event now or the event now is the last playable event
|
||||||
runtimeState.eventNext = null;
|
runtimeState.eventNext = null;
|
||||||
|
|
||||||
//TODO: do we already have a it as a list of not skipped events
|
|
||||||
for (let i = eventIndex + 1; i < timedEvents.length; i++) {
|
|
||||||
const event = timedEvents[i];
|
|
||||||
// we dont deal with events that are not playable
|
|
||||||
if (!isPlayableEvent(event)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
runtimeState.eventNext = event;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const nextId = metadata.playableEventOrder[nowPlayableIndex + 1];
|
||||||
|
runtimeState.eventNext = rundown.entries[nextId] as PlayableEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resume from restore point
|
* Resume from restore point
|
||||||
*/
|
*/
|
||||||
export function resume(restorePoint: RestorePoint, event: PlayableEvent, rundown: Rundown, timedEventOrder: EntryId[]) {
|
export function resume(restorePoint: RestorePoint, event: PlayableEvent, rundown: Rundown, metadata: RundownMetadata) {
|
||||||
load(event, rundown, timedEventOrder, restorePoint);
|
load(event, rundown, metadata, restorePoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -325,12 +322,12 @@ export function updateLoaded(event?: PlayableEvent): string | undefined {
|
|||||||
/**
|
/**
|
||||||
* Used in situations when we want to hot-reload all events without interrupting timer
|
* Used in situations when we want to hot-reload all events without interrupting timer
|
||||||
*/
|
*/
|
||||||
export function updateAll(rundown: Rundown, timedEventsOrder: EntryId[]) {
|
export function updateAll(rundown: Rundown, metadata: RundownMetadata) {
|
||||||
const timedEvents = filterTimedEvents(rundown, timedEventsOrder);
|
// event now might have moved so we find the event now id and recalculate the the index again
|
||||||
// TODO(remove public): we dont need to make the timedEvents object, we pass primitives and let the functions handle it
|
const eventNowIndex = metadata.timedEventOrder.findIndex((id) => id === runtimeState.eventNow?.id);
|
||||||
const eventNowIndex = timedEventsOrder.findIndex((id) => id === runtimeState.eventNow?.id);
|
|
||||||
loadNow(timedEvents, eventNowIndex >= 0 ? eventNowIndex : undefined);
|
loadNow(rundown, metadata, eventNowIndex >= 0 ? eventNowIndex : undefined);
|
||||||
loadNext(timedEvents, eventNowIndex >= 0 ? eventNowIndex : undefined);
|
loadNext(rundown, metadata, eventNowIndex >= 0 ? eventNowIndex : undefined);
|
||||||
updateLoaded(runtimeState.eventNow ?? undefined);
|
updateLoaded(runtimeState.eventNow ?? undefined);
|
||||||
loadBlock(rundown);
|
loadBlock(rundown);
|
||||||
}
|
}
|
||||||
@@ -542,7 +539,7 @@ export function update(): UpdateResult {
|
|||||||
|
|
||||||
export function roll(
|
export function roll(
|
||||||
rundown: Rundown,
|
rundown: Rundown,
|
||||||
timedEventOrder: EntryId[],
|
metadata: RundownMetadata,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
): { eventId: MaybeString; didStart: boolean } {
|
): { eventId: MaybeString; didStart: boolean } {
|
||||||
// 1. if an event is running, we simply take over the playback
|
// 1. if an event is running, we simply take over the playback
|
||||||
@@ -601,8 +598,7 @@ export function roll(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. if there is no event running, we need to find the next event
|
// 3. if there is no event running, we need to find the next event
|
||||||
const timedEvents = filterTimedEvents(rundown, timedEventOrder);
|
if (metadata.playableEventOrder.length === 0) {
|
||||||
if (timedEvents.length === 0) {
|
|
||||||
throw new Error('No playable events found');
|
throw new Error('No playable events found');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,16 +609,16 @@ export function roll(
|
|||||||
runtimeState.runtime.offset = offset;
|
runtimeState.runtime.offset = offset;
|
||||||
const offsetClock = runtimeState.clock + runtimeState.runtime.offset;
|
const offsetClock = runtimeState.clock + runtimeState.runtime.offset;
|
||||||
|
|
||||||
const { index, isPending } = loadRoll(timedEvents, offsetClock);
|
const { index, isPending } = loadRoll(rundown, metadata, offsetClock);
|
||||||
|
|
||||||
// load events in memory along with their data
|
// load events in memory along with their data
|
||||||
loadNow(timedEvents, index);
|
loadNow(rundown, metadata, index);
|
||||||
loadNext(timedEvents, index);
|
loadNext(rundown, metadata, index);
|
||||||
loadBlock(rundown);
|
loadBlock(rundown);
|
||||||
|
|
||||||
// update roll state
|
// update roll state
|
||||||
runtimeState.timer.playback = Playback.Roll;
|
runtimeState.timer.playback = Playback.Roll;
|
||||||
runtimeState.runtime.numEvents = timedEvents.length;
|
runtimeState.runtime.numEvents = metadata.timedEventOrder.length;
|
||||||
|
|
||||||
// in roll mode spec, there should always be something to load
|
// in roll mode spec, there should always be something to load
|
||||||
// as long as playableEvents is not empty
|
// as long as playableEvents is not empty
|
||||||
|
|||||||
Reference in New Issue
Block a user