mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 17:33:55 +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:
@@ -0,0 +1,104 @@
|
||||
import { Log, LogLevel } from 'ontime-types';
|
||||
import { generateId, millisToString } from 'ontime-utils';
|
||||
|
||||
import { clock } from '../services/Clock.js';
|
||||
import { isProduction } from '../setup.js';
|
||||
import { socket } from '../adapters/WebsocketAdapter.js';
|
||||
|
||||
class Logger {
|
||||
private queue: Log[];
|
||||
|
||||
constructor() {
|
||||
this.queue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enabling setup logger after init
|
||||
*/
|
||||
init() {
|
||||
this.queue.forEach((log) => {
|
||||
this._push(log);
|
||||
});
|
||||
this.queue = [];
|
||||
}
|
||||
|
||||
private addToQueue(log: Log) {
|
||||
this.queue.push(log);
|
||||
if (this.queue.length > 100) {
|
||||
this.queue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal safe push method, adds log to queue if callback not available
|
||||
* @param log
|
||||
*/
|
||||
private _push(log: Log) {
|
||||
if (!isProduction) {
|
||||
console.log(`[${log.level}] \t ${log.origin} \t ${log.text}`);
|
||||
}
|
||||
|
||||
try {
|
||||
socket.send({
|
||||
type: 'ontime-log',
|
||||
payload: log,
|
||||
});
|
||||
} catch (_e) {
|
||||
this.addToQueue(log);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits logging message
|
||||
* @param level
|
||||
* @param origin
|
||||
* @param text
|
||||
*/
|
||||
emit(level, origin: string, text: string) {
|
||||
const log = {
|
||||
id: generateId(),
|
||||
level,
|
||||
origin,
|
||||
text,
|
||||
time: millisToString(clock.getSystemTime() || 0),
|
||||
};
|
||||
this._push(log);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to emit logging message of type INFO
|
||||
* @param origin
|
||||
* @param text
|
||||
*/
|
||||
info(origin: string, text: string) {
|
||||
this.emit(LogLevel.Info, origin, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to emit logging message of type WARN
|
||||
* @param origin
|
||||
* @param text
|
||||
*/
|
||||
warning(origin: string, text: string) {
|
||||
this.emit(LogLevel.Warn, origin, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to emit logging message of type ERROR
|
||||
* @param origin
|
||||
* @param text
|
||||
*/
|
||||
error(origin: string, text: string) {
|
||||
this.emit(LogLevel.Error, origin, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown logger
|
||||
*/
|
||||
shutdown() {
|
||||
console.log('Shutting down logger');
|
||||
this.queue = [];
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new Logger();
|
||||
@@ -1,31 +1,17 @@
|
||||
import { OntimeEvent, TitleBlock, Loaded } from 'ontime-types';
|
||||
|
||||
import { DataProvider } from '../data-provider/DataProvider.js';
|
||||
import { getRollTimers } from '../../services/rollUtils.js';
|
||||
import { eventStore } from '../../stores/EventStore.js';
|
||||
|
||||
let instance;
|
||||
|
||||
type TitleBlock = {
|
||||
titleNow: string | null;
|
||||
subtitleNow: string | null;
|
||||
presenterNow: string | null;
|
||||
noteNow: string | null;
|
||||
titleNext: string | null;
|
||||
subtitleNext: string | null;
|
||||
presenterNext: string | null;
|
||||
noteNext: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages business logic around loading events
|
||||
*/
|
||||
export class EventLoader {
|
||||
loadedEvent: object | null;
|
||||
numEvents: number | null;
|
||||
selectedEventIndex: number | null;
|
||||
selectedEventId: string | null;
|
||||
selectedPublicEventId: string | null;
|
||||
nextEventId: string | null;
|
||||
nextPublicEventId: string | null;
|
||||
loadedEvent: OntimeEvent | null;
|
||||
loaded: Loaded;
|
||||
titles: TitleBlock;
|
||||
titlesPublic: TitleBlock;
|
||||
|
||||
@@ -36,7 +22,10 @@ export class EventLoader {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias -- this logic is used to ensure singleton
|
||||
instance = this;
|
||||
this.reset(false);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.reset();
|
||||
this.loadedEvent = null;
|
||||
}
|
||||
|
||||
@@ -122,16 +111,16 @@ export class EventLoader {
|
||||
*/
|
||||
findPrevious() {
|
||||
const timedEvents = EventLoader.getPlayableEvents();
|
||||
if (timedEvents === null || !timedEvents.length || this.selectedEventIndex === 0) {
|
||||
if (timedEvents === null || !timedEvents.length || this.loaded.selectedEventIndex === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if there is no event running, go to first
|
||||
if (this.selectedEventIndex === null) {
|
||||
if (this.loaded.selectedEventIndex === null) {
|
||||
return timedEvents[0];
|
||||
}
|
||||
|
||||
const newIndex = this.selectedEventIndex - 1;
|
||||
const newIndex = this.loaded.selectedEventIndex - 1;
|
||||
return timedEvents?.[newIndex];
|
||||
}
|
||||
|
||||
@@ -141,15 +130,15 @@ export class EventLoader {
|
||||
*/
|
||||
findNext() {
|
||||
const timedEvents = EventLoader.getPlayableEvents();
|
||||
if (timedEvents === null || !timedEvents.length || this.selectedEventIndex === this.numEvents - 1) {
|
||||
if (timedEvents === null || !timedEvents.length || this.loaded.selectedEventIndex === this.loaded.numEvents - 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if there is no event running, go to first
|
||||
if (this.selectedEventIndex === null) {
|
||||
if (this.loaded.selectedEventIndex === null) {
|
||||
return timedEvents[0];
|
||||
}
|
||||
const newIndex = this.selectedEventIndex + 1;
|
||||
const newIndex = this.loaded.selectedEventIndex + 1;
|
||||
return timedEvents?.[newIndex];
|
||||
}
|
||||
|
||||
@@ -167,9 +156,9 @@ export class EventLoader {
|
||||
getRollTimers(timedEvents, timeNow);
|
||||
|
||||
this.loadedEvent = currentEvent;
|
||||
this.selectedEventIndex = nowIndex;
|
||||
this.selectedEventId = currentEvent?.id || null;
|
||||
this.numEvents = timedEvents.length;
|
||||
this.loaded.selectedEventIndex = nowIndex;
|
||||
this.loaded.selectedEventId = currentEvent?.id || null;
|
||||
this.loaded.numEvents = timedEvents.length;
|
||||
|
||||
// titles
|
||||
this._loadThisTitles(currentEvent, 'now-private');
|
||||
@@ -187,28 +176,33 @@ export class EventLoader {
|
||||
getLoaded() {
|
||||
return {
|
||||
loadedEvent: this.loadedEvent,
|
||||
selectedEventIndex: this.selectedEventIndex,
|
||||
selectedEventId: this.selectedEventId,
|
||||
selectedPublicEventId: this.selectedPublicEventId,
|
||||
nextEventId: this.nextEventId,
|
||||
nextPublicEventId: this.nextPublicEventId,
|
||||
numEvents: this.numEvents,
|
||||
loaded: this.loaded,
|
||||
titles: this.titles,
|
||||
titlesPublic: this.titlesPublic,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces event loader to update the event count
|
||||
*/
|
||||
updateNumEvents() {
|
||||
this.loaded.numEvents = EventLoader.getPlayableEvents().length;
|
||||
eventStore.set('loaded', this.loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets instance state
|
||||
*/
|
||||
reset(emit?: boolean) {
|
||||
reset(emit = true) {
|
||||
this.loadedEvent = null;
|
||||
this.selectedEventIndex = null;
|
||||
this.selectedEventId = null;
|
||||
this.selectedPublicEventId = null;
|
||||
this.nextEventId = null;
|
||||
this.nextPublicEventId = null;
|
||||
this.numEvents = null;
|
||||
this.loaded = {
|
||||
selectedEventIndex: null,
|
||||
selectedEventId: null,
|
||||
selectedPublicEventId: null,
|
||||
nextEventId: null,
|
||||
nextPublicEventId: null,
|
||||
numEvents: EventLoader.getPlayableEvents().length,
|
||||
};
|
||||
this.titles = {
|
||||
titleNow: null,
|
||||
subtitleNow: null,
|
||||
@@ -250,9 +244,9 @@ export class EventLoader {
|
||||
|
||||
// we know some stuff now
|
||||
this.loadedEvent = event;
|
||||
this.selectedEventIndex = eventIndex;
|
||||
this.selectedEventId = event.id;
|
||||
this.numEvents = timedEvents.length;
|
||||
this.loaded.selectedEventIndex = eventIndex;
|
||||
this.loaded.selectedEventId = event.id;
|
||||
this.loaded.numEvents = timedEvents.length;
|
||||
// this.nextEventId = playableEvents[eventIndex + 1].id;
|
||||
this._loadTitlesNow(event, playableEvents);
|
||||
this._loadTitlesNext(playableEvents);
|
||||
@@ -266,6 +260,7 @@ export class EventLoader {
|
||||
* Handle side effects from event loading
|
||||
*/
|
||||
private _loadEvent() {
|
||||
eventStore.set('loaded', this.loaded);
|
||||
eventStore.set('titles', this.titles);
|
||||
eventStore.set('titlesPublic', this.titlesPublic);
|
||||
}
|
||||
@@ -288,13 +283,13 @@ export class EventLoader {
|
||||
this.titlesPublic.titleNow = null;
|
||||
this.titlesPublic.subtitleNow = null;
|
||||
this.titlesPublic.presenterNow = null;
|
||||
this.selectedPublicEventId = null;
|
||||
this.loaded.selectedPublicEventId = null;
|
||||
|
||||
// if there is nothing before, return
|
||||
if (this.selectedEventIndex === 0) return;
|
||||
if (this.loaded.selectedEventIndex === 0) return;
|
||||
|
||||
// iterate backwards to find it
|
||||
for (let i = this.selectedEventIndex; i >= 0; i--) {
|
||||
for (let i = this.loaded.selectedEventIndex; i >= 0; i--) {
|
||||
if (rundown[i].isPublic) {
|
||||
this._loadThisTitles(rundown[i], 'now-public');
|
||||
break;
|
||||
@@ -309,27 +304,27 @@ export class EventLoader {
|
||||
*/
|
||||
private _loadTitlesNext(rundown) {
|
||||
// maybe there is nothing to load
|
||||
if (this.selectedEventIndex === null) return;
|
||||
if (this.loaded.selectedEventIndex === null) return;
|
||||
|
||||
// assume there is no next event
|
||||
this.titles.titleNext = null;
|
||||
this.titles.subtitleNext = null;
|
||||
this.titles.presenterNext = null;
|
||||
this.titles.noteNext = null;
|
||||
this.nextEventId = null;
|
||||
this.loaded.nextEventId = null;
|
||||
|
||||
this.titlesPublic.titleNext = null;
|
||||
this.titlesPublic.subtitleNext = null;
|
||||
this.titlesPublic.presenterNext = null;
|
||||
this.nextPublicEventId = null;
|
||||
this.loaded.nextPublicEventId = null;
|
||||
|
||||
const numEvents = rundown.length;
|
||||
|
||||
if (this.selectedEventIndex < numEvents - 1) {
|
||||
if (this.loaded.selectedEventIndex < numEvents - 1) {
|
||||
let nextPublic = false;
|
||||
let nextPrivate = false;
|
||||
|
||||
for (let i = this.selectedEventIndex + 1; i < numEvents; i++) {
|
||||
for (let i = this.loaded.selectedEventIndex + 1; i < numEvents; i++) {
|
||||
// if we have not set private
|
||||
if (!nextPrivate) {
|
||||
this._loadThisTitles(rundown[i], 'next-private');
|
||||
@@ -367,14 +362,14 @@ export class EventLoader {
|
||||
this.titlesPublic.subtitleNow = event.subtitle;
|
||||
this.titlesPublic.presenterNow = event.presenter;
|
||||
this.titlesPublic.noteNow = event.note;
|
||||
this.selectedPublicEventId = event.id;
|
||||
this.loaded.selectedPublicEventId = event.id;
|
||||
|
||||
// private
|
||||
this.titles.titleNow = event.title;
|
||||
this.titles.subtitleNow = event.subtitle;
|
||||
this.titles.presenterNow = event.presenter;
|
||||
this.titles.noteNow = event.note;
|
||||
this.selectedEventId = event.id;
|
||||
this.loaded.selectedEventId = event.id;
|
||||
break;
|
||||
|
||||
case 'now-public':
|
||||
@@ -382,7 +377,7 @@ export class EventLoader {
|
||||
this.titlesPublic.subtitleNow = event.subtitle;
|
||||
this.titlesPublic.presenterNow = event.presenter;
|
||||
this.titlesPublic.noteNow = event.note;
|
||||
this.selectedPublicEventId = event.id;
|
||||
this.loaded.selectedPublicEventId = event.id;
|
||||
break;
|
||||
|
||||
case 'now-private':
|
||||
@@ -390,7 +385,7 @@ export class EventLoader {
|
||||
this.titles.subtitleNow = event.subtitle;
|
||||
this.titles.presenterNow = event.presenter;
|
||||
this.titles.noteNow = event.note;
|
||||
this.selectedEventId = event.id;
|
||||
this.loaded.selectedEventId = event.id;
|
||||
break;
|
||||
|
||||
// next, load to both public and private
|
||||
@@ -400,14 +395,14 @@ export class EventLoader {
|
||||
this.titlesPublic.subtitleNext = event.subtitle;
|
||||
this.titlesPublic.presenterNext = event.presenter;
|
||||
this.titlesPublic.noteNext = event.note;
|
||||
this.nextPublicEventId = event.id;
|
||||
this.loaded.nextPublicEventId = event.id;
|
||||
|
||||
// private
|
||||
this.titles.titleNext = event.title;
|
||||
this.titles.subtitleNext = event.subtitle;
|
||||
this.titles.presenterNext = event.presenter;
|
||||
this.titles.noteNext = event.note;
|
||||
this.nextEventId = event.id;
|
||||
this.loaded.nextEventId = event.id;
|
||||
break;
|
||||
|
||||
case 'next-public':
|
||||
@@ -415,7 +410,7 @@ export class EventLoader {
|
||||
this.titlesPublic.subtitleNext = event.subtitle;
|
||||
this.titlesPublic.presenterNext = event.presenter;
|
||||
this.titlesPublic.noteNext = event.note;
|
||||
this.nextPublicEventId = event.id;
|
||||
this.loaded.nextPublicEventId = event.id;
|
||||
break;
|
||||
|
||||
case 'next-private':
|
||||
@@ -423,7 +418,7 @@ export class EventLoader {
|
||||
this.titles.subtitleNext = event.subtitle;
|
||||
this.titles.presenterNext = event.presenter;
|
||||
this.titles.noteNext = event.note;
|
||||
this.nextEventId = event.id;
|
||||
this.loaded.nextEventId = event.id;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import { MessageControl } from 'ontime-types';
|
||||
|
||||
import { eventStore } from '../../stores/EventStore.js';
|
||||
|
||||
let instance;
|
||||
|
||||
class MessageService {
|
||||
messages: MessageControl;
|
||||
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.messages = {
|
||||
presenter: {
|
||||
text: '',
|
||||
visible: false,
|
||||
},
|
||||
public: {
|
||||
text: '',
|
||||
visible: false,
|
||||
},
|
||||
lower: {
|
||||
text: '',
|
||||
visible: false,
|
||||
},
|
||||
};
|
||||
this.onAir = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message on stage timer screen
|
||||
*/
|
||||
setTimerText(payload: string) {
|
||||
this.messages.presenter.text = payload;
|
||||
eventStore.set('feat-messagecontrol', { messages: this.messages });
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message visibility on stage timer screen
|
||||
*/
|
||||
setTimerVisibility(status: boolean) {
|
||||
this.messages.presenter.visible = status;
|
||||
eventStore.set('feat-messagecontrol', { messages: this.messages });
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message on public screen
|
||||
*/
|
||||
setPublicText(payload: string) {
|
||||
this.messages.public.text = payload;
|
||||
eventStore.set('feat-messagecontrol', { messages: this.messages });
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message visibility on public screen
|
||||
*/
|
||||
setPublicVisibility(status: boolean) {
|
||||
this.messages.public.visible = status;
|
||||
eventStore.set('feat-messagecontrol', { messages: this.messages });
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message on lower third screen
|
||||
*/
|
||||
setLowerText(payload: string) {
|
||||
this.messages.lower.text = payload;
|
||||
eventStore.set('feat-messagecontrol', { messages: this.messages });
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message visibility on lower third screen
|
||||
*/
|
||||
setLowerVisibility(status: boolean) {
|
||||
this.messages.lower.visible = status;
|
||||
eventStore.set('feat-messagecontrol', { messages: this.messages });
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description set state of onAir
|
||||
*/
|
||||
setOnAir(status: boolean) {
|
||||
this.onAir = status;
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns feature data
|
||||
*/
|
||||
getAll() {
|
||||
return {
|
||||
messages: this.messages,
|
||||
onAir: this.onAir,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const messageManager = new MessageService();
|
||||
@@ -1,446 +0,0 @@
|
||||
import { Server } from 'socket.io';
|
||||
import { generateId } from 'ontime-utils';
|
||||
|
||||
import getRandomName from '../../utils/getRandomName.js';
|
||||
import { stringFromMillis } from '../../utils/time.js';
|
||||
import { messageManager } from '../message-manager/MessageManager.js';
|
||||
import { PlaybackService } from '../../services/PlaybackService.js';
|
||||
|
||||
import { eventTimer } from '../../services/TimerService.js';
|
||||
import { EventLoader, eventLoader } from '../event-loader/EventLoader.js';
|
||||
import { clock } from '../../services/Clock.js';
|
||||
|
||||
class SocketController {
|
||||
constructor() {
|
||||
this.numClients = 0;
|
||||
this.messageStack = [];
|
||||
this._MAX_MESSAGES = 100;
|
||||
this._clientNames = {};
|
||||
this.socket = null;
|
||||
}
|
||||
|
||||
initServer(httpServer) {
|
||||
this.socket = new Server(httpServer, {
|
||||
cors: {
|
||||
origin: '*',
|
||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||
preflightContinue: false,
|
||||
optionsSuccessStatus: 204,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
startListener() {
|
||||
this._socketMessageHandler();
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
this.info('SERVER', 'Shutting down ontime');
|
||||
if (this.socket) {
|
||||
this.info('TX', '... Closing socket server');
|
||||
this.socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle socket io connections
|
||||
* @private
|
||||
*/
|
||||
_socketMessageHandler() {
|
||||
this.socket.on('connection', (socket) => {
|
||||
/*******************************/
|
||||
/*** HANDLE NEW CONNECTION ***/
|
||||
/*** --------------------- ***/
|
||||
/*******************************/
|
||||
// keep track of connections
|
||||
this.numClients++;
|
||||
this._clientNames[socket.id] = getRandomName();
|
||||
const message = `${this.numClients} Clients with new connection: ${this._clientNames[socket.id]}`;
|
||||
this.info('CLIENT', message);
|
||||
|
||||
// Todo: review in favour of features
|
||||
// send state
|
||||
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
|
||||
*/
|
||||
socket.on('disconnect', () => {
|
||||
this.numClients--;
|
||||
const message = `${this.numClients} Clients with disconnection: ${this._clientNames[socket.id]}`;
|
||||
delete this._clientNames[socket.id];
|
||||
this.info('CLIENT', message);
|
||||
});
|
||||
|
||||
/**
|
||||
* @description utility for renaming a user
|
||||
*/
|
||||
socket.on('rename-client', (newName) => {
|
||||
if (newName) {
|
||||
const previousName = this._clientNames[socket.id];
|
||||
this._clientNames[socket.id] = newName;
|
||||
this.info('CLIENT', `Client ${previousName} renamed to ${newName}`);
|
||||
}
|
||||
});
|
||||
|
||||
/***************************************/
|
||||
/*** TIMER STATE GETTERS / SETTERS ***/
|
||||
/*** ------- WEBSOCKET API ------- ***/
|
||||
/*** ----------------------------- ***/
|
||||
/***************************************/
|
||||
|
||||
/*******************************************/
|
||||
socket.on('ontime-test', () => {
|
||||
socket.emit('hello', socket.id);
|
||||
});
|
||||
|
||||
socket.on('set-start', () => {
|
||||
PlaybackService.start();
|
||||
});
|
||||
|
||||
socket.on('set-startid', (data) => {
|
||||
if (data) {
|
||||
PlaybackService.startById(data);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('set-startindex', (data) => {
|
||||
const eventIndex = Number(data);
|
||||
if (!isNaN(eventIndex)) {
|
||||
PlaybackService.startByIndex(eventIndex);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('set-loadid', (data) => {
|
||||
if (data) {
|
||||
PlaybackService.loadById(data);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('set-loadindex', (data) => {
|
||||
const eventIndex = Number(data);
|
||||
if (!isNaN(eventIndex)) {
|
||||
PlaybackService.loadByIndex(eventIndex - 1);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('set-pause', () => {
|
||||
PlaybackService.pause();
|
||||
});
|
||||
|
||||
socket.on('set-stop', () => {
|
||||
PlaybackService.stop();
|
||||
});
|
||||
|
||||
socket.on('set-reload', () => {
|
||||
PlaybackService.reload();
|
||||
});
|
||||
|
||||
socket.on('set-previous', () => {
|
||||
PlaybackService.loadPrevious();
|
||||
});
|
||||
|
||||
socket.on('set-next', () => {
|
||||
PlaybackService.loadNext();
|
||||
});
|
||||
|
||||
socket.on('set-roll', () => {
|
||||
PlaybackService.roll();
|
||||
});
|
||||
|
||||
socket.on('set-delay', (data) => {
|
||||
const delayTime = Number(data);
|
||||
if (!isNaN(delayTime)) {
|
||||
PlaybackService.setDelay(delayTime);
|
||||
}
|
||||
});
|
||||
|
||||
/*******************************************/
|
||||
// general playback state, useful for external sync
|
||||
// Todo: add delayed value (will come from rundownService)
|
||||
socket.on('ontime-poll', () => {
|
||||
const timerPoll = eventTimer.timer;
|
||||
const isDelayed = false;
|
||||
const colour = '';
|
||||
socket.emit('ontime-poll', { isDelayed, colour, ...timerPoll });
|
||||
});
|
||||
|
||||
/*******************************************/
|
||||
socket.on('get-playback', () => {
|
||||
socket.emit('playback', eventTimer.playback);
|
||||
});
|
||||
|
||||
socket.on('get-onAir', () => {
|
||||
socket.emit('onAir', messageManager.onAir);
|
||||
});
|
||||
|
||||
/*******************************************/
|
||||
socket.on('get-selected', () => {
|
||||
socket.emit('selected', {
|
||||
id: eventLoader.selectedEventId,
|
||||
index: eventLoader.selectedEventIndex,
|
||||
total: eventLoader.numEvents,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('get-titles', () => {
|
||||
socket.emit('titles', eventLoader.titles);
|
||||
});
|
||||
|
||||
socket.on('get-publictitles', () => {
|
||||
socket.emit('publictitles', eventLoader.titlesPublic);
|
||||
});
|
||||
|
||||
/***********************************/
|
||||
/*** MESSAGE GETTERS / SETTERS ***/
|
||||
/*** ------------------------- ***/
|
||||
/***********************************/
|
||||
|
||||
// On Air
|
||||
socket.on('set-onAir', (data) => {
|
||||
if (typeof data === 'boolean') {
|
||||
try {
|
||||
const featureData = messageManager.setOnAir(data);
|
||||
this.info('PLAYBACK', featureData.onAir ? 'Going On Air' : 'Going Off Air');
|
||||
} catch (error) {
|
||||
this.error('RX', `Failed to parse message ${data} : ${error}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Presenter message
|
||||
socket.on('set-timer-message-text', (data) => {
|
||||
if (typeof data !== 'string') {
|
||||
return;
|
||||
}
|
||||
messageManager.setTimerText(data);
|
||||
});
|
||||
|
||||
socket.on('set-timer-message-visible', (data) => {
|
||||
if (typeof data !== 'boolean') {
|
||||
return;
|
||||
}
|
||||
messageManager.setTimerVisibility(data);
|
||||
});
|
||||
|
||||
/*******************************************/
|
||||
// Public message
|
||||
socket.on('set-public-message-text', (data) => {
|
||||
if (typeof data !== 'string') {
|
||||
return;
|
||||
}
|
||||
messageManager.setPublicText(data);
|
||||
});
|
||||
|
||||
socket.on('set-public-message-visible', (data) => {
|
||||
if (typeof data !== 'boolean') {
|
||||
return;
|
||||
}
|
||||
messageManager.setPublicVisibility(data);
|
||||
});
|
||||
|
||||
/*******************************************/
|
||||
// Lower third message
|
||||
socket.on('set-lower-message-text', (data) => {
|
||||
if (typeof data !== 'string') {
|
||||
return;
|
||||
}
|
||||
messageManager.setLowerText(data);
|
||||
});
|
||||
|
||||
socket.on('set-lower-message-visible', (data) => {
|
||||
if (typeof data !== 'boolean') {
|
||||
return;
|
||||
}
|
||||
messageManager.setLowerVisibility(data);
|
||||
});
|
||||
|
||||
/* MOLECULAR ENDPOINTS
|
||||
* =====================
|
||||
* 1. RUNDOWN
|
||||
* 2. MESSAGE CONTROL
|
||||
* 3. PLAYBACK CONTROL
|
||||
* 4. INFO
|
||||
* 5. CUE SHEET
|
||||
* 6. TIMER OBJECT
|
||||
* */
|
||||
|
||||
// 1. RUNDOWN
|
||||
socket.on('get-feat-rundown', () => {
|
||||
this.broadcastFeatureRundown();
|
||||
});
|
||||
|
||||
// 2. MESSAGE CONTROL
|
||||
socket.on('get-feat-messagecontrol', () => {
|
||||
this.broadcastFeatureMessageControl();
|
||||
});
|
||||
|
||||
// 3. PLAYBACK CONTROL
|
||||
socket.on('get-feat-playbackcontrol', () => {
|
||||
this.broadcastFeaturePlaybackControl();
|
||||
});
|
||||
|
||||
// 4. INFO
|
||||
socket.on('get-feat-info', () => {
|
||||
this.broadcastFeatureInfo();
|
||||
});
|
||||
|
||||
// 5. CUE SHEET
|
||||
socket.on('get-feat-cuesheet', () => {
|
||||
this.broadcastFeatureCuesheet();
|
||||
});
|
||||
|
||||
// 6. TIMER
|
||||
socket.on('get-timer', () => {
|
||||
// TODO: Not ideal workaround
|
||||
socket.emit('timer', eventTimer.timer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
send(topic, payload) {
|
||||
this.socket?.emit(topic, payload);
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
/**
|
||||
* Logger logic
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utility method, sends message and pushes into stack
|
||||
* @param {string} level
|
||||
* @param {string} origin
|
||||
* @param {string} text
|
||||
*/
|
||||
_push(level, origin, text) {
|
||||
const logMessage = {
|
||||
id: generateId(),
|
||||
level,
|
||||
origin,
|
||||
text,
|
||||
time: stringFromMillis(clock.getSystemTime() || 0),
|
||||
};
|
||||
|
||||
this.messageStack.unshift(logMessage);
|
||||
this.socket?.emit('logger', logMessage);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log(`[${logMessage.level}] \t ${logMessage.origin} \t ${logMessage.text}`);
|
||||
}
|
||||
|
||||
if (this.messageStack.length > this._MAX_MESSAGES) {
|
||||
this.messageStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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('feat-messagecontrol', 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);
|
||||
}
|
||||
|
||||
// TODO: ouch, services should update the store
|
||||
// make middleware to maintain the features OR remove the feature endpoints
|
||||
broadcastState() {
|
||||
this.broadcastFeatureRundown();
|
||||
this.broadcastFeatureMessageControl();
|
||||
this.broadcastFeaturePlaybackControl();
|
||||
this.broadcastFeatureInfo();
|
||||
this.broadcastFeatureCuesheet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message with level LOG
|
||||
* @param {string} origin
|
||||
* @param {string} text
|
||||
*/
|
||||
info(origin, text) {
|
||||
this._push('INFO', origin, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message with level WARN
|
||||
* @param {string} origin
|
||||
* @param {string} text
|
||||
*/
|
||||
warning(origin, text) {
|
||||
this._push('WARN', origin, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message with level ERROR
|
||||
* @param {string} origin
|
||||
* @param {string} text
|
||||
*/
|
||||
error(origin, text) {
|
||||
this._push('ERROR', origin, text);
|
||||
}
|
||||
}
|
||||
|
||||
export const socketProvider = new SocketController();
|
||||
Reference in New Issue
Block a user