UNPKG

atem-connection

Version:

Typescript Node.js library for connecting with an ATEM switcher.

163 lines 6.53 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AtemSocket = void 0; const eventemitter3_1 = require("eventemitter3"); const atemCommandParser_1 = require("./atemCommandParser"); const exitHook = require("exit-hook"); const commands_1 = require("../commands"); const atem_1 = require("../atem"); const threadedclass_1 = require("threadedclass"); const packetBuilder_1 = require("./packetBuilder"); class AtemSocket extends eventemitter3_1.EventEmitter { constructor(options) { super(); this._commandParser = new atemCommandParser_1.CommandParser(); this._nextPacketTrackingId = 0; this._isDisconnecting = false; this._port = atem_1.DEFAULT_PORT; this._address = options.address; this._port = options.port; this._debugBuffers = options.debugBuffers; this._disableMultithreaded = options.disableMultithreaded; this._childProcessTimeout = options.childProcessTimeout; this._maxPacketSize = options.maxPacketSize; } async connect(address, port) { this._isDisconnecting = false; if (address) { this._address = address; } if (port) { this._port = port; } if (!this._socketProcess) { // cache the creation promise, in case `destroy` is called before it completes this._creatingSocket = this._createSocketProcess(); await this._creatingSocket; if (this._isDisconnecting || !this._socketProcess) { throw new Error('Disconnecting'); } } await this._socketProcess.connect(this._address, this._port); } async destroy() { await this.disconnect(); // Ensure thread creation has finished if it was started if (this._creatingSocket) await this._creatingSocket.catch(() => null); if (this._socketProcess) { await threadedclass_1.ThreadedClassManager.destroy(this._socketProcess); this._socketProcess = undefined; } if (this._exitUnsubscribe) { this._exitUnsubscribe(); this._exitUnsubscribe = undefined; } } async disconnect() { this._isDisconnecting = true; if (this._socketProcess) { await this._socketProcess.disconnect(); } } get nextPacketTrackingId() { if (this._nextPacketTrackingId >= Number.MAX_SAFE_INTEGER) { this._nextPacketTrackingId = 0; } return ++this._nextPacketTrackingId; } async sendCommands(commands) { if (!this._socketProcess) throw new Error('Socket process is not open'); const maxPacketSize = this._maxPacketSize - 12; // MTU minus ATEM header const packetBuilder = new packetBuilder_1.PacketBuilder(maxPacketSize, this._commandParser.version); for (const cmd of commands) { packetBuilder.addCommand(cmd); } const packets = packetBuilder.getPackets().map((buffer) => ({ payloadLength: buffer.length, payloadHex: buffer.toString('hex'), trackingId: this.nextPacketTrackingId, })); if (this._debugBuffers) this.emit('debug', `PAYLOAD PACKETS ${JSON.stringify(packets)}`); if (packets.length > 0) { await this._socketProcess.sendPackets(packets); } return packets.map((packet) => packet.trackingId); } async _createSocketProcess() { this._socketProcess = await (0, threadedclass_1.threadedClass)('./atemSocketChild', 'AtemSocketChild', [ { address: this._address, port: this._port, debugBuffers: this._debugBuffers, }, async () => { this.emit('disconnect'); }, async (message) => { this.emit('info', message); }, async (payload) => { this._parseCommands(Buffer.from(payload)); }, async (ids) => { this.emit('ackPackets', ids.map((id) => id.trackingId)); }, // onPacketsAcknowledged ], { instanceName: 'atem-connection', freezeLimit: this._childProcessTimeout, autoRestart: true, disableMultithreading: this._disableMultithreaded, }); threadedclass_1.ThreadedClassManager.onEvent(this._socketProcess, 'restarted', () => { this.connect().catch((error) => { const errorMsg = `Failed to reconnect after respawning socket process: ${error}`; this.emit('error', errorMsg); }); }); threadedclass_1.ThreadedClassManager.onEvent(this._socketProcess, 'thread_closed', () => { this.emit('disconnect'); }); this._exitUnsubscribe = exitHook(() => { this.destroy().catch(() => null); }); } _parseCommands(buffer) { const parsedCommands = []; while (buffer.length > 8) { const length = buffer.readUInt16BE(0); const name = buffer.toString('ascii', 4, 8); if (length < 8) { // Commands are never less than 8, as that is the header break; } const cmdConstructor = this._commandParser.commandFromRawName(name); if (cmdConstructor && typeof cmdConstructor.deserialize === 'function') { try { const cmd = cmdConstructor.deserialize(buffer.slice(8, length), this._commandParser.version); if (cmd instanceof commands_1.VersionCommand) { // init started this._commandParser.version = cmd.properties.version; } parsedCommands.push(cmd); } catch (e) { this.emit('error', `Failed to deserialize command: ${cmdConstructor.constructor.name}: ${e}`); } } else { this.emit('debug', `Unknown command ${name} (${length}b)`); } // Trim the buffer buffer = buffer.slice(length); } if (parsedCommands.length > 0) { this.emit('receivedCommands', parsedCommands); } return parsedCommands; } } exports.AtemSocket = AtemSocket; //# sourceMappingURL=atemSocket.js.map