@arcanewizards/timecode-toolbox 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/components/frontend/index.js +865 -272
- package/dist/components/frontend/index.mjs +855 -262
- package/dist/entrypoint.css +163 -53
- package/dist/entrypoint.js +1474 -493
- package/dist/entrypoint.js.map +4 -4
- package/dist/frontend.js +1474 -493
- package/dist/frontend.js.map +4 -4
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +308 -37
- package/dist/index.mjs +329 -49
- package/dist/start.d.mts +1 -2
- package/dist/start.d.ts +1 -2
- package/dist/start.js +311 -38
- package/dist/start.mjs +332 -50
- package/package.json +12 -6
- package/.turbo/turbo-build.log +0 -58
- package/.turbo/turbo-lint.log +0 -4
- package/CHANGELOG.md +0 -40
- package/eslint.config.mjs +0 -49
- package/src/app.tsx +0 -147
- package/src/components/backend/index.ts +0 -6
- package/src/components/backend/toolbox-root.ts +0 -119
- package/src/components/frontend/constants.ts +0 -81
- package/src/components/frontend/entrypoint.ts +0 -12
- package/src/components/frontend/frontend.css +0 -108
- package/src/components/frontend/index.tsx +0 -46
- package/src/components/frontend/toolbox/content.tsx +0 -45
- package/src/components/frontend/toolbox/context.tsx +0 -63
- package/src/components/frontend/toolbox/core/size-aware-div.tsx +0 -51
- package/src/components/frontend/toolbox/core/timecode-display.tsx +0 -592
- package/src/components/frontend/toolbox/generators.tsx +0 -318
- package/src/components/frontend/toolbox/inputs.tsx +0 -484
- package/src/components/frontend/toolbox/outputs.tsx +0 -581
- package/src/components/frontend/toolbox/preferences.ts +0 -25
- package/src/components/frontend/toolbox/root.tsx +0 -335
- package/src/components/frontend/toolbox/settings.tsx +0 -54
- package/src/components/frontend/toolbox/types.ts +0 -28
- package/src/components/frontend/toolbox/util.tsx +0 -61
- package/src/components/proto.ts +0 -420
- package/src/config.ts +0 -7
- package/src/generators/clock.tsx +0 -206
- package/src/generators/index.tsx +0 -15
- package/src/index.ts +0 -38
- package/src/inputs/artnet.tsx +0 -305
- package/src/inputs/index.tsx +0 -13
- package/src/inputs/tcnet.tsx +0 -272
- package/src/outputs/artnet.tsx +0 -170
- package/src/outputs/index.tsx +0 -11
- package/src/start.ts +0 -47
- package/src/tree.ts +0 -133
- package/src/types.ts +0 -12
- package/src/urls.ts +0 -49
- package/src/util.ts +0 -82
- package/tailwind.config.cjs +0 -7
- package/tsconfig.json +0 -10
- package/tsup.config.ts +0 -10
package/src/inputs/artnet.tsx
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import { useDataFileData } from '@arcanejs/react-toolkit/data';
|
|
2
|
-
import { FC, ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { ToolboxConfigData } from '../config';
|
|
4
|
-
import {
|
|
5
|
-
InputArtnetDefinition,
|
|
6
|
-
InputConfig,
|
|
7
|
-
InputState,
|
|
8
|
-
isInputArtnetDefinition,
|
|
9
|
-
TimecodeState,
|
|
10
|
-
} from '../components/proto';
|
|
11
|
-
import { useLogger } from '@arcanewizards/sigil';
|
|
12
|
-
import {
|
|
13
|
-
ArtNet,
|
|
14
|
-
ArtNetTimecodeEvent,
|
|
15
|
-
createArtnet,
|
|
16
|
-
} from '@arcanewizards/artnet';
|
|
17
|
-
import { TIMECODE_FPS, TimecodeMode } from '@arcanewizards/artnet/constants';
|
|
18
|
-
import { StateSensitiveComponentProps } from '../types';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* How much of a difference between the calculated timecode state,
|
|
22
|
-
* and previous timecode state is required to trigger an update.
|
|
23
|
-
*/
|
|
24
|
-
const MINIMUM_DRIFT_FOR_UPDATE_MS: Record<TimecodeMode, number> = {
|
|
25
|
-
SMPTE: 1000 / TIMECODE_FPS.SMPTE / 2,
|
|
26
|
-
FILM: 1000 / TIMECODE_FPS.FILM / 2,
|
|
27
|
-
EBU: 1000 / TIMECODE_FPS.EBU / 2,
|
|
28
|
-
DF: 1000 / TIMECODE_FPS.DF / 2,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* How many frames need to be missed before we consider a timecode to be lagging.
|
|
33
|
-
*/
|
|
34
|
-
const LAGGING_FRAME_COUNT = 2;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* If we haven't received a timecode update for this amount of time,
|
|
38
|
-
* consider the timecode to be lagging
|
|
39
|
-
*/
|
|
40
|
-
const LAGGING_TIMEOUT_MS: Record<TimecodeMode, number> = {
|
|
41
|
-
SMPTE: (1000 / TIMECODE_FPS.SMPTE) * LAGGING_FRAME_COUNT,
|
|
42
|
-
FILM: (1000 / TIMECODE_FPS.FILM) * LAGGING_FRAME_COUNT,
|
|
43
|
-
EBU: (1000 / TIMECODE_FPS.EBU) * LAGGING_FRAME_COUNT,
|
|
44
|
-
DF: (1000 / TIMECODE_FPS.DF) * LAGGING_FRAME_COUNT,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* How long should we wait not receiving any packets
|
|
49
|
-
* before considering a timecode to be stopped.
|
|
50
|
-
*/
|
|
51
|
-
const TIMEOUT_MS = 500;
|
|
52
|
-
|
|
53
|
-
type ArtnetInputConnectionProps = StateSensitiveComponentProps & {
|
|
54
|
-
uuid: string;
|
|
55
|
-
config: InputConfig;
|
|
56
|
-
connection: InputArtnetDefinition;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const ArtnetInputConnection: FC<ArtnetInputConnectionProps> = ({
|
|
60
|
-
uuid,
|
|
61
|
-
config: { name, delayMs },
|
|
62
|
-
connection: { iface, port },
|
|
63
|
-
setState,
|
|
64
|
-
}) => {
|
|
65
|
-
const log = useLogger();
|
|
66
|
-
|
|
67
|
-
const [artnetInstance, setArtnetInstance] = useState<ArtNet | null>(null);
|
|
68
|
-
|
|
69
|
-
// Use ref here to allow for updates without requiring re-init of node
|
|
70
|
-
const delayRef = useRef(delayMs ?? 0);
|
|
71
|
-
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
delayRef.current = delayMs ?? 0;
|
|
74
|
-
}, [delayMs]);
|
|
75
|
-
|
|
76
|
-
const setConnection = useCallback(
|
|
77
|
-
(state: InputState) =>
|
|
78
|
-
setState((current) => ({
|
|
79
|
-
...current,
|
|
80
|
-
inputs: {
|
|
81
|
-
...current.inputs,
|
|
82
|
-
[uuid]: state,
|
|
83
|
-
},
|
|
84
|
-
})),
|
|
85
|
-
[setState, uuid],
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
const connectionConfig: Omit<InputState, 'status'> = {
|
|
90
|
-
timecode: {
|
|
91
|
-
name: null,
|
|
92
|
-
state: {
|
|
93
|
-
state: 'none',
|
|
94
|
-
accuracyMillis: null,
|
|
95
|
-
smpteMode: null,
|
|
96
|
-
onAir: null,
|
|
97
|
-
},
|
|
98
|
-
metadata: null,
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
let artnet: ArtNet | null = null;
|
|
102
|
-
setConnection({ ...connectionConfig, status: 'connecting' });
|
|
103
|
-
const created = createArtnet({
|
|
104
|
-
type: 'interface',
|
|
105
|
-
interface: iface,
|
|
106
|
-
mode: 'receive',
|
|
107
|
-
port,
|
|
108
|
-
});
|
|
109
|
-
created.on('error', (err) => {
|
|
110
|
-
const error = new Error('ArtNet input connection error');
|
|
111
|
-
error.cause = err instanceof Error ? err : new Error(String(err));
|
|
112
|
-
log.error(error);
|
|
113
|
-
setConnection({
|
|
114
|
-
...connectionConfig,
|
|
115
|
-
status: 'error',
|
|
116
|
-
errors: [`${err}`],
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
created
|
|
120
|
-
.connect()
|
|
121
|
-
.then(() => {
|
|
122
|
-
artnet = created;
|
|
123
|
-
setArtnetInstance(created);
|
|
124
|
-
log.info('ArtNet Timecode output initialized');
|
|
125
|
-
setConnection({ ...connectionConfig, status: 'active' });
|
|
126
|
-
})
|
|
127
|
-
.catch((err) => {
|
|
128
|
-
const error = new Error('Failed to start ArtNet Timecode output');
|
|
129
|
-
error.cause = err instanceof Error ? err : new Error(String(err));
|
|
130
|
-
log.error(error);
|
|
131
|
-
setConnection({
|
|
132
|
-
...connectionConfig,
|
|
133
|
-
status: 'error',
|
|
134
|
-
errors: [`${err}`],
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
return () => {
|
|
139
|
-
if (artnet) {
|
|
140
|
-
artnet.destroy();
|
|
141
|
-
setArtnetInstance((current) => (artnet === current ? null : current));
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
}, [setConnection, uuid, iface, port, log]);
|
|
145
|
-
|
|
146
|
-
useEffect(() => {
|
|
147
|
-
type ReceivedTimecode = {
|
|
148
|
-
clockMillis: number;
|
|
149
|
-
effectiveStartTimeMillis: number;
|
|
150
|
-
mode: TimecodeMode;
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
let lastTimecode: ReceivedTimecode | null = null;
|
|
154
|
-
let lastUsedTimecode: ReceivedTimecode | null = lastTimecode;
|
|
155
|
-
let timecode: TimecodeState | null = null;
|
|
156
|
-
let driftApproximation = 0;
|
|
157
|
-
let laggingTimeout: NodeJS.Timeout | null = null;
|
|
158
|
-
|
|
159
|
-
let isMounted = true;
|
|
160
|
-
|
|
161
|
-
const updateTimecodeState = () => {
|
|
162
|
-
if (!lastTimecode) {
|
|
163
|
-
// No change
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
if (!isMounted) {
|
|
167
|
-
// Update received after being unmounted, ignore
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const now = Date.now();
|
|
171
|
-
if (
|
|
172
|
-
lastUsedTimecode === lastTimecode &&
|
|
173
|
-
lastTimecode.clockMillis + TIMEOUT_MS < now
|
|
174
|
-
) {
|
|
175
|
-
// Timecode has become stale
|
|
176
|
-
timecode = {
|
|
177
|
-
state: 'stopped',
|
|
178
|
-
positionMillis:
|
|
179
|
-
lastUsedTimecode.clockMillis -
|
|
180
|
-
lastUsedTimecode.effectiveStartTimeMillis,
|
|
181
|
-
accuracyMillis: driftApproximation,
|
|
182
|
-
smpteMode: lastTimecode.mode,
|
|
183
|
-
onAir: null,
|
|
184
|
-
};
|
|
185
|
-
setConnection({
|
|
186
|
-
status: 'active',
|
|
187
|
-
timecode: {
|
|
188
|
-
name: null,
|
|
189
|
-
state: timecode,
|
|
190
|
-
metadata: null,
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
lastTimecode = null;
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const isLagging =
|
|
197
|
-
lastTimecode.clockMillis + LAGGING_TIMEOUT_MS[lastTimecode.mode] < now;
|
|
198
|
-
timecode = {
|
|
199
|
-
state: isLagging ? 'lagging' : 'playing',
|
|
200
|
-
effectiveStartTimeMillis: lastTimecode.effectiveStartTimeMillis,
|
|
201
|
-
accuracyMillis: driftApproximation,
|
|
202
|
-
smpteMode: lastTimecode.mode,
|
|
203
|
-
speed: 1,
|
|
204
|
-
onAir: null,
|
|
205
|
-
};
|
|
206
|
-
setConnection({
|
|
207
|
-
status: 'active',
|
|
208
|
-
timecode: {
|
|
209
|
-
name: null,
|
|
210
|
-
state: timecode,
|
|
211
|
-
metadata: null,
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
lastUsedTimecode = lastTimecode;
|
|
215
|
-
|
|
216
|
-
if (!isLagging) {
|
|
217
|
-
// Set up a timeout to mark the timecode as lagging,
|
|
218
|
-
// if we don't receive an update quickly enough
|
|
219
|
-
if (laggingTimeout) {
|
|
220
|
-
clearTimeout(laggingTimeout);
|
|
221
|
-
}
|
|
222
|
-
// Set a bit later than lagging timeout to ensure that the
|
|
223
|
-
// condition is met when the timeout triggers,
|
|
224
|
-
laggingTimeout = setTimeout(
|
|
225
|
-
updateTimecodeState,
|
|
226
|
-
LAGGING_TIMEOUT_MS[lastTimecode.mode] * 1.1,
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
const interval = setInterval(updateTimecodeState, TIMEOUT_MS / 2);
|
|
232
|
-
|
|
233
|
-
const onTimecode = (tc: ArtNetTimecodeEvent) => {
|
|
234
|
-
const clockMillis = Date.now();
|
|
235
|
-
const effectiveStartTimeMillis =
|
|
236
|
-
clockMillis - tc.timeMillis + delayRef.current;
|
|
237
|
-
lastTimecode = {
|
|
238
|
-
clockMillis,
|
|
239
|
-
effectiveStartTimeMillis,
|
|
240
|
-
mode: tc.mode,
|
|
241
|
-
};
|
|
242
|
-
if (timecode?.state === 'playing') {
|
|
243
|
-
const drift = Math.abs(
|
|
244
|
-
effectiveStartTimeMillis - timecode.effectiveStartTimeMillis,
|
|
245
|
-
);
|
|
246
|
-
// Decay the drift approximation over time
|
|
247
|
-
// so that temporary spikes don't last
|
|
248
|
-
driftApproximation = Math.max(driftApproximation * 0.9, drift);
|
|
249
|
-
if (drift < MINIMUM_DRIFT_FOR_UPDATE_MS[tc.mode]) {
|
|
250
|
-
// Skip update, difference not significant enough
|
|
251
|
-
// just rely on the interal to update the driftApproximation
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
updateTimecodeState();
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
artnetInstance?.addListener('timecode', onTimecode);
|
|
260
|
-
|
|
261
|
-
return () => {
|
|
262
|
-
isMounted = false;
|
|
263
|
-
clearInterval(interval);
|
|
264
|
-
artnetInstance?.removeListener('timecode', onTimecode);
|
|
265
|
-
};
|
|
266
|
-
}, [artnetInstance, log, iface, name, setConnection]);
|
|
267
|
-
|
|
268
|
-
useEffect(() => {
|
|
269
|
-
return () => {
|
|
270
|
-
// Remove the connection when it's no longer mounted / configured
|
|
271
|
-
setState((current) => {
|
|
272
|
-
const { [uuid]: _, ...rest } = current.inputs;
|
|
273
|
-
return {
|
|
274
|
-
...current,
|
|
275
|
-
inputs: rest,
|
|
276
|
-
};
|
|
277
|
-
});
|
|
278
|
-
};
|
|
279
|
-
}, [setState, uuid]);
|
|
280
|
-
|
|
281
|
-
return null;
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
export const ArtnetInputConnections: FC<StateSensitiveComponentProps> = (
|
|
285
|
-
props,
|
|
286
|
-
) => {
|
|
287
|
-
const { inputs } = useDataFileData(ToolboxConfigData);
|
|
288
|
-
return Object.entries(inputs)
|
|
289
|
-
.filter(([_, { enabled }]) => enabled)
|
|
290
|
-
.map<ReactNode>(([uuid, input]) => {
|
|
291
|
-
const connection = input.definition;
|
|
292
|
-
if (!isInputArtnetDefinition(connection)) {
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
return (
|
|
296
|
-
<ArtnetInputConnection
|
|
297
|
-
key={uuid}
|
|
298
|
-
uuid={uuid}
|
|
299
|
-
config={input}
|
|
300
|
-
connection={connection}
|
|
301
|
-
{...props}
|
|
302
|
-
/>
|
|
303
|
-
);
|
|
304
|
-
});
|
|
305
|
-
};
|
package/src/inputs/index.tsx
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { FC } from 'react';
|
|
2
|
-
import { ArtnetInputConnections } from './artnet';
|
|
3
|
-
import { TcNetInputConnections } from './tcnet';
|
|
4
|
-
import { StateSensitiveComponentProps } from '../types';
|
|
5
|
-
|
|
6
|
-
export const InputConnections: FC<StateSensitiveComponentProps> = (props) => {
|
|
7
|
-
return (
|
|
8
|
-
<>
|
|
9
|
-
<ArtnetInputConnections {...props} />
|
|
10
|
-
<TcNetInputConnections {...props} />
|
|
11
|
-
</>
|
|
12
|
-
);
|
|
13
|
-
};
|
package/src/inputs/tcnet.tsx
DELETED
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import { useDataFileData } from '@arcanejs/react-toolkit/data';
|
|
2
|
-
import {
|
|
3
|
-
FC,
|
|
4
|
-
ReactNode,
|
|
5
|
-
useCallback,
|
|
6
|
-
useContext,
|
|
7
|
-
useEffect,
|
|
8
|
-
useMemo,
|
|
9
|
-
useRef,
|
|
10
|
-
} from 'react';
|
|
11
|
-
import { ToolboxConfigData } from '../config';
|
|
12
|
-
import {
|
|
13
|
-
ConnectedClient,
|
|
14
|
-
InputConfig,
|
|
15
|
-
InputState,
|
|
16
|
-
InputTcnetDefinition,
|
|
17
|
-
isInputTcnetDefinition,
|
|
18
|
-
TimecodeGroup,
|
|
19
|
-
} from '../components/proto';
|
|
20
|
-
import {
|
|
21
|
-
AppInformationContext,
|
|
22
|
-
useLogger,
|
|
23
|
-
useShutdownHandler,
|
|
24
|
-
} from '@arcanewizards/sigil';
|
|
25
|
-
|
|
26
|
-
import { createTCNetNode } from '@arcanewizards/tcnet';
|
|
27
|
-
import { createTCNetTimecodeMonitor } from '@arcanewizards/tcnet/monitor';
|
|
28
|
-
import {
|
|
29
|
-
TCNetConnectedNodes,
|
|
30
|
-
TCNetPortUsage,
|
|
31
|
-
} from '@arcanewizards/tcnet/types';
|
|
32
|
-
import { NetworkPortStatus } from '@arcanewizards/net-utils';
|
|
33
|
-
import { StateSensitiveComponentProps } from '../types';
|
|
34
|
-
|
|
35
|
-
type TcnetInputConnectionProps = StateSensitiveComponentProps & {
|
|
36
|
-
uuid: string;
|
|
37
|
-
config: InputConfig;
|
|
38
|
-
connection: InputTcnetDefinition;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const TcnetInputConnection: FC<TcnetInputConnectionProps> = ({
|
|
42
|
-
uuid,
|
|
43
|
-
config: { name, delayMs },
|
|
44
|
-
connection: { iface, nodeName },
|
|
45
|
-
setState,
|
|
46
|
-
}) => {
|
|
47
|
-
const logger = useLogger();
|
|
48
|
-
|
|
49
|
-
const appInformation = useContext(AppInformationContext);
|
|
50
|
-
|
|
51
|
-
const nodeRef = useRef<ReturnType<typeof createTCNetNode> | null>(null);
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Variable that can be set to prevent further updates,
|
|
55
|
-
* in particular when unmounting to prevent updates from this function
|
|
56
|
-
* once it is unmounted,
|
|
57
|
-
* since further updates may come from the TCNet node after it's been destroyed.
|
|
58
|
-
*/
|
|
59
|
-
const isMountedRef = useRef(true);
|
|
60
|
-
|
|
61
|
-
// Use ref here to allow for updates without requiring re-init of node
|
|
62
|
-
const delayRef = useRef(delayMs ?? 0);
|
|
63
|
-
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
delayRef.current = delayMs ?? 0;
|
|
66
|
-
}, [delayMs]);
|
|
67
|
-
|
|
68
|
-
const setConnection = useCallback(
|
|
69
|
-
(state: InputState) =>
|
|
70
|
-
setState((current) => ({
|
|
71
|
-
...current,
|
|
72
|
-
inputs: {
|
|
73
|
-
...current.inputs,
|
|
74
|
-
[uuid]: state,
|
|
75
|
-
},
|
|
76
|
-
})),
|
|
77
|
-
[setState, uuid],
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* TCNet has multiple connections / ports,
|
|
82
|
-
* this convenience function ensures that all the required ports are kept up-to-date,
|
|
83
|
-
* and have unique IDs
|
|
84
|
-
*/
|
|
85
|
-
const updateState = useMemo(() => {
|
|
86
|
-
return (
|
|
87
|
-
connections: Record<TCNetPortUsage, NetworkPortStatus | null>,
|
|
88
|
-
nodes: TCNetConnectedNodes,
|
|
89
|
-
timecodeGroup: TimecodeGroup,
|
|
90
|
-
) => {
|
|
91
|
-
if (!isMountedRef.current) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const warnings =
|
|
95
|
-
Object.values(nodes).length === 0
|
|
96
|
-
? ['No other TCNet nodes detected on the network']
|
|
97
|
-
: [];
|
|
98
|
-
const clients: ConnectedClient[] = Object.entries(nodes)
|
|
99
|
-
.sort(([nodeIdA], [nodeIdB]) => nodeIdA.localeCompare(nodeIdB))
|
|
100
|
-
.map(([_, nodeInfo]) => ({
|
|
101
|
-
name: nodeInfo.nodeName,
|
|
102
|
-
host: nodeInfo.host,
|
|
103
|
-
port: nodeInfo.nodeListenerPort,
|
|
104
|
-
protocolVersion: nodeInfo.protocolVersion,
|
|
105
|
-
details: [`Type: ${nodeInfo.nodeType}`],
|
|
106
|
-
}));
|
|
107
|
-
const hasError = Object.values(connections).some(
|
|
108
|
-
(port) => port?.status === 'error',
|
|
109
|
-
);
|
|
110
|
-
const isConnecting = Object.values(connections).some(
|
|
111
|
-
(port) => port?.status === 'connecting',
|
|
112
|
-
);
|
|
113
|
-
setConnection({
|
|
114
|
-
status: hasError ? 'error' : isConnecting ? 'connecting' : 'active',
|
|
115
|
-
clients,
|
|
116
|
-
warnings,
|
|
117
|
-
timecode: timecodeGroup,
|
|
118
|
-
});
|
|
119
|
-
};
|
|
120
|
-
}, [setConnection]);
|
|
121
|
-
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
const node = createTCNetNode({
|
|
124
|
-
logger,
|
|
125
|
-
networkInterface: iface,
|
|
126
|
-
nodeName: nodeName?.substring(0, 8) ?? 'TC-TLBOX',
|
|
127
|
-
vendorName: 'Arcane Wizards',
|
|
128
|
-
appName: appInformation.title.substring(0, 16),
|
|
129
|
-
appVersion: appInformation.version.substring(0, 16),
|
|
130
|
-
});
|
|
131
|
-
nodeRef.current = node;
|
|
132
|
-
|
|
133
|
-
let lastPortInformation = node.getPortInformation();
|
|
134
|
-
let lastNodes: TCNetConnectedNodes = {};
|
|
135
|
-
let timecodeGroup: TimecodeGroup = {
|
|
136
|
-
name: null,
|
|
137
|
-
color: null,
|
|
138
|
-
timecodes: {},
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const updateConnectionsState = () => {
|
|
142
|
-
updateState(lastPortInformation, lastNodes, timecodeGroup);
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
updateConnectionsState();
|
|
146
|
-
|
|
147
|
-
node.on('port-state-changed', (info) => {
|
|
148
|
-
lastPortInformation = info;
|
|
149
|
-
updateConnectionsState();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
node.on('nodes-changed', (nodes) => {
|
|
153
|
-
lastNodes = nodes;
|
|
154
|
-
updateConnectionsState();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
node.on('ready', () => {
|
|
158
|
-
logger.info(`TCNet node ${uuid} is ready`);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const monitor = createTCNetTimecodeMonitor(node, logger);
|
|
162
|
-
|
|
163
|
-
monitor.addListener(
|
|
164
|
-
'timecode-changed',
|
|
165
|
-
({ layerId, playState, ...timecodeState }) => {
|
|
166
|
-
timecodeGroup = {
|
|
167
|
-
...timecodeGroup,
|
|
168
|
-
timecodes: {
|
|
169
|
-
...timecodeGroup.timecodes,
|
|
170
|
-
[layerId]: {
|
|
171
|
-
name: timecodeState.layerName,
|
|
172
|
-
metadata: {
|
|
173
|
-
totalTime: timecodeState.totalTime,
|
|
174
|
-
title: timecodeState?.info?.title ?? null,
|
|
175
|
-
artist: timecodeState?.info?.artist ?? null,
|
|
176
|
-
},
|
|
177
|
-
state:
|
|
178
|
-
playState.state === 'playing'
|
|
179
|
-
? {
|
|
180
|
-
state: 'playing',
|
|
181
|
-
effectiveStartTimeMillis:
|
|
182
|
-
playState.effectiveStartTime + delayRef.current,
|
|
183
|
-
speed: playState.speed,
|
|
184
|
-
onAir: playState.onAir,
|
|
185
|
-
accuracyMillis: null,
|
|
186
|
-
smpteMode: null,
|
|
187
|
-
}
|
|
188
|
-
: {
|
|
189
|
-
state: 'stopped',
|
|
190
|
-
positionMillis:
|
|
191
|
-
playState.currentTimeMillis - delayRef.current,
|
|
192
|
-
onAir: playState.onAir,
|
|
193
|
-
accuracyMillis: null,
|
|
194
|
-
smpteMode: null,
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
updateConnectionsState();
|
|
200
|
-
},
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
monitor.addListener('layer-removed', ({ layerId }) => {
|
|
204
|
-
logger.info(`Layer removed from node ${uuid} layer ${layerId}`);
|
|
205
|
-
timecodeGroup = {
|
|
206
|
-
...timecodeGroup,
|
|
207
|
-
timecodes: Object.fromEntries(
|
|
208
|
-
Object.entries(timecodeGroup.timecodes).filter(
|
|
209
|
-
([layerIdKey]) => layerIdKey !== layerId,
|
|
210
|
-
),
|
|
211
|
-
),
|
|
212
|
-
};
|
|
213
|
-
updateConnectionsState();
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
node.connect();
|
|
217
|
-
|
|
218
|
-
return () => {
|
|
219
|
-
logger.info(`Destroying TCNet connection ${uuid}...`);
|
|
220
|
-
node.destroy();
|
|
221
|
-
if (nodeRef.current === node) {
|
|
222
|
-
nodeRef.current = null;
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
}, [uuid, iface, nodeName, logger, appInformation, updateState]);
|
|
226
|
-
|
|
227
|
-
useShutdownHandler(async () => {
|
|
228
|
-
if (nodeRef.current) {
|
|
229
|
-
logger.info(`Shutting down TCNet node ${name ?? uuid}...`);
|
|
230
|
-
await nodeRef.current.destroy();
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
useEffect(() => {
|
|
235
|
-
return () => {
|
|
236
|
-
// Prevent the connection state being re-added with delayed updates from node
|
|
237
|
-
isMountedRef.current = false;
|
|
238
|
-
// Remove the connection when it's no longer mounted / configured
|
|
239
|
-
setState((current) => {
|
|
240
|
-
const { [uuid]: _, ...rest } = current.inputs;
|
|
241
|
-
return {
|
|
242
|
-
...current,
|
|
243
|
-
inputs: rest,
|
|
244
|
-
};
|
|
245
|
-
});
|
|
246
|
-
};
|
|
247
|
-
}, [setState, uuid]);
|
|
248
|
-
return null;
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
export const TcNetInputConnections: FC<StateSensitiveComponentProps> = (
|
|
252
|
-
props,
|
|
253
|
-
) => {
|
|
254
|
-
const { inputs } = useDataFileData(ToolboxConfigData);
|
|
255
|
-
return Object.entries(inputs)
|
|
256
|
-
.filter(([_, { enabled }]) => enabled)
|
|
257
|
-
.map<ReactNode>(([uuid, input]) => {
|
|
258
|
-
const connection = input.definition;
|
|
259
|
-
if (!isInputTcnetDefinition(connection)) {
|
|
260
|
-
return null;
|
|
261
|
-
}
|
|
262
|
-
return (
|
|
263
|
-
<TcnetInputConnection
|
|
264
|
-
key={uuid}
|
|
265
|
-
uuid={uuid}
|
|
266
|
-
config={input}
|
|
267
|
-
connection={connection}
|
|
268
|
-
{...props}
|
|
269
|
-
/>
|
|
270
|
-
);
|
|
271
|
-
});
|
|
272
|
-
};
|