mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-13 19:33:46 +00:00
ff5735fe36
* refactor: extract jest config * refactor: rename params in reoder endpoint * refactor: avoid app exports * refactor: small code smells * refactor: run development server * refactor: extract rundown service and event loader logic * refactor: rename events > rundown * refactor: code style * fix: handle external change of title * refactor: cleanup dictionary * refactor: rename MessageService * refactor: use eventID for operations * refactor: migrate OSC controller to service * refactor: migrate Socket controller to service * refactor: migrate HTTP controller to service * refactor: extract logic into discrete services * refactor: remove rundown from event timer * refactor: remove duplicate * refactor: remove unused * chore: update tests
772 lines
20 KiB
JavaScript
772 lines
20 KiB
JavaScript
import { Timer } from './Timer.js';
|
|
import { DAY_TO_MS, replacePlaceholder, updateRoll } from './classUtils.js';
|
|
import { OSCIntegration } from './integrations/Osc.js';
|
|
import { HTTPIntegration } from './integrations/Http.js';
|
|
import { cleanURL } from '../../utils/url.js';
|
|
import { EventLoader, eventLoader } from '../event-loader/EventLoader.js';
|
|
import { DataProvider } from '../data-provider/DataProvider.js';
|
|
|
|
/*
|
|
* Class EventTimer adds functions specific to APP
|
|
* @extends Timer
|
|
*/
|
|
export class EventTimer extends Timer {
|
|
/**
|
|
* Instantiates an event timer object
|
|
* @param {object} socket
|
|
* @param {object} timerConfig
|
|
* @param {object} [oscConfig]
|
|
* @param {object} [httpConfig]
|
|
*/
|
|
constructor(socket, timerConfig, oscConfig, httpConfig) {
|
|
// call super constructor
|
|
super();
|
|
|
|
this.cycleState = {
|
|
/* idle: before it is initialised */
|
|
idle: 'idle',
|
|
/* onLoad: when a new event is loaded */
|
|
onLoad: 'onLoad',
|
|
/* armed: when a new event is loaded but hasn't started */
|
|
armed: 'armed',
|
|
onStart: 'onStart',
|
|
/* update: every update call cycle (1 x second) */
|
|
onUpdate: 'onUpdate',
|
|
onPause: 'onPause',
|
|
onStop: 'onStop',
|
|
onFinish: 'onFinish',
|
|
};
|
|
this.ontimeCycle = 'idle';
|
|
this.prevCycle = null;
|
|
|
|
// Socket Object
|
|
this.socket = socket;
|
|
|
|
// OSC Object
|
|
this.osc = null;
|
|
|
|
// HTTP Client Object
|
|
this.http = null;
|
|
|
|
// call general title reset
|
|
this._resetSelection();
|
|
|
|
// set recurrent emits
|
|
this._interval = setInterval(() => this.runCycle(), timerConfig?.refresh || 1000);
|
|
|
|
if (oscConfig != null) {
|
|
this._initOscClient(oscConfig);
|
|
}
|
|
|
|
if (httpConfig != null) {
|
|
this._initHTTPClient(httpConfig);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @description Shutdown process
|
|
*/
|
|
shutdown() {
|
|
clearInterval(this._interval);
|
|
if (this.osc != null) {
|
|
this.socket.info('TX', '... Closing OSC Client');
|
|
this.osc.shutdown();
|
|
}
|
|
if (this.http != null) {
|
|
this.socket.info('TX', '... Closing HTTP Client');
|
|
this.http.shutdown();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialises OSC Integration object
|
|
* @param {object} oscConfig
|
|
* @private
|
|
*/
|
|
_initOscClient(oscConfig) {
|
|
this.osc = new OSCIntegration();
|
|
const r = this.osc.init(oscConfig);
|
|
r.success ? this.socket.info('TX', r.message) : this.socket.error('TX', r.message);
|
|
}
|
|
|
|
/**
|
|
* Initialises HTTP Integration object
|
|
* @param {object} httpConfig
|
|
* @private
|
|
*/
|
|
_initHTTPClient(httpConfig) {
|
|
this.socket.info('TX', `Initialise HTTP Client on port`);
|
|
this.http = new HTTPIntegration();
|
|
this.http.init(httpConfig);
|
|
this.httpMessages = httpConfig.messages;
|
|
}
|
|
|
|
/**
|
|
* Sends time object over websockets
|
|
*/
|
|
broadcastTimer() {
|
|
// through websockets
|
|
this.socket.send('timer', this.getTimeObject());
|
|
}
|
|
|
|
/**
|
|
* @description Broadcast timer data
|
|
* @private
|
|
*/
|
|
_broadcastFeatureTimer() {
|
|
const featureData = {
|
|
clock: this.clock,
|
|
current: this.current,
|
|
secondaryTimer: this.secondaryTimer,
|
|
duration: this.duration,
|
|
expectedFinish: this._getExpectedFinish(),
|
|
startedAt: this._startedAt,
|
|
};
|
|
this.socket.send('ontime-timer', featureData);
|
|
}
|
|
|
|
/**
|
|
* @description Broadcast data for Event List feature
|
|
* @private
|
|
*/
|
|
_broadcastFeatureRundown() {
|
|
const featureData = {
|
|
selectedEventId: this.selectedEventId,
|
|
nextEventId: this.nextEventId,
|
|
playback: this.state,
|
|
};
|
|
this.socket.send('feat-rundown', featureData);
|
|
}
|
|
|
|
/**
|
|
* @description Broadcast data for Playback Control feature
|
|
* @private
|
|
*/
|
|
_broadcastFeaturePlaybackControl() {
|
|
const numEvents = DataProvider.getNumEvents();
|
|
const featureData = {
|
|
playback: this.state,
|
|
selectedEventId: this.selectedEventId,
|
|
numEvents: numEvents,
|
|
};
|
|
this.socket.send('feat-playbackcontrol', featureData);
|
|
}
|
|
|
|
/**
|
|
* @description Broadcast data for Info feature
|
|
* @private
|
|
*/
|
|
_broadcastFeatureInfo() {
|
|
const numEvents = DataProvider.getNumEvents();
|
|
const featureData = {
|
|
titles: this.titles,
|
|
playback: this.state,
|
|
selectedEventId: this.selectedEventId,
|
|
selectedEventIndex: this.selectedEventIndex,
|
|
numEvents: numEvents,
|
|
};
|
|
this.socket.send('feat-info', featureData);
|
|
}
|
|
|
|
_broadcastFeatureCuesheet() {
|
|
const numEvents = DataProvider.getNumEvents();
|
|
const featureData = {
|
|
playback: this.state,
|
|
selectedEventId: this.selectedEventId,
|
|
selectedEventIndex: this.selectedEventIndex,
|
|
numEvents: numEvents,
|
|
titleNow: this.titles.titleNow,
|
|
};
|
|
this.socket.send('feat-cuesheet', featureData);
|
|
}
|
|
|
|
/**
|
|
* Broadcasts complete object state
|
|
*/
|
|
broadcastState() {
|
|
// feature sync
|
|
this._broadcastFeatureRundown();
|
|
this._broadcastFeaturePlaybackControl();
|
|
this._broadcastFeatureInfo();
|
|
this._broadcastFeatureCuesheet();
|
|
this._broadcastFeatureTimer();
|
|
|
|
this.broadcastTimer();
|
|
this.socket.send('playstate', this.state);
|
|
this.socket.send('selected-id', this.selectedEventId);
|
|
this.socket.send('next-id', this.nextEventId);
|
|
this.socket.send('publicselected-id', this.selectedPublicEventId);
|
|
this.socket.send('publicnext-id', this.nextPublicEventId);
|
|
this.socket.send('titles', this.titles);
|
|
this.socket.send('publictitles', this.titlesPublic);
|
|
}
|
|
|
|
/**
|
|
* @description State machine checks what actions need to
|
|
* happen at every app cycle
|
|
*/
|
|
runCycle() {
|
|
const h = this.httpMessages?.messages;
|
|
let httpMessage = null;
|
|
|
|
switch (this.ontimeCycle) {
|
|
case 'idle':
|
|
break;
|
|
case 'armed':
|
|
// if we come from roll, see if we can start
|
|
if (this.state === 'roll') {
|
|
this.update();
|
|
}
|
|
break;
|
|
case 'onLoad':
|
|
// check integrations - http
|
|
if (h?.onLoad?.enabled) {
|
|
if (h?.onLoad?.url != null || h?.onLoad?.url !== '') {
|
|
httpMessage = h?.onLoad?.url;
|
|
}
|
|
}
|
|
|
|
// update lifecycle: armed
|
|
this.ontimeCycle = this.cycleState.armed;
|
|
break;
|
|
case 'onStart':
|
|
// send OSC if there is something running
|
|
// _finish at is only set when an event is loaded
|
|
if (this._finishAt > 0) {
|
|
this.sendOsc(this.osc.implemented.play);
|
|
this.sendOsc(this.osc.implemented.eventNumber, this.selectedEventIndex || 0);
|
|
}
|
|
// check integrations - http
|
|
if (h?.onLoad?.enabled) {
|
|
if (h?.onLoad?.url != null || h?.onStart?.url !== '') {
|
|
httpMessage = h?.onStart?.url;
|
|
}
|
|
}
|
|
|
|
// update lifecycle: onUpdate
|
|
this.ontimeCycle = this.cycleState.onUpdate;
|
|
break;
|
|
case 'onUpdate':
|
|
// call update
|
|
this.update();
|
|
// through OSC, only if running
|
|
if (this.state === 'start' || this.state === 'roll') {
|
|
if (this.current != null && this.secondaryTimer == null) {
|
|
this.sendOsc(this.osc.implemented.time, this.timeTag);
|
|
this.sendOsc(this.osc.implemented.overtime, this.current > 0 ? 0 : 1);
|
|
this.sendOsc(this.osc.implemented.title, this.titles?.titleNow || '');
|
|
this.sendOsc(this.osc.implemented.presenter, this.titles?.presenterNow || '');
|
|
}
|
|
}
|
|
|
|
// check integrations - http
|
|
if (h?.onLoad?.enabled) {
|
|
if (h?.onLoad?.url != null || h?.onUpdate?.url !== '') {
|
|
httpMessage = h?.onUpdate?.url;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case 'onPause':
|
|
// send OSC
|
|
if (this.prevCycle === this.cycleState.onUpdate) {
|
|
this.sendOsc(this.osc.implemented.pause);
|
|
}
|
|
|
|
// check integrations - http
|
|
if (h?.onLoad?.enabled) {
|
|
if (h?.onLoad?.url != null || h?.onPause?.url !== '') {
|
|
httpMessage = h?.onPause?.url;
|
|
}
|
|
}
|
|
|
|
// update lifecycle: armed
|
|
this.ontimeCycle = this.cycleState.armed;
|
|
|
|
break;
|
|
case 'onStop':
|
|
// send OSC if something was actually stopped
|
|
if (this.prevCycle === this.cycleState.onUpdate) {
|
|
this.sendOsc(this.osc.implemented.stop);
|
|
}
|
|
|
|
// check integrations - http
|
|
if (h?.onLoad?.enabled) {
|
|
if (h?.onLoad?.url != null || h?.onStop?.url !== '') {
|
|
httpMessage = h?.onStop?.url;
|
|
}
|
|
}
|
|
|
|
// update lifecycle: idle
|
|
this.ontimeCycle = this.cycleState.idle;
|
|
break;
|
|
case 'onFinish':
|
|
// finished an event
|
|
this.sendOsc(this.osc.implemented.finished);
|
|
|
|
// check integrations - http
|
|
if (h?.onLoad?.enabled) {
|
|
if (h?.onLoad?.url != null || h?.onFinish?.url !== '') {
|
|
httpMessage = h?.onFinish?.url;
|
|
}
|
|
}
|
|
|
|
// update lifecycle: onUpdate
|
|
this.ontimeCycle = this.cycleState.onUpdate;
|
|
break;
|
|
default:
|
|
this.socket.error('SERVER', `Unhandled cycle: ${this.ontimeCycle}`);
|
|
}
|
|
|
|
// send http message if any
|
|
if (httpMessage != null) {
|
|
const v = {
|
|
$timer: this.timeTag,
|
|
$title: this.titles.titleNow,
|
|
$presenter: this.titles.presenterNow,
|
|
$subtitle: this.titles.subtitleNow,
|
|
'$next-title': this.titles.titleNext,
|
|
'$next-presenter': this.titles.presenterNext,
|
|
'$next-subtitle': this.titles.subtitleNext,
|
|
};
|
|
const m = cleanURL(replacePlaceholder(httpMessage, v));
|
|
this.http.send(m);
|
|
}
|
|
|
|
// update
|
|
this.update();
|
|
this.broadcastState();
|
|
|
|
// reset cycle
|
|
this.prevCycle = this.ontimeCycle;
|
|
}
|
|
|
|
update() {
|
|
// if there is nothing selected, update clock
|
|
this.clock = Timer.getCurrentTime();
|
|
this._broadcastFeatureTimer();
|
|
this.broadcastTimer();
|
|
|
|
// if we are not updating, send the timers
|
|
if (this.ontimeCycle !== this.cycleState.onUpdate) {
|
|
this.socket.send('timer', this.getTimeObject());
|
|
}
|
|
|
|
// Have we skipped onStart?
|
|
if (this.state === 'start' || this.state === 'roll') {
|
|
if (this.ontimeCycle === this.cycleState.armed) {
|
|
// update lifecycle: onStart
|
|
this.ontimeCycle = this.cycleState.onStart;
|
|
this.runCycle();
|
|
}
|
|
}
|
|
|
|
// update default functions
|
|
super.update();
|
|
|
|
if (this._finishedFlag) {
|
|
// update lifecycle: onFinish and call cycle
|
|
|
|
this.ontimeCycle = this.cycleState.onFinish;
|
|
this._finishedFlag = false;
|
|
this.runCycle();
|
|
}
|
|
|
|
// only implement roll here, rest implemented in super
|
|
if (this.state === 'roll') {
|
|
const u = {
|
|
selectedEventId: this.selectedEventId,
|
|
current: this.current,
|
|
// safeguard on midnight rollover
|
|
_finishAt: this._finishAt >= this._startedAt ? this._finishAt : this._finishAt + DAY_TO_MS,
|
|
clock: this.clock,
|
|
secondaryTimer: this.secondaryTimer,
|
|
_secondaryTarget: this._secondaryTarget,
|
|
};
|
|
|
|
const { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished } = updateRoll(u);
|
|
|
|
this.current = updatedTimer;
|
|
this.secondaryTimer = updatedSecondaryTimer;
|
|
|
|
if (isFinished) {
|
|
// update lifecycle: onFinish
|
|
this.ontimeCycle = this.cycleState.onFinish;
|
|
this.runCycle();
|
|
}
|
|
|
|
if (doRollLoad) {
|
|
this.rollLoad();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} eventId
|
|
*/
|
|
syncLoaded(eventId) {
|
|
if (this.state === 'roll') {
|
|
this.rollLoad();
|
|
} else {
|
|
const event = EventLoader.getEventWithId(eventId);
|
|
this.loadEvent(event, 'reload');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads a given event by index
|
|
* @typedef ('load'|'reload') loadEventOptions
|
|
* @param {object} event
|
|
* @param {string} [type='load'] - 'load' or 'reload', whether we are keeping running time
|
|
*/
|
|
loadEvent(event, type = 'load') {
|
|
const loadedData = eventLoader.loadById(event.id);
|
|
if (!loadedData) {
|
|
return;
|
|
}
|
|
|
|
const { loadedEvent, loadedEventIndex, loadedEventId, titles, titlesPublic } = loadedData;
|
|
|
|
const start = loadedEvent.timeStart || 0;
|
|
let end = loadedEvent.timeEnd || 0;
|
|
|
|
// in case the end is earlier than start, we assume is the day after
|
|
if (end < start) {
|
|
end += DAY_TO_MS;
|
|
}
|
|
|
|
this.duration = end - start;
|
|
this.selectedEventIndex = loadedEventIndex;
|
|
this.selectedEventId = loadedEventId;
|
|
if (type === 'load') {
|
|
this._resetTimers();
|
|
this.current = this.duration;
|
|
} else {
|
|
const now = Timer.getCurrentTime();
|
|
const elapsed = this.getElapsed();
|
|
this._finishAt = now + (this.duration - elapsed);
|
|
}
|
|
|
|
this.titles = titles;
|
|
this.titlesPublic = titlesPublic;
|
|
|
|
// update lifecycle: onLoad
|
|
this.ontimeCycle = this.cycleState.onLoad;
|
|
}
|
|
|
|
/**
|
|
* @description resets selected event data
|
|
* @private
|
|
*/
|
|
_resetSelection() {
|
|
this.titles = {
|
|
titleNow: null,
|
|
subtitleNow: null,
|
|
presenterNow: null,
|
|
noteNow: null,
|
|
titleNext: null,
|
|
subtitleNext: null,
|
|
presenterNext: null,
|
|
noteNext: null,
|
|
};
|
|
|
|
this.titlesPublic = {
|
|
titleNow: null,
|
|
subtitleNow: null,
|
|
presenterNow: null,
|
|
titleNext: null,
|
|
subtitleNext: null,
|
|
presenterNext: null,
|
|
};
|
|
|
|
this.selectedEventIndex = null;
|
|
this.selectedEventId = null;
|
|
this.nextEventId = null;
|
|
this.selectedPublicEventId = null;
|
|
this.nextPublicEventId = null;
|
|
}
|
|
|
|
/**
|
|
* @description start timer
|
|
* @return {('start'|'pause'|'stop'|'roll')} Playback state
|
|
*/
|
|
start() {
|
|
// do we need to change
|
|
if (this.state === 'start') return 'start';
|
|
|
|
// call super
|
|
super.start();
|
|
|
|
// update lifecycle: onStart
|
|
this.ontimeCycle = this.cycleState.onStart;
|
|
|
|
return this.state;
|
|
}
|
|
|
|
/**
|
|
* @description pause timer
|
|
* @return {('start'|'pause'|'stop')} Playback state
|
|
*/
|
|
pause() {
|
|
// do we need to change
|
|
if (this.state === 'pause') return 'pause';
|
|
|
|
// call super
|
|
super.pause();
|
|
|
|
// update lifecycle: onPause
|
|
this.ontimeCycle = this.cycleState.onPause;
|
|
|
|
return this.state;
|
|
}
|
|
|
|
/**
|
|
* @description stop timer
|
|
* @return {('start'|'pause'|'stop'|'roll')} Playback state
|
|
*/
|
|
stop() {
|
|
// do we need to change
|
|
if (this.state === 'stop') return 'stop';
|
|
|
|
// call super
|
|
super.stop();
|
|
this._resetTimers(true);
|
|
this._resetSelection();
|
|
|
|
// update lifecycle: onStop
|
|
this.ontimeCycle = this.cycleState.onStop;
|
|
|
|
// broadcast state
|
|
this.broadcastState();
|
|
|
|
return this.state;
|
|
}
|
|
|
|
/**
|
|
* @description increment timer by amount
|
|
* @param amount
|
|
*/
|
|
increment(amount) {
|
|
// call super
|
|
super.increment(amount);
|
|
|
|
// run cycle
|
|
this.runCycle();
|
|
}
|
|
|
|
/**
|
|
* @description Look for current event considering local clock
|
|
*/
|
|
rollLoad() {
|
|
const prevLoaded = this.selectedEventId;
|
|
|
|
// maybe roll has already been loaded
|
|
if (this.secondaryTimer === null) {
|
|
this._resetTimers(true);
|
|
this._resetSelection();
|
|
}
|
|
|
|
const { nowIndex, nowId, publicIndex, nextIndex, publicNextIndex, timers, timeToNext } =
|
|
eventLoader.findRoll();
|
|
|
|
// nothing to play, unload
|
|
if (nowIndex === null && nextIndex === null) {
|
|
this.stop();
|
|
this.socket.warning('SERVER', 'Roll: no events found');
|
|
return;
|
|
}
|
|
|
|
// there is something running, load
|
|
if (nowIndex !== null) {
|
|
// clear secondary timers
|
|
this.secondaryTimer = null;
|
|
this._secondaryTarget = null;
|
|
|
|
// set timers
|
|
this._startedAt = timers._startedAt;
|
|
this._finishAt = timers._finishAt;
|
|
this.duration = timers.duration;
|
|
this.current = timers.current;
|
|
|
|
// set selection
|
|
this.selectedEventId = nowId;
|
|
this.selectedEventIndex = nowIndex;
|
|
}
|
|
|
|
// found something to run next
|
|
if (nextIndex != null) {
|
|
const eventNext = EventLoader.getPlayableAtIndex(nextIndex);
|
|
|
|
// Set running timers
|
|
if (nowIndex === null) {
|
|
// only warn the first time
|
|
if (this.secondaryTimer === null) {
|
|
this.socket.info('SERVER', 'Roll: waiting for event start');
|
|
}
|
|
|
|
// reset running timer
|
|
// ??? should this not have been reset?
|
|
this.current = null;
|
|
|
|
// timer counts to next event
|
|
this.secondaryTimer = timeToNext;
|
|
this._secondaryTarget = eventNext.timeStart;
|
|
}
|
|
|
|
// TITLES: Load next private
|
|
// Todo: this should be an ID
|
|
// todo: this logic should be removed
|
|
if (eventNext) {
|
|
this.titles.titleNext = eventNext.title;
|
|
this.titles.subtitleNext = eventNext.subtitle;
|
|
this.titles.presenterNext = eventNext.presenter;
|
|
this.titles.noteNext = eventNext.note;
|
|
this.nextEventId = eventNext.id;
|
|
}
|
|
}
|
|
|
|
// Todo: this should be an ID
|
|
// todo: this logic should be removed
|
|
// TITLES: Load next public
|
|
if (publicNextIndex !== null) {
|
|
const eventNextPublic = EventLoader.getPlayableAtIndex(publicNextIndex);
|
|
if (eventNextPublic) {
|
|
this.titlesPublic.titleNext = eventNextPublic.title;
|
|
this.titlesPublic.subtitleNext = eventNextPublic.subtitle;
|
|
this.titlesPublic.presenterNext = eventNextPublic.presenter;
|
|
this.titlesPublic.noteNext = eventNextPublic.note;
|
|
this.nextPublicEventId = eventNextPublic.id;
|
|
}
|
|
}
|
|
|
|
// Todo: this should be an ID
|
|
// todo: this logic should be removed
|
|
// TITLES: Load now private
|
|
if (nowIndex !== null) {
|
|
const eventNowPrivate = EventLoader.getPlayableAtIndex(nowIndex);
|
|
if (eventNowPrivate) {
|
|
this.titles.titleNow = eventNowPrivate.title;
|
|
this.titles.subtitleNow = eventNowPrivate.subtitle;
|
|
this.titles.presenterNow = eventNowPrivate.presenter;
|
|
this.titles.noteNow = eventNowPrivate.note;
|
|
this.selectedEventId = eventNowPrivate.id;
|
|
}
|
|
}
|
|
|
|
// Todo: this should be an ID
|
|
// todo: this logic should be removed
|
|
// TITLES: Load now public
|
|
if (publicIndex !== null) {
|
|
const eventNowPublic = EventLoader.getPlayableAtIndex(nowIndex);
|
|
if (eventNowPublic) {
|
|
this.titlesPublic.titleNow = eventNowPublic.title;
|
|
this.titlesPublic.subtitleNow = eventNowPublic.subtitle;
|
|
this.titlesPublic.presenterNow = eventNowPublic.presenter;
|
|
this.titlesPublic.noteNow = eventNowPublic.note;
|
|
this.selectedPublicEventId = eventNowPublic.id;
|
|
}
|
|
}
|
|
|
|
if (prevLoaded !== this.selectedEventId) {
|
|
// update lifecycle: onLoad
|
|
this.ontimeCycle = this.cycleState.onLoad;
|
|
// ensure we go through onLoad cycle
|
|
this.runCycle();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Starts roll mode
|
|
* @return {('start'|'pause'|'stop'|'roll')} Playback state
|
|
*/
|
|
roll() {
|
|
if (this.state === 'roll') {
|
|
return 'roll';
|
|
}
|
|
|
|
this.state = 'roll';
|
|
|
|
// update lifecycle: armed
|
|
this.ontimeCycle = this.cycleState.armed;
|
|
|
|
// load into event
|
|
this.rollLoad();
|
|
|
|
return this.state;
|
|
}
|
|
|
|
previous() {
|
|
this.sendOsc(this.osc.implemented.previous);
|
|
this.pause();
|
|
this.runCycle();
|
|
}
|
|
|
|
next() {
|
|
this.sendOsc(this.osc.implemented.next);
|
|
this.pause();
|
|
this.runCycle();
|
|
}
|
|
|
|
/**
|
|
* @description reloads current event
|
|
* @return {('start'|'pause'|'stop'|'roll')} Playback state
|
|
*/
|
|
reload() {
|
|
if (!this.selectedEventId) {
|
|
return this.state;
|
|
}
|
|
|
|
// change playstate
|
|
this.pause();
|
|
|
|
// send OSC
|
|
this.sendOsc(this.osc.implemented.reload);
|
|
|
|
// reload data
|
|
const event = EventLoader.getEventWithId(this.selectedEventId);
|
|
this.loadEvent(event);
|
|
|
|
this.runCycle();
|
|
|
|
return this.state;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
|
|
/**
|
|
* Integrations
|
|
* -------------
|
|
*
|
|
* Code related to integrations
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* Calls OSC send message and resolves reply to logger
|
|
* @param {string} message
|
|
* @param {any} [payload]
|
|
*/
|
|
async sendOsc(message, payload) {
|
|
const reply = await this.osc.send(message, payload);
|
|
if (!reply.success) {
|
|
this.socket.error('TX', reply.message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @description Builds sync object
|
|
*/
|
|
poll() {
|
|
return {
|
|
currentId: this.selectedEventId,
|
|
timer: this.timeTag,
|
|
clock: this.clock,
|
|
playback: this.state,
|
|
currentColour: null,
|
|
title: this.titles.titleNow,
|
|
presenter: this.titles.presenterNow,
|
|
};
|
|
}
|
|
}
|