diff --git a/apps/client/src/features/control/playback/playback-buttons/PlaybackButtons.tsx b/apps/client/src/features/control/playback/playback-buttons/PlaybackButtons.tsx
index 045afd63ea..1f6d49bf72 100644
--- a/apps/client/src/features/control/playback/playback-buttons/PlaybackButtons.tsx
+++ b/apps/client/src/features/control/playback/playback-buttons/PlaybackButtons.tsx
@@ -5,8 +5,9 @@ import { IoPlaySkipBack } from '@react-icons/all-files/io5/IoPlaySkipBack';
import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward';
import { IoReload } from '@react-icons/all-files/io5/IoReload';
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 { validatePlayback } from 'ontime-utils';
import { setPlayback } from '../../../../common/hooks/useSocket';
import { tooltipDelayMid } from '../../../../ontimeConfig';
@@ -34,7 +35,13 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) {
const noEvents = numEvents === 0;
const disableGo = isRolling || noEvents || (isLast && !isArmed);
- const disablePrev = isRolling || noEvents || isFirst;
+ const disablePrev = noEvents || isFirst;
+
+ const playbackCan = validatePlayback(playback);
+ const disableStart = !playbackCan.start;
+ const disablePause = !playbackCan.pause;
+ const disableRoll = !playbackCan.roll || noEvents;
+ const disableStop = !playbackCan.stop;
const goModeText = selectedEventIndex === null || isArmed ? 'Start' : 'Next';
const goModeAction = () => {
@@ -51,21 +58,11 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) {
{goModeText}
-
+
-
+
@@ -82,21 +79,16 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) {
-
+
-
+
-
+
diff --git a/apps/client/src/theme/_v2Styles.scss b/apps/client/src/theme/_v2Styles.scss
index 74b0c77715..0f64a0c5b9 100644
--- a/apps/client/src/theme/_v2Styles.scss
+++ b/apps/client/src/theme/_v2Styles.scss
@@ -24,7 +24,7 @@ $ontime-delay-text: #E69056;
$ontime-paused: #c05621;
$ontime-stop: #E4281E;
$playback-negative: $red-500;
-$active-indicator: #899948;
+$active-indicator: #8bb33d;
$text-black: $gray-1350;
// interface panels
diff --git a/apps/server/src/services/PlaybackService.ts b/apps/server/src/services/PlaybackService.ts
index 5831a23760..813906f4f7 100644
--- a/apps/server/src/services/PlaybackService.ts
+++ b/apps/server/src/services/PlaybackService.ts
@@ -1,4 +1,5 @@
-import { OntimeEvent, Playback } from 'ontime-types';
+import { OntimeEvent } from 'ontime-types';
+import { validatePlayback } from 'ontime-utils';
import { eventLoader, EventLoader } from '../classes/event-loader/EventLoader.js';
import { eventStore } from '../stores/EventStore.js';
@@ -133,7 +134,7 @@ export class PlaybackService {
* Starts playback on selected event
*/
static start() {
- if (eventTimer.playback === Playback.Armed || eventTimer.playback === Playback.Pause) {
+ if (validatePlayback(eventTimer.playback).start) {
eventTimer.start();
const newState = eventTimer.playback;
logger.info('PLAYBACK', `Play Mode ${newState.toUpperCase()}`);
@@ -155,7 +156,7 @@ export class PlaybackService {
* Pauses playback on selected event
*/
static pause() {
- if (eventTimer.playback === Playback.Play) {
+ if (validatePlayback(eventTimer.playback).pause) {
eventTimer.pause();
const newState = eventTimer.playback;
logger.info('PLAYBACK', `Play Mode ${newState.toUpperCase()}`);
@@ -166,7 +167,7 @@ export class PlaybackService {
* Stops timer and unloads any events
*/
static stop() {
- if (eventTimer.playback !== Playback.Stop) {
+ if (validatePlayback(eventTimer.playback).stop) {
eventLoader.reset();
eventTimer.stop();
const newState = eventTimer.playback;
diff --git a/apps/server/src/services/TimerService.ts b/apps/server/src/services/TimerService.ts
index 96c9852834..0608dcd241 100644
--- a/apps/server/src/services/TimerService.ts
+++ b/apps/server/src/services/TimerService.ts
@@ -7,6 +7,13 @@ import { DAY_TO_MS } from '../utils/time.js';
import { integrationService } from './integration-service/IntegrationService.js';
import { getCurrent, getElapsed, getExpectedFinish } from './timerUtils.js';
import { clock } from './Clock.js';
+import { logger } from '../classes/Logger.js';
+
+type initialLoadingData = {
+ startedAt?: number | null;
+ expectedFinish?: number | null;
+ current?: number | null;
+};
export class TimerService {
private readonly _interval: NodeJS.Timer;
@@ -113,7 +120,7 @@ export class TimerService {
* @param {string} timer.timerType
* @param {boolean} timer.skip
*/
- load(timer) {
+ load(timer, initialData?: initialLoadingData) {
if (timer.skip) {
throw new Error('Refuse load of skipped event');
}
@@ -129,6 +136,10 @@ export class TimerService {
this.pausedTime = 0;
this.pausedAt = 0;
+ if (typeof initialData !== 'undefined') {
+ this.timer = { ...this.timer, ...initialData };
+ }
+
this._onLoad();
}
@@ -146,6 +157,9 @@ export class TimerService {
start() {
if (!this.loadedTimerId) {
+ if (this.playback === Playback.Roll) {
+ logger.error('PLAYBACK', 'Cannot start while waiting for event');
+ }
return;
}
@@ -155,12 +169,12 @@ export class TimerService {
this.timer.clock = clock.timeNow();
- // add paused time
+ // add paused time if it exists
if (this.pausedTime) {
this.timer.addedTime += this.pausedTime;
this.pausedAt = null;
this.pausedTime = 0;
- } else {
+ } else if (this.timer.startedAt === null) {
this.timer.startedAt = this.timer.clock;
}
@@ -188,10 +202,6 @@ export class TimerService {
}
pause() {
- if (this.playback !== Playback.Play) {
- return;
- }
-
this.playback = Playback.Pause;
this.timer.clock = clock.timeNow();
this.pausedAt = this.timer.clock;
@@ -370,9 +380,11 @@ export class TimerService {
// when we load a timer in roll, we do the same things as before
// but also pre-populate some data as to the running state
- this.load(currentEvent);
- this.timer.startedAt = currentEvent.timeStart;
- this.timer.expectedFinish = currentEvent.timeEnd;
+ this.load(currentEvent, {
+ startedAt: currentEvent.timeStart,
+ expectedFinish: currentEvent.timeEnd,
+ current: currentEvent.timeEnd - this.timer.clock,
+ });
} else if (nextEvent) {
// account for day after
const nextStart = nextEvent.timeStart < this.timer.clock ? nextEvent.timeStart + DAY_TO_MS : nextEvent.timeStart;
diff --git a/packages/utils/index.ts b/packages/utils/index.ts
index 323704a96a..6fceea01a6 100644
--- a/packages/utils/index.ts
+++ b/packages/utils/index.ts
@@ -3,3 +3,4 @@ export { formatFromMillis } from './src/date-utils/formatFromMillis.js';
export { isTimeString } from './src/date-utils/isTimeString.js';
export { millisToString } from './src/date-utils/millisToString.js';
export { generateId } from './src/generate-id/generateId.js';
+export { validatePlayback } from './src/validate-action/validatePlayback.js';
diff --git a/packages/utils/package.json b/packages/utils/package.json
index f2e3b63d94..1e5bdd7cfd 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -23,6 +23,7 @@
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-simple-import-sort": "^8.0.0",
+ "ontime-types": "workspace:*",
"prettier": "^2.8.3",
"typescript": "^4.9.4",
"vitest": "^0.30.1"
diff --git a/packages/utils/src/validate-action/validatePlayback.ts b/packages/utils/src/validate-action/validatePlayback.ts
new file mode 100644
index 0000000000..6911c73ff8
--- /dev/null
+++ b/packages/utils/src/validate-action/validatePlayback.ts
@@ -0,0 +1,10 @@
+import { Playback } from 'ontime-types';
+
+export function validatePlayback(currentPlayback: Playback) {
+ return {
+ start: currentPlayback !== Playback.Stop,
+ pause: currentPlayback === Playback.Play || currentPlayback === Playback.Roll,
+ roll: true,
+ stop: currentPlayback !== Playback.Stop,
+ };
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e07297b7d4..4e3da0fb66 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -267,6 +267,7 @@ importers:
eslint-plugin-simple-import-sort: ^8.0.0
luxon: ^3.3.0
nanoid: ^4.0.1
+ ontime-types: workspace:*
prettier: ^2.8.3
typescript: ^4.9.4
vitest: ^0.30.1
@@ -281,6 +282,7 @@ importers:
eslint-config-prettier: 8.6.0_eslint@8.31.0
eslint-plugin-prettier: 4.2.1_do5yx3dogbskqc4h5x5ilvlwyy
eslint-plugin-simple-import-sort: 8.0.0_eslint@8.31.0
+ ontime-types: link:../types
prettier: 2.8.3
typescript: 4.9.4
vitest: 0.30.1