mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-11 18:33:53 +00:00
V2 ws store (#310)
* Update TimerService.ts * refactor: message service publishes to store * refactor: several type improvements * V2 ws store wss (#309) * refactor: shared logging types * refactor: simplify message service consumption * refactor: create discrete logging system * refactor: move socket.io > websocket
This commit is contained in:
+24
-25
@@ -1,11 +1,10 @@
|
||||
/**
|
||||
* starts loaded timer
|
||||
*/
|
||||
import { socketProvider } from '../classes/socket/SocketController.js';
|
||||
import { Playback } from 'ontime-types';
|
||||
|
||||
import { eventLoader, EventLoader } from '../classes/event-loader/EventLoader.js';
|
||||
import { eventStore } from '../stores/EventStore.js';
|
||||
import { eventTimer } from './TimerService.js';
|
||||
import { clock } from './Clock.js';
|
||||
import { logger } from '../classes/Logger.js';
|
||||
|
||||
/**
|
||||
* Service manages playback status of app
|
||||
@@ -20,9 +19,9 @@ export class PlaybackService {
|
||||
static loadEvent(event) {
|
||||
let success = false;
|
||||
if (!event) {
|
||||
socketProvider.error('PLAYBACK', 'No event found');
|
||||
logger.error('PLAYBACK', 'No event found');
|
||||
} else if (event.skip) {
|
||||
socketProvider.warning('PLAYBACK', `Refused playback of skipped event ID ${event.id}`);
|
||||
logger.warning('PLAYBACK', `Refused playback of skipped event ID ${event.id}`);
|
||||
} else {
|
||||
eventLoader.loadEvent(event);
|
||||
eventTimer.load(event);
|
||||
@@ -41,7 +40,7 @@ export class PlaybackService {
|
||||
const event = EventLoader.getEventWithId(eventId);
|
||||
const success = PlaybackService.loadEvent(event);
|
||||
if (success) {
|
||||
socketProvider.info('PLAYBACK', `Loaded event with ID ${event.id}`);
|
||||
logger.info('PLAYBACK', `Loaded event with ID ${event.id}`);
|
||||
PlaybackService.start();
|
||||
}
|
||||
return success;
|
||||
@@ -56,7 +55,7 @@ export class PlaybackService {
|
||||
const event = EventLoader.getEventAtIndex(eventIndex);
|
||||
const success = PlaybackService.loadEvent(event);
|
||||
if (success) {
|
||||
socketProvider.info('PLAYBACK', `Loaded event with ID ${event.id}`);
|
||||
logger.info('PLAYBACK', `Loaded event with ID ${event.id}`);
|
||||
PlaybackService.start();
|
||||
}
|
||||
return success;
|
||||
@@ -71,7 +70,7 @@ export class PlaybackService {
|
||||
const event = EventLoader.getEventWithId(eventId);
|
||||
const success = PlaybackService.loadEvent(event);
|
||||
if (success) {
|
||||
socketProvider.info('PLAYBACK', `Loaded event with ID ${event.id}`);
|
||||
logger.info('PLAYBACK', `Loaded event with ID ${event.id}`);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -85,7 +84,7 @@ export class PlaybackService {
|
||||
const event = EventLoader.getEventAtIndex(eventIndex);
|
||||
const success = PlaybackService.loadEvent(event);
|
||||
if (success) {
|
||||
socketProvider.info('PLAYBACK', `Loaded event with ID ${event.id}`);
|
||||
logger.info('PLAYBACK', `Loaded event with ID ${event.id}`);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -98,7 +97,7 @@ export class PlaybackService {
|
||||
if (previousEvent) {
|
||||
const success = PlaybackService.loadEvent(previousEvent);
|
||||
if (success) {
|
||||
socketProvider.info('PLAYBACK', `Loaded event with ID ${previousEvent.id}`);
|
||||
logger.info('PLAYBACK', `Loaded event with ID ${previousEvent.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,7 +110,7 @@ export class PlaybackService {
|
||||
if (nextEvent) {
|
||||
const success = PlaybackService.loadEvent(nextEvent);
|
||||
if (success) {
|
||||
socketProvider.info('PLAYBACK', `Loaded event with ID ${nextEvent.id}`);
|
||||
logger.info('PLAYBACK', `Loaded event with ID ${nextEvent.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,10 +119,10 @@ export class PlaybackService {
|
||||
* Starts playback on selected event
|
||||
*/
|
||||
static start() {
|
||||
if (eventLoader.selectedEventId) {
|
||||
if (eventTimer.playback === Playback.Armed || eventTimer.playback === Playback.Pause) {
|
||||
eventTimer.start();
|
||||
const newState = eventTimer.playback;
|
||||
socketProvider.info('PLAYBACK', `Play Mode ${newState.toUpperCase()}`);
|
||||
logger.info('PLAYBACK', `Play Mode ${newState.toUpperCase()}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,10 +130,10 @@ export class PlaybackService {
|
||||
* Pauses playback on selected event
|
||||
*/
|
||||
static pause() {
|
||||
if (eventLoader.selectedEventId) {
|
||||
if (eventTimer.playback === Playback.Play) {
|
||||
eventTimer.pause();
|
||||
const newState = eventTimer.playback;
|
||||
socketProvider.info('PLAYBACK', `Play Mode ${newState.toUpperCase()}`);
|
||||
logger.info('PLAYBACK', `Play Mode ${newState.toUpperCase()}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,11 +141,11 @@ export class PlaybackService {
|
||||
* Stops timer and unloads any events
|
||||
*/
|
||||
static stop() {
|
||||
if (eventLoader.selectedEventId || eventTimer.playback === 'roll') {
|
||||
if (eventTimer.playback !== Playback.Stop) {
|
||||
eventLoader.reset();
|
||||
eventTimer.stop();
|
||||
const newState = eventTimer.playback;
|
||||
socketProvider.info('PLAYBACK', `Play Mode ${newState.toUpperCase()}`);
|
||||
logger.info('PLAYBACK', `Play Mode ${newState.toUpperCase()}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,8 +153,8 @@ export class PlaybackService {
|
||||
* Reloads current event
|
||||
*/
|
||||
static reload() {
|
||||
if (eventLoader.selectedEventId) {
|
||||
this.loadById(eventLoader.selectedEventId);
|
||||
if (eventTimer.loadedTimerId) {
|
||||
this.loadById(eventTimer.loadedTimerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,14 +167,14 @@ export class PlaybackService {
|
||||
|
||||
// nothing to play
|
||||
if (rollTimers === null) {
|
||||
socketProvider.error('SERVER', 'Roll: no events found');
|
||||
logger.warning('SERVER', 'Roll: no events found');
|
||||
PlaybackService.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const { currentEvent, nextEvent, timers } = rollTimers;
|
||||
if (!currentEvent && !nextEvent) {
|
||||
socketProvider.error('SERVER', 'Roll: no events found');
|
||||
logger.warning('SERVER', 'Roll: no events found');
|
||||
PlaybackService.stop();
|
||||
return;
|
||||
}
|
||||
@@ -183,7 +182,7 @@ export class PlaybackService {
|
||||
eventTimer.roll(currentEvent, nextEvent, timers);
|
||||
|
||||
const newState = eventTimer.playback;
|
||||
socketProvider.info('PLAYBACK', `Play Mode ${newState.toUpperCase()}`);
|
||||
logger.info('PLAYBACK', `Play Mode ${newState.toUpperCase()}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,10 +191,10 @@ export class PlaybackService {
|
||||
* @param {number} delayTime time in minutes
|
||||
*/
|
||||
static setDelay(delayTime) {
|
||||
if (eventLoader.selectedEventId) {
|
||||
if (eventTimer.loadedTimerId) {
|
||||
const delayInMs = delayTime * 1000 * 60;
|
||||
eventTimer.delay(delayInMs);
|
||||
socketProvider.info('PLAYBACK', `Added ${delayTime} min delay`);
|
||||
logger.info('PLAYBACK', `Added ${delayTime} min delay`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,15 @@ import { block as blockDef, delay as delayDef, event as eventDef } from '../mode
|
||||
import { MAX_EVENTS } from '../settings.js';
|
||||
import { EventLoader, eventLoader } from '../classes/event-loader/EventLoader.js';
|
||||
import { eventTimer } from './TimerService.js';
|
||||
import { eventStore } from '../stores/EventStore.js';
|
||||
|
||||
/**
|
||||
* Checks if a list of IDs is in the current selection
|
||||
*/
|
||||
const affectedLoaded = (affectedIds: string[]) => {
|
||||
const now = eventLoader.selectedEventId;
|
||||
const nowPublic = eventLoader.selectedPublicEventId;
|
||||
const next = eventLoader.nextEventId;
|
||||
const nextPublic = eventLoader.nextPublicEventId;
|
||||
const now = eventLoader.loaded.selectedEventId;
|
||||
const nowPublic = eventLoader.loaded.selectedPublicEventId;
|
||||
const next = eventLoader.loaded.nextEventId;
|
||||
const nextPublic = eventLoader.loaded.nextPublicEventId;
|
||||
return (
|
||||
affectedIds.includes(now) ||
|
||||
affectedIds.includes(nowPublic) ||
|
||||
@@ -28,8 +27,8 @@ const affectedLoaded = (affectedIds: string[]) => {
|
||||
*/
|
||||
const isNewNext = () => {
|
||||
const timedEvents = EventLoader.getTimedEvents();
|
||||
const now = eventLoader.selectedEventId;
|
||||
const next = eventLoader.nextEventId;
|
||||
const now = eventLoader.loaded.selectedEventId;
|
||||
const next = eventLoader.loaded.nextEventId;
|
||||
|
||||
// check whether the index of now and next are consecutive
|
||||
const indexNow = timedEvents.findIndex((event) => event.id === now);
|
||||
@@ -39,8 +38,8 @@ const isNewNext = () => {
|
||||
return true;
|
||||
}
|
||||
// iterate through timed events and see if there are public events between nowPublic and nextPublic
|
||||
const nowPublic = eventLoader.selectedPublicEventId;
|
||||
const nextPublic = eventLoader.nextPublicEventId;
|
||||
const nowPublic = eventLoader.loaded.selectedPublicEventId;
|
||||
const nextPublic = eventLoader.loaded.nextPublicEventId;
|
||||
|
||||
let foundNew = false;
|
||||
let isAfter = false;
|
||||
@@ -67,7 +66,7 @@ const isNewNext = () => {
|
||||
* Updates timer object
|
||||
*/
|
||||
export function updateTimer(affectedIds?: string[]) {
|
||||
const runningEventId = eventLoader.selectedEventId;
|
||||
const runningEventId = eventLoader.loaded.selectedEventId;
|
||||
|
||||
if (runningEventId === null) {
|
||||
return false;
|
||||
@@ -112,7 +111,7 @@ export function updateTimer(affectedIds?: string[]) {
|
||||
* @param {object} eventData
|
||||
* @return {unknown[]}
|
||||
*/
|
||||
export async function addEvent(eventData) {
|
||||
export async function addEvent(eventData: Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>) {
|
||||
const numEvents = DataProvider.getRundownLength();
|
||||
if (numEvents > MAX_EVENTS) {
|
||||
throw new Error(`ERROR: Reached limit number of ${MAX_EVENTS} events`);
|
||||
@@ -145,7 +144,7 @@ export async function addEvent(eventData) {
|
||||
throw new Error(error);
|
||||
}
|
||||
updateTimer([id]);
|
||||
eventStore.broadcast();
|
||||
updateChangeNumEvents();
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
@@ -157,7 +156,6 @@ export async function editEvent(eventData) {
|
||||
}
|
||||
const newEvent = await DataProvider.updateEventById(eventId, eventData);
|
||||
updateTimer([eventId]);
|
||||
eventStore.broadcast();
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
@@ -169,7 +167,7 @@ export async function editEvent(eventData) {
|
||||
export async function deleteEvent(eventId) {
|
||||
await DataProvider.deleteEvent(eventId);
|
||||
updateTimer([eventId]);
|
||||
eventStore.broadcast();
|
||||
updateChangeNumEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,7 +177,7 @@ export async function deleteEvent(eventId) {
|
||||
export async function deleteAllEvents() {
|
||||
await DataProvider.clearRundown();
|
||||
updateTimer();
|
||||
eventStore.broadcast();
|
||||
updateChangeNumEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,7 +202,6 @@ export async function reorderEvent(eventId, from, to) {
|
||||
// save rundown
|
||||
await DataProvider.setRundown(rundown);
|
||||
updateTimer();
|
||||
|
||||
return reorderedItem;
|
||||
}
|
||||
|
||||
@@ -260,5 +257,12 @@ export async function applyDelay(eventId) {
|
||||
// update rundown
|
||||
await DataProvider.setRundown(rundown);
|
||||
updateTimer();
|
||||
eventStore.broadcast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces update in the store
|
||||
* Called when we make changes to the rundown object
|
||||
*/
|
||||
function updateChangeNumEvents() {
|
||||
eventLoader.updateNumEvents();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TimerLifeCycle, TimerType } from 'ontime-types';
|
||||
import { Playback, TimerLifeCycle, TimerState } from 'ontime-types';
|
||||
|
||||
import { eventStore } from '../stores/EventStore.js';
|
||||
import { PlaybackService } from './PlaybackService.js';
|
||||
@@ -11,27 +11,14 @@ import { clock } from './Clock.js';
|
||||
export class TimerService {
|
||||
private readonly _interval: NodeJS.Timer;
|
||||
|
||||
playback: string;
|
||||
playback: Playback;
|
||||
timer: TimerState;
|
||||
|
||||
loadedTimerId: null;
|
||||
private pausedTime: number;
|
||||
private pausedAt: number | null;
|
||||
private secondaryTarget: number | null;
|
||||
|
||||
timer: {
|
||||
clock: number; // realtime clock
|
||||
current: number | null; // running countdown
|
||||
elapsed: number | null; // elapsed time in current timer
|
||||
expectedFinish: number | null;
|
||||
addedTime: number; // time added by user, can be negative
|
||||
startedAt: number | null;
|
||||
finishedAt: number | null; // only if timer has already finished
|
||||
secondaryTimer: number | null; // used for roll mode
|
||||
selectedEventId: string | null;
|
||||
duration: number | null;
|
||||
timerType: TimerType | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {object} [timerConfig]
|
||||
@@ -47,7 +34,7 @@ export class TimerService {
|
||||
* @private
|
||||
*/
|
||||
_clear() {
|
||||
this.playback = 'stop';
|
||||
this.playback = Playback.Stop;
|
||||
this.timer = {
|
||||
clock: clock.timeNow(),
|
||||
current: null,
|
||||
@@ -128,7 +115,7 @@ export class TimerService {
|
||||
this.loadedTimerId = timer.id;
|
||||
this.timer.duration = timer.duration;
|
||||
this.timer.current = timer.duration;
|
||||
this.playback = 'armed';
|
||||
this.playback = Playback.Armed;
|
||||
this.timer.timerType = timer.timerType;
|
||||
this.pausedTime = 0;
|
||||
this.pausedAt = 0;
|
||||
@@ -151,7 +138,7 @@ export class TimerService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.playback === 'play') {
|
||||
if (this.playback === Playback.Play) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -166,7 +153,7 @@ export class TimerService {
|
||||
this.timer.startedAt = this.timer.clock;
|
||||
}
|
||||
|
||||
this.playback = 'play';
|
||||
this.playback = Playback.Play;
|
||||
this.timer.expectedFinish = getExpectedFinish(
|
||||
this.timer.startedAt,
|
||||
this.timer.finishedAt,
|
||||
@@ -188,11 +175,11 @@ export class TimerService {
|
||||
}
|
||||
|
||||
pause() {
|
||||
if (this.playback !== 'play') {
|
||||
if (this.playback !== Playback.Play) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.playback = 'pause';
|
||||
this.playback = Playback.Pause;
|
||||
this.timer.clock = clock.timeNow();
|
||||
this.pausedAt = this.timer.clock;
|
||||
this._onPause();
|
||||
@@ -205,7 +192,7 @@ export class TimerService {
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.playback === 'stop') {
|
||||
if (this.playback === Playback.Stop) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -248,7 +235,7 @@ export class TimerService {
|
||||
update() {
|
||||
this.timer.clock = clock.timeNow();
|
||||
|
||||
if (this.playback === 'roll') {
|
||||
if (this.playback === Playback.Roll) {
|
||||
const tempCurrentTimer = {
|
||||
selectedEventId: this.loadedTimerId,
|
||||
current: this.timer.current,
|
||||
@@ -279,11 +266,11 @@ export class TimerService {
|
||||
} else {
|
||||
// we only update timer if a timer has been started
|
||||
if (this.timer.startedAt !== null) {
|
||||
if (this.playback === 'pause') {
|
||||
if (this.playback === Playback.Pause) {
|
||||
this.pausedTime = this.timer.clock - this.pausedAt;
|
||||
}
|
||||
|
||||
if (this.playback === 'play' && this.timer.current <= 0 && this.timer.finishedAt === null) {
|
||||
if (this.playback === Playback.Play && this.timer.current <= 0 && this.timer.finishedAt === null) {
|
||||
this.timer.finishedAt = this.timer.clock;
|
||||
this._onFinish();
|
||||
} else {
|
||||
@@ -338,7 +325,7 @@ export class TimerService {
|
||||
this.secondaryTarget = nextEvent.timeStart;
|
||||
}
|
||||
|
||||
this.playback = 'roll';
|
||||
this.playback = Playback.Roll;
|
||||
this._onRoll();
|
||||
this.update();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Message } from 'ontime-types';
|
||||
|
||||
import { eventStore } from '../../stores/EventStore.js';
|
||||
|
||||
let instance;
|
||||
|
||||
class MessageService {
|
||||
timerMessage: Message;
|
||||
publicMessage: Message;
|
||||
lowerMessage: Message;
|
||||
onAir: boolean;
|
||||
|
||||
constructor() {
|
||||
if (instance) {
|
||||
throw new Error('There can be only one');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias -- this logic is used to ensure singleton
|
||||
instance = this;
|
||||
|
||||
this.timerMessage = {
|
||||
text: '',
|
||||
visible: false,
|
||||
};
|
||||
|
||||
this.publicMessage = {
|
||||
text: '',
|
||||
visible: false,
|
||||
};
|
||||
|
||||
this.lowerMessage = {
|
||||
text: '',
|
||||
visible: false,
|
||||
};
|
||||
|
||||
this.onAir = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message on stage timer screen
|
||||
*/
|
||||
setTimerText(payload: string) {
|
||||
this.timerMessage.text = payload;
|
||||
eventStore.set('timerMessage', this.timerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message visibility on stage timer screen
|
||||
*/
|
||||
setTimerVisibility(status: boolean) {
|
||||
this.timerMessage.visible = status;
|
||||
eventStore.set('timerMessage', this.timerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message on public screen
|
||||
*/
|
||||
setPublicText(payload: string) {
|
||||
this.publicMessage.text = payload;
|
||||
eventStore.set('publicMessage', this.publicMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message visibility on public screen
|
||||
*/
|
||||
setPublicVisibility(status: boolean) {
|
||||
this.publicMessage.visible = status;
|
||||
eventStore.set('publicMessage', this.publicMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message on lower third screen
|
||||
*/
|
||||
setLowerText(payload: string) {
|
||||
this.lowerMessage.text = payload;
|
||||
eventStore.set('lowerMessage', this.lowerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message visibility on lower third screen
|
||||
*/
|
||||
setLowerVisibility(status: boolean) {
|
||||
this.lowerMessage.visible = status;
|
||||
eventStore.set('lowerMessage', this.lowerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description set state of onAir, toggles if parameters are offered
|
||||
*/
|
||||
setOnAir(status?: boolean) {
|
||||
if (typeof status === 'undefined') {
|
||||
this.onAir = !this.onAir;
|
||||
} else {
|
||||
this.onAir = status;
|
||||
}
|
||||
eventStore.set('onAir', this.onAir);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns feature data
|
||||
*/
|
||||
getAll() {
|
||||
return {
|
||||
timerMessage: this.timerMessage,
|
||||
publicMessage: this.publicMessage,
|
||||
lowerMessage: this.lowerMessage,
|
||||
onAir: this.onAir,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const messageService = new MessageService();
|
||||
Reference in New Issue
Block a user