mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 23:19:09 +00:00
Keep offset when taking over playback with roll v2 (#1184)
* pass on offset * account for offset in roll * prevent roll from overtime * add test
This commit is contained in:
committed by
GitHub
parent
1b1823e0fe
commit
ee1b5b7fdd
@@ -50,6 +50,7 @@ export const usePlaybackControl = () => {
|
|||||||
playback: state.timer.playback,
|
playback: state.timer.playback,
|
||||||
selectedEventIndex: state.runtime.selectedEventIndex,
|
selectedEventIndex: state.runtime.selectedEventIndex,
|
||||||
numEvents: state.runtime.numEvents,
|
numEvents: state.runtime.numEvents,
|
||||||
|
timerPhase: state.timer.phase,
|
||||||
});
|
});
|
||||||
|
|
||||||
return useRuntimeStore(featureSelector);
|
return useRuntimeStore(featureSelector);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export default function PlaybackControl() {
|
|||||||
playback={data.playback}
|
playback={data.playback}
|
||||||
numEvents={data.numEvents}
|
numEvents={data.numEvents}
|
||||||
selectedEventIndex={data.selectedEventIndex}
|
selectedEventIndex={data.selectedEventIndex}
|
||||||
|
timerPhase={data.timerPhase}
|
||||||
/>
|
/>
|
||||||
<AuxTimer />
|
<AuxTimer />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward'
|
|||||||
import { IoReload } from '@react-icons/all-files/io5/IoReload';
|
import { IoReload } from '@react-icons/all-files/io5/IoReload';
|
||||||
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
||||||
import { IoTime } from '@react-icons/all-files/io5/IoTime';
|
import { IoTime } from '@react-icons/all-files/io5/IoTime';
|
||||||
import { Playback } from 'ontime-types';
|
import { Playback, TimerPhase } from 'ontime-types';
|
||||||
import { validatePlayback } from 'ontime-utils';
|
import { validatePlayback } from 'ontime-utils';
|
||||||
|
|
||||||
import { setPlayback } from '../../../../common/hooks/useSocket';
|
import { setPlayback } from '../../../../common/hooks/useSocket';
|
||||||
@@ -19,10 +19,11 @@ interface PlaybackButtonsProps {
|
|||||||
playback: Playback;
|
playback: Playback;
|
||||||
numEvents: number;
|
numEvents: number;
|
||||||
selectedEventIndex: number | null;
|
selectedEventIndex: number | null;
|
||||||
|
timerPhase: TimerPhase;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PlaybackButtons(props: PlaybackButtonsProps) {
|
export default function PlaybackButtons(props: PlaybackButtonsProps) {
|
||||||
const { playback, numEvents, selectedEventIndex } = props;
|
const { playback, numEvents, selectedEventIndex, timerPhase } = props;
|
||||||
|
|
||||||
const isRolling = playback === Playback.Roll;
|
const isRolling = playback === Playback.Roll;
|
||||||
const isPlaying = playback === Playback.Play;
|
const isPlaying = playback === Playback.Play;
|
||||||
@@ -37,7 +38,7 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) {
|
|||||||
const disableNext = isRolling || noEvents || isLast;
|
const disableNext = isRolling || noEvents || isLast;
|
||||||
const disablePrev = isRolling || noEvents || isFirst;
|
const disablePrev = isRolling || noEvents || isFirst;
|
||||||
|
|
||||||
const playbackCan = validatePlayback(playback);
|
const playbackCan = validatePlayback(playback, timerPhase);
|
||||||
const disableStart = !playbackCan.start;
|
const disableStart = !playbackCan.start;
|
||||||
const disablePause = !playbackCan.pause;
|
const disablePause = !playbackCan.pause;
|
||||||
const disableRoll = !playbackCan.roll || noEvents;
|
const disableRoll = !playbackCan.roll || noEvents;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { OntimeRundown } from 'ontime-types';
|
|
||||||
|
|
||||||
import * as runtimeState from '../stores/runtimeState.js';
|
import * as runtimeState from '../stores/runtimeState.js';
|
||||||
import type { UpdateResult } from '../stores/runtimeState.js';
|
import type { UpdateResult } from '../stores/runtimeState.js';
|
||||||
import { timerConfig } from '../config/config.js';
|
import { timerConfig } from '../config/config.js';
|
||||||
@@ -100,13 +98,6 @@ export class EventTimer {
|
|||||||
this.onUpdateCallback?.(updateResult);
|
this.onUpdateCallback?.(updateResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads roll information into timer service
|
|
||||||
*/
|
|
||||||
roll(rundown: OntimeRundown) {
|
|
||||||
return runtimeState.roll(rundown);
|
|
||||||
}
|
|
||||||
|
|
||||||
shutdown() {
|
shutdown() {
|
||||||
clearInterval(this._interval);
|
clearInterval(this._interval);
|
||||||
clearTimeout(this.endCallback);
|
clearTimeout(this.endCallback);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
RuntimeStore,
|
RuntimeStore,
|
||||||
TimerLifeCycle,
|
TimerLifeCycle,
|
||||||
TimerPhase,
|
TimerPhase,
|
||||||
|
TimerState,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
import { millisToString, validatePlayback } from 'ontime-utils';
|
import { millisToString, validatePlayback } from 'ontime-utils';
|
||||||
|
|
||||||
@@ -86,18 +87,18 @@ class RuntimeService {
|
|||||||
// 2. handle edge cases related to roll
|
// 2. handle edge cases related to roll
|
||||||
if (newState.timer.playback === Playback.Roll) {
|
if (newState.timer.playback === Playback.Roll) {
|
||||||
// check if we need to call any side effects
|
// check if we need to call any side effects
|
||||||
|
const keepOffset = newState.runtime.offset;
|
||||||
if (hasSecondaryTimerFinished) {
|
if (hasSecondaryTimerFinished) {
|
||||||
// if the secondary timer has finished, we need to call roll
|
// if the secondary timer has finished, we need to call roll
|
||||||
// since event is already loaded
|
// since event is already loaded
|
||||||
this.rollLoaded();
|
this.rollLoaded(keepOffset);
|
||||||
} else if (hasTimerFinished) {
|
} else if (hasTimerFinished) {
|
||||||
// if the timer has finished, we need to load next and keep rolling
|
// if the timer has finished, we need to load next and keep rolling
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
integrationService.dispatch(TimerLifeCycle.onFinish);
|
integrationService.dispatch(TimerLifeCycle.onFinish);
|
||||||
});
|
});
|
||||||
this.handleLoadNext();
|
this.handleLoadNext();
|
||||||
this.rollLoaded();
|
this.rollLoaded(keepOffset);
|
||||||
} else if (skippedOutOfEvent(newState, this.lastIntegrationClockUpdate, timerConfig.skipLimit)) {
|
} else if (skippedOutOfEvent(newState, this.lastIntegrationClockUpdate, timerConfig.skipLimit)) {
|
||||||
// if we have skipped out of the event, we will recall roll
|
// if we have skipped out of the event, we will recall roll
|
||||||
// to push the playback to the right place
|
// to push the playback to the right place
|
||||||
@@ -271,16 +272,17 @@ class RuntimeService {
|
|||||||
/**
|
/**
|
||||||
* makes calls for loading and starting given event
|
* makes calls for loading and starting given event
|
||||||
* @param {PlayableEvent} event
|
* @param {PlayableEvent} event
|
||||||
|
* @param {Partial<TimerState & RestorePoint>} initialData
|
||||||
* @return {boolean} success - whether an event was loaded
|
* @return {boolean} success - whether an event was loaded
|
||||||
*/
|
*/
|
||||||
private loadEvent(event: OntimeEvent): boolean {
|
private loadEvent(event: OntimeEvent, initialData?: Partial<TimerState & RestorePoint>): boolean {
|
||||||
if (!isPlayableEvent(event)) {
|
if (!isPlayableEvent(event)) {
|
||||||
logger.warning(LogOrigin.Playback, `Refused skipped event with ID ${event.id}`);
|
logger.warning(LogOrigin.Playback, `Refused skipped event with ID ${event.id}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rundown = getRundown();
|
const rundown = getRundown();
|
||||||
const success = runtimeState.load(event, rundown);
|
const success = runtimeState.load(event, rundown, 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}`);
|
||||||
@@ -416,11 +418,15 @@ class RuntimeService {
|
|||||||
*
|
*
|
||||||
* we need to isolate handleLoadNext so we have control over the side effects
|
* we need to isolate handleLoadNext so we have control over the side effects
|
||||||
* startSelected being a private function does not trigger emits
|
* startSelected being a private function does not trigger emits
|
||||||
|
* and pass on runtime offset in case of roll mode
|
||||||
*/
|
*/
|
||||||
private handleLoadNext(): boolean {
|
private handleLoadNext(): boolean {
|
||||||
const state = runtimeState.getState();
|
const state = runtimeState.getState();
|
||||||
const nextEvent = findNext(state.eventNow?.id);
|
const nextEvent = findNext(state.eventNow?.id);
|
||||||
if (nextEvent) {
|
if (nextEvent) {
|
||||||
|
if (state.timer.playback === Playback.Roll) {
|
||||||
|
return this.loadEvent(nextEvent, { firstStart: state.runtime.actualStart });
|
||||||
|
}
|
||||||
return this.loadEvent(nextEvent);
|
return this.loadEvent(nextEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +451,7 @@ class RuntimeService {
|
|||||||
*/
|
*/
|
||||||
private handleStart(): boolean {
|
private handleStart(): boolean {
|
||||||
const previousState = runtimeState.getState();
|
const previousState = runtimeState.getState();
|
||||||
const canStart = validatePlayback(previousState.timer.playback).start;
|
const canStart = validatePlayback(previousState.timer.playback, previousState.timer.phase).start;
|
||||||
if (!canStart) {
|
if (!canStart) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -501,7 +507,7 @@ class RuntimeService {
|
|||||||
@broadcastResult
|
@broadcastResult
|
||||||
public pause() {
|
public pause() {
|
||||||
const state = runtimeState.getState();
|
const state = runtimeState.getState();
|
||||||
const canPause = validatePlayback(state.timer.playback).pause;
|
const canPause = validatePlayback(state.timer.playback, state.timer.phase).pause;
|
||||||
if (!canPause) {
|
if (!canPause) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -519,7 +525,7 @@ class RuntimeService {
|
|||||||
@broadcastResult
|
@broadcastResult
|
||||||
public stop(): boolean {
|
public stop(): boolean {
|
||||||
const state = runtimeState.getState();
|
const state = runtimeState.getState();
|
||||||
const canStop = validatePlayback(state.timer.playback).stop;
|
const canStop = validatePlayback(state.timer.playback, state.timer.phase).stop;
|
||||||
if (!canStop) {
|
if (!canStop) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -551,10 +557,10 @@ class RuntimeService {
|
|||||||
/**
|
/**
|
||||||
* Handles special case to call roll on a loaded event which we do not want to discard
|
* Handles special case to call roll on a loaded event which we do not want to discard
|
||||||
*/
|
*/
|
||||||
private rollLoaded() {
|
private rollLoaded(offset?: number) {
|
||||||
const rundown = getRundown();
|
const rundown = getRundown();
|
||||||
try {
|
try {
|
||||||
this.eventTimer.roll(rundown);
|
runtimeState.roll(rundown, offset);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(LogOrigin.Server, `Roll: ${error}`);
|
logger.error(LogOrigin.Server, `Roll: ${error}`);
|
||||||
}
|
}
|
||||||
@@ -567,7 +573,7 @@ class RuntimeService {
|
|||||||
public roll(skipCheck: boolean = false) {
|
public roll(skipCheck: boolean = false) {
|
||||||
const previousState = runtimeState.getState();
|
const previousState = runtimeState.getState();
|
||||||
if (!skipCheck) {
|
if (!skipCheck) {
|
||||||
const canRoll = validatePlayback(previousState.timer.playback).roll;
|
const canRoll = validatePlayback(previousState.timer.playback, previousState.timer.phase).roll;
|
||||||
if (!canRoll) {
|
if (!canRoll) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -575,7 +581,7 @@ class RuntimeService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const rundown = getRundown();
|
const rundown = getRundown();
|
||||||
const result = this.eventTimer.roll(rundown);
|
const result = runtimeState.roll(rundown);
|
||||||
if (result.eventId !== previousState.eventNow?.id) {
|
if (result.eventId !== previousState.eventNow?.id) {
|
||||||
logger.info(LogOrigin.Playback, `Loaded event with ID ${result.eventId}`);
|
logger.info(LogOrigin.Playback, `Loaded event with ID ${result.eventId}`);
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { PlayableEvent, Playback } from 'ontime-types';
|
import { PlayableEvent, Playback, TimerPhase } from 'ontime-types';
|
||||||
import { deepmerge } from 'ontime-utils';
|
import { deepmerge } from 'ontime-utils';
|
||||||
|
|
||||||
import { RuntimeState, addTime, clear, getState, load, pause, start, stop } from '../runtimeState.js';
|
import { RuntimeState, addTime, clear, getState, load, pause, roll, start, stop } from '../runtimeState.js';
|
||||||
import { initRundown } from '../../services/rundown-service/RundownService.js';
|
import { initRundown } from '../../services/rundown-service/RundownService.js';
|
||||||
|
|
||||||
const mockEvent = {
|
const mockEvent = {
|
||||||
@@ -227,7 +227,103 @@ describe('mutation on runtimeState', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.todo('runtime offset on timers in overtime', () => {});
|
test.todo('runtime offset on timers in overtime', () => {});
|
||||||
|
});
|
||||||
test.todo('roll mode', () => {});
|
});
|
||||||
|
|
||||||
|
describe('roll mode', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime('jan 1 00:00');
|
||||||
|
clear();
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('normal roll', () => {
|
||||||
|
const rundown = [
|
||||||
|
{ ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
|
||||||
|
{ ...mockEvent, id: '2', timeStart: 2000, duration: 1000, timeEnd: 3000 },
|
||||||
|
{ ...mockEvent, id: '3', timeStart: 3000, duration: 1000, timeEnd: 4000 },
|
||||||
|
] as PlayableEvent[];
|
||||||
|
|
||||||
|
test('pending event', () => {
|
||||||
|
const { eventId, didStart } = roll(rundown);
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
expect(eventId).toBe('1');
|
||||||
|
expect(didStart).toBe(false);
|
||||||
|
expect(state.timer.phase).toBe(TimerPhase.Pending);
|
||||||
|
expect(state.timer.secondaryTimer).toBe(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('roll events', () => {
|
||||||
|
vi.setSystemTime('jan 1 00:00:01');
|
||||||
|
let result = roll(rundown);
|
||||||
|
expect(result).toStrictEqual({ eventId: '1', didStart: true });
|
||||||
|
|
||||||
|
vi.setSystemTime('jan 1 00:00:02');
|
||||||
|
result = roll(rundown);
|
||||||
|
expect(result).toStrictEqual({ eventId: '2', didStart: true });
|
||||||
|
|
||||||
|
vi.setSystemTime('jan 1 00:00:03:500');
|
||||||
|
result = roll(rundown);
|
||||||
|
expect(result).toStrictEqual({ eventId: '3', didStart: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('roll takover', () => {
|
||||||
|
const rundown = [
|
||||||
|
{ ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
|
||||||
|
{ ...mockEvent, id: '2', timeStart: 2000, duration: 1000, timeEnd: 3000 },
|
||||||
|
{ ...mockEvent, id: '3', timeStart: 3000, duration: 1000, timeEnd: 4000 },
|
||||||
|
] as PlayableEvent[];
|
||||||
|
|
||||||
|
test('from load', () => {
|
||||||
|
load(rundown[2], rundown);
|
||||||
|
const result = roll(rundown);
|
||||||
|
expect(result).toStrictEqual({ eventId: '3', didStart: false });
|
||||||
|
const state = getState();
|
||||||
|
expect(state.timer.phase).toBe(TimerPhase.Pending);
|
||||||
|
expect(state.timer.secondaryTimer).toBe(3000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('from play', () => {
|
||||||
|
load(rundown[0], rundown);
|
||||||
|
start();
|
||||||
|
const result = roll(rundown);
|
||||||
|
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
||||||
|
expect(getState().runtime.offset).toBe(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('roll continue with offset', () => {
|
||||||
|
test('no gaps', () => {
|
||||||
|
const rundown = [
|
||||||
|
{ ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
|
||||||
|
{ ...mockEvent, id: '2', timeStart: 2000, duration: 1000, timeEnd: 3000 },
|
||||||
|
{ ...mockEvent, id: '3', timeStart: 3000, duration: 1000, timeEnd: 4000 },
|
||||||
|
] as PlayableEvent[];
|
||||||
|
|
||||||
|
load(rundown[0], rundown);
|
||||||
|
start();
|
||||||
|
let result = roll(rundown, getState().runtime.offset);
|
||||||
|
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
||||||
|
expect(getState().runtime.offset).toBe(1000);
|
||||||
|
|
||||||
|
vi.setSystemTime('jan 1 00:00:01');
|
||||||
|
result = roll(rundown, getState().runtime.offset);
|
||||||
|
expect(result).toStrictEqual({ eventId: '2', didStart: true });
|
||||||
|
expect(getState().runtime.offset).toBe(1000);
|
||||||
|
|
||||||
|
vi.setSystemTime('jan 1 00:00:02');
|
||||||
|
result = roll(rundown, getState().runtime.offset);
|
||||||
|
expect(result).toStrictEqual({ eventId: '3', didStart: true });
|
||||||
|
expect(getState().runtime.offset).toBe(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.todo('with gaps', () => {
|
||||||
|
//this is a bit involved as it also depends somewhat on the RintimeService
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -314,17 +314,15 @@ 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.offset;
|
||||||
// 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 (
|
if (runtimeState.eventNow.timeStart < offsetClock && offsetClock < runtimeState.eventNow.timeEnd) {
|
||||||
runtimeState.eventNow.timeStart < runtimeState.clock &&
|
|
||||||
runtimeState.clock < runtimeState.eventNow.timeEnd
|
|
||||||
) {
|
|
||||||
// if the event is now, we queue a start
|
// if the event is now, we queue a start
|
||||||
runtimeState._timer.secondaryTarget = runtimeState.eventNow.timeStart;
|
runtimeState._timer.secondaryTarget = runtimeState.eventNow.timeStart;
|
||||||
runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget - runtimeState.clock;
|
runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget - offsetClock;
|
||||||
} else {
|
} else {
|
||||||
runtimeState._timer.secondaryTarget = normaliseRollStart(runtimeState.eventNow.timeStart, runtimeState.clock);
|
runtimeState._timer.secondaryTarget = normaliseRollStart(runtimeState.eventNow.timeStart, offsetClock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -516,7 +514,6 @@ export function update(): UpdateResult {
|
|||||||
|
|
||||||
if (finishedNow) {
|
if (finishedNow) {
|
||||||
// reset state
|
// reset state
|
||||||
runtimeState._timer.forceFinish;
|
|
||||||
runtimeState.timer.finishedAt = runtimeState._timer.forceFinish ?? runtimeState.clock;
|
runtimeState.timer.finishedAt = runtimeState._timer.forceFinish ?? runtimeState.clock;
|
||||||
} else {
|
} else {
|
||||||
runtimeState.timer.expectedFinish = getExpectedFinish(runtimeState);
|
runtimeState.timer.expectedFinish = getExpectedFinish(runtimeState);
|
||||||
@@ -536,14 +533,15 @@ export function update(): UpdateResult {
|
|||||||
throw new Error('runtimeState.updateIfWaitingToRoll: invalid state received');
|
throw new Error('runtimeState.updateIfWaitingToRoll: invalid state received');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//account for offset
|
||||||
|
const offsetClock = runtimeState.clock + runtimeState.runtime.offset;
|
||||||
runtimeState.timer.phase = TimerPhase.Pending;
|
runtimeState.timer.phase = TimerPhase.Pending;
|
||||||
runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget - runtimeState.clock;
|
runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget - offsetClock;
|
||||||
return { hasTimerFinished: false, hasSecondaryTimerFinished: runtimeState.timer.secondaryTimer < 0 };
|
return { hasTimerFinished: false, hasSecondaryTimerFinished: runtimeState.timer.secondaryTimer <= 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function roll(rundown: OntimeRundown): { eventId: MaybeString; didStart: boolean } {
|
export function roll(rundown: OntimeRundown, offset = 0): { 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
|
||||||
if (runtimeState.timer.playback === Playback.Play && runtimeState.runtime.selectedEventIndex !== null) {
|
if (runtimeState.timer.playback === Playback.Play && runtimeState.runtime.selectedEventIndex !== null) {
|
||||||
runtimeState.timer.playback = Playback.Roll;
|
runtimeState.timer.playback = Playback.Roll;
|
||||||
@@ -551,7 +549,15 @@ export function roll(rundown: OntimeRundown): { eventId: MaybeString; didStart:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. if there is an event armed, we use it
|
// 2. if there is an event armed, we use it
|
||||||
if (runtimeState.timer.playback === Playback.Armed && runtimeState.eventNow !== null) {
|
if (runtimeState.timer.playback === Playback.Armed || runtimeState.timer.phase === TimerPhase.Pending) {
|
||||||
|
// eslint-disable-next-line no-unused-labels -- dev code path
|
||||||
|
DEV: {
|
||||||
|
if (runtimeState.eventNow === null) {
|
||||||
|
throw new Error('runtimeState.roll: invalid state received');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runtimeState.runtime.offset = offset;
|
||||||
runtimeState.timer.playback = Playback.Roll;
|
runtimeState.timer.playback = Playback.Roll;
|
||||||
|
|
||||||
// account for event that finishes the day after
|
// account for event that finishes the day after
|
||||||
@@ -561,14 +567,16 @@ export function roll(rundown: OntimeRundown): { eventId: MaybeString; didStart:
|
|||||||
: runtimeState.eventNow.timeEnd;
|
: runtimeState.eventNow.timeEnd;
|
||||||
runtimeState.timer.expectedFinish = normalisedEndTime;
|
runtimeState.timer.expectedFinish = normalisedEndTime;
|
||||||
|
|
||||||
|
//account for offset
|
||||||
|
const offsetClock = runtimeState.clock + runtimeState.runtime.offset;
|
||||||
|
|
||||||
// state catch up
|
// state catch up
|
||||||
runtimeState.timer.duration = calculateDuration(runtimeState.eventNow.timeStart, normalisedEndTime);
|
runtimeState.timer.duration = calculateDuration(runtimeState.eventNow.timeStart, normalisedEndTime);
|
||||||
runtimeState.timer.current = runtimeState.timer.duration;
|
runtimeState.timer.current = runtimeState.timer.duration;
|
||||||
runtimeState.timer.elapsed = 0;
|
runtimeState.timer.elapsed = 0;
|
||||||
|
|
||||||
// check if the event is ready to start or if needs to be waited
|
// check if the event is ready to start or if needs to be pending
|
||||||
const isNow = checkIsNow(runtimeState.eventNow.timeStart, runtimeState.eventNow.timeEnd, runtimeState.clock);
|
const isNow = checkIsNow(runtimeState.eventNow.timeStart, runtimeState.eventNow.timeEnd, offsetClock);
|
||||||
|
|
||||||
if (isNow) {
|
if (isNow) {
|
||||||
runtimeState.timer.startedAt = runtimeState.clock;
|
runtimeState.timer.startedAt = runtimeState.clock;
|
||||||
|
|
||||||
@@ -579,9 +587,10 @@ export function roll(rundown: OntimeRundown): { eventId: MaybeString; didStart:
|
|||||||
if (!runtimeState.runtime.actualStart) {
|
if (!runtimeState.runtime.actualStart) {
|
||||||
runtimeState.runtime.actualStart = runtimeState.clock;
|
runtimeState.runtime.actualStart = runtimeState.clock;
|
||||||
}
|
}
|
||||||
|
runtimeState.timer.secondaryTimer = null;
|
||||||
} else {
|
} else {
|
||||||
runtimeState._timer.secondaryTarget = normaliseRollStart(runtimeState.eventNow.timeStart, runtimeState.clock);
|
runtimeState._timer.secondaryTarget = normaliseRollStart(runtimeState.eventNow.timeStart, offsetClock);
|
||||||
runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget - runtimeState.clock;
|
runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget - offsetClock;
|
||||||
runtimeState.timer.phase = TimerPhase.Pending;
|
runtimeState.timer.phase = TimerPhase.Pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,7 +608,11 @@ export function roll(rundown: OntimeRundown): { eventId: MaybeString; didStart:
|
|||||||
clear();
|
clear();
|
||||||
runtimeState.currentBlock = prevCurrentBlock;
|
runtimeState.currentBlock = prevCurrentBlock;
|
||||||
|
|
||||||
const { index, isPending } = loadRoll(timedEvents, runtimeState.clock);
|
//account for offset but we only keep it if passed to us
|
||||||
|
runtimeState.runtime.offset = offset;
|
||||||
|
const offsetClock = runtimeState.clock + runtimeState.runtime.offset;
|
||||||
|
|
||||||
|
const { index, isPending } = loadRoll(timedEvents, offsetClock);
|
||||||
|
|
||||||
// load events in memory along with their data
|
// load events in memory along with their data
|
||||||
loadNow(timedEvents, index);
|
loadNow(timedEvents, index);
|
||||||
@@ -623,8 +636,8 @@ export function roll(rundown: OntimeRundown): { eventId: MaybeString; didStart:
|
|||||||
// there is nothing now, but something coming up
|
// there is nothing now, but something coming up
|
||||||
runtimeState.timer.phase = TimerPhase.Pending;
|
runtimeState.timer.phase = TimerPhase.Pending;
|
||||||
// we need to normalise start time in case it is the day after
|
// we need to normalise start time in case it is the day after
|
||||||
runtimeState._timer.secondaryTarget = normaliseRollStart(runtimeState.eventNow.timeStart, runtimeState.clock);
|
runtimeState._timer.secondaryTarget = normaliseRollStart(runtimeState.eventNow.timeStart, offsetClock);
|
||||||
runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget - runtimeState.clock;
|
runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget - offsetClock;
|
||||||
|
|
||||||
// preload timer properties
|
// preload timer properties
|
||||||
runtimeState.timer.duration = calculateDuration(runtimeState.eventNow.timeStart, runtimeState.eventNow.timeEnd);
|
runtimeState.timer.duration = calculateDuration(runtimeState.eventNow.timeStart, runtimeState.eventNow.timeEnd);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Playback } from 'ontime-types';
|
import { Playback, TimerPhase } from 'ontime-types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple rules to determine whether a playback action is valid
|
* Simple rules to determine whether a playback action is valid
|
||||||
*/
|
*/
|
||||||
export function validatePlayback(currentPlayback: Playback) {
|
export function validatePlayback(currentPlayback: Playback, timerPhase: TimerPhase) {
|
||||||
return {
|
return {
|
||||||
start: currentPlayback !== Playback.Stop,
|
start: currentPlayback !== Playback.Stop,
|
||||||
pause: currentPlayback === Playback.Play,
|
pause: currentPlayback === Playback.Play,
|
||||||
roll: currentPlayback !== Playback.Roll,
|
roll: currentPlayback !== Playback.Roll && timerPhase !== TimerPhase.Overtime,
|
||||||
stop: currentPlayback !== Playback.Stop,
|
stop: currentPlayback !== Playback.Stop,
|
||||||
reload: currentPlayback !== Playback.Stop && currentPlayback !== Playback.Roll,
|
reload: currentPlayback !== Playback.Stop && currentPlayback !== Playback.Roll,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user