@@ -131,20 +136,28 @@ export default function EventList(props) {
ref={provided.innerRef}
>
{events.map((e, index) => {
- let isCursor = cursor === index;
- if (index === 0) cumulativeDelay = 0;
+ if (index === 0) {
+ cumulativeDelay = 0;
+ eventIndex = -1;
+ }
if (e.type === 'delay' && e.duration != null) {
cumulativeDelay += e.duration;
- } else if (e.type === 'block') cumulativeDelay = 0;
+ } else if (e.type === 'block') {
+ cumulativeDelay = 0;
+ } else if (e.type === 'event') {
+ eventIndex++;
+ }
+
return (
{
const {
type,
index,
+ eventIndex,
data,
selected,
next,
@@ -71,6 +72,7 @@ const EventListItem = (props) => {
return (
}
- _expanded={{ bg: 'pink.300', color: 'white' }}
+ _expanded={{ bg: 'orange.300', color: 'white' }}
_focus={{ boxShadow: 'none' }}
backgroundColor={'orange.200'}
color={'orange.500'}
diff --git a/client/src/features/viewers/ViewWrapper.jsx b/client/src/features/viewers/ViewWrapper.jsx
index 6e198eac3..738d6acfb 100644
--- a/client/src/features/viewers/ViewWrapper.jsx
+++ b/client/src/features/viewers/ViewWrapper.jsx
@@ -37,7 +37,7 @@ const withSocket = (Component) => {
});
const [timer, setTimer] = useState({
clock: null,
- currentSeconds: null,
+ running: null,
startedAt: null,
expectedFinish: null,
});
@@ -221,7 +221,7 @@ const withSocket = (Component) => {
// get clock string
const timeManager = {
...timer,
- finished: timer.running <= 0 && timer.startedAt,
+ finished: playback === 'start' && timer.running <= 0 && timer.startedAt,
clock: stringFromMillis(timer.clock),
playstate: playback,
};
diff --git a/client/src/features/viewers/backstage/StageManager.jsx b/client/src/features/viewers/backstage/StageManager.jsx
index 0fa299413..6ee5cadc8 100644
--- a/client/src/features/viewers/backstage/StageManager.jsx
+++ b/client/src/features/viewers/backstage/StageManager.jsx
@@ -42,7 +42,7 @@ export default function StageManager(props) {
// Format messages
const showPubl = publ.text !== '' && publ.visible;
let stageTimer = formatDisplay(Math.abs(time.running), true);
- if (time.running < 0) stageTimer = '-' + stageTimer;
+ if (time.running < 0) stageTimer = `-${stageTimer}`;
// motion
const titleVariants = {
diff --git a/client/src/features/viewers/presenter/PresenterView.jsx b/client/src/features/viewers/presenter/PresenterView.jsx
index 5d76d6df7..53a74faab 100644
--- a/client/src/features/viewers/presenter/PresenterView.jsx
+++ b/client/src/features/viewers/presenter/PresenterView.jsx
@@ -14,10 +14,9 @@ export default function PresenterView(props) {
document.title = 'ontime - Speaker Screen';
}, []);
-
-
const showOverlay = pres.text !== '' && pres.visible;
- const isPlaying = time.playstate === 'start';
+ const isPlaying = time.playstate !== 'pause';
+ const normalisedTime = Math.max(time.running, 0);
// motion
const titleVariants = {
@@ -61,7 +60,7 @@ export default function PresenterView(props) {
TIME UP
) : (
-
+
)}
@@ -73,7 +72,7 @@ export default function PresenterView(props) {
}
>
diff --git a/client/src/features/viewers/production/Pip.jsx b/client/src/features/viewers/production/Pip.jsx
index c2dbe6d8d..15d6385a0 100644
--- a/client/src/features/viewers/production/Pip.jsx
+++ b/client/src/features/viewers/production/Pip.jsx
@@ -51,11 +51,8 @@ export default function Pip(props) {
// Format messages
const showInfo =
general.backstageInfo !== '' && general.backstageInfo != null;
-
- const stageTimer =
- time.currentSeconds != null && !isNaN(time.currentSeconds)
- ? formatDisplay(time.currentSeconds, true)
- : '';
+ let stageTimer = formatDisplay(Math.abs(time.running), true);
+ if (time.running < 0) stageTimer = `-${stageTimer}`;
return (
diff --git a/server/package.json b/server/package.json
index 2b399881d..6ff8e68f8 100644
--- a/server/package.json
+++ b/server/package.json
@@ -10,7 +10,7 @@
"lowdb": "2.1.0",
"multer": "^1.4.2",
"nanoid": "^3.1.22",
- "node-osc": "^6.0.1",
+ "node-osc": "6.0.2",
"passport": "~0.4.1",
"passport-local": "~1.0.0",
"socket.io": "^4.0.0"
diff --git a/server/src/classes/EventTimer.js b/server/src/classes/EventTimer.js
index a81ee4388..911ea8e58 100644
--- a/server/src/classes/EventTimer.js
+++ b/server/src/classes/EventTimer.js
@@ -109,8 +109,29 @@ export class EventTimer extends Timer {
update() {
// if there is nothing selected, no nothing
- if (this.selectedEventId == null) return;
- super.update();
+ if (this.selectedEventId == null && this.state !== 'roll') return;
+
+ // only implement roll here
+ if (this.state !== 'roll') {
+ super.update();
+ return;
+ }
+
+ // get current time
+ const now = this._getCurrentTime();
+ this.clock = now;
+
+ if (this.selectedEventId && this.current > 0) {
+ // update timer as usual
+ this.current = this._finishAt - now;
+ } else {
+ // look for event if none is loaded
+ if (this.current <= 0 || this.secondaryTimer <= 0) this.rollLoad();
+
+ // count to next event
+ // TODO: replace with proper counter
+ if (this.secondaryTimer != null) this.secondaryTimer -= 1000;
+ }
}
start() {
@@ -140,10 +161,11 @@ export class EventTimer extends Timer {
else if (payload === 'next') this.next();
else if (payload === 'reload') this.reload();
else if (payload === 'unload') this.unload();
+ else if (payload === 'roll') this.roll();
- // Not yet implemented
- // else if (payload === 'roll') this.roll();
-
+ // TODO: Cleanup
+ // here tdo this.broadcastState;
+ // remove broadcast from functions
this.broadcastThis('playstate', this.state);
this.broadcastThis('selected-id', this.selectedEventId);
this.broadcastThis('titles', this.titles);
@@ -472,7 +494,7 @@ export class EventTimer extends Timer {
if (eventIndex === -1) return;
this.pause();
- this.loadEvent(eventIndex, true, true);
+ this.loadEvent(eventIndex, 'load', true);
}
// Loads a given event
@@ -522,18 +544,12 @@ export class EventTimer extends Timer {
if (e == null) return;
// private title is always current
- this.titles.titleNow = e.title;
- this.titles.subtitleNow = e.subtitle;
- this.titles.presenterNow = e.presenter;
- this.selectedEventId = e.id;
-
// check if current is also public
if (e.isPublic) {
- this.titlesPublic.titleNow = e.title;
- this.titlesPublic.subtitleNow = e.subtitle;
- this.titlesPublic.presenterNow = e.presenter;
- this.selectedPublicEventId = e.id;
+ this._loadThisTitles(e, 'now');
} else {
+ this._loadThisTitles(e, 'now-private');
+
// assume there is no public event
this.titlesPublic.titleNow = null;
this.titlesPublic.subtitleNow = null;
@@ -549,16 +565,77 @@ export class EventTimer extends Timer {
this._eventlist[i].type === 'event' &&
this._eventlist[i].isPublic
) {
- this.titlesPublic.titleNow = this._eventlist[i].title;
- this.titlesPublic.subtitleNow = this._eventlist[i].subtitle;
- this.titlesPublic.presenterNow = this._eventlist[i].presenter;
- this.selectedPublicEventId = this._eventlist[i].id;
+ this._loadThisTitles(this._eventlist[i], 'now-public');
break;
}
}
}
}
+ _loadThisTitles(e, type) {
+ if (e == null) return;
+
+ switch (type) {
+ // now, load to both public and private
+ case 'now':
+ // public
+ this.titlesPublic.titleNow = e.title;
+ this.titlesPublic.subtitleNow = e.subtitle;
+ this.titlesPublic.presenterNow = e.presenter;
+ this.selectedPublicEventId = e.id;
+
+ // private
+ this.titles.titleNow = e.title;
+ this.titles.subtitleNow = e.subtitle;
+ this.titles.presenterNow = e.presenter;
+ this.selectedEventId = e.id;
+
+ break;
+ case 'now-public':
+ this.titlesPublic.titleNow = e.title;
+ this.titlesPublic.subtitleNow = e.subtitle;
+ this.titlesPublic.presenterNow = e.presenter;
+ this.selectedPublicEventId = e.id;
+ break;
+ case 'now-private':
+ this.titles.titleNow = e.title;
+ this.titles.subtitleNow = e.subtitle;
+ this.titles.presenterNow = e.presenter;
+ this.selectedEventId = e.id;
+ break;
+
+ // next, load to both public and private
+ case 'next':
+ // public
+ this.titlesPublic.titleNext = e.title;
+ this.titlesPublic.subtitleNext = e.subtitle;
+ this.titlesPublic.presenterNext = e.presenter;
+ this.nextPublicEventId = e.id;
+
+ // private
+ this.titles.titleNext = e.title;
+ this.titles.subtitleNext = e.subtitle;
+ this.titles.presenterNext = e.presenter;
+ this.nextEventId = e.id;
+ break;
+ case 'next-public':
+ this.titlesPublic.titleNext = e.title;
+ this.titlesPublic.subtitleNext = e.subtitle;
+ this.titlesPublic.presenterNext = e.presenter;
+ this.nextPublicEventId = e.id;
+ break;
+ case 'next-private':
+ this.titles.titleNext = e.title;
+ this.titles.subtitleNext = e.subtitle;
+ this.titles.presenterNext = e.presenter;
+ this.nextEventId = e.id;
+ break;
+
+ default:
+ break;
+ }
+ }
+
_loadTitlesNext() {
// maybe there is nothing to load
if (this.selectedEventIndex == null) return;
@@ -583,19 +660,13 @@ export class EventTimer extends Timer {
if (this._eventlist[i].type === 'event') {
// if we have not set private
if (!nextPrivate) {
- this.titles.titleNext = this._eventlist[i].title;
- this.titles.subtitleNext = this._eventlist[i].subtitle;
- this.titles.presenterNext = this._eventlist[i].presenter;
- this.nextEventId = this._eventlist[i].id;
+ this._loadThisTitles(this._eventlist[i], 'next-private');
nextPrivate = true;
}
// if event is public
if (this._eventlist[i].isPublic) {
- this.titlesPublic.titleNext = this._eventlist[i].title;
- this.titlesPublic.subtitleNext = this._eventlist[i].subtitle;
- this.titlesPublic.presenterNext = this._eventlist[i].presenter;
- this.nextPublicEventId = this._eventlist[i].id;
+ this._loadThisTitles(this._eventlist[i], 'next-public');
nextPublic = true;
}
}
@@ -642,6 +713,7 @@ export class EventTimer extends Timer {
state = ${this.state}
current = ${this.current}
duration = ${this.duration}
+ secondaryTimer = ${this.secondaryTimer}
Events
------------------------------
@@ -730,10 +802,77 @@ export class EventTimer extends Timer {
this.broadcastState();
}
+ rollLoad() {
+ const now = this._getCurrentTime();
+ this._resetTimers(true);
+ this._resetSelection();
+
+ let foundNow = null;
+ let nextIndex = null;
+ let nextStart = null;
+
+ // loop through events, look for where we should be
+ for (const [index, e] of this._eventlist.entries()) {
+ if (!foundNow) {
+ if (e.timeStart <= now && now < e.timeEnd) {
+ // set flag
+ foundNow = true;
+
+ // set timers
+ this._startedAt = e.timeStart;
+ this._finishAt = e.timeEnd;
+ this.duration = e.timeEnd - e.timeStart;
+ this.current = e.timeEnd - now;
+
+ // set selection
+ this.selectedEventId = e.id;
+ this.selectedEventIndex = index;
+
+ // set titles
+ this._loadTitlesNow();
+
+ // skip this entry for next
+ continue;
+ }
+ }
+ // check how far the start is from now
+ let wait = e.timeStart - now;
+ if (wait > 0) {
+ if (nextStart == null || wait < nextStart) {
+ nextStart = wait;
+ nextIndex = index;
+ }
+ }
+ }
+
+ // nothing to play next, unload
+ if (!foundNow && !nextIndex) {
+ this.unload();
+ return;
+ }
+
+ if (nextIndex) {
+ // load titles
+ this._loadThisTitles(nextIndex, 'next');
+
+ if (!foundNow) {
+ // timer counts to nextStart
+ this.secondaryTimer = nextStart;
+ }
+ }
+ }
+
roll() {
- console.log('roll: not yet implemented');
- return false;
+ // do we need to change
+ if (this.state === 'roll') return;
+
+ // load into event
+ this.rollLoad();
+
+ // set state
this.state = 'roll';
+
+ this.broadcastState();
}
previous() {
diff --git a/server/src/classes/Timer.js b/server/src/classes/Timer.js
index 3a402ccd7..29a5ca793 100644
--- a/server/src/classes/Timer.js
+++ b/server/src/classes/Timer.js
@@ -8,6 +8,7 @@ export class Timer {
clock = null;
duration = null;
current = null;
+ secondaryTimer = null;
_finishAt = null;
_finishedAt = null;
_startedAt = null;
@@ -50,6 +51,9 @@ export class Timer {
// check playstate
switch (this.state) {
case 'start':
+ // ensure we have a start time
+ if (this._startedAt == null) this._startedAt = now;
+
// update current timer
this.current =
this._startedAt + this.duration + this._pausedTotal - now;
@@ -110,8 +114,10 @@ export class Timer {
);
}
- _resetTimers() {
+ _resetTimers(total = false) {
+ if (total) this.duration = null;
this.current = this.duration;
+ this.secondaryTimer = null;
this._finishAt = null;
this._finishedAt = null;
this._startedAt = null;
@@ -132,7 +138,7 @@ export class Timer {
return {
clock: this.clock,
running: Timer.toSeconds(this.current),
- currentSeconds: Timer.toSeconds(Math.max(this.current, 0)),
+ secondary: Timer.toSeconds(this.secondaryTimer),
durationSeconds: Timer.toSeconds(this.duration),
expectedFinish: this._getExpectedFinish(),
startedAt: this._startedAt,
diff --git a/server/src/controllers/OscController.js b/server/src/controllers/OscController.js
index 89b1010f2..939ee829b 100644
--- a/server/src/controllers/OscController.js
+++ b/server/src/controllers/OscController.js
@@ -5,6 +5,9 @@ export const initiateOSC = (config) => {
console.log(`OSC Server is listening on port ${config.port}`);
});
+ // error
+ oscServer.on('error', console.error);
+
oscServer.on('message', function (msg) {
// message should look like /ontime/{path}/{args} where
// ontime: fixed message for app
@@ -62,7 +65,8 @@ export const initiateOSC = (config) => {
console.log('calling goto with', args);
try {
let eventIndex = parseInt(args);
- if (isNaN(eventIndex) || eventIndex <= 0) return;
+ if (isNaN(eventIndex) || eventIndex <= 0 || eventIndex == null)
+ return;
global.timer.loadEvent(eventIndex - 1, undefined, true);
} catch (error) {
console.log('error calling goto: ', error);
@@ -70,8 +74,9 @@ export const initiateOSC = (config) => {
break;
case 'gotoid':
console.log('calling gotoid with', args);
+ if (args == null) return;
try {
- global.timer.loadEventById(args.toLowerCase());
+ global.timer.loadEventById(args.toString().toLowerCase());
} catch (error) {
console.log('error calling goto: ', error);
}
diff --git a/server/yarn.lock b/server/yarn.lock
index 2e29b0705..ba9e9c7b2 100644
--- a/server/yarn.lock
+++ b/server/yarn.lock
@@ -438,10 +438,10 @@ negotiator@0.6.2:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
-node-osc@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/node-osc/-/node-osc-6.0.1.tgz#6f2c39996c64460435b678dc91c128d3a9711031"
- integrity sha512-UZBQGH4vVTw3Ub5/xcnhlrhL6bpaYz3hhNjO4eup4tknyP5qOi2GVVnEmEn/qaTgTvs7SVyytj3OHg9JWRD4Aw==
+node-osc@6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/node-osc/-/node-osc-6.0.2.tgz#185d387928b186a84e8d0eebcae9e54a8bf50729"
+ integrity sha512-RW8PRLMh4H14veC5gZVUTcRkrk5pXd+b4vmitWL55c9ZI+5yEvMXkrokfnRH0u7DYveqw4h+uCPIUDifY/vjfA==
dependencies:
osc-min "^1.1.1"