mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 15:09:10 +00:00
Invert overtime (#1715)
* refactor: invert offset calculation * chore: update tests * chore: flip offset in UI * fix seconds display on group target duration * update comment * fix rebase
This commit is contained in:
committed by
Carlos Valente
parent
4665f9c2f7
commit
e0149c1cbf
@@ -21,5 +21,6 @@ export function getOffsetText(offset: MaybeNumber): string {
|
|||||||
export function getOffsetState(offset: MaybeNumber): 'over' | 'under' | 'muted' | null {
|
export function getOffsetState(offset: MaybeNumber): 'over' | 'under' | 'muted' | null {
|
||||||
if (offset === null) return 'muted';
|
if (offset === null) return 'muted';
|
||||||
if (offset === 0) return null;
|
if (offset === 0) return null;
|
||||||
return offset < 0 ? 'over' : 'under';
|
// a positive value means that we are in over time aka behind schedule
|
||||||
|
return offset > 0 ? 'over' : 'under';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,9 +167,8 @@ export function OffsetOverview() {
|
|||||||
const { offset, playback } = useRuntimePlaybackOverview();
|
const { offset, playback } = useRuntimePlaybackOverview();
|
||||||
|
|
||||||
const isPlaying = isPlaybackActive(playback);
|
const isPlaying = isPlaybackActive(playback);
|
||||||
const correctedOffset = offset * -1;
|
|
||||||
const offsetState = getOffsetState(isPlaying ? offset : null);
|
const offsetState = getOffsetState(isPlaying ? offset : null);
|
||||||
const offsetText = getOffsetText(isPlaying ? correctedOffset : null);
|
const offsetText = getOffsetText(isPlaying ? offset : null);
|
||||||
|
|
||||||
return <OverUnder state={offsetState} value={offsetText} testId='offset' />;
|
return <OverUnder state={offsetState} value={offsetText} testId='offset' />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ export default function GroupEditor({ group }: GroupEditorProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isEditor = window.location.pathname.includes('editor');
|
const isEditor = window.location.pathname.includes('editor');
|
||||||
const planOffset = typeof group.targetDuration !== 'number' ? null : group.duration - group.targetDuration;
|
const planOffset = group.targetDuration === null ? null : group.duration - group.targetDuration;
|
||||||
const planOffsetLabel = planOffset !== null ? getOffsetState(planOffset * -1) : null;
|
const planOffsetLabel = planOffset !== null ? getOffsetState(planOffset) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.content}>
|
<div className={style.content}>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { EntryId, OntimeGroup } from 'ontime-types';
|
import { EntryId, OntimeGroup } from 'ontime-types';
|
||||||
|
import { MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||||
|
|
||||||
import IconButton from '../../../common/components/buttons/IconButton';
|
import IconButton from '../../../common/components/buttons/IconButton';
|
||||||
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
||||||
@@ -102,8 +103,11 @@ export default function RundownGroup({ data, hasCursor, collapsed, onCollapse }:
|
|||||||
if (offset === 0) {
|
if (offset === 0) {
|
||||||
return [null, 'under'];
|
return [null, 'under'];
|
||||||
}
|
}
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
return [offset < 0 ? `-${formatDuration(offset * -1)}` : `+${formatDuration(offset)}`, getOffsetState(offset * -1)];
|
return [
|
||||||
|
`${offset < 0 ? '-' : '+'}${formatDuration(absOffset, absOffset > 2 * MILLIS_PER_MINUTE)}`,
|
||||||
|
getOffsetState(offset),
|
||||||
|
];
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const dragStyle = {
|
const dragStyle = {
|
||||||
|
|||||||
@@ -742,7 +742,7 @@ describe('getRuntimeOffset()', () => {
|
|||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { offsetAbs } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(-50);
|
expect(offsetAbs).toBe(50);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('added time subtracts time offset (positive offset)', () => {
|
it('added time subtracts time offset (positive offset)', () => {
|
||||||
@@ -766,7 +766,7 @@ describe('getRuntimeOffset()', () => {
|
|||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { offsetAbs } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(-60);
|
expect(offsetAbs).toBe(60);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('considers running overtime (negative offset)', () => {
|
it('considers running overtime (negative offset)', () => {
|
||||||
@@ -791,7 +791,7 @@ describe('getRuntimeOffset()', () => {
|
|||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { offsetAbs } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(-10);
|
expect(offsetAbs).toBe(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('paused time is delayed time (negative offset)', () => {
|
it('paused time is delayed time (negative offset)', () => {
|
||||||
@@ -817,7 +817,7 @@ describe('getRuntimeOffset()', () => {
|
|||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { offsetAbs } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(-25);
|
expect(offsetAbs).toBe(25);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('offset doesnt exist if we havent started', () => {
|
it('offset doesnt exist if we havent started', () => {
|
||||||
@@ -857,46 +857,6 @@ describe('getRuntimeOffset()', () => {
|
|||||||
expect(offsetAbs).toBe(0);
|
expect(offsetAbs).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles loaded event', () => {
|
|
||||||
const state = {
|
|
||||||
clock: 79521653,
|
|
||||||
eventNow: {
|
|
||||||
id: '835242',
|
|
||||||
timeStart: 81000000,
|
|
||||||
timeEnd: 84600000,
|
|
||||||
duration: 3600000,
|
|
||||||
timeStrategy: 'lock-duration',
|
|
||||||
linkStart: false,
|
|
||||||
endAction: 'none',
|
|
||||||
timerType: 'count-down',
|
|
||||||
delay: 0,
|
|
||||||
},
|
|
||||||
runtime: {
|
|
||||||
selectedEventIndex: 1,
|
|
||||||
numEvents: 2,
|
|
||||||
offsetAbs: -81000000,
|
|
||||||
plannedStart: 77400000,
|
|
||||||
plannedEnd: 84600000,
|
|
||||||
actualStart: 79443403,
|
|
||||||
expectedEnd: null,
|
|
||||||
},
|
|
||||||
timer: {
|
|
||||||
addedTime: 0,
|
|
||||||
current: 3600000,
|
|
||||||
duration: 3600000,
|
|
||||||
elapsed: null,
|
|
||||||
expectedFinish: null,
|
|
||||||
playback: 'armed',
|
|
||||||
secondaryTimer: null,
|
|
||||||
startedAt: null,
|
|
||||||
},
|
|
||||||
_timer: { pausedAt: null },
|
|
||||||
} as RuntimeState;
|
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
|
||||||
expect(offsetAbs).toBe(81000000 - 79521653); // clock - timestart
|
|
||||||
});
|
|
||||||
|
|
||||||
it('with time-to-end, offsets dont exist if we are not in overtime', () => {
|
it('with time-to-end, offsets dont exist if we are not in overtime', () => {
|
||||||
const state = {
|
const state = {
|
||||||
clock: 80000000, // 22:13:20
|
clock: 80000000, // 22:13:20
|
||||||
@@ -996,7 +956,7 @@ describe('getRuntimeOffset()', () => {
|
|||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { offsetAbs } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(-400000); // <--- offset is always the overtime
|
expect(offsetAbs).toBe(400000); // <--- offset is always the overtime
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles time-to-end started after the end time', () => {
|
it('handles time-to-end started after the end time', () => {
|
||||||
@@ -1038,8 +998,8 @@ describe('getRuntimeOffset()', () => {
|
|||||||
const updateCurrent = getCurrent(state);
|
const updateCurrent = getCurrent(state);
|
||||||
state.timer.current = updateCurrent;
|
state.timer.current = updateCurrent;
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { offsetAbs } = getRuntimeOffset(state);
|
||||||
expect(millisToString(offsetAbs)).toBe('-00:16:40');
|
expect(millisToString(offsetAbs)).toBe('00:16:40');
|
||||||
expect(offsetAbs).toBe(81000000 - 82000000); // <-- planned end - now
|
expect(offsetAbs).toBe(82000000 - 81000000); // <-- now - planned end
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1089,7 +1049,7 @@ describe('getoffsetRel()', () => {
|
|||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs, offsetRel } = getRuntimeOffset(state);
|
const { offsetAbs, offsetRel } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(-50);
|
expect(offsetAbs).toBe(50);
|
||||||
expect(offsetRel).toBe(0);
|
expect(offsetRel).toBe(0);
|
||||||
});
|
});
|
||||||
it('relative offset is 0 when starting before the planed time', () => {
|
it('relative offset is 0 when starting before the planed time', () => {
|
||||||
@@ -1113,7 +1073,7 @@ describe('getoffsetRel()', () => {
|
|||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs, offsetRel } = getRuntimeOffset(state);
|
const { offsetAbs, offsetRel } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(50);
|
expect(offsetAbs).toBe(-50);
|
||||||
expect(offsetRel).toBe(0);
|
expect(offsetRel).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -109,57 +109,44 @@ export function skippedOutOfEvent(state: RuntimeState, previousTime: number, ski
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates difference between the runtime and the schedule of an event
|
* Calculates difference between the runtime and the schedule of an event
|
||||||
* Positive offset is time ahead
|
* Positive offset is over time / behind schedule
|
||||||
* Negative offset is time delayed
|
* Negative offset is under time / ahead of schedule
|
||||||
*/
|
*/
|
||||||
export function getRuntimeOffset(state: RuntimeState): { offsetAbs: number; offsetRel: number } {
|
export function getRuntimeOffset(state: RuntimeState): { offsetAbs: number; offsetRel: number } {
|
||||||
|
const { eventNow, clock } = state;
|
||||||
|
const { addedTime, current, startedAt } = state.timer;
|
||||||
// nothing to calculate if there are no loaded events or if we havent started
|
// nothing to calculate if there are no loaded events or if we havent started
|
||||||
if (state.eventNow === null || state.runtime.actualStart === null) {
|
if (eventNow === null || startedAt === null) {
|
||||||
return { offsetAbs: 0, offsetRel: 0 };
|
return { offsetAbs: 0, offsetRel: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { clock } = state;
|
const { countToEnd, timeStart } = eventNow;
|
||||||
const { countToEnd, timeStart } = state.eventNow;
|
const { plannedStart, actualStart } = state.runtime;
|
||||||
const { addedTime, current, startedAt } = state.timer;
|
|
||||||
const { actualStart, plannedStart } = state.runtime;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-labels -- dev code path
|
// eslint-disable-next-line no-unused-labels -- dev code path
|
||||||
DEV: {
|
DEV: {
|
||||||
// we know current exists as long as eventNow exists
|
// we know current exists as long as eventNow exists
|
||||||
if (current === null) throw new Error('timerUtils.getRuntimeOffset: state.timer.current must be set');
|
if (current === null) throw new Error('timerUtils.getRuntimeOffset: state.timer.current must be set');
|
||||||
if (plannedStart === null) throw new Error('timerUtils.getRuntimeOffset: state.runtime.plannedStart must be set');
|
if (plannedStart === null) throw new Error('timerUtils.getRuntimeOffset: state.runtime.plannedStart must be set');
|
||||||
|
if (actualStart === null) throw new Error('timerUtils.getRuntimeOffset: state.runtime.actualStart must be set');
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we havent started, but the timer is armed
|
// difference between planned event start and actual event start (will be positive if we stared behind )
|
||||||
// the offset is the difference to the schedule
|
const eventStartOffset = startedAt - timeStart;
|
||||||
if (startedAt === null) {
|
|
||||||
return { offsetAbs: timeStart - clock, offsetRel: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const overtime = Math.min(current, 0);
|
// how long has the event been running over (is a negative number when in over timer so inverted before adding to offset)
|
||||||
// in time-to-end, offset is overtime
|
const overtime = Math.abs(Math.min(current, 0));
|
||||||
|
|
||||||
const startOffset = timeStart - startedAt;
|
// time the playback was paused, the different from now to when we paused is added to the offset TODO: brakes when crossing midnight
|
||||||
const pausedTime = state._timer.pausedAt === null ? 0 : clock - state._timer.pausedAt;
|
const pausedTime = state._timer.pausedAt === null ? 0 : clock - state._timer.pausedAt;
|
||||||
|
|
||||||
// startOffset - difference between scheduled start and actual start
|
const offsetAbs = eventStartOffset + overtime + pausedTime + addedTime;
|
||||||
// addedTime - time added by user (negative offset)
|
|
||||||
// pausedTime - time the playback was paused (negative offset)
|
|
||||||
// overtime - how long the timer has been over-running (negative offset)
|
|
||||||
const offset = startOffset - addedTime - pausedTime + overtime;
|
|
||||||
|
|
||||||
// offset between planned rundown start and actual rundown start
|
// the relative offset i the same as the absolute offset but adjusted relative to the actual start time
|
||||||
const rundownStartOffset = actualStart - plannedStart;
|
const offsetRel = offsetAbs + plannedStart - actualStart;
|
||||||
|
|
||||||
// offset offset relative to the actual rundown start
|
// in case of count to end, the absolute offset is just the overtime
|
||||||
const offsetRel = offset + rundownStartOffset;
|
return countToEnd ? { offsetAbs: overtime, offsetRel } : { offsetAbs, offsetRel };
|
||||||
|
|
||||||
// in time-to-end, offset is overtime
|
|
||||||
if (countToEnd) {
|
|
||||||
return { offsetAbs: overtime, offsetRel };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { offsetAbs: offset, offsetRel };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -67,17 +67,20 @@ beforeAll(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('mutation on runtimeState', () => {
|
describe('mutation on runtimeState', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime('jan 1 00:01');
|
||||||
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('playback operations', async () => {
|
describe('playback operations', async () => {
|
||||||
it('refuses if nothing is loaded', async () => {
|
it('refuses if nothing is loaded', async () => {
|
||||||
// force update
|
// force update
|
||||||
vi.useFakeTimers();
|
|
||||||
await initRundown(makeRundown({}), {});
|
await initRundown(makeRundown({}), {});
|
||||||
vi.runAllTimers();
|
vi.runAllTimers();
|
||||||
vi.useRealTimers();
|
|
||||||
|
|
||||||
let success = start(mockState);
|
let success = start(mockState);
|
||||||
expect(success).toBe(false);
|
expect(success).toBe(false);
|
||||||
@@ -93,10 +96,8 @@ describe('mutation on runtimeState', () => {
|
|||||||
order: [mockEvent.id, 'event2'],
|
order: [mockEvent.id, 'event2'],
|
||||||
});
|
});
|
||||||
// force update
|
// force update
|
||||||
vi.useFakeTimers();
|
|
||||||
await initRundown(mockRundown, {});
|
await initRundown(mockRundown, {});
|
||||||
vi.runAllTimers();
|
vi.runAllTimers();
|
||||||
vi.useRealTimers();
|
|
||||||
|
|
||||||
const { metadata, rundown } = rundownCache.get();
|
const { metadata, rundown } = rundownCache.get();
|
||||||
load(mockEvent, rundown, metadata);
|
load(mockEvent, rundown, metadata);
|
||||||
@@ -108,6 +109,7 @@ describe('mutation on runtimeState', () => {
|
|||||||
expect(newState.groupNow).toBeNull();
|
expect(newState.groupNow).toBeNull();
|
||||||
|
|
||||||
// 2. Start event
|
// 2. Start event
|
||||||
|
vi.setSystemTime('jan 1 00:02');
|
||||||
let success = start();
|
let success = start();
|
||||||
newState = getState();
|
newState = getState();
|
||||||
expect(success).toBe(true);
|
expect(success).toBe(true);
|
||||||
@@ -117,6 +119,7 @@ describe('mutation on runtimeState', () => {
|
|||||||
expect(newState.runtime.actualStart).toBe(newState.clock);
|
expect(newState.runtime.actualStart).toBe(newState.clock);
|
||||||
|
|
||||||
// 3. Pause event
|
// 3. Pause event
|
||||||
|
vi.setSystemTime('jan 1 00:03');
|
||||||
success = pause();
|
success = pause();
|
||||||
newState = getState();
|
newState = getState();
|
||||||
expect(success).toBe(true);
|
expect(success).toBe(true);
|
||||||
@@ -131,6 +134,7 @@ describe('mutation on runtimeState', () => {
|
|||||||
expect(success).toBe(false);
|
expect(success).toBe(false);
|
||||||
|
|
||||||
// 4. Restart event
|
// 4. Restart event
|
||||||
|
vi.setSystemTime('jan 1 00:04');
|
||||||
success = start();
|
success = start();
|
||||||
newState = getState();
|
newState = getState();
|
||||||
expect(success).toBe(true);
|
expect(success).toBe(true);
|
||||||
@@ -150,6 +154,7 @@ describe('mutation on runtimeState', () => {
|
|||||||
expect(newState._timer.pausedAt).toBeNull();
|
expect(newState._timer.pausedAt).toBeNull();
|
||||||
|
|
||||||
// 5. Stop event
|
// 5. Stop event
|
||||||
|
vi.setSystemTime('jan 1 00:05');
|
||||||
success = stop();
|
success = stop();
|
||||||
newState = getState();
|
newState = getState();
|
||||||
expect(success).toBe(true);
|
expect(success).toBe(true);
|
||||||
@@ -163,80 +168,81 @@ describe('mutation on runtimeState', () => {
|
|||||||
});
|
});
|
||||||
expect(newState.runtime.actualStart).toBeNull();
|
expect(newState.runtime.actualStart).toBeNull();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('runtime offset', async () => {
|
test('runtime offset', async () => {
|
||||||
const entries = {
|
const entries = {
|
||||||
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 mockRundown = makeRundown({ entries, order: ['event1', 'event2'] });
|
const mockRundown = makeRundown({ entries, order: ['event1', 'event2'] });
|
||||||
|
|
||||||
// force update
|
// force update
|
||||||
vi.useFakeTimers();
|
await initRundown(mockRundown, {});
|
||||||
await initRundown(mockRundown, {});
|
vi.runAllTimers();
|
||||||
vi.runAllTimers();
|
|
||||||
vi.useRealTimers();
|
|
||||||
|
|
||||||
const { metadata, rundown } = rundownCache.get();
|
const { metadata, rundown } = rundownCache.get();
|
||||||
|
|
||||||
// 1. Load event
|
// 1. Load event
|
||||||
load(entries.event1, rundown, metadata);
|
vi.setSystemTime('jan 1 00:09');
|
||||||
let newState = getState();
|
load(entries.event1, rundown, metadata);
|
||||||
expect(newState.runtime.actualStart).toBeNull();
|
let newState = getState();
|
||||||
expect(newState.runtime.plannedStart).toBe(0);
|
expect(newState.runtime.actualStart).toBeNull();
|
||||||
expect(newState.runtime.plannedEnd).toBe(1500);
|
expect(newState.runtime.plannedStart).toBe(0);
|
||||||
expect(newState.groupNow).toBeNull();
|
expect(newState.runtime.plannedEnd).toBe(1500);
|
||||||
expect(newState.runtime.offsetAbs).toBe(0);
|
expect(newState.groupNow).toBeNull();
|
||||||
|
expect(newState.runtime.offsetAbs).toBe(0);
|
||||||
|
|
||||||
// 2. Start event
|
// 2. Start event
|
||||||
start();
|
vi.setSystemTime('jan 1 00:10');
|
||||||
newState = getState();
|
start();
|
||||||
const firstStart = newState.clock;
|
newState = getState();
|
||||||
if (newState.runtime.offsetAbs === null) {
|
const firstStart = newState.clock;
|
||||||
throw new Error('Value cannot be null at this stage');
|
if (newState.runtime.offsetAbs === null) {
|
||||||
}
|
throw new Error('Value cannot be null at this stage');
|
||||||
|
}
|
||||||
|
|
||||||
expect(newState.runtime.actualStart).toBe(newState.clock);
|
expect(newState.runtime.actualStart).toBe(newState.clock);
|
||||||
expect(newState.runtime.offsetAbs).toBe(entries.event1.timeStart - newState.clock);
|
expect(newState.runtime.offsetAbs).toBe(newState.clock - entries.event1.timeStart);
|
||||||
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd - newState.runtime.offsetAbs);
|
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd + newState.runtime.offsetAbs);
|
||||||
|
|
||||||
// 3. Next event
|
// 3. Next event
|
||||||
load(entries.event2, rundown, metadata);
|
vi.setSystemTime('jan 1 00:12');
|
||||||
start();
|
load(entries.event2, rundown, metadata);
|
||||||
|
start();
|
||||||
|
|
||||||
newState = getState();
|
newState = getState();
|
||||||
if (newState.runtime.actualStart === null || newState.runtime.offsetAbs === null) {
|
if (newState.runtime.actualStart === null || newState.runtime.offsetAbs === null) {
|
||||||
throw new Error('Value cannot be null at this stage');
|
throw new Error('Value cannot be null at this stage');
|
||||||
}
|
}
|
||||||
|
|
||||||
// there is a case where the calculation time overflows the millisecond which makes
|
// there is a case where the calculation time overflows the millisecond which makes
|
||||||
// tests fail
|
// tests fail
|
||||||
const forgivingActualStart = Math.abs(newState.runtime.actualStart - firstStart);
|
const forgivingActualStart = Math.abs(newState.runtime.actualStart - firstStart);
|
||||||
expect(forgivingActualStart).toBeLessThanOrEqual(1);
|
expect(forgivingActualStart).toBeLessThanOrEqual(1);
|
||||||
// we are over-under, the difference between the schedule and the actual start
|
// we are over-under, the difference between the schedule and the actual start
|
||||||
const delayBefore = entries.event2.timeStart - newState.clock;
|
const delayBefore = newState.clock - entries.event2.timeStart;
|
||||||
expect(newState.runtime.offsetAbs).toBe(delayBefore);
|
expect(newState.runtime.offsetAbs).toBe(delayBefore);
|
||||||
// finish is the difference between the runtime and the schedule
|
// finish is the difference between the runtime and the schedule
|
||||||
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd - newState.runtime.offsetAbs);
|
expect(newState.runtime.expectedEnd).toBe(newState.runtime.offsetAbs + entries.event2.timeEnd);
|
||||||
expect(newState.groupNow).toBeNull();
|
expect(newState.groupNow).toBeNull();
|
||||||
|
|
||||||
// 4. Add time
|
// 4. Add time
|
||||||
addTime(10);
|
addTime(10);
|
||||||
newState = getState();
|
newState = getState();
|
||||||
if (newState.runtime.offsetAbs === null) {
|
if (newState.runtime.offsetAbs === null) {
|
||||||
throw new Error('Value cannot be null at this stage');
|
throw new Error('Value cannot be null at this stage');
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(newState.runtime.offsetAbs).toBe(delayBefore - 10);
|
expect(newState.runtime.offsetAbs).toBe(delayBefore + 10);
|
||||||
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd - newState.runtime.offsetAbs);
|
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd + newState.runtime.offsetAbs);
|
||||||
|
|
||||||
// 5. Stop event
|
// 5. Stop event
|
||||||
stop();
|
stop();
|
||||||
newState = getState();
|
newState = getState();
|
||||||
expect(newState.runtime.actualStart).toBeNull();
|
expect(newState.runtime.actualStart).toBeNull();
|
||||||
expect(newState.runtime.offsetAbs).toBe(0);
|
expect(newState.runtime.offsetAbs).toBe(0);
|
||||||
expect(newState.runtime.expectedEnd).toBeNull();
|
expect(newState.runtime.expectedEnd).toBeNull();
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -326,7 +332,7 @@ describe('roll mode', () => {
|
|||||||
start();
|
start();
|
||||||
const result = roll(rundown, metadata);
|
const result = roll(rundown, metadata);
|
||||||
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
||||||
expect(getState().runtime.offsetAbs).toBe(1000);
|
expect(getState().runtime.offsetAbs).toBe(-1000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -353,18 +359,18 @@ describe('roll mode', () => {
|
|||||||
const currentOffset = getState().runtime.offsetAbs;
|
const currentOffset = getState().runtime.offsetAbs;
|
||||||
let result = roll(rundown, metadata, getState().runtime.offsetAbs);
|
let result = roll(rundown, metadata, getState().runtime.offsetAbs);
|
||||||
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 when taking over from play
|
||||||
expect(getState().runtime.offsetAbs).toBe(currentOffset);
|
expect(getState().runtime.offsetAbs).toBe(currentOffset);
|
||||||
|
|
||||||
vi.setSystemTime('jan 1 00:00:01');
|
vi.setSystemTime('jan 1 00:00:01');
|
||||||
result = roll(rundown, metadata, getState().runtime.offsetAbs);
|
result = roll(rundown, metadata, getState().runtime.offsetAbs);
|
||||||
expect(result).toStrictEqual({ eventId: '2', didStart: true });
|
expect(result).toStrictEqual({ eventId: '2', didStart: true });
|
||||||
expect(getState().runtime.offsetAbs).toBe(1000);
|
expect(getState().runtime.offsetAbs).toBe(-1000);
|
||||||
|
|
||||||
vi.setSystemTime('jan 1 00:00:02');
|
vi.setSystemTime('jan 1 00:00:02');
|
||||||
result = roll(rundown, metadata, getState().runtime.offsetAbs);
|
result = roll(rundown, metadata, getState().runtime.offsetAbs);
|
||||||
expect(result).toStrictEqual({ eventId: '3', didStart: true });
|
expect(result).toStrictEqual({ eventId: '3', didStart: true });
|
||||||
expect(getState().runtime.offsetAbs).toBe(1000);
|
expect(getState().runtime.offsetAbs).toBe(-1000);
|
||||||
|
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ export function updateLoaded(event?: PlayableEvent): string | undefined {
|
|||||||
|
|
||||||
// handle edge cases with roll
|
// handle edge cases with roll
|
||||||
if (runtimeState.timer.playback === Playback.Roll) {
|
if (runtimeState.timer.playback === Playback.Roll) {
|
||||||
const offsetClock = runtimeState.clock + runtimeState.runtime.offsetAbs;
|
const offsetClock = runtimeState.clock - runtimeState.runtime.offsetAbs;
|
||||||
// if waiting to roll, we update the targets and potentially start the timer
|
// if waiting to roll, we update the targets and potentially start the timer
|
||||||
if (runtimeState._timer.secondaryTarget !== null) {
|
if (runtimeState._timer.secondaryTarget !== null) {
|
||||||
if (runtimeState.eventNow.timeStart < offsetClock && offsetClock < runtimeState.eventNow.timeEnd) {
|
if (runtimeState.eventNow.timeStart < offsetClock && offsetClock < runtimeState.eventNow.timeEnd) {
|
||||||
@@ -391,7 +391,6 @@ export function start(state: RuntimeState = runtimeState): boolean {
|
|||||||
state.timer.expectedFinish = getExpectedFinish(state);
|
state.timer.expectedFinish = getExpectedFinish(state);
|
||||||
state.timer.elapsed = 0;
|
state.timer.elapsed = 0;
|
||||||
|
|
||||||
// update runtime delays: over - under
|
|
||||||
if (state.runtime.actualStart === null) {
|
if (state.runtime.actualStart === null) {
|
||||||
state.runtime.actualStart = state.clock;
|
state.runtime.actualStart = state.clock;
|
||||||
}
|
}
|
||||||
@@ -521,7 +520,6 @@ export function update(): UpdateResult {
|
|||||||
const { offsetAbs, offsetRel } = getRuntimeOffset(runtimeState);
|
const { offsetAbs, offsetRel } = getRuntimeOffset(runtimeState);
|
||||||
runtimeState.runtime.offsetAbs = offsetAbs;
|
runtimeState.runtime.offsetAbs = offsetAbs;
|
||||||
runtimeState.runtime.offsetRel = offsetRel;
|
runtimeState.runtime.offsetRel = offsetRel;
|
||||||
// runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
|
||||||
|
|
||||||
const finishedNow =
|
const finishedNow =
|
||||||
Boolean(runtimeState._timer.forceFinish) ||
|
Boolean(runtimeState._timer.forceFinish) ||
|
||||||
@@ -596,7 +594,7 @@ export function roll(
|
|||||||
runtimeState.timer.expectedFinish = normalisedEndTime;
|
runtimeState.timer.expectedFinish = normalisedEndTime;
|
||||||
|
|
||||||
//account for offset
|
//account for offset
|
||||||
const offsetClock = runtimeState.clock + runtimeState.runtime.offsetAbs;
|
const offsetClock = runtimeState.clock - runtimeState.runtime.offsetAbs;
|
||||||
|
|
||||||
// state catch up
|
// state catch up
|
||||||
runtimeState.timer.duration = calculateDuration(runtimeState.eventNow.timeStart, normalisedEndTime);
|
runtimeState.timer.duration = calculateDuration(runtimeState.eventNow.timeStart, normalisedEndTime);
|
||||||
@@ -635,7 +633,7 @@ export function roll(
|
|||||||
|
|
||||||
//account for offset but we only keep it if passed to us
|
//account for offset but we only keep it if passed to us
|
||||||
runtimeState.runtime.offsetAbs = offset;
|
runtimeState.runtime.offsetAbs = offset;
|
||||||
const offsetClock = runtimeState.clock + runtimeState.runtime.offsetAbs;
|
const offsetClock = runtimeState.clock - runtimeState.runtime.offsetAbs;
|
||||||
|
|
||||||
const { index, isPending } = loadRoll(rundown, metadata, offsetClock);
|
const { index, isPending } = loadRoll(rundown, metadata, offsetClock);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export enum OffsetMode {
|
|||||||
export type Runtime = {
|
export type Runtime = {
|
||||||
selectedEventIndex: MaybeNumber;
|
selectedEventIndex: MaybeNumber;
|
||||||
numEvents: number;
|
numEvents: number;
|
||||||
offsetAbs: number;
|
offsetAbs: number; // a positive value means that we are in over time aka behind schedule
|
||||||
offsetRel: number;
|
offsetRel: number;
|
||||||
plannedStart: MaybeNumber;
|
plannedStart: MaybeNumber;
|
||||||
actualStart: MaybeNumber;
|
actualStart: MaybeNumber;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ describe('getExpectedStart()', () => {
|
|||||||
const testState = {
|
const testState = {
|
||||||
currentDay: 0,
|
currentDay: 0,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
offset: -20,
|
offset: 20,
|
||||||
offsetMode: OffsetMode.Absolute,
|
offsetMode: OffsetMode.Absolute,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
plannedStart: null,
|
plannedStart: null,
|
||||||
@@ -52,7 +52,7 @@ describe('getExpectedStart()', () => {
|
|||||||
const testState = {
|
const testState = {
|
||||||
currentDay: 0,
|
currentDay: 0,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
offset: 10,
|
offset: -10,
|
||||||
offsetMode: OffsetMode.Absolute,
|
offsetMode: OffsetMode.Absolute,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
plannedStart: null,
|
plannedStart: null,
|
||||||
@@ -71,7 +71,7 @@ describe('getExpectedStart()', () => {
|
|||||||
const testState = {
|
const testState = {
|
||||||
currentDay: 0,
|
currentDay: 0,
|
||||||
totalGap: 20,
|
totalGap: 20,
|
||||||
offset: -20,
|
offset: 20,
|
||||||
offsetMode: OffsetMode.Absolute,
|
offsetMode: OffsetMode.Absolute,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
plannedStart: null,
|
plannedStart: null,
|
||||||
@@ -90,7 +90,7 @@ describe('getExpectedStart()', () => {
|
|||||||
const testState = {
|
const testState = {
|
||||||
currentDay: 0,
|
currentDay: 0,
|
||||||
totalGap: 10,
|
totalGap: 10,
|
||||||
offset: -20,
|
offset: 20,
|
||||||
offsetMode: OffsetMode.Absolute,
|
offsetMode: OffsetMode.Absolute,
|
||||||
actualStart: 0,
|
actualStart: 0,
|
||||||
plannedStart: 0,
|
plannedStart: 0,
|
||||||
@@ -219,12 +219,12 @@ describe('getExpectedStart()', () => {
|
|||||||
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: true })).toBe(120);
|
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: true })).toBe(120);
|
||||||
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: false })).toBe(120);
|
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: false })).toBe(120);
|
||||||
|
|
||||||
testState.offset = 5; // remove 5 with addtime - we are ahead of time
|
testState.offset = -5; // remove 5 with addtime - we are ahead of time
|
||||||
|
|
||||||
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: true })).toBe(115);
|
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: true })).toBe(115);
|
||||||
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: false })).toBe(120); // unlocked evets will stay on schedule
|
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: false })).toBe(120); // unlinked events will stay on schedule
|
||||||
|
|
||||||
testState.offset = -5; // add 5 with addtime - we are behind
|
testState.offset = +5; // add 5 with addtime - we are behind
|
||||||
|
|
||||||
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: true })).toBe(125);
|
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: true })).toBe(125);
|
||||||
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: false })).toBe(125);
|
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: false })).toBe(125);
|
||||||
|
|||||||
@@ -43,14 +43,14 @@ export function getExpectedStart(
|
|||||||
|
|
||||||
const scheduledStartTime = normalisedTimeStart + relativeStartOffset;
|
const scheduledStartTime = normalisedTimeStart + relativeStartOffset;
|
||||||
|
|
||||||
const offsetStartTime = scheduledStartTime - offset;
|
const offsetStartTime = scheduledStartTime + offset;
|
||||||
|
|
||||||
if (isLinkedToLoaded) {
|
if (isLinkedToLoaded) {
|
||||||
//if we are directly linked back to the loaded event we just follow the offset
|
//if we are directly linked back to the loaded event we just follow the offset
|
||||||
return offsetStartTime;
|
return offsetStartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gapsCanCompensateForOffset = totalGap + offset >= 0;
|
const gapsCanCompensateForOffset = totalGap > offset;
|
||||||
if (gapsCanCompensateForOffset) {
|
if (gapsCanCompensateForOffset) {
|
||||||
// if we are ahead of schedule or the gap can compensate for the amount we are behind then expect to start at the scheduled time
|
// if we are ahead of schedule or the gap can compensate for the amount we are behind then expect to start at the scheduled time
|
||||||
return scheduledStartTime;
|
return scheduledStartTime;
|
||||||
|
|||||||
Reference in New Issue
Block a user