Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC: nodejs control plane (Feedback wanted) #1533

Draft
wants to merge 66 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
dfe0c41
it launches and runs like a normal caspar
Julusian Jan 11, 2024
d64952c
wip: integrating a bit better
Julusian Jan 11, 2024
c803801
wip: nodejs can start and stop casparcg
Julusian Jan 12, 2024
44fa60c
wip: can send amcp strings from nodejs
Julusian Jan 12, 2024
605ebde
wip: infinite command line input
Julusian Jan 12, 2024
f02fe57
chore: format
Julusian Jan 12, 2024
a505c7e
wip: simplify
Julusian Jan 12, 2024
122c708
wip
Julusian Jan 12, 2024
56497c2
wip: disable c++ amcp sockets
Julusian Jan 12, 2024
651e854
wip: move some amcp parsing to nodejs
Julusian Jan 12, 2024
7947ccd
wip: rewrite some amcp in js
Julusian Jan 12, 2024
17f47cf
wip: purge some old c++ amcp code
Julusian Jan 12, 2024
8257976
wip: amcp errors
Julusian Jan 12, 2024
2fa46c3
wip: move channel/index parsing into js
Julusian Jan 12, 2024
585cea2
wip: reimplement channel locks in js
Julusian Jan 12, 2024
0d5cb3d
wip: initial tcp amcp server
Julusian Jan 12, 2024
ac52880
reimplement BYE in js land
Julusian Jan 12, 2024
e0f6171
wip: start on reworking flow of command replies
Julusian Jan 12, 2024
802cd09
wip: simplify
Julusian Jan 12, 2024
9fb0472
wip something
Julusian Jan 13, 2024
b0e15c5
wip: crude reimplementation of amcp responses (no RES/REQ support)
Julusian Jan 13, 2024
775c542
wip: start of xml config parsing
Julusian Jan 13, 2024
6fa1d19
wip: start moving some startup xml usage out to js
Julusian Jan 13, 2024
968b6ac
wip: init channels from js
Julusian Jan 13, 2024
5b658eb
wip: move custom video format loading to js
Julusian Jan 13, 2024
c94b950
fix: some casing of 'xml'
Julusian Jan 13, 2024
8f64dfa
wip: start moving some commands to js
Julusian Jan 13, 2024
01466a9
wip: move remaining data commands to node
Julusian Jan 13, 2024
675b546
wip: move scanner commands to js
Julusian Jan 13, 2024
1ddaf37
wip: move kill and restart
Julusian Jan 13, 2024
6c80d55
wip: start on stage/producer commands
Julusian Jan 14, 2024
1806ed1
wip more commands
Julusian Jan 14, 2024
76f8116
wip: more commands
Julusian Jan 14, 2024
5df1f8d
wip
Julusian Jan 15, 2024
f4f8657
tidy
Julusian Jan 18, 2024
c82a17f
wip: try simplify
Julusian Jan 18, 2024
3e9127e
wip: parse loadbg in nodejs
Julusian Jan 18, 2024
d46c326
wip: play and load commands
Julusian Jan 18, 2024
1c8b324
wip: disable osc in c++, push state to js (only partially implemented)
Julusian Jan 19, 2024
4b48505
wip: some plumbing for osc
Julusian Jan 19, 2024
c9314ee
wip: tidy
Julusian Jan 19, 2024
fce6a93
wip: reimplement osc subscribe commands
Julusian Jan 19, 2024
6b56545
wip: consumer add/remove/print
Julusian Jan 19, 2024
8c61a1e
wip: start of mixer commands
Julusian Jan 23, 2024
e8ece4e
wip: missing stage commands
Julusian Jan 26, 2024
2fdd164
wip: remove old amcp c++ flow
Julusian Jan 26, 2024
3d838c3
wip: tidy remaining amcp mess
Julusian Jan 26, 2024
995d3ac
wip: fixup batch
Julusian Jan 26, 2024
ae1e1a0
wip: pass channel state back to js
Julusian Jan 26, 2024
6b9bbe1
wip: reimplement some info commands
Julusian Jan 26, 2024
8f338b5
wip: start of cg
Julusian Jan 29, 2024
a5d1da9
wip: refactor amcp implementation to reduce duplication
Julusian Jan 29, 2024
dc690c2
wip: remaining cg (untested)
Julusian Jan 29, 2024
08bef91
wip: apply mixer
Julusian Jan 29, 2024
51baddb
wip: types
Julusian Jan 29, 2024
2de71f8
fix: change amcp implementations to a two step execution, to allow fo…
Julusian Jan 29, 2024
2d9f751
wip: remove channels parameter from create_consumers
Julusian Jan 30, 2024
1dc7a90
wip
Julusian Jan 30, 2024
1682dd0
chore: format
Julusian Jan 30, 2024
f6bed43
wip
Julusian Apr 18, 2024
f7b324a
wip: remove c++ config file parsing
Julusian Apr 21, 2024
2f083f2
wip: pipe logs to nodejs
Julusian Apr 21, 2024
a43a97e
wip: cleanup
Julusian Apr 21, 2024
4240ca3
hack: fiddle width paths
Julusian Apr 24, 2024
f666873
Merge remote-tracking branch 'upstream/master' into hack/nodejs
Julusian Apr 24, 2024
8d6cecc
fix: producers not loading
Julusian Apr 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions node/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
*.lock
/log/
/media/
/template/
/data/
/casparcg.config
/_media/
32 changes: 32 additions & 0 deletions node/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "casparcg-server-node",
"version": "1.0.0",
"type": "module",
"main": "main.js",
"license": "MIT",
"dependencies": {
"cmake-js": "^7.2.1",
"date-fns": "^3.6.0",
"fast-xml-parser": "^4.3.3",
"node-addon-api": "^7.0.0",
"object-path": "^0.11.8",
"osc": "^2.4.4",
"type-fest": "^4.10.0"
},
"scripts": {
"start": "yarn ts-node-esm src/main.ts",
"build": "cmake-js -d ../src build --prefer-clang=false"
},
"binary": {
"napi_versions": [
7
]
},
"devDependencies": {
"@sofie-automation/code-standard-preset": "^2.5.2",
"@types/node": "^20.11.0",
"@types/object-path": "^0.11.4",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
}
141 changes: 141 additions & 0 deletions node/src/@types/osc.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copied from https://github.com/colinbdclark/osc.js/pull/105

declare module "osc" {
export class EventEmitter<T> {
addListener<E extends keyof T>(event: E, listener: T[E]): this;

on<E extends keyof T>(event: E, listener: T[E]): this;

once<E extends keyof T>(event: E, listener: T[E]): this;

removeListener<E extends keyof T>(event: E, listener: T[E]): this;

removeAllListeners(event?: keyof T): this;

setMaxListeners(n: number): this;

getMaxListeners(): number;

listeners<E extends keyof T>(event: E): T[E][];

// eslint-disable-next-line @typescript-eslint/no-explicit-any
emit(event: string | symbol, ...args: any[]): boolean;
listenerCount(type: keyof T): number;

// Added in Node 6...
prependListener<E extends keyof T>(event: E, listener: T[E]): this;

prependOnceListener<E extends keyof T>(event: E, listener: T[E]): this;

eventNames(): Array<keyof T>;
}

export const defaults: {
metadata: boolean;
unpackSingleArgs: boolean;
};
export type Argument = number | string | Uint8Array;
export type MetaArgument =
| { type: "i" | "f"; value: number }
| { type: "s"; value: string }
| { type: "b"; value: Uint8Array };

export abstract class SLIPPort {}

export interface OscMessage {
address: string;
args: Argument | Array<Argument> | MetaArgument | Array<MetaArgument>;
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface OscBundle {}

export interface SenderInfo {
address: string;
port: number;
size: number;
family: "IPv4" | "IPv6";
}

export interface PortEvents {
ready: () => void;
message: (
message: OscMessage,
timeTag: number | undefined,
info: SenderInfo
) => void;
bundle: (bundle: OscBundle, timeTag: number, info: SenderInfo) => void;
osc: (packet: OscBundle | OscMessage, info: SenderInfo) => void;
raw: (data: Uint8Array, info: SenderInfo) => void;
error: (err: Error) => void;
}

export interface UdpOptions {
/**
* The port to listen on
*/
localPort?: number; // 57121
/**
* The local address to bind to
*/
localAddress?: string; // '127.0.0.1'
/**
* The remote port to send messages to
*/
remotePort?: number;
/**
* The remote address to send messages to
*/
remoteAddress?: string;
broadcast?: boolean; // false
/**
* The time to live (number of hops) for a multicast connection
*/
multicastTTL?: number;
/**
* An array of multicast addresses to join when listening for multicast messages
*/
multicastMembership?: string[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
socket?: any;

/**
* should message arguments be wrapped with type?
*/
metadata?: boolean;
unpackSingleArgs?: boolean;
}

export interface OscSender {
send(msg: OscMessage, address?: string, port?: number): void;
}

export abstract class Port
extends EventEmitter<PortEvents>
implements OscSender
{
send(msg: OscMessage, address?: string, port?: number): void;
}

export class SerialPort extends SLIPPort {
open(): void;

close(): void;

listen(): void;
}

export class UDPPort extends Port {
static setupMulticast(that: UDPPort): void;

options: UdpOptions;

constructor(options: UdpOptions);

open(): void;

close(): void;

listen(): void;
}
}
76 changes: 76 additions & 0 deletions node/src/amcp/channel_locks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { Client } from "./client.js";

interface ChannelLock {
clientIds: Set<string>;
lockPhrase: string;
}

export class ChannelLocks {
readonly #locked = new Map<number, ChannelLock>();
readonly #overridePhrase: string;

constructor(overridePhrase: string) {
this.#overridePhrase = overridePhrase;
}

public forgetClient(client: Client): void {
for (const key of this.#locked.keys()) {
this.releaseLock(client, key);
}
}

public isChannelLocked(client: Client, channelIndex: number): boolean {
if (channelIndex < 0) return false;

const lock = this.#locked.get(channelIndex);
if (!lock) return false;
return !lock.clientIds.has(client.id);
}

public releaseLock(client: Client, channelIndex: number): void {
const lock = this.#locked.get(channelIndex);
if (!lock) return;

if (lock.clientIds.delete(client.id)) {
console.info(`Channel ${channelIndex} released`);

if (lock.clientIds.size === 0) {
this.#locked.delete(channelIndex);
}
}
}

public tryLock(
client: Client,
channelIndex: number,
lockPhrase: string
): boolean {
const lock = this.#locked.get(channelIndex) ?? {
clientIds: new Set(),
lockPhrase: "",
};
this.#locked.set(channelIndex, lock);

if (lock.lockPhrase && lock.lockPhrase !== lockPhrase) {
return false;
}

lock.lockPhrase = lockPhrase;
lock.clientIds.add(client.id);

console.info(`Channel ${channelIndex} acquired`);

return true;
}

public clearLocks(overridePhrase: string | undefined): boolean {
if (this.#overridePhrase && this.#overridePhrase !== overridePhrase) {
return false;
}

this.#locked.clear();

console.info("Channel locks cleared");
return true;
}
}
8 changes: 8 additions & 0 deletions node/src/amcp/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { AMCPClientBatchInfo } from "./protocol.js";

export interface Client {
readonly id: string;
readonly address: string;

readonly batch: AMCPClientBatchInfo;
}
Loading
Loading