diff --git a/apps/client/src/__mocks__/QueryClient.mock.js b/apps/client/src/__mocks__/QueryClient.mock.js
deleted file mode 100644
index 00a9653a2..000000000
--- a/apps/client/src/__mocks__/QueryClient.mock.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { QueryClient } from '@tanstack/react-query';
-
-export const queryClientMock = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- },
- },
-});
diff --git a/apps/client/src/appConstants.ts b/apps/client/src/appConstants.ts
deleted file mode 100644
index a44531dca..000000000
--- a/apps/client/src/appConstants.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-// Exported viewer link location
-const minimalLocation = 'minimal';
-const speakerLocation = 'speaker';
-const smLocation = 'sm';
-const publicLocation = 'public';
-const studioLocation = 'studio';
-const cuesheetLocation = 'cuesheet';
-const countdownLocation = 'countdown';
-const clockLocation = 'clock';
-const lowerLocation = 'lower';
-
-export const viewerLocations = [
- { link: speakerLocation, label: 'Stage timer' },
- { link: clockLocation, label: 'Clock' },
- { link: minimalLocation, label: 'Minimal timer' },
- { link: smLocation, label: 'Backstage screen' },
- { link: publicLocation, label: 'Public screen' },
- { link: lowerLocation, label: 'Lower thirds' },
- { link: studioLocation, label: 'Studio clock' },
- { link: countdownLocation, label: 'Countdown' },
- { link: cuesheetLocation, label: 'Cuesheet' },
-];
diff --git a/apps/client/src/common/components/buttons/EnableBtn.jsx b/apps/client/src/common/components/buttons/EnableBtn.jsx
deleted file mode 100644
index c9dace127..000000000
--- a/apps/client/src/common/components/buttons/EnableBtn.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Button } from '@chakra-ui/react';
-import { IoCheckmarkSharp } from '@react-icons/all-files/io5/IoCheckmarkSharp';
-import { IoCloseSharp } from '@react-icons/all-files/io5/IoCloseSharp';
-import PropTypes from 'prop-types';
-
-export default function EnableBtn(props) {
- const { active, text, actionHandler, size = 'xs' } = props;
- return (
- : }
- colorScheme='blue'
- variant={active ? 'solid' : 'outline'}
- onClick={actionHandler}
- >
- {text}
-
- );
-}
-
-EnableBtn.propTypes = {
- active: PropTypes.bool,
- text: PropTypes.string,
- actionHandler: PropTypes.func,
- size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']),
-}
diff --git a/apps/client/src/common/components/buttons/PauseIconBtn.tsx b/apps/client/src/common/components/buttons/PauseIconBtn.tsx
deleted file mode 100644
index 5bc581a6c..000000000
--- a/apps/client/src/common/components/buttons/PauseIconBtn.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { IconButton, Tooltip } from '@chakra-ui/react';
-import { IoPause } from '@react-icons/all-files/io5/IoPause';
-
-import { tooltipDelayMid } from '../../../ontimeConfig';
-
-interface PauseIconBtnProps {
- clickhandler: (event: React.MouseEvent) => void;
- active: boolean;
- disabled: boolean;
-}
-
-export default function PauseIconBtn(props: PauseIconBtnProps) {
- const { clickhandler, active, disabled, ...rest } = props;
- return (
-
- }
- colorScheme='orange'
- variant={active ? 'solid' : 'outline'}
- onClick={clickhandler}
- width={120}
- disabled={disabled}
- aria-label='Pause playback'
- {...rest}
- />
-
- );
-}
diff --git a/apps/client/src/common/components/buttons/PublicIconBtn.jsx b/apps/client/src/common/components/buttons/PublicIconBtn.jsx
deleted file mode 100644
index c9f9c0374..000000000
--- a/apps/client/src/common/components/buttons/PublicIconBtn.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { IconButton, Tooltip } from '@chakra-ui/react';
-import { FiUsers } from '@react-icons/all-files/fi/FiUsers';
-import PropTypes from 'prop-types';
-
-export default function PublicIconBtn(props) {
- const { actionHandler, active, size = 'xs', ...rest } = props;
- return (
-
- }
- colorScheme='blue'
- variant={active ? 'solid' : 'outline'}
- onClick={() => actionHandler('update', { field: 'isPublic', value: !active })}
- {...rest}
- />
-
- );
-}
-
-PublicIconBtn.propTypes = {
- actionHandler: PropTypes.func,
- active: PropTypes.bool,
- size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']),
-};
diff --git a/apps/client/src/common/components/buttons/RollIconBtn.jsx b/apps/client/src/common/components/buttons/RollIconBtn.jsx
deleted file mode 100644
index aa78ea7ba..000000000
--- a/apps/client/src/common/components/buttons/RollIconBtn.jsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { IconButton, Tooltip } from '@chakra-ui/react';
-import { IoTimeOutline } from '@react-icons/all-files/io5/IoTimeOutline';
-import PropTypes from 'prop-types';
-
-import { tooltipDelayMid } from '../../../ontimeConfig';
-
-export default function RollIconBtn(props) {
- const { clickhandler, active, disabled, ...rest } = props;
- return (
-
- }
- colorScheme='blue'
- variant={active ? 'solid' : 'outline'}
- onClick={clickhandler}
- width={120}
- disabled={disabled}
- {...rest}
- />
-
- );
-}
-
-RollIconBtn.propTypes = {
- clickhandler: PropTypes.func,
- active: PropTypes.bool,
- disabled: PropTypes.bool,
-};
diff --git a/apps/client/src/common/components/buttons/StartIconBtn.jsx b/apps/client/src/common/components/buttons/StartIconBtn.jsx
deleted file mode 100644
index 73479816f..000000000
--- a/apps/client/src/common/components/buttons/StartIconBtn.jsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { IconButton, Tooltip } from '@chakra-ui/react';
-import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
-import PropTypes from 'prop-types';
-
-import { tooltipDelayMid } from '../../../ontimeConfig';
-
-export default function StartIconBtn(props) {
- const { clickhandler, active, disabled, ...rest } = props;
- return (
-
- }
- colorScheme='green'
- variant={active ? 'solid' : 'outline'}
- onClick={clickhandler}
- width={120}
- disabled={disabled}
- {...rest}
- />
-
- );
-}
-
-StartIconBtn.propTypes = {
- clickhandler: PropTypes.func,
- active: PropTypes.bool,
- disabled: PropTypes.bool
-}
diff --git a/apps/client/src/common/components/buttons/TransportIconBtn.jsx b/apps/client/src/common/components/buttons/TransportIconBtn.jsx
deleted file mode 100644
index d8b07e731..000000000
--- a/apps/client/src/common/components/buttons/TransportIconBtn.jsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { IconButton, Tooltip } from '@chakra-ui/react';
-import PropTypes from 'prop-types';
-
-import { tooltipDelayMid } from '../../../ontimeConfig';
-
-export default function TransportIconBtn(props) {
- const { clickHandler, icon, tooltip, disabled, ...rest } = props;
- return (
-
-
-
- );
-}
-
-TransportIconBtn.propTypes = {
- clickHandler: PropTypes.func,
- icon: PropTypes.element,
- tooltip: PropTypes.string,
- disabled: PropTypes.bool,
-};
diff --git a/apps/client/src/common/components/buttons/UnloadIconBtn.jsx b/apps/client/src/common/components/buttons/UnloadIconBtn.jsx
deleted file mode 100644
index a6e1c1f2e..000000000
--- a/apps/client/src/common/components/buttons/UnloadIconBtn.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { IconButton, Tooltip } from '@chakra-ui/react';
-import { IoStop } from '@react-icons/all-files/io5/IoStop';
-import PropTypes from 'prop-types';
-
-import { tooltipDelayMid } from '../../../ontimeConfig';
-
-export default function UnloadIconBtn(props) {
- const { clickHandler, disabled, ...rest } = props;
- return (
-
- }
- colorScheme='red'
- variant='outline'
- onClick={clickHandler}
- width={90}
- disabled={disabled}
- {...rest}
- />
-
- );
-}
-
-UnloadIconBtn.propTypes = {
- clickHandler: PropTypes.func,
- disabled: PropTypes.bool,
-};
diff --git a/apps/client/src/common/models/Alias.ts b/apps/client/src/common/models/Alias.ts
deleted file mode 100644
index a5bd50985..000000000
--- a/apps/client/src/common/models/Alias.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Alias } from 'ontime-types';
-
-export const aliasPlaceholder: Alias = {
- enabled: false,
- alias: '',
- pathAndParams: '',
-};
diff --git a/apps/client/src/common/models/OntimeVars.ts b/apps/client/src/common/models/OntimeVars.ts
deleted file mode 100644
index 9b36bfa0d..000000000
--- a/apps/client/src/common/models/OntimeVars.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-export const ontimeVars = [
- {
- name: '$timer',
- description: 'Current running timer',
- },
- {
- name: '$title',
- description: 'Current title',
- },
- {
- name: '$presenter',
- description: 'Current timer',
- },
- {
- name: '$subtitle',
- description: 'Current subtitle',
- },
- {
- name: '$next-title',
- description: 'Next title',
- },
- {
- name: '$next-presenter',
- description: 'Next timer',
- },
- {
- name: '$next-subtitle',
- description: 'Next subtitle',
- },
-];
diff --git a/apps/client/src/features/modals/integration-modal/OscIntegration.tsx b/apps/client/src/features/modals/integration-modal/OscIntegration.tsx
index 6c2023bc6..2875e9aab 100644
--- a/apps/client/src/features/modals/integration-modal/OscIntegration.tsx
+++ b/apps/client/src/features/modals/integration-modal/OscIntegration.tsx
@@ -33,7 +33,7 @@ const sectionText: { [key in TimerLifeCycle]: { title: string; subtitle: string
},
onUpdate: {
title: 'On Every Second',
- subtitle: 'Triggers when timers are updated (at least once a second, can be more)',
+ subtitle: 'Triggers when a running timer is updated (at least once a second, can be more)',
},
onFinish: {
title: 'On Finish',
diff --git a/apps/client/src/features/modals/modalHelper.js b/apps/client/src/features/modals/modalHelper.js
index cbb4e70b8..5b47f35de 100644
--- a/apps/client/src/features/modals/modalHelper.js
+++ b/apps/client/src/features/modals/modalHelper.js
@@ -2,10 +2,3 @@ export const inputProps = {
size: 'sm',
autoComplete: 'off',
};
-
-export const portInputProps = {
- ...inputProps,
- type: 'number',
- min: '1024',
- max: '65535',
-};
diff --git a/apps/server/src/services/TimerService.ts b/apps/server/src/services/TimerService.ts
index 6197ff239..96c985283 100644
--- a/apps/server/src/services/TimerService.ts
+++ b/apps/server/src/services/TimerService.ts
@@ -10,6 +10,8 @@ import { clock } from './Clock.js';
export class TimerService {
private readonly _interval: NodeJS.Timer;
+ private _updateInterval: number;
+ private _lastUpdate: number | null;
playback: Playback;
timer: TimerState;
@@ -23,10 +25,12 @@ export class TimerService {
* @constructor
* @param {object} [timerConfig]
* @param {number} [timerConfig.refresh]
+ * @param {number} [timerConfig.updateInterval]
*/
- constructor(timerConfig?) {
+ constructor(timerConfig: { refresh?: number; updateInterval?: number } = {}) {
this._clear();
- this._interval = setInterval(() => this.update(), timerConfig?.refresh || 1000);
+ this._interval = setInterval(() => this.update(), timerConfig?.refresh ?? 1000);
+ this._updateInterval = timerConfig?.updateInterval ?? 1000;
}
/**
@@ -53,6 +57,8 @@ export class TimerService {
this.pausedTime = 0;
this.pausedAt = null;
this.secondaryTarget = null;
+
+ this._lastUpdate = null;
}
/**
@@ -93,7 +99,7 @@ export class TimerService {
if (this.timer.startedAt === null) {
this.timer.current = timer.duration;
}
- this.update();
+ this.update(true);
}
/**
@@ -240,77 +246,95 @@ export class TimerService {
}
// force an update
- this.update();
+ this.update(true);
}
- update() {
+ private updateRoll() {
+ const tempCurrentTimer = {
+ selectedEventId: this.loadedTimerId,
+ current: this.timer.current,
+ // safeguard on midnight rollover
+ _finishAt:
+ this.timer.expectedFinish >= this.timer.startedAt
+ ? this.timer.expectedFinish
+ : this.timer.expectedFinish + DAY_TO_MS,
+
+ clock: this.timer.clock,
+ secondaryTimer: this.timer.secondaryTimer,
+ secondaryTarget: this.secondaryTarget,
+ };
+ const { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished } = updateRoll(tempCurrentTimer);
+
+ this.timer.current = updatedTimer;
+ this.timer.secondaryTimer = updatedSecondaryTimer;
+ this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock);
+
+ if (isFinished) {
+ this.timer.selectedEventId = null;
+ this.loadedTimerId = null;
+ this._onFinish();
+ }
+
+ // to load the next event we have to escalate to parent service
+ if (doRollLoad) {
+ PlaybackService.roll();
+ }
+ }
+
+ private updatePlay() {
+ if (this.playback === Playback.Pause) {
+ this.pausedTime = this.timer.clock - this.pausedAt;
+ }
+
+ if (this.playback === Playback.Play && this.timer.current <= 0 && this.timer.finishedAt === null) {
+ this.timer.finishedAt = this.timer.clock;
+ this._onFinish();
+ } else {
+ this.timer.expectedFinish = getExpectedFinish(
+ this.timer.startedAt,
+ this.timer.finishedAt,
+ this.timer.duration,
+ this.pausedTime,
+ this.timer.addedTime,
+ );
+ }
+ this.timer.current = getCurrent(
+ this.timer.startedAt,
+ this.timer.duration,
+ this.timer.addedTime,
+ this.pausedTime,
+ this.timer.clock,
+ );
+ this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock);
+ }
+
+ update(force = false) {
this.timer.clock = clock.timeNow();
+ // we call integrations if we update timers
+ let shouldNotify = false;
if (this.playback === Playback.Roll) {
- const tempCurrentTimer = {
- selectedEventId: this.loadedTimerId,
- current: this.timer.current,
- // safeguard on midnight rollover
- _finishAt:
- this.timer.expectedFinish >= this.timer.startedAt
- ? this.timer.expectedFinish
- : this.timer.expectedFinish + DAY_TO_MS,
-
- clock: this.timer.clock,
- secondaryTimer: this.timer.secondaryTimer,
- secondaryTarget: this.secondaryTarget,
- };
- const { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished } = updateRoll(tempCurrentTimer);
-
- this.timer.current = updatedTimer;
- this.timer.secondaryTimer = updatedSecondaryTimer;
- this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock);
-
- if (isFinished) {
- this.timer.selectedEventId = null;
- this.loadedTimerId = null;
- this._onFinish();
- }
-
- // to load the next event we have to escalate to parent service
- if (doRollLoad) {
- PlaybackService.roll();
- }
- } else {
+ shouldNotify = true;
+ this.updateRoll();
+ } else if (this.timer.startedAt !== null) {
// we only update timer if a timer has been started
- if (this.timer.startedAt !== null) {
- if (this.playback === Playback.Pause) {
- this.pausedTime = this.timer.clock - this.pausedAt;
- }
-
- if (this.playback === Playback.Play && this.timer.current <= 0 && this.timer.finishedAt === null) {
- this.timer.finishedAt = this.timer.clock;
- this._onFinish();
- } else {
- this.timer.expectedFinish = getExpectedFinish(
- this.timer.startedAt,
- this.timer.finishedAt,
- this.timer.duration,
- this.pausedTime,
- this.timer.addedTime,
- );
- }
- this.timer.current = getCurrent(
- this.timer.startedAt,
- this.timer.duration,
- this.timer.addedTime,
- this.pausedTime,
- this.timer.clock,
- );
- this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock);
- }
+ shouldNotify = true;
+ this.updatePlay();
+ }
+
+ // we only update the store at the updateInterval
+ // side effects such as onFinish will still be triggered in the update functions
+ if (force || this.timer.clock > this._lastUpdate + this._updateInterval) {
+ this._lastUpdate = this.timer.clock;
+ this._onUpdate(shouldNotify);
}
- this._onUpdate();
}
- _onUpdate() {
+ _onUpdate(shouldNotify: boolean) {
eventStore.set('timer', this.timer);
- integrationService.dispatch(TimerLifeCycle.onUpdate);
+ if (shouldNotify) {
+ integrationService.dispatch(TimerLifeCycle.onUpdate);
+ }
}
_onFinish() {
@@ -358,7 +382,7 @@ export class TimerService {
}
this.playback = Playback.Roll;
this._onRoll();
- this.update();
+ this.update(true);
}
_onRoll() {
@@ -370,4 +394,5 @@ export class TimerService {
}
}
-export const eventTimer = new TimerService();
+// calculate at 30fps, refresh at 1fps
+export const eventTimer = new TimerService({ refresh: 32, updateInterval: 1000 });