v2 event timer (#274)

* lint: cleanup unused

* refactor: rename component to avoid conflict

* style: gap in element row

* refactor: rename playstate -> playback

* refactor: cleanup usages of timer and prepare integration manager

* style: re-arrange button order

* refactor: improve event loading

* feat(timer-service): hot reload

* fix: issue with duration input

* chore: cleanup debug

* refactor: resolve poll from runtime store

* refactor: cleanup merge

* refactor: small improvements in timer hot-reload
This commit is contained in:
Carlos Valente
2022-12-26 12:58:33 +01:00
committed by GitHub
parent 062ff7226a
commit 110d3fb7a3
39 changed files with 637 additions and 601 deletions
-1
View File
@@ -49,7 +49,6 @@ let tray = null;
// Start OSC Server
await startOSCServer();
} catch (error) {
console.log(error);
loaded = error;
}
})();
+30 -33
View File
@@ -1,33 +1,26 @@
// get environment vars
import 'dotenv/config';
// import config
import { config } from './config/config.js';
// import dependencies
import { dirname, join, resolve } from 'path';
// dependencies
import express from 'express';
import http from 'http';
import cors from 'cors';
// import utils
import { config } from './config/config.js';
import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js';
import { fileURLToPath } from 'url';
import { ONTIME_VERSION } from './version.js';
import { initSentry } from './modules/sentry.js';
import { dirname, join, resolve } from 'path';
// Import Routes
import { router as rundownRouter } from './routes/rundownRouter.js';
import { router as eventRouter } from './routes/eventRouter.js';
import { router as ontimeRouter } from './routes/ontimeRouter.js';
import { router as playbackRouter } from './routes/playbackRouter.js';
// Global Objects
import { EventTimer } from './classes/timer/EventTimer.js';
import { socketProvider } from './classes/socket/SocketController.js';
// Start OSC server
import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js';
import { fileURLToPath } from 'url';
// Services
import { DataProvider } from './classes/data-provider/DataProvider.js';
import { ONTIME_VERSION } from './version.js';
import { initSentry } from './modules/sentry.js';
import { socketProvider } from './classes/socket/SocketController.js';
import { eventTimer } from './services/TimerService.js';
// get environment
const env = process.env.NODE_ENV || 'production';
@@ -65,7 +58,7 @@ app.use('/playback', playbackRouter);
// serve static - css
app.use('/external', express.static(join(__dirname, 'external')));
// serve static - react, in test mode we fetch the react app from module
// serve static - react, in test mode we fetch the React app from module
const resolvedPath = () => {
const sameModule = '../';
const siblingModule = '../../';
@@ -74,6 +67,7 @@ const resolvedPath = () => {
}
return siblingModule;
};
app.use(express.static(join(__dirname, resolvedPath(), 'client/build')));
app.get('*', (req, res) => {
@@ -81,7 +75,7 @@ app.get('*', (req, res) => {
});
// Implement catch all
app.use((error, response, _next) => {
app.use((error, response) => {
response.status(400).send('Unhandled request');
});
@@ -128,13 +122,10 @@ export const startOSCServer = async (overrideConfig = null) => {
const server = http.createServer(app);
/**
* @description Starts all necessary services
* @param overrideConfig
* Starts servers
* @return {Promise<string>}
*/
export const startServer = async (overrideConfig = null) => {
const { http } = DataProvider.getData();
export const startServer = async () => {
// Start server
const returnMessage = `Ontime is listening on port ${serverPort}`;
server.listen(serverPort, '0.0.0.0');
@@ -143,18 +134,24 @@ export const startServer = async (overrideConfig = null) => {
await socket.initServer(server);
socket.info('SERVER', 'Socket initialised');
socket.info('SERVER', returnMessage);
socket.startListener();
return returnMessage;
};
/**
* starts integrations
* @param overrideConfig
* @return {Promise<void>}
*/
export const startIntegrations = async (overrideConfig = null) => {
const { http } = DataProvider.getData();
// OSC Config
const oscConfig = {
ip: oscIP,
port: overrideConfig?.port || oscOutPort,
};
// init timer
global.timer = new EventTimer(socket, config.timer, oscConfig, http);
socket.info('SERVER', returnMessage);
socket.startListener();
return returnMessage;
};
/**
@@ -166,7 +163,7 @@ export const shutdown = async () => {
server.close();
shutdownOSCServer();
global.timer.shutdown();
eventTimer.shutdown();
socket.shutdown();
};
@@ -42,7 +42,7 @@ export class DataProvider {
await this.persist();
}
static getRundownLenght() {
static getRundownLength() {
return data.rundown.length;
}
@@ -56,7 +56,6 @@ export class DataProvider {
* @param entry
* @param index
* @return {Promise<void>}
* @private
*/
static async insertEventAt(entry, index) {
// get events
@@ -91,7 +90,6 @@ export class DataProvider {
* @param entry
* @param id
* @return {Promise<void>}
* @private
*/
static async insertEventAfterId(entry, id) {
const index = [...data.rundown].findIndex((event) => event.id === id);
+12 -11
View File
@@ -80,7 +80,7 @@ export class EventLoader {
*/
loadById(eventId) {
const event = EventLoader.getEventWithId(eventId);
return this._loadEvent(event);
return this.loadEvent(event);
}
/**
@@ -90,12 +90,12 @@ export class EventLoader {
*/
loadByIndex(eventIndex) {
const event = EventLoader.getEventAtIndex(eventIndex);
return this._loadEvent(event);
return this.loadEvent(event);
}
/**
* finds the ID of the previous event
* @returns {{id: string}|null}
* finds the previous event
* @return {object | undefined}
*/
findPrevious() {
const timedEvents = EventLoader.getPlayableEvents();
@@ -105,15 +105,16 @@ export class EventLoader {
// if there is no event running, go to first
if (this.selectedEventIndex === null) {
return { id: timedEvents[0].id };
return timedEvents[0];
}
const newIndex = this.selectedEventIndex - 1;
return { id: timedEvents?.[newIndex].id };
return timedEvents?.[newIndex];
}
/**
* finds the ID of the next event
* @returns {{id: string}|null}
* finds the next event
* @return {object | undefined}
*/
findNext() {
const timedEvents = EventLoader.getPlayableEvents();
@@ -127,10 +128,10 @@ export class EventLoader {
// if there is no event running, go to first
if (this.selectedEventIndex === null) {
return { id: timedEvents[0].id };
return timedEvents[0];
}
const newIndex = this.selectedEventIndex + 1;
return { id: timedEvents?.[newIndex].id };
return timedEvents?.[newIndex];
}
/**
@@ -193,7 +194,7 @@ export class EventLoader {
* loads an event given its id
* @param {object} event
*/
_loadEvent(event) {
loadEvent(event) {
if (typeof event === 'undefined') {
return null;
}
+103 -32
View File
@@ -8,6 +8,8 @@ import { messageManager } from '../message-manager/MessageManager.js';
import { PlaybackService } from '../../services/playbackService.js';
import { ADDRESS_MESSAGE_CONTROL } from './socketConfig.js';
import { eventTimer } from '../../services/TimerService.js';
import { EventLoader, eventLoader } from '../event-loader/EventLoader.js';
class SocketController {
constructor() {
@@ -61,12 +63,16 @@ class SocketController {
// Todo: review in favour of features
// send state
socket.emit('timer', global.timer.getTimeObject());
socket.emit('playstate', global.timer.state);
socket.emit('selected-id', global.timer.selectedEventId);
socket.emit('next-id', global.timer.nextEventId);
socket.emit('publicselected-id', global.timer.selectedPublicEventId);
socket.emit('publicnext-id', global.timer.nextPublicEventId);
socket.emit('timer', eventTimer.timer);
socket.emit('playback', eventTimer.playback);
socket.emit('selected', {
id: eventLoader.selectedEventId,
index: eventLoader.selectedEventIndex,
total: eventLoader.numEvents,
});
socket.emit('next-id', eventLoader.nextEventId);
socket.emit('publicselected-id', eventLoader.selectedPublicEventId);
socket.emit('publicnext-id', eventLoader.nextPublicEventId);
/**
* @description handle disconnecting a user
@@ -167,16 +173,15 @@ class SocketController {
// general playback state, useful for external sync
// Todo: add delayed value (will come from rundownService)
socket.on('ontime-poll', () => {
const timerPoll = global.timer.poll();
const timerPoll = eventTimer.timer;
const isDelayed = false;
const colour = '';
socket.emit('ontime-poll', { isDelayed, colour, ...timerPoll });
});
/*******************************************/
// playstate
socket.on('get-playstate', () => {
socket.emit('playstate', global.timer.state);
socket.on('get-playback', () => {
socket.emit('playback', eventTimer.playback);
});
socket.on('get-onAir', () => {
@@ -184,30 +189,20 @@ class SocketController {
});
/*******************************************/
// selection data
socket.on('get-selected', () => {
socket.emit('selected', {
id: global.timer.selectedEventId,
index: global.timer.selectedEventIndex,
total: global.timer._eventlist.length,
id: eventLoader.selectedEventId,
index: eventLoader.selectedEventIndex,
total: eventLoader.numEvents,
});
});
socket.on('get-selected-id', () => {
socket.emit('selected-id', global.timer.selectedEventId);
});
socket.on('get-next-id', () => {
socket.emit('next-id', global.timer.nextEventId);
});
// title data
socket.on('get-titles', () => {
socket.emit('titles', global.timer.titles);
socket.emit('titles', eventLoader.titles);
});
socket.on('get-publictitles', () => {
socket.emit('publictitles', global.timer.titlesPublic);
socket.emit('publictitles', eventLoader.titlesPublic);
});
/***********************************/
@@ -294,33 +289,32 @@ class SocketController {
// 1. RUNDOWN
socket.on('get-feat-rundown', () => {
global.timer._broadcastFeatureRundown();
this.broadcastFeatureRundown();
});
// 2. MESSAGE CONTROL
socket.on('get-feat-messagecontrol', () => {
const featureData = messageManager.getAll();
this.socket.emit(ADDRESS_MESSAGE_CONTROL, featureData);
this.broadcastFeatureMessageControl();
});
// 3. PLAYBACK CONTROL
socket.on('get-feat-playbackcontrol', () => {
global.timer._broadcastFeaturePlaybackControl();
this.broadcastFeaturePlaybackControl();
});
// 4. INFO
socket.on('get-feat-info', () => {
global.timer._broadcastFeatureInfo();
this.broadcastFeatureInfo();
});
// 5. CUE SHEET
socket.on('get-feat-cuesheet', () => {
global.timer._broadcastFeatureCuesheet();
this.broadcastFeatureCuesheet();
});
// 6. TIMER
socket.on('get-ontime-timer', () => {
global.timer._broadcastFeatureTimer();
this.broadcastTimer();
});
});
}
@@ -363,6 +357,83 @@ class SocketController {
}
}
/**
* Broadcast data for Event List feature
*/
broadcastFeatureRundown() {
const featureData = {
selectedEventId: eventLoader.selectedEventId,
nextEventId: eventLoader.nextEventId,
playback: eventTimer.playback,
};
this.send('feat-rundown', featureData);
}
/**
* Broadcast data for Message Control feature
*/
broadcastFeatureMessageControl() {
const featureData = messageManager.getAll();
this.send(ADDRESS_MESSAGE_CONTROL, featureData);
}
/**
* Broadcast data for Playback Control feature
*/
broadcastFeaturePlaybackControl() {
const featureData = {
playback: eventTimer.playback,
selectedEventId: eventLoader.selectedEventId,
numEvents: EventLoader.getNumEvents(),
};
this.send('feat-playbackcontrol', featureData);
}
/**
* Broadcast data for Info feature
*/
broadcastFeatureInfo() {
const featureData = {
titles: eventLoader.titles,
playback: eventTimer.playback,
selectedEventId: eventLoader.selectedEventId,
selectedEventIndex: eventLoader.selectedEventIndex,
numEvents: EventLoader.getNumEvents(),
};
this.send('feat-info', featureData);
}
/**
* Broadcast data for Cuesheet feature
*/
broadcastFeatureCuesheet() {
const featureData = {
playback: eventTimer.playback,
selectedEventId: eventLoader.selectedEventId,
selectedEventIndex: eventLoader.selectedEventIndex,
numEvents: EventLoader.getNumEvents(),
titleNow: eventLoader.titles.titleNow,
};
this.send('feat-cuesheet', featureData);
}
/**
* Broadcast Timer feature
*/
broadcastTimer() {
const featureData = eventTimer.timer;
this.send('ontime-timer', featureData);
}
broadcastState() {
this.broadcastFeatureRundown();
this.broadcastFeatureMessageControl();
this.broadcastFeaturePlaybackControl();
this.broadcastFeatureInfo();
this.broadcastFeatureCuesheet();
this.broadcastTimer();
}
/**
* Sends a message with level LOG
* @param {string} origin
+2 -1
View File
@@ -7,12 +7,13 @@ import { DataProvider } from '../classes/data-provider/DataProvider.js';
import { failEmptyObjects, failIsNotArray } from '../utils/routerUtils.js';
import { mergeObject } from '../utils/parserUtils.js';
import { PlaybackService } from '../services/playbackService.js';
import { runtimeState } from '../stores/EventStore.js';
// Create controller for GET request to '/ontime/poll'
// Returns data for current state
export const poll = async (req, res) => {
try {
const s = global.timer.poll();
const s = runtimeState.poll();
res.status(200).send(s);
} catch (error) {
res.status(500).send({
+1 -1
View File
@@ -3,9 +3,9 @@ export const event = {
subtitle: '',
presenter: '',
note: '',
timeType: 'start-end',
timeStart: 0,
timeEnd: 0,
timeType: 'start-end',
duration: 0,
isPublic: false,
skip: false,
+272
View File
@@ -0,0 +1,272 @@
import { runtimeState } from '../stores/EventStore.js';
class TimerService {
/**
* @constructor
* @param {object} [timerConfig]
* @param {number} [timerConfig.refresh]
*/
constructor(timerConfig) {
this._clear();
this._interval = setInterval(() => this.update(), timerConfig?.refresh || 1000);
}
/**
* Get current time in ms from midnight
* @static
* @return {number}
*/
static getCurrentTime() {
const now = new Date();
// extract milliseconds since midnight
let elapsed = now.getHours() * 3600000;
elapsed += now.getMinutes() * 60000;
elapsed += now.getSeconds() * 1000;
elapsed += now.getMilliseconds();
return elapsed;
}
/**
* Returns expected time finish
* @private
*/
_getExpectedFinish() {
if (this.timer.startedAt === null) {
return null;
}
if (this.timer.finishedAt) {
return this.timer.finishedAt;
}
return Math.max(
this.timer.startedAt + this.timer.duration + this._pausedInterval + this.timer.addedTime,
this.timer.startedAt
);
}
/**
* Clears internal state
* @private
*/
_clear() {
this.playback = 'stop';
this.timer = {
clock: TimerService.getCurrentTime(),
current: null,
elapsed: null,
expectedFinish: null,
addedTime: 0,
startedAt: null,
finishedAt: null,
secondaryTimer: null,
};
this.loadedTimer = null;
this.loadedTimerId = null;
this._pausedInterval = 0;
this._pausedAt = null;
}
/**
* Reloads information for currently running timer
* @param timer
*/
hotReload(timer) {
if (timer?.id !== this.loadedTimerId) {
return;
}
if (timer?.skip) {
this.stop();
}
// update relevant information and force update
this.loadedTimer = timer;
this.timer.duration = timer.duration;
if (this.timer.startedAt === null) {
this.timer.current = timer.duration;
}
this.update();
}
/**
* Loads given timer to object
* @param {object} timer
* @param {number} timer.id
* @param {number} timer.timeStart
* @param {number} timer.timeEnd
* @param {number} timer.duration
* @param {string} timer.timeType
* @param {boolean} timer.skip
*/
load(timer) {
if (timer.skip) {
throw new Error('Refuse load of skipped event');
}
this._clear();
this.loadedTimer = timer;
this.loadedTimerId = timer.id;
this.timer.duration = timer.duration;
this.timer.current = timer.duration;
this.playback = 'armed';
this._pausedInterval = 0;
this._pausedAt = 0;
this._onLoad();
}
/**
* Handles side effects related to onLoad event
* @private
*/
_onLoad() {
runtimeState.set('playback', this.playback);
runtimeState.set('ontime-timer', this.timer);
}
start() {
if (!this.loadedTimerId) {
return;
}
if (this.playback === 'play') {
return;
}
this.timer.clock = TimerService.getCurrentTime();
// add paused time
if (this._pausedInterval) {
this.timer.addedTime += this._pausedInterval;
this._pausedAt = null;
this._pausedInterval = 0;
} else {
this.timer.startedAt = this.timer.clock;
}
this.playback = 'play';
this.timer.expectedFinish = this._getExpectedFinish();
this._onStart();
}
/**
* Handles side effects related to onStart event
* @private
*/
_onStart() {
runtimeState.set('playback', this.playback);
runtimeState.set('ontime-timer', this.timer);
}
pause() {
if (this.playback !== 'play') {
return;
}
this.playback = 'pause';
this.timer.clock = TimerService.getCurrentTime();
this._pausedAt = this.timer.clock;
this._onPause();
}
_onPause() {
runtimeState.set('playback', this.playback);
runtimeState.set('ontime-timer', this.timer);
}
stop() {
if (this.playback === 'stop') {
return;
}
this._clear();
this._onStop();
}
_onStop() {
runtimeState.set('playback', this.playback);
runtimeState.set('ontime-timer', this.timer);
}
/**
* Delays running timer by given amount
* @param {number} amount
*/
delay(amount) {
if (!this.loadedTimerId) {
return;
}
this.timer.addedTime += amount;
this.timer.current += amount;
this.timer.elapsed += amount;
// handle edge cases
if (amount < 0 && Math.abs(amount) > this.timer.current) {
if (this.timer.finishedAt === null) {
// if we will make the clock negative
this.timer.finishedAt = TimerService.getCurrentTime();
}
} else if (this.timer.current < 0 && this.timer.current + amount > 0) {
// clock will go from negative to positive
this.timer.finishedAt = null;
}
// force an update
this.update();
}
update() {
this.timer.clock = TimerService.getCurrentTime();
// we only update timer if a timer has been started
if (this.timer.startedAt !== null) {
if (this.playback === 'pause') {
this._pausedInterval = this.timer.clock - this._pausedAt;
}
this.timer.current =
this.timer.startedAt +
this.timer.duration +
this.timer.addedTime +
this._pausedInterval -
this.timer.clock;
this.timer.elapsed = this.timer.duration - this.timer.current;
if (this.playback === 'play' && this.timer.current <= 0 && this.timer.finishedAt === null) {
this.timer.finishedAt = this.timer.clock;
this._onFinish();
} else {
this.timer.finishedAt = null;
}
this.timer.expectedFinish = this._getExpectedFinish();
}
this._onUpdate();
}
_onUpdate() {
runtimeState.set('playback', this.playback);
runtimeState.set('ontime-timer', this.timer);
}
_onFinish() {
runtimeState.set('playback', this.playback);
runtimeState.set('ontime-timer', this.timer);
}
roll() {
this._onRoll();
}
_onRoll() {
throw new Error('Roll not implemented');
}
shutdown() {
clearInterval(this._interval);
}
}
export const eventTimer = new TimerService();
+48 -57
View File
@@ -3,6 +3,7 @@
*/
import { socketProvider } from '../classes/socket/SocketController.js';
import { eventLoader, EventLoader } from '../classes/event-loader/EventLoader.js';
import { eventTimer } from './TimerService.js';
/**
* Service manages playback status of app
@@ -15,18 +16,18 @@ export class PlaybackService {
* @return {boolean} success
*/
static loadEvent(event) {
let success = false;
if (!event) {
socketProvider.error('PLAYBACK', 'No event found');
return false;
}
if (event.skip) {
} else if (event.skip) {
socketProvider.warning('PLAYBACK', `Refused playback of skipped event ID ${event.id}`);
return false;
} else {
eventLoader.loadEvent(event);
eventTimer.load(event);
success = true;
}
global.timer.pause();
global.timer.loadEvent(event);
return true;
socketProvider.broadcastState();
return success;
}
/**
@@ -93,8 +94,10 @@ export class PlaybackService {
static loadPrevious() {
const previousEvent = eventLoader.findPrevious();
if (previousEvent) {
PlaybackService.loadById(previousEvent.id);
global.timer.previous();
const success = PlaybackService.loadEvent(previousEvent);
if (success) {
socketProvider.info('PLAYBACK', `Loaded event with ID ${previousEvent.id}`);
}
}
}
@@ -104,8 +107,10 @@ export class PlaybackService {
static loadNext() {
const nextEvent = eventLoader.findNext();
if (nextEvent) {
PlaybackService.loadById(nextEvent.id);
global.timer.next();
const success = PlaybackService.loadEvent(nextEvent);
if (success) {
socketProvider.info('PLAYBACK', `Loaded event with ID ${nextEvent.id}`);
}
}
}
@@ -113,86 +118,72 @@ export class PlaybackService {
* Starts playback on selected event
*/
static start() {
if (!eventLoader.selectedEventId) {
return;
if (eventLoader.selectedEventId) {
eventTimer.start();
const newState = eventTimer.playback;
socketProvider.info('PLAYBACK', `Play Mode ${newState}`);
}
const newState = global.timer.start();
if (newState === 'start') {
socketProvider.info('PLAYBACK', 'Play Mode Start');
}
socketProvider.send('playstate', newState);
socketProvider.broadcastState();
}
/**
* Pauses playback on selected event
*/
static pause() {
if (!eventLoader.selectedEventId) {
return;
if (eventLoader.selectedEventId) {
eventTimer.pause();
const newState = eventTimer.playback;
socketProvider.info('PLAYBACK', `Play Mode ${newState}`);
}
const newState = global.timer.pause();
if (newState === 'pause') {
socketProvider.info('PLAYBACK', 'Play Mode Paused');
}
socketProvider.send('playstate', newState);
socketProvider.broadcastState();
}
/**
* Stops timer and unloads any events
*/
static stop() {
if (!eventLoader.selectedEventId && global.timer.state !== 'roll') {
return;
if (eventLoader.selectedEventId || eventTimer.playback === 'roll') {
eventLoader.reset();
eventTimer.stop();
const newState = eventTimer.playback;
socketProvider.info('PLAYBACK', `Play Mode ${newState}`);
}
eventLoader.reset();
const newState = global.timer.stop();
if (newState === 'stop') {
socketProvider.info('PLAYBACK', 'Play Mode Stopped');
}
socketProvider.send('playstate', newState);
socketProvider.broadcastState();
}
/**
* Reloads current event
*/
static reload() {
if (!eventLoader.selectedEventId) {
return;
if (eventLoader.selectedEventId) {
this.loadById(eventLoader.selectedEventId);
}
const newState = global.timer.reload();
socketProvider.info('PLAYBACK', 'Reloaded event');
socketProvider.send('playstate', newState);
socketProvider.broadcastState();
}
/**
* Sets playback to roll
*/
static roll() {
if (!EventLoader.getNumEvents()) {
return;
if (EventLoader.getNumEvents() && eventTimer.playback !== 'roll') {
eventTimer.roll();
const newState = eventTimer.playback;
socketProvider.info('PLAYBACK', `Play Mode ${newState}`);
socketProvider.send('playback', newState);
}
if (global.timer.state === 'roll') {
return;
}
const newState = global.timer.roll();
if (newState === 'roll') {
socketProvider.info('PLAYBACK', 'Play Mode Roll');
}
socketProvider.send('playstate', newState);
socketProvider.broadcastState();
}
/**
* Adds delay to current event
* @param {number} delayTime time in ms
* @param {number} delayTime time in minutes
*/
static setDelay(delayTime) {
if (!eventLoader.selectedEventId) {
return;
if (eventLoader.selectedEventId) {
const delayInMs = delayTime * 1000 * 60;
eventTimer.delay(delayInMs);
socketProvider.info('PLAYBACK', `Added ${delayTime} min delay`);
}
const delayInMs = delayTime * 1000 * 60;
global.timer.increment(delayInMs);
socketProvider.info('PLAYBACK', `Added ${delayTime} min delay`);
socketProvider.broadcastState();
}
}
+38 -10
View File
@@ -7,14 +7,20 @@ import {
} from '../models/eventsDefinition.js';
import { MAX_EVENTS } from '../settings.js';
import { EventLoader, eventLoader } from '../classes/event-loader/EventLoader.js';
import { eventTimer } from './TimerService.js';
import { socketProvider } from '../classes/socket/SocketController.js';
/**
*
* @param affectedIds
* @returns boolean
*/
const affectedLoaded = (affectedIds) => {
const now = eventLoader.selectedEventId;
const nowPublic = eventLoader.selectedPublicEventId;
const next = eventLoader.nextEventId;
const nextPublic = eventLoader.nextPublicEventId;
return (
affectedIds.includes(now) ||
affectedIds.includes(now) ||
affectedIds.includes(nowPublic) ||
affectedIds.includes(next) ||
@@ -65,24 +71,41 @@ const isNewNext = () => {
*/
export function updateTimer(affectedIds) {
const runningEventId = eventLoader.selectedEventId;
if (runningEventId === null) {
return false;
}
// we need to reload in a few scenarios:
// 1. we are not confident that changes do not affect running event
// 2. the edited event is currently being used (now or next)
// 3. the edited event replaces one of the previous (next)
if (typeof affectedIds === 'undefined') {
global.timer.syncLoaded(runningEventId);
const safeOption = typeof affectedIds === 'undefined';
// 2. the edited event is in memory (now or next) running
const eventInMemory = safeOption ? false : affectedLoaded(affectedIds);
// 3. the edited event replaces next event
const isNext = isNewNext();
if (safeOption) {
eventLoader.reset();
const loadedEvent = eventLoader.loadById(runningEventId);
eventTimer.hotReload(loadedEvent);
return true;
}
if (affectedLoaded(affectedIds)) {
global.timer.syncLoaded(runningEventId);
if (eventInMemory) {
const loadedEvent = eventLoader.loadById(runningEventId);
if (!loadedEvent) {
// event was deleted
eventLoader.reset();
eventTimer.stop();
} else {
eventTimer.hotReload(loadedEvent);
}
return true;
}
if (isNewNext()) {
global.timer.syncLoaded(runningEventId);
if (isNext) {
const loadedEvent = eventLoader.loadById(runningEventId);
eventTimer.hotReload(loadedEvent);
return true;
}
return false;
@@ -94,7 +117,7 @@ export function updateTimer(affectedIds) {
* @return {unknown[]}
*/
export async function addEvent(eventData) {
const numEvents = DataProvider.getRundownLenght();
const numEvents = DataProvider.getRundownLength();
if (numEvents > MAX_EVENTS) {
throw new Error(`ERROR: Reached limit number of ${MAX_EVENTS} events`);
}
@@ -126,6 +149,7 @@ export async function addEvent(eventData) {
throw new Error(error);
}
updateTimer([id]);
socketProvider.broadcastState();
return newEvent;
}
@@ -137,6 +161,7 @@ export async function editEvent(eventData) {
}
const newEvent = await DataProvider.updateEventById(eventId, eventData);
updateTimer([eventId]);
socketProvider.broadcastState();
return newEvent;
}
@@ -148,6 +173,7 @@ export async function editEvent(eventData) {
export async function deleteEvent(eventId) {
await DataProvider.deleteEvent(eventId);
updateTimer([eventId]);
socketProvider.broadcastState();
}
/**
@@ -157,6 +183,7 @@ export async function deleteEvent(eventId) {
export async function deleteAllEvents() {
await DataProvider.clearRundown();
updateTimer();
socketProvider.broadcastState();
}
/**
@@ -237,4 +264,5 @@ export async function applyDelay(eventId) {
// update rundown
await DataProvider.setRundown(rundown);
updateTimer();
socketProvider.broadcastState();
}
+19
View File
@@ -0,0 +1,19 @@
import { socketProvider } from '../classes/socket/SocketController.js';
const store = {};
/**
* A runtime store that broadcasts its payload
*/
export const runtimeState = {
get(key) {
return store[key];
},
set(key, value) {
store[key] = value;
socketProvider.send(key, value);
},
poll() {
return store;
},
};